import { assert, codedError, hashValue, sleep } from '../assertions/core.mjs'; import { absolutePathVariants, isNonEmptyString, } from '../assertions/runtime.mjs'; import { createHash, fs, fsConstants, path, randomUUID, spawn, watchFileSystem, } from '../dependencies.mjs'; import { activeCommandChildren, appRoot, BlockedError, configFileName, contextCompactionAppDataSentinelFileName, contextCompactionAppDataSentinelSchema, goalAppDataSentinelFileName, goalAppDataSentinelSchema, linuxPidfdHelperSource, localConfigFileName, mainAgentId, parallelReadAppDataSentinelFileName, parallelReadAppDataSentinelSchema, projectSkillAppDataSentinelFileName, projectSkillAppDataSentinelSchema, projectSupervisorAgentId, responseStreamAppDataSentinelFileName, responseStreamAppDataSentinelSchema, runnerEndpointFileName, scopedAgentsAppDataSentinelFileName, scopedAgentsAppDataSentinelSchema, state, steerRunnerKillAppDataSentinelFileName, steerRunnerKillAppDataSentinelSchema, supervisorAutonomousPlayableAppDataSentinelFileName, supervisorAutonomousPlayableAppDataSentinelSchema, supervisorSwarmAppDataSentinelFileName, supervisorSwarmAppDataSentinelSchema, supervisorSwarmAutonomousChatAppDataSentinelFileName, supervisorSwarmAutonomousChatAppDataSentinelSchema, supervisorSwarmCollaborationPolicyAppDataSentinelFileName, supervisorSwarmCollaborationPolicyAppDataSentinelSchema, supervisorSwarmFinalReplyTransientRetryAppDataSentinelFileName, supervisorSwarmFinalReplyTransientRetryAppDataSentinelSchema, supervisorSwarmRequiredAgentIds, supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelFileName, supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelSchema, supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelFileName, supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelSchema, supervisorSwarmTransientRetryAppDataSentinelFileName, supervisorSwarmTransientRetryAppDataSentinelSchema, supervisorSwarmTransientRetryTargetAgentId, userInputAppDataSentinelFileName, userInputAppDataSentinelSchema, webSearchAppDataSentinelFileName, webSearchAppDataSentinelSchema, windowsProcessHandleHelperSource, } from '../runtime-state.mjs'; import { isContextCompactionSuite } from '../suites/context-compaction.mjs'; import { isGoalRuntimeSuite } from '../suites/goal.mjs'; import { isParallelReadSuite } from '../suites/parallel-read.mjs'; import { isProjectSkillSuite } from '../suites/project-skill.mjs'; import { isResponseStreamSuite } from '../suites/response-stream.mjs'; import { isScopedAgentsSuite } from '../suites/scoped-agents.mjs'; import { isSteerRunnerKillSuite } from '../suites/steer-runner-kill.mjs'; import { isSupervisorAutonomousPlayableLaneDefenseSuite } from '../suites/supervisor-autonomous-playable.mjs'; import { isSupervisorSwarmAutonomousChatSuite, isSupervisorSwarmCollaborationPolicyMixedRecoverySuite, isSupervisorSwarmFinalReplyTransientRetrySuite, isSupervisorSwarmInitialTransientRetrySuite, isSupervisorSwarmInteractiveChatSuite, isSupervisorSwarmMixedHarnessSuite, isSupervisorSwarmStaticIsolatedAutonomousChatSuite, isSupervisorSwarmSuite, isSupervisorSwarmToolPlanHandoffRunnerKillSuite, isSupervisorSwarmTransientRetrySuite, rebuildSupervisorSwarmTranscriptScanner, } from '../suites/supervisor-swarm.mjs'; import { isUserInputRuntimeSuite } from '../suites/user-input.mjs'; import { isWebSearchSuite, sameEffectiveAgentLlmWithoutWebSearch, } from '../suites/web-search.mjs'; import { effectiveAgentLlmConfig, isPlainObject, loadConfig, mergeConfigPatch, safeEffectiveAgentLlmPolicy, sameEffectiveAgentLlmWithoutStream, } from './config.mjs'; import { collectApiKeys, isPathInside, readJson } from './io.mjs'; import { appendBounded, runProcess } from './process.mjs'; import { decodeUtf8Fatal, isIsolatedRunnerSuite } from './reporting.mjs'; import { killRunnerOnce, readRunnerStatus, runnerBootId } from './runtime.mjs'; const platformSessionFixtureEnv = 'GENARRATIVE_AGC_PLATFORM_SESSION_FIXTURE'; const platformSessionFixtureMaxBytes = 16 * 1024; const isolatedPlatformSessionFixtureName = '.deterministic-platform-session.json'; const platformSessionFixtureSchema = 'genarrative-agc-platform-session-fixture.v1'; export function isolatedSuiteProtectsSourceAppData() { return ( isSupervisorSwarmTransientRetrySuite() || isSupervisorSwarmToolPlanHandoffRunnerKillSuite() || isSupervisorAutonomousPlayableLaneDefenseSuite() || isSupervisorSwarmInteractiveChatSuite() ); } export function isolatedSuiteUsesSiblingAppData() { return isWebSearchSuite() || isolatedSuiteProtectsSourceAppData(); } export function sameSupervisorPlayableProviderBinding(left, right) { const scalarFields = [ 'providerAgentMode', 'providerModel', 'providerApiKind', 'providerReasoningEffort', 'providerBaseUrlSha256', ]; return ( left != null && right != null && scalarFields.every((field) => left[field] === right[field]) && Array.isArray(left.boundAgentIds) && Array.isArray(right.boundAgentIds) && left.boundAgentIds.length === right.boundAgentIds.length && left.boundAgentIds.every( (agentId, index) => agentId === right.boundAgentIds[index], ) ); } export function isolatedSuiteAppDataProfile() { if (isSteerRunnerKillSuite()) { return { prefix: '.agent-runtime-real-e2e-steer-runner-kill-', sentinelName: steerRunnerKillAppDataSentinelFileName, sentinelSchema: steerRunnerKillAppDataSentinelSchema, codePrefix: 'steer-runner-kill-appdata', }; } if (isGoalRuntimeSuite()) { return { prefix: '.agent-runtime-real-e2e-goal-', sentinelName: goalAppDataSentinelFileName, sentinelSchema: goalAppDataSentinelSchema, codePrefix: 'goal-appdata', }; } if (isWebSearchSuite()) { return { prefix: '.agent-runtime-real-e2e-web-search-', sentinelName: webSearchAppDataSentinelFileName, sentinelSchema: webSearchAppDataSentinelSchema, codePrefix: 'web-search-appdata', }; } if (isContextCompactionSuite()) { return { prefix: '.agent-runtime-real-e2e-context-compaction-', sentinelName: contextCompactionAppDataSentinelFileName, sentinelSchema: contextCompactionAppDataSentinelSchema, codePrefix: 'context-compaction-appdata', }; } if (isUserInputRuntimeSuite()) { return { prefix: '.agent-runtime-real-e2e-user-input-', sentinelName: userInputAppDataSentinelFileName, sentinelSchema: userInputAppDataSentinelSchema, codePrefix: 'user-input-appdata', }; } if (isScopedAgentsSuite()) { return { prefix: '.agent-runtime-real-e2e-scoped-agents-', sentinelName: scopedAgentsAppDataSentinelFileName, sentinelSchema: scopedAgentsAppDataSentinelSchema, codePrefix: 'scoped-agents-appdata', }; } if (isProjectSkillSuite()) { return { prefix: '.agent-runtime-real-e2e-project-skill-', sentinelName: projectSkillAppDataSentinelFileName, sentinelSchema: projectSkillAppDataSentinelSchema, codePrefix: 'project-skill-appdata', }; } if (isParallelReadSuite()) { return { prefix: '.agent-runtime-real-e2e-parallel-read-', sentinelName: parallelReadAppDataSentinelFileName, sentinelSchema: parallelReadAppDataSentinelSchema, codePrefix: 'parallel-read-appdata', }; } if (isSupervisorSwarmFinalReplyTransientRetrySuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-swarm-final-reply-transient-retry-', sentinelName: supervisorSwarmFinalReplyTransientRetryAppDataSentinelFileName, sentinelSchema: supervisorSwarmFinalReplyTransientRetryAppDataSentinelSchema, codePrefix: 'supervisor-swarm-final-reply-transient-retry-appdata', }; } if (isSupervisorSwarmToolPlanHandoffRunnerKillSuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-swarm-tool-plan-handoff-runner-kill-', sentinelName: supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelFileName, sentinelSchema: supervisorSwarmToolPlanHandoffRunnerKillAppDataSentinelSchema, codePrefix: 'supervisor-swarm-tool-plan-handoff-runner-kill-appdata', }; } if (isSupervisorSwarmInitialTransientRetrySuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-swarm-transient-retry-', sentinelName: supervisorSwarmTransientRetryAppDataSentinelFileName, sentinelSchema: supervisorSwarmTransientRetryAppDataSentinelSchema, codePrefix: 'supervisor-swarm-transient-retry-appdata', }; } if (isSupervisorAutonomousPlayableLaneDefenseSuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-autonomous-playable-', sentinelName: supervisorAutonomousPlayableAppDataSentinelFileName, sentinelSchema: supervisorAutonomousPlayableAppDataSentinelSchema, codePrefix: 'supervisor-autonomous-playable-appdata', }; } if (isSupervisorSwarmAutonomousChatSuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-swarm-autonomous-chat-', sentinelName: supervisorSwarmAutonomousChatAppDataSentinelFileName, sentinelSchema: supervisorSwarmAutonomousChatAppDataSentinelSchema, codePrefix: 'supervisor-swarm-autonomous-chat-appdata', }; } if (isSupervisorSwarmStaticIsolatedAutonomousChatSuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-swarm-static-isolated-autonomous-chat-', sentinelName: supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelFileName, sentinelSchema: supervisorSwarmStaticIsolatedAutonomousChatAppDataSentinelSchema, codePrefix: 'supervisor-swarm-static-isolated-autonomous-chat-appdata', }; } if (isSupervisorSwarmCollaborationPolicyMixedRecoverySuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-swarm-collaboration-policy-', sentinelName: supervisorSwarmCollaborationPolicyAppDataSentinelFileName, sentinelSchema: supervisorSwarmCollaborationPolicyAppDataSentinelSchema, codePrefix: 'supervisor-swarm-collaboration-policy-appdata', }; } if (isSupervisorSwarmSuite()) { return { prefix: '.agent-runtime-real-e2e-supervisor-swarm-', sentinelName: supervisorSwarmAppDataSentinelFileName, sentinelSchema: supervisorSwarmAppDataSentinelSchema, codePrefix: 'supervisor-swarm-appdata', }; } assert( isResponseStreamSuite(), 'isolated-appdata-used-outside-isolated-suite', ); return { prefix: '.agent-runtime-real-e2e-response-stream-', sentinelName: responseStreamAppDataSentinelFileName, sentinelSchema: responseStreamAppDataSentinelSchema, codePrefix: 'response-stream-appdata', }; } export async function createSentinelOwnedTempDirectory({ prefix, sentinelName, sentinel, codePrefix, }) { const directory = await fs.mkdtemp(prefix); try { if (process.platform === 'win32') { await secureWindowsOwnedTempPath(directory, true); } else { await fs.chmod(directory, 0o700); } const sentinelPath = path.join(directory, sentinelName); await fs.writeFile(sentinelPath, `${JSON.stringify(sentinel)}\n`, { flag: 'wx', mode: 0o600, }); if (process.platform === 'win32') { await secureWindowsOwnedTempPath(sentinelPath, false); } return directory; } catch (error) { try { await fs.rm(directory, { recursive: true, force: true }); } catch (cleanupError) { throw codedError( `${codePrefix}-sentinel-create-cleanup-failed`, cleanupError, ); } throw codedError(`${codePrefix}-sentinel-create-failed`, error); } } export async function secureWindowsOwnedTempPath(targetPath, isDirectory) { assert( process.platform === 'win32' && path.isAbsolute(targetPath), 'isolated-windows-owned-path-precondition-invalid', ); const powershell = await findControlledWindowsPowerShell(); const script = String.raw` $ErrorActionPreference = 'Stop' $targetPath = [Environment]::GetEnvironmentVariable( 'AGC_OWNED_TEMP_PATH', [EnvironmentVariableTarget]::Process) $isDirectory = [Environment]::GetEnvironmentVariable( 'AGC_OWNED_TEMP_PATH_IS_DIRECTORY', [EnvironmentVariableTarget]::Process) -eq '1' if ([String]::IsNullOrWhiteSpace($targetPath)) { exit 70 } $sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User $inheritance = [System.Security.AccessControl.InheritanceFlags]::None if ($isDirectory) { if (-not (Test-Path -LiteralPath $targetPath -PathType Container)) { exit 71 } $acl = [System.Security.AccessControl.DirectorySecurity]::new() $acl.SetAccessRuleProtection($true, $false) $acl.SetOwner($sid) $inheritance = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit $rule = [System.Security.AccessControl.FileSystemAccessRule]::new( $sid, [System.Security.AccessControl.FileSystemRights]::FullControl, $inheritance, [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow) } else { if (-not (Test-Path -LiteralPath $targetPath -PathType Leaf)) { exit 72 } $acl = [System.Security.AccessControl.FileSecurity]::new() $acl.SetAccessRuleProtection($true, $false) $acl.SetOwner($sid) $rule = [System.Security.AccessControl.FileSystemAccessRule]::new( $sid, [System.Security.AccessControl.FileSystemRights]::FullControl, [System.Security.AccessControl.AccessControlType]::Allow) } [void]$acl.AddAccessRule($rule) Set-Acl -LiteralPath $targetPath -AclObject $acl $targetItem = Get-Item -LiteralPath $targetPath -Force $verified = $targetItem.GetAccessControl() $verifiedOwner = $verified.GetOwner( [System.Security.Principal.SecurityIdentifier]) $verifiedRules = @($verified.GetAccessRules( $true, $true, [System.Security.Principal.SecurityIdentifier])) if ( -not $verifiedOwner.Equals($sid) -or -not $verified.AreAccessRulesProtected -or $verifiedRules.Count -ne 1 ) { exit 73 } $verifiedRule = $verifiedRules[0] if ( -not $verifiedRule.IdentityReference.Equals($sid) -or $verifiedRule.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow -or [int]$verifiedRule.FileSystemRights -ne [int][System.Security.AccessControl.FileSystemRights]::FullControl -or $verifiedRule.InheritanceFlags -ne $inheritance -or $verifiedRule.PropagationFlags -ne [System.Security.AccessControl.PropagationFlags]::None -or $verifiedRule.IsInherited ) { exit 74 } `; try { await runProcess( powershell, ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', script], { cwd: appRoot, timeoutMs: 30_000, env: controlledWindowsPowerShellEnvironment({ AGC_OWNED_TEMP_PATH: targetPath, AGC_OWNED_TEMP_PATH_IS_DIRECTORY: isDirectory ? '1' : '0', }), }, ); } catch (error) { throw codedError('isolated-windows-owned-path-secure-failed', error); } } export async function captureSourceRunnerEndpointSnapshot(sourceConfigDir) { const endpointPath = path.join(sourceConfigDir, runnerEndpointFileName); const metadata = await fs.lstat(endpointPath).catch((error) => { if (error?.code === 'ENOENT') return null; throw error; }); if (!metadata) return { exists: false, fingerprint: null }; assert( metadata.isFile() && !metadata.isSymbolicLink(), 'source-runner-endpoint-not-regular-file', ); const endpoint = await readJson(endpointPath); const stableEndpoint = { ...endpoint }; delete stableEndpoint.heartbeatAt; return { exists: true, fingerprint: hashValue(JSON.stringify(stableEndpoint)), }; } export async function verifySourceRunnerEndpointUnchanged() { const sourceConfigDir = await fs.realpath(state.options.configDir); const current = await captureSourceRunnerEndpointSnapshot(sourceConfigDir); assert( JSON.stringify(current) === JSON.stringify(state.isolatedRunner.sourceEndpointSnapshot), 'source-runner-endpoint-changed-during-isolated-suite', ); state.isolatedRunner.sourceRunnerEndpointUnchanged = true; } export function closeSourceAppDataDirectoryGuard() { const watcher = state.isolatedRunner.sourceAppDataDirectoryWatcher; if (!watcher) return; watcher.close(); state.isolatedRunner.sourceAppDataDirectoryWatcher = null; } export function sourceAppDataDirectoryEventIsViolation( fileName, profilePrefix, sourceEndpointSnapshot, ) { const name = Buffer.isBuffer(fileName) ? fileName.toString('utf8') : String(fileName ?? ''); return ( name.startsWith(profilePrefix) || (sourceEndpointSnapshot?.exists === false && name === runnerEndpointFileName) ); } export function startSourceAppDataDirectoryGuard(sourceConfigDir, profile) { if (!isolatedSuiteProtectsSourceAppData()) return; assert( !state.isolatedRunner.sourceAppDataDirectoryWatcher, 'source-appdata-directory-guard-already-started', ); const watcher = watchFileSystem( sourceConfigDir, { persistent: false }, (_eventType, fileName) => { if ( sourceAppDataDirectoryEventIsViolation( fileName, profile.prefix, state.isolatedRunner.sourceEndpointSnapshot, ) ) { state.isolatedRunner.sourceAppDataDirectoryViolationCount += 1; } }, ); watcher.on('error', () => { state.isolatedRunner.sourceAppDataDirectoryViolationCount += 1; }); state.isolatedRunner.sourceAppDataDirectoryWatcher = watcher; } export async function verifySourceAppDataDirectoryUntouched() { if (!isolatedSuiteProtectsSourceAppData()) return; closeSourceAppDataDirectoryGuard(); const sourceConfigDir = await fs.realpath(state.options.configDir); const profile = isolatedSuiteAppDataProfile(); const entries = await fs.readdir(sourceConfigDir); assert( state.isolatedRunner.sourceAppDataDirectoryViolationCount === 0 && !entries.some((name) => name.startsWith(profile.prefix)), 'source-appdata-directory-touched-by-suite', ); state.isolatedRunner.sourceAppDataDirectoryUntouched = true; } async function readPlatformSessionFixtureForIsolatedSuite(sourceConfigDir) { const rawPath = process.env[platformSessionFixtureEnv]; assert( isNonEmptyString(rawPath) && path.isAbsolute(rawPath), 'supervisor-autonomous-playable-platform-session-fixture-missing', ); const sourceRealPath = await fs.realpath(sourceConfigDir); const requestedPath = path.resolve(rawPath); const requestedMetadata = await fs.lstat(requestedPath).catch((error) => { if (error?.code === 'ENOENT') return null; throw error; }); assert( requestedMetadata?.isFile() && !requestedMetadata.isSymbolicLink(), 'supervisor-autonomous-playable-platform-session-fixture-not-regular', ); assert( requestedMetadata.size <= platformSessionFixtureMaxBytes, 'supervisor-autonomous-playable-platform-session-fixture-too-large', ); const realPath = await fs.realpath(requestedPath); assert( isPathInside(sourceRealPath, realPath), 'supervisor-autonomous-playable-platform-session-fixture-outside-config', ); const bytes = await fs.readFile(realPath); assert( bytes.length <= platformSessionFixtureMaxBytes, 'supervisor-autonomous-playable-platform-session-fixture-too-large', ); let fixture; try { fixture = JSON.parse( decodeUtf8Fatal(bytes, 'platform-session-fixture-invalid-utf8'), ); } catch (error) { throw codedError( 'supervisor-autonomous-playable-platform-session-fixture-invalid', error, ); } const expectedKeys = [ 'schemaVersion', 'userId', 'accessToken', 'apiBaseUrl', 'generation', ]; assert( isPlainObject(fixture) && JSON.stringify(Object.keys(fixture).sort()) === JSON.stringify([...expectedKeys].sort()) && fixture.schemaVersion === platformSessionFixtureSchema && isNonEmptyString(fixture.userId) && isNonEmptyString(fixture.accessToken) && isNonEmptyString(fixture.apiBaseUrl) && Number.isSafeInteger(fixture.generation) && fixture.generation > 0, 'supervisor-autonomous-playable-platform-session-fixture-invalid', ); return { sourcePath: realPath, bytes, fixture, sha256: createHash('sha256').update(bytes).digest('hex'), }; } async function installPlatformSessionFixtureIntoIsolatedAppData( sourceConfigDir, appDataDir, ) { if (!isSupervisorAutonomousPlayableLaneDefenseSuite()) return; const source = await readPlatformSessionFixtureForIsolatedSuite(sourceConfigDir); const isolatedPath = path.join( appDataDir, isolatedPlatformSessionFixtureName, ); await fs.copyFile( source.sourcePath, isolatedPath, fsConstants.COPYFILE_EXCL | fsConstants.COPYFILE_FICLONE, ); await fs.chmod(isolatedPath, 0o600).catch(() => {}); const isolatedMetadata = await fs.lstat(isolatedPath); assert( isolatedMetadata.isFile() && !isolatedMetadata.isSymbolicLink() && isolatedMetadata.size === source.bytes.length, 'supervisor-autonomous-playable-platform-session-fixture-copy-invalid', ); const isolatedBytes = await fs.readFile(isolatedPath); assert( createHash('sha256').update(isolatedBytes).digest('hex') === source.sha256, 'supervisor-autonomous-playable-platform-session-fixture-copy-mismatch', ); state.isolatedRunner.platformSessionFixtureSourcePath = source.sourcePath; state.isolatedRunner.platformSessionFixturePath = isolatedPath; state.isolatedRunner.platformSessionFixtureSha256 = source.sha256; state.isolatedRunner.platformSessionFixturePreviousEnv = Object.prototype.hasOwnProperty.call(process.env, platformSessionFixtureEnv) ? process.env[platformSessionFixtureEnv] : undefined; process.env[platformSessionFixtureEnv] = isolatedPath; state.formalConfigPathTranscriptScanner?.addSecrets( absolutePathVariants(source.sourcePath, isolatedPath), ); const previousLeakCount = state.transcriptScanner?.count ?? 0; state.secrets = [...new Set([...state.secrets, source.fixture.accessToken])]; rebuildSupervisorSwarmTranscriptScanner(); state.transcriptScanner.count = previousLeakCount; } export async function prepareIsolatedSuiteAppData({ streamAgentId = null, webSearchAgentId = null, configOverlay = null, } = {}) { assert( isIsolatedRunnerSuite(), 'isolated-appdata-used-outside-isolated-suite', ); assert( [streamAgentId, webSearchAgentId, configOverlay].filter(Boolean).length <= 1, 'isolated-appdata-multiple-overlays-forbidden', ); if (configOverlay) { assert( isPlainObject(configOverlay) && collectApiKeys(configOverlay).length === 0, 'isolated-appdata-config-overlay-invalid', ); } const profile = isolatedSuiteAppDataProfile(); const suiteSecrets = new Set(state.secrets); const sourceConfigDir = await fs.realpath(state.options.configDir); state.isolatedRunner.sourceEndpointSnapshot = await captureSourceRunnerEndpointSnapshot(sourceConfigDir); const ownerToken = randomUUID(); const createdAt = Date.now(); const appDataParent = isolatedSuiteUsesSiblingAppData() ? path.dirname(sourceConfigDir) : sourceConfigDir; const appDataDir = await createSentinelOwnedTempDirectory({ prefix: path.join(appDataParent, profile.prefix), sentinelName: profile.sentinelName, sentinel: { schemaVersion: profile.sentinelSchema, token: ownerToken, ownerPid: process.pid, createdAt, }, codePrefix: profile.codePrefix, }); state.isolatedRunner.appDataDir = appDataDir; state.isolatedRunner.ownerToken = ownerToken; state.isolatedRunner.createdAt = createdAt; const realAppDataDir = await fs.realpath(appDataDir); state.formalConfigPathTranscriptScanner?.addSecrets( absolutePathVariants(appDataDir, realAppDataDir), ); if (isolatedSuiteProtectsSourceAppData()) { assert( !isPathInside(sourceConfigDir, realAppDataDir) && path.dirname(realAppDataDir) === path.dirname(sourceConfigDir), `${profile.codePrefix}-source-appdata-write-boundary-invalid`, ); startSourceAppDataDirectoryGuard(sourceConfigDir, profile); } const sourceConfigs = []; for (const name of [configFileName, localConfigFileName]) { const sourcePath = path.join(sourceConfigDir, name); const metadata = await fs.lstat(sourcePath).catch((error) => { if (error?.code === 'ENOENT') return null; throw error; }); if (!metadata) { assert( name !== configFileName, `${profile.codePrefix}-source-config-missing`, ); continue; } assert( metadata.isFile() && !metadata.isSymbolicLink(), `${profile.codePrefix}-source-config-not-regular-file`, ); const sourceContent = await fs.readFile(sourcePath); let sourceConfig; try { sourceConfig = JSON.parse( decodeUtf8Fatal( sourceContent, `${profile.codePrefix}-linked-config-invalid-utf8`, ), ); } catch (error) { throw codedError( `${profile.codePrefix}-linked-config-json-invalid`, error, ); } assert( isPlainObject(sourceConfig), `${profile.codePrefix}-linked-config-root-invalid`, ); for (const secret of collectApiKeys(sourceConfig)) suiteSecrets.add(secret); sourceConfigs.push({ name, sourcePath, metadata, sourceContent, config: sourceConfig, }); } const mergedSourceConfig = {}; for (const source of sourceConfigs) { mergeConfigPatch(mergedSourceConfig, source.config); } const overlayAgentId = streamAgentId ?? webSearchAgentId; const sourceEffective = overlayAgentId ? effectiveAgentLlmConfig(mergedSourceConfig, overlayAgentId) : null; let activeConfigSource = null; if (overlayAgentId && (webSearchAgentId || sourceEffective.stream !== true)) { const sameEffective = webSearchAgentId ? sameEffectiveAgentLlmWithoutWebSearch : sameEffectiveAgentLlmWithoutStream; activeConfigSource = sourceConfigs.find( (source) => source.name === configFileName && sameEffective( effectiveAgentLlmConfig(source.config, overlayAgentId), sourceEffective, ), ) ?? sourceConfigs.find((source) => sameEffective( effectiveAgentLlmConfig(source.config, overlayAgentId), sourceEffective, ), ); assert( Boolean(activeConfigSource), webSearchAgentId ? 'web-search-source-config-cannot-accept-search-only-overlay' : 'response-stream-source-config-cannot-accept-stream-only-overlay', ); } for (const source of sourceConfigs) { const linkedName = configOverlay ? `.source-${source.name}` : activeConfigSource ? source === activeConfigSource ? configFileName : `.source-${source.name}` : source.name; const linkedPath = path.join(appDataDir, linkedName); const storageMode = isWebSearchSuite() || isScopedAgentsSuite() || isProjectSkillSuite() || isParallelReadSuite() || isSupervisorSwarmSuite() || isSupervisorAutonomousPlayableLaneDefenseSuite() ? 'private-copy' : 'hardlink'; try { if (storageMode === 'private-copy') { await fs.copyFile( source.sourcePath, linkedPath, fsConstants.COPYFILE_EXCL | fsConstants.COPYFILE_FICLONE, ); await fs.chmod(linkedPath, 0o600); } else { // Existing isolated suites share credential-bearing config inodes. await fs.link(source.sourcePath, linkedPath); } } catch (error) { throw codedError(`${profile.codePrefix}-config-replica-failed`, error); } const linkedMetadata = await fs.lstat(linkedPath); const privateCopyValid = storageMode === 'private-copy' && (linkedMetadata.dev !== source.metadata.dev || linkedMetadata.ino !== source.metadata.ino) && (process.platform === 'win32' || (linkedMetadata.mode & 0o077) === 0); const hardlinkValid = storageMode === 'hardlink' && linkedMetadata.dev === source.metadata.dev && linkedMetadata.ino === source.metadata.ino; assert( linkedMetadata.isFile() && !linkedMetadata.isSymbolicLink() && (privateCopyValid || hardlinkValid), `${profile.codePrefix}-config-replica-identity-invalid`, ); state.isolatedRunner.configLinks.push({ storageMode, sourceName: source.name, linkedName, sourcePath: source.sourcePath, linkedPath, dev: source.metadata.dev, ino: source.metadata.ino, linkedDev: linkedMetadata.dev, linkedIno: linkedMetadata.ino, nlink: source.metadata.nlink, sourceMode: source.metadata.mode, sourceSize: source.metadata.size, sourceMtimeMs: source.metadata.mtimeMs, sourceCtimeMs: source.metadata.ctimeMs, sha256: createHash('sha256').update(source.sourceContent).digest('hex'), }); } if (configOverlay) { const primaryConfigPath = path.join(appDataDir, configFileName); const localOverlayPath = path.join(appDataDir, localConfigFileName); await fs.writeFile( primaryConfigPath, `${JSON.stringify(mergedSourceConfig)}\n`, { flag: 'wx', mode: 0o600 }, ); await fs.writeFile(localOverlayPath, `${JSON.stringify(configOverlay)}\n`, { flag: 'wx', mode: 0o600, }); const [primaryMetadata, overlayMetadata] = await Promise.all([ fs.lstat(primaryConfigPath), fs.lstat(localOverlayPath), ]); assert( primaryMetadata.isFile() && !primaryMetadata.isSymbolicLink() && overlayMetadata.isFile() && !overlayMetadata.isSymbolicLink() && (primaryMetadata.mode & 0o077) === 0 && (overlayMetadata.mode & 0o077) === 0, `${profile.codePrefix}-materialized-config-invalid`, ); state.isolatedRunner.configOverlayCreated = true; } else { assert( state.isolatedRunner.configLinks.some( (link) => link.linkedName === configFileName, ), `${profile.codePrefix}-primary-config-link-missing`, ); } if (activeConfigSource) { const overrideKey = webSearchAgentId ? 'webSearchEnabled' : 'stream'; const overlay = { agentLlm: { [overlayAgentId]: { [overrideKey]: true } } }; assert( collectApiKeys(overlay).length === 0 && JSON.stringify(Object.keys(overlay)) === JSON.stringify(['agentLlm']) && JSON.stringify(Object.keys(overlay.agentLlm)) === JSON.stringify([overlayAgentId]) && JSON.stringify(Object.keys(overlay.agentLlm[overlayAgentId])) === JSON.stringify([overrideKey]), webSearchAgentId ? 'web-search-overlay-shape-invalid' : 'response-stream-overlay-shape-invalid', ); await fs.writeFile( path.join(appDataDir, localConfigFileName), `${JSON.stringify(overlay)}\n`, { flag: 'wx', mode: 0o600 }, ); if (webSearchAgentId) { state.isolatedRunner.webSearchOverrideCreated = true; } else { state.isolatedRunner.streamOverrideCreated = true; } } const previousLeakCount = state.transcriptScanner?.count ?? 0; state.secrets = [...suiteSecrets]; rebuildSupervisorSwarmTranscriptScanner(); state.transcriptScanner.count = previousLeakCount; await installPlatformSessionFixtureIntoIsolatedAppData( sourceConfigDir, appDataDir, ); const unexpectedEndpoint = await fs .lstat(path.join(appDataDir, runnerEndpointFileName)) .catch((error) => { if (error?.code === 'ENOENT') return null; throw error; }); assert(!unexpectedEndpoint, `${profile.codePrefix}-endpoint-preexisted`); state.runtimeConfigDir = appDataDir; if (isSupervisorAutonomousPlayableLaneDefenseSuite()) { const isolatedConfig = await loadConfig(appDataDir); const expectedBinding = state.config.providerBinding; const agentIds = [projectSupervisorAgentId]; const bindings = agentIds.map((agentId) => { const effective = effectiveAgentLlmConfig(isolatedConfig.config, agentId); assert( ['apiKey', 'baseUrl', 'model', 'apiKind', 'reasoningEffort'].every( (key) => isNonEmptyString(effective[key]), ), 'supervisor-autonomous-playable-effective-provider-incomplete', ); return { providerModel: effective.model.trim(), providerApiKind: effective.apiKind.trim(), providerReasoningEffort: effective.reasoningEffort.trim(), providerBaseUrlSha256: hashValue(effective.baseUrl.trim()), }; }); const effectiveBinding = { ...bindings[0], boundAgentIds: [...agentIds].sort(), }; assert( expectedBinding && bindings.every( (binding) => JSON.stringify(binding) === JSON.stringify(bindings[0]), ) && sameSupervisorPlayableProviderBinding( effectiveBinding, expectedBinding, ), 'supervisor-autonomous-playable-effective-provider-binding-mismatch', ); state.supervisorAutonomousPlayable.expectedProviderBinding = { ...expectedBinding, }; state.supervisorAutonomousPlayable.effectiveProviderBinding = effectiveBinding; } if (streamAgentId) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( isolatedConfig.config, streamAgentId, ); assert( isolatedEffective.stream === true && ['apiKey', 'baseUrl', 'model'].every( (key) => typeof isolatedEffective[key] === 'string' && isolatedEffective[key].trim().length > 0, ), 'response-stream-effective-llm-config-invalid', ); state.responseStream.effectiveStreamEnabled = true; } if (webSearchAgentId) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( isolatedConfig.config, webSearchAgentId, ); if (isolatedEffective.apiKind === 'anthropic') { state.webSearch.gatewayDiagnosis = 'anthropic-web-search-unsupported'; throw new BlockedError(['web-search-anthropic-unsupported']); } assert( isolatedEffective.webSearchEnabled === true && ['apiKey', 'baseUrl', 'model'].every( (key) => typeof isolatedEffective[key] === 'string' && isolatedEffective[key].trim().length > 0, ), 'web-search-effective-llm-config-invalid', ); state.webSearch.effectiveEnabled = true; } if (isUserInputRuntimeSuite()) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( isolatedConfig.config, projectSupervisorAgentId, ); assert( isolatedEffective.model === 'gpt-5.5' && ['apiKey', 'baseUrl', 'model'].every( (key) => typeof isolatedEffective[key] === 'string' && isolatedEffective[key].trim().length > 0, ), 'user-input-effective-gpt-5-5-config-invalid', ); } if (isScopedAgentsSuite()) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( isolatedConfig.config, mainAgentId, ); assert( isolatedEffective.model === 'gpt-5.5' && isolatedEffective.apiKind === 'openai_chat' && ['apiKey', 'baseUrl', 'model'].every( (key) => typeof isolatedEffective[key] === 'string' && isolatedEffective[key].trim().length > 0, ), 'scoped-agents-effective-openai-chat-gpt-5-5-config-invalid', ); state.scopedAgents.effectiveModel = isolatedEffective.model; state.scopedAgents.effectiveApiKind = isolatedEffective.apiKind; } if (isProjectSkillSuite()) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( isolatedConfig.config, mainAgentId, ); assert( isolatedEffective.model === 'gpt-5.5' && isolatedEffective.apiKind === 'openai_chat' && ['apiKey', 'baseUrl', 'model'].every( (key) => typeof isolatedEffective[key] === 'string' && isolatedEffective[key].trim().length > 0, ), 'project-skill-effective-openai-chat-gpt-5-5-config-invalid', ); state.projectSkill.effectiveModel = isolatedEffective.model; state.projectSkill.effectiveApiKind = isolatedEffective.apiKind; } if (isParallelReadSuite()) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( isolatedConfig.config, mainAgentId, ); assert( isolatedEffective.model === 'gpt-5.5' && isolatedEffective.apiKind === 'openai_chat' && ['apiKey', 'baseUrl', 'model'].every( (key) => typeof isolatedEffective[key] === 'string' && isolatedEffective[key].trim().length > 0, ), 'parallel-read-effective-openai-chat-gpt-5-5-config-invalid', ); state.parallelRead.effectiveModel = isolatedEffective.model; state.parallelRead.effectiveApiKind = isolatedEffective.apiKind; } if (isSupervisorSwarmSuite()) { const isolatedConfig = await loadConfig(appDataDir); const requiredAgentIds = supervisorSwarmRequiredAgentIds; const requiredEffectiveConfigs = requiredAgentIds.map((agentId) => [ agentId, effectiveAgentLlmConfig(isolatedConfig.config, agentId), ]); assert( requiredEffectiveConfigs.every( ([agentId, effective]) => (isSupervisorSwarmToolPlanHandoffRunnerKillSuite() ? isNonEmptyString(effective.model) : effective.model === 'gpt-5.5') && effective.apiKind === 'openai_chat' && isNonEmptyString(effective.reasoningEffort) && Number.isSafeInteger(effective.requestTimeoutMs) && effective.requestTimeoutMs > 0 && Number.isSafeInteger(effective.maxRetries) && effective.maxRetries >= (isSupervisorSwarmInteractiveChatSuite() || (isSupervisorSwarmTransientRetrySuite() && agentId !== supervisorSwarmTransientRetryTargetAgentId) ? 0 : 1) && effective.maxRetries <= 3 && Number.isSafeInteger(effective.retryBackoffMs) && effective.retryBackoffMs > 0 && ['apiKey', 'baseUrl', 'model'].every( (key) => typeof effective[key] === 'string' && effective[key].trim().length > 0, ), ), isSupervisorSwarmToolPlanHandoffRunnerKillSuite() ? 'supervisor-swarm-effective-openai-chat-configured-model-invalid' : 'supervisor-swarm-effective-openai-chat-gpt-5-5-config-invalid', ); const globalEffective = effectiveAgentLlmConfig( { llm: isolatedConfig.config.llm }, '', ); if (isSupervisorSwarmMixedHarnessSuite()) { assert( globalEffective.model === 'gpt-5.5' && globalEffective.apiKind === 'openai_chat' && isNonEmptyString(globalEffective.reasoningEffort) && Number.isSafeInteger(globalEffective.requestTimeoutMs) && globalEffective.requestTimeoutMs > 0 && Number.isSafeInteger(globalEffective.maxRetries) && globalEffective.maxRetries >= 0 && globalEffective.maxRetries <= 3 && Number.isSafeInteger(globalEffective.retryBackoffMs) && globalEffective.retryBackoffMs > 0 && ['apiKey', 'baseUrl', 'model'].every((key) => isNonEmptyString(globalEffective[key]), ), 'supervisor-swarm-mixed-default-provider-policy-invalid', ); } const configuredAgentIds = Object.keys( isPlainObject(isolatedConfig.config.agentLlm) ? isolatedConfig.config.agentLlm : {}, ).filter(isNonEmptyString); const policyAgentIds = [ ...new Set([...requiredAgentIds, ...configuredAgentIds]), ].sort(); const effectivePolicies = policyAgentIds.map((agentId) => [ agentId, effectiveAgentLlmConfig(isolatedConfig.config, agentId), ]); const supervisorEffective = requiredEffectiveConfigs.find( ([agentId]) => agentId === projectSupervisorAgentId, )[1]; state.supervisorSwarm.effectiveModel = supervisorEffective.model; state.supervisorSwarm.effectiveApiKind = supervisorEffective.apiKind; state.supervisorSwarm.effectiveReasoningEffort = supervisorEffective.reasoningEffort; state.supervisorSwarm.effectiveRequestTimeoutMs = supervisorEffective.requestTimeoutMs; state.supervisorSwarm.effectiveMaxRetries = supervisorEffective.maxRetries; state.supervisorSwarm.effectiveRetryBackoffMs = supervisorEffective.retryBackoffMs; state.supervisorSwarm.effectiveDefaultPolicy = safeEffectiveAgentLlmPolicy(globalEffective); state.supervisorSwarm.effectiveAgentPolicies = Object.fromEntries( effectivePolicies.map(([agentId, effective]) => [ agentId, safeEffectiveAgentLlmPolicy(effective), ]), ); } if (isSteerRunnerKillSuite()) { const isolatedConfig = await loadConfig(appDataDir); const isolatedEffective = effectiveAgentLlmConfig( isolatedConfig.config, mainAgentId, ); assert( isolatedEffective.model === 'gpt-5.5' && isolatedEffective.apiKind === 'openai_chat' && ['apiKey', 'baseUrl', 'model'].every( (key) => typeof isolatedEffective[key] === 'string' && isolatedEffective[key].trim().length > 0, ), 'steer-runner-kill-effective-openai-chat-gpt-5-5-config-invalid', ); state.steerRunnerKill.effectiveModel = isolatedEffective.model; state.steerRunnerKill.effectiveApiKind = isolatedEffective.apiKind; } } export async function readIsolatedAppDataSentinel() { const runner = state.isolatedRunner; const profile = isolatedSuiteAppDataProfile(); assert( isNonEmptyString(runner.appDataDir) && isNonEmptyString(runner.ownerToken), 'isolated-appdata-ownership-missing', ); const sentinelPath = path.join(runner.appDataDir, profile.sentinelName); const metadata = await fs.lstat(sentinelPath); const sentinel = await readJson(sentinelPath); assert( metadata.isFile() && !metadata.isSymbolicLink() && sentinel.schemaVersion === profile.sentinelSchema && sentinel.token === runner.ownerToken && sentinel.ownerPid === process.pid && sentinel.createdAt === runner.createdAt, 'isolated-appdata-ownership-invalid', ); return sentinel; } export async function inspectOwnedRunnerIdentity(status) { await readIsolatedAppDataSentinel(); const runner = state.isolatedRunner; const pid = Number(status?.pid ?? status?.status?.pid); const bootId = runnerBootId(status); assert( status?.running === true && Number.isSafeInteger(pid) && pid > 1 && pid !== process.pid && isNonEmptyString(bootId), 'isolated-owned-runner-status-invalid', ); const endpointPath = path.join(runner.appDataDir, runnerEndpointFileName); const endpointMetadata = await fs.lstat(endpointPath); const endpoint = await readJson(endpointPath); assert( endpointMetadata.isFile() && !endpointMetadata.isSymbolicLink() && endpoint.pid === pid && endpoint.bootId === bootId && endpoint.protocolVersion === status.protocolVersion && endpoint.port === status.port && Number.isSafeInteger(endpoint.heartbeatAt) && endpoint.heartbeatAt >= runner.createdAt && isNonEmptyString(endpoint.token) && endpoint.token.length >= 32, 'isolated-owned-runner-endpoint-identity-invalid', ); return { pid, bootId, protocolVersion: endpoint.protocolVersion, port: endpoint.port, processIdentity: await captureOwnedRunnerProcessIdentity(pid), }; } export async function claimOwnedRunner(status = null) { const liveStatus = status ?? (await readRunnerStatus()); const identity = await inspectOwnedRunnerIdentity(liveStatus); const current = state.isolatedRunner.current; if (current) { assert( current.pid === identity.pid && current.bootId === identity.bootId && current.processIdentity.fingerprint === identity.processIdentity.fingerprint && current.killHandle?.closed === false, 'isolated-owned-runner-identity-changed-after-claim', ); return current; } const killHandle = await openOwnedRunnerKillHandle(identity.pid); try { const rechecked = await inspectOwnedRunnerIdentity( await readRunnerStatus(), ); assert( rechecked.pid === identity.pid && rechecked.bootId === identity.bootId && rechecked.protocolVersion === identity.protocolVersion && rechecked.port === identity.port && rechecked.processIdentity.fingerprint === identity.processIdentity.fingerprint, 'isolated-owned-runner-identity-changed-during-pidfd-claim', ); } catch (error) { await closeOwnedRunnerKillHandle(killHandle).catch(() => {}); throw error; } state.isolatedRunner.current = { ...identity, killHandle }; state.isolatedRunner.pidfdClaimCount += 1; return state.isolatedRunner.current; } export async function verifyOwnedRunnerForKill() { const claimed = state.isolatedRunner.current; assert(claimed, 'isolated-owned-runner-not-claimed'); const current = await inspectOwnedRunnerIdentity(await readRunnerStatus()); assert( current.pid === claimed.pid && current.bootId === claimed.bootId && current.protocolVersion === claimed.protocolVersion && current.port === claimed.port && current.processIdentity.fingerprint === claimed.processIdentity.fingerprint && claimed.killHandle?.pid === claimed.pid && claimed.killHandle.closed === false, 'isolated-owned-runner-identity-changed-before-kill', ); return claimed; } export async function ensureOwnedRunnerStableKillSupport() { if (process.platform === 'linux') { const python = await findControlledLinuxPython(); const probe = 'import os, signal; assert hasattr(os, "pidfd_open") and hasattr(signal, "pidfd_send_signal"); fd = os.pidfd_open(os.getpid(), 0); os.close(fd)'; try { await runProcess(python, ['-I', '-S', '-c', probe], { cwd: appRoot, timeoutMs: 30_000, env: { LANG: 'C', LC_ALL: 'C', PATH: '/usr/bin:/bin' }, }); } catch (error) { throw codedError('isolated-runner-pidfd-support-unavailable', error); } return; } if (process.platform === 'win32') { const handle = await openOwnedRunnerKillHandle(process.pid); await closeOwnedRunnerKillHandle(handle); return; } throw codedError('isolated-runner-stable-kill-handle-platform-unsupported'); } export async function findControlledLinuxPython() { if (state.linuxPidfdPythonPath) return state.linuxPidfdPythonPath; for (const candidate of ['/usr/bin/python3', '/usr/local/bin/python3']) { const resolved = await fs.realpath(candidate).catch(() => null); const metadata = resolved ? await fs.stat(resolved).catch(() => null) : null; if (metadata?.isFile() && (metadata.mode & 0o111) !== 0) { state.linuxPidfdPythonPath = resolved; return resolved; } } throw codedError('isolated-runner-controlled-python-unavailable'); } export async function findControlledWindowsPowerShell() { if (state.windowsProcessHandlePowerShellPath) return state.windowsProcessHandlePowerShellPath; const systemRoot = process.env.SystemRoot ?? process.env.WINDIR; assert( isNonEmptyString(systemRoot) && path.isAbsolute(systemRoot), 'isolated-runner-windows-system-root-invalid', ); const candidate = path.join( systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe', ); const resolved = await fs.realpath(candidate).catch(() => null); const metadata = resolved ? await fs.stat(resolved).catch(() => null) : null; assert( metadata?.isFile(), 'isolated-runner-controlled-powershell-unavailable', ); state.windowsProcessHandlePowerShellPath = resolved; return resolved; } export function controlledWindowsPowerShellEnvironment(extra = {}) { const systemRoot = process.env.SystemRoot ?? process.env.windir; assert( isNonEmptyString(systemRoot) && path.isAbsolute(systemRoot), 'isolated-runner-windows-system-root-invalid', ); const powershellRoot = path.join( systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', ); const programFiles = process.env.ProgramW6432 ?? process.env.ProgramFiles; const moduleRoots = [path.join(powershellRoot, 'Modules')]; if (isNonEmptyString(programFiles) && path.isAbsolute(programFiles)) { moduleRoots.push(path.join(programFiles, 'WindowsPowerShell', 'Modules')); } const environment = { SystemRoot: systemRoot, windir: systemRoot, ComSpec: path.join(systemRoot, 'System32', 'cmd.exe'), PATH: [powershellRoot, path.join(systemRoot, 'System32'), systemRoot].join( path.delimiter, ), PATHEXT: '.COM;.EXE;.BAT;.CMD', PSModulePath: moduleRoots.join(path.delimiter), }; for (const name of ['TEMP', 'TMP', 'LOCALAPPDATA', 'USERPROFILE']) { if (isNonEmptyString(process.env[name])) environment[name] = process.env[name]; } if (isNonEmptyString(programFiles)) environment.ProgramFiles = programFiles; return { ...environment, ...extra }; } export async function openOwnedRunnerKillHandle(pid) { assert( Number.isSafeInteger(pid) && pid > 1, 'isolated-runner-stable-kill-handle-open-precondition-invalid', ); let child; let readyMarker; let exitedMarker; let kind; if (process.platform === 'linux') { const python = await findControlledLinuxPython(); child = spawn( python, ['-I', '-S', '-c', linuxPidfdHelperSource, String(pid)], { cwd: appRoot, env: { LANG: 'C', LC_ALL: 'C', PATH: '/usr/bin:/bin' }, stdio: ['pipe', 'pipe', 'pipe'], }, ); readyMarker = Buffer.from('PIDFD_READY\n'); exitedMarker = Buffer.from('PIDFD_EXITED\n'); kind = 'linux-pidfd'; } else if (process.platform === 'win32') { const powershell = await findControlledWindowsPowerShell(); child = spawn( powershell, [ '-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(windowsProcessHandleHelperSource, 'utf16le').toString( 'base64', ), ], { cwd: appRoot, env: controlledWindowsPowerShellEnvironment({ AGC_OWNED_RUNNER_PID: String(pid), }), stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true, }, ); readyMarker = Buffer.from('HANDLE_READY'); exitedMarker = Buffer.from('HANDLE_EXITED'); kind = 'windows-process-handle'; } else { throw codedError('isolated-runner-stable-kill-handle-platform-unsupported'); } const handle = { kind, readyMarker, exitedMarker, pid, child, stdout: Buffer.alloc(0), stderr: Buffer.alloc(0), closed: false, command: null, stdinError: null, }; activeCommandChildren.add(child); child.once('error', () => { if (!Number.isSafeInteger(child.pid)) activeCommandChildren.delete(child); }); child.once('close', () => { activeCommandChildren.delete(child); handle.closed = true; }); child.stdin.on('error', (error) => { handle.stdinError ??= error; }); child.stdout.on('data', (chunk) => { handle.stdout = appendBounded(handle.stdout, chunk, 4_096); }); child.stderr.on('data', (chunk) => { handle.stderr = appendBounded(handle.stderr, chunk, 4_096); }); await waitForOwnedRunnerKillHandleReady(handle); return handle; } export async function waitForOwnedRunnerKillHandleReady(handle) { await new Promise((resolve, reject) => { const timer = setTimeout(() => { settle(() => { void terminateOwnedRunnerKillHandleHelper(handle).then( () => reject( codedError('isolated-runner-stable-kill-handle-open-timeout'), ), reject, ); }); }, 10_000); const settle = (callback) => { clearTimeout(timer); handle.child.stdout.off('data', onData); handle.child.off('error', onError); handle.child.off('close', onClose); callback(); }; const onData = () => { if (handle.stdout.includes(handle.readyMarker)) { settle(resolve); } }; const onError = (error) => settle(() => reject( codedError( 'isolated-runner-stable-kill-handle-helper-spawn-failed', error, ), ), ); const onClose = () => settle(() => reject(codedError('isolated-runner-stable-kill-handle-open-failed')), ); handle.child.stdout.on('data', onData); handle.child.on('error', onError); handle.child.on('close', onClose); onData(); }); } export async function closeOwnedRunnerKillHandle(handle) { if (!handle || handle.closed) return; if (handle.child.exitCode !== null || handle.child.signalCode !== null) { await waitForChildClose(handle.child, 2_000, 5_000); handle.closed = true; return; } if (handle.command !== null) { await terminateOwnedRunnerKillHandleHelper(handle); return; } handle.command = 'CLOSE'; try { await writeOwnedRunnerKillHandleCommand(handle, 'CLOSE'); const result = await waitForChildClose(handle.child, 10_000); assert( result.code === 0, 'isolated-runner-stable-kill-handle-close-failed', ); handle.closed = true; } catch (error) { await rethrowOwnedRunnerKillHandleFailureAfterCleanup(handle, error); } } export async function signalOwnedRunnerKillHandle(handle) { assert( handle && handle.closed === false && handle.command === null && handle.child.exitCode === null && handle.child.signalCode === null, 'isolated-runner-stable-kill-handle-not-live', ); handle.command = 'KILL'; try { await writeOwnedRunnerKillHandleCommand(handle, 'KILL'); const result = await waitForChildClose(handle.child, 15_000); assert( result.code === 0 && handle.stdout.includes(handle.exitedMarker), 'isolated-runner-stable-kill-handle-signal-failed', ); handle.closed = true; } catch (error) { await rethrowOwnedRunnerKillHandleFailureAfterCleanup(handle, error); } } export async function writeOwnedRunnerKillHandleCommand( handle, command, timeoutMs = 5_000, ) { assert( handle?.child?.stdin && !handle.child.stdin.destroyed && !handle.child.stdin.writableEnded && ['CLOSE', 'KILL'].includes(command) && Number.isSafeInteger(timeoutMs) && timeoutMs > 0, 'isolated-runner-stable-kill-handle-stdin-unavailable', ); await new Promise((resolve, reject) => { let settled = false; let timer = null; const finish = (error = null) => { if (settled) return; settled = true; clearTimeout(timer); handle.child.stdin.off('error', onError); if (error) reject(error); else resolve(); }; const onError = (error) => { finish( codedError('isolated-runner-stable-kill-handle-stdin-failed', error), ); }; timer = setTimeout(() => { finish(codedError('isolated-runner-stable-kill-handle-stdin-timeout')); }, timeoutMs); handle.child.stdin.once('error', onError); try { handle.child.stdin.end(`${command}\n`, () => finish()); } catch (error) { onError(error); } }); } async function rethrowOwnedRunnerKillHandleFailureAfterCleanup(handle, error) { try { await terminateOwnedRunnerKillHandleHelper(handle); } catch (cleanupError) { throw codedError( isNonEmptyString(error?.code) ? error.code : 'isolated-runner-stable-kill-handle-operation-failed', new AggregateError( [error, cleanupError], 'stable kill handle operation and helper cleanup both failed', ), ); } throw error; } async function terminateOwnedRunnerKillHandleHelper(handle) { if (!handle || handle.closed) return; const child = handle.child; if (child.exitCode !== null || child.signalCode !== null) { await waitForChildClose(child, 2_000, 5_000); handle.closed = true; return; } let initialKillError = null; try { if (!child.kill('SIGKILL')) { initialKillError = codedError( 'isolated-runner-stable-kill-handle-helper-kill-rejected', ); } } catch (error) { initialKillError = codedError( 'isolated-runner-stable-kill-handle-helper-kill-failed', error, ); } try { await waitForChildClose(child, 2_000, 5_000); handle.closed = true; } catch (error) { if (child.exitCode !== null || child.signalCode !== null) { handle.closed = true; return; } if (initialKillError) { throw codedError( 'isolated-runner-stable-kill-handle-helper-cleanup-failed', new AggregateError( [initialKillError, error], 'stable kill handle helper could not be terminated and reaped', ), ); } throw error; } } export async function waitForChildClose( child, timeoutMs, reapTimeoutMs = 5_000, ) { assert( child && Number.isSafeInteger(timeoutMs) && timeoutMs > 0 && Number.isSafeInteger(reapTimeoutMs) && reapTimeoutMs > 0, 'isolated-runner-stable-kill-handle-wait-precondition-invalid', ); if ( (child.exitCode !== null || child.signalCode !== null) && childProcessStdioClosed(child) ) { return { code: child.exitCode, signal: child.signalCode }; } return new Promise((resolve, reject) => { let settled = false; let timedOut = false; let killError = null; let reapTimer = null; const finish = (error = null, result = null) => { if (settled) return; settled = true; clearTimeout(timer); clearTimeout(reapTimer); child.off('error', onError); child.off('close', onClose); if (error) reject(error); else resolve(result); }; const timer = setTimeout(() => { timedOut = true; try { if (!child.kill('SIGKILL')) { killError = codedError( 'isolated-runner-stable-kill-handle-helper-kill-rejected', ); } } catch (error) { killError = codedError( 'isolated-runner-stable-kill-handle-helper-kill-failed', error, ); } reapTimer = setTimeout(() => { finish( codedError( 'isolated-runner-stable-kill-handle-helper-reap-timeout', killError, ), ); }, reapTimeoutMs); if ( (child.exitCode !== null || child.signalCode !== null) && childProcessStdioClosed(child) ) { onClose(child.exitCode, child.signalCode); } }, timeoutMs); const onError = (error) => { finish( codedError('isolated-runner-stable-kill-handle-helper-failed', error), ); }; const onClose = (code, signal) => { if (timedOut) { finish( codedError( 'isolated-runner-stable-kill-handle-helper-timeout', killError, ), ); } else { finish(null, { code, signal }); } }; child.once('error', onError); child.once('close', onClose); if ( (child.exitCode !== null || child.signalCode !== null) && childProcessStdioClosed(child) ) { onClose(child.exitCode, child.signalCode); } }); } function childProcessStdioClosed(child) { return [child.stdin, child.stdout, child.stderr] .filter(Boolean) .every((stream) => stream.destroyed || stream.closed === true); } export async function captureOwnedRunnerProcessIdentity(pid) { const expectedAppData = state.isolatedRunner.appDataDir; assert( isNonEmptyString(expectedAppData) && Boolean(state.cliBinary), 'isolated-runner-process-identity-context-missing', ); if (process.platform === 'linux') { const [executable, expectedExecutable, stat, commandLine] = await Promise.all([ fs.realpath(`/proc/${pid}/exe`), fs.realpath(state.cliBinary), fs.readFile(`/proc/${pid}/stat`, 'utf8'), fs.readFile(`/proc/${pid}/cmdline`), ]); const closeParenthesis = stat.lastIndexOf(')'); const fields = stat .slice(closeParenthesis + 1) .trim() .split(/\s+/u); const startTime = fields[19]; const argv = commandLine.toString('utf8').split('\0').filter(Boolean); const configIndex = argv.indexOf('--config-dir'); assert( closeParenthesis > 0 && isNonEmptyString(startTime) && executable === expectedExecutable && argv.includes('--agent-runner') && configIndex >= 0 && argv[configIndex + 1] === expectedAppData, 'isolated-runner-linux-process-identity-invalid', ); return { kind: 'linux-proc', fingerprint: hashValue(JSON.stringify({ executable, startTime, argv })), }; } if (process.platform === 'darwin') { const result = await runProcess( '/bin/ps', ['-p', String(pid), '-o', 'lstart=', '-o', 'command='], { cwd: appRoot, timeoutMs: 30_000 }, ); assert( result.stdout.includes(path.basename(state.cliBinary)) && result.stdout.includes('--agent-runner') && result.stdout.includes(expectedAppData), 'isolated-runner-darwin-process-identity-invalid', ); return { kind: 'darwin-ps', fingerprint: hashValue(result.stdout.trim()), }; } if (process.platform === 'win32') { const powershell = await findControlledWindowsPowerShell(); const script = `$process = Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}"; if ($null -eq $process) { exit 3 }; $process | Select-Object ProcessId,CreationDate,ExecutablePath,CommandLine | ConvertTo-Json -Compress`; const result = await runProcess( powershell, ['-NoProfile', '-NonInteractive', '-Command', script], { cwd: appRoot, timeoutMs: 30_000, env: controlledWindowsPowerShellEnvironment(), }, ); const value = JSON.parse(result.stdout); const executable = path.resolve(String(value.ExecutablePath ?? '')); const expectedExecutable = path.resolve(state.cliBinary); const commandLine = String(value.CommandLine ?? ''); assert( Number(value.ProcessId) === pid && executable.toLowerCase() === expectedExecutable.toLowerCase() && isNonEmptyString(value.CreationDate) && commandLine.includes('--agent-runner') && commandLine.includes(expectedAppData), 'isolated-runner-windows-process-identity-invalid', ); return { kind: 'windows-cim', fingerprint: hashValue( JSON.stringify({ pid, creationDate: value.CreationDate, executable: executable.toLowerCase(), commandLine, }), ), }; } throw codedError('isolated-runner-process-identity-platform-unsupported'); } export function isProcessAlive(pid) { if (!Number.isSafeInteger(pid) || pid <= 1 || pid === process.pid) return false; try { process.kill(pid, 0); return true; } catch (error) { return error?.code === 'EPERM'; } } export async function killRunnerPidOnce(pid, ownedRunner) { if (isIsolatedRunnerSuite()) { assert(ownedRunner, 'isolated-runner-pid-kill-fallback-forbidden'); } if (ownedRunner) { const claimed = state.isolatedRunner.current; assert( claimed?.pid === pid && claimed.killHandle?.pid === pid, 'isolated-runner-stable-kill-handle-identity-missing', ); await signalOwnedRunnerKillHandle(claimed.killHandle); state.isolatedRunner.pidfdSignalCount += 1; state.runnerKilled = true; state.isolatedRunner.current = null; return; } try { process.kill(pid, 'SIGKILL'); } catch (error) { throw codedError('runner-sigkill-failed', error); } state.runnerKilled = true; const deadline = Date.now() + 10_000; while (Date.now() < deadline) { if (!isProcessAlive(pid)) { if (ownedRunner) state.isolatedRunner.current = null; return; } await sleep(50); } throw codedError('runner-still-alive-after-sigkill'); } export async function stopClaimedOwnedRunnerWithoutEndpoint() { const claimed = state.isolatedRunner.current; if (!claimed) return; if (!isProcessAlive(claimed.pid)) { await closeOwnedRunnerKillHandle(claimed.killHandle); state.isolatedRunner.current = null; return; } let currentIdentity; try { currentIdentity = await captureOwnedRunnerProcessIdentity(claimed.pid); } catch (error) { if (!isProcessAlive(claimed.pid)) { await closeOwnedRunnerKillHandle(claimed.killHandle); state.isolatedRunner.current = null; return; } throw error; } assert( currentIdentity.fingerprint === claimed.processIdentity.fingerprint, 'isolated-owned-runner-identity-changed-without-endpoint', ); await killRunnerPidOnce(claimed.pid, true); } export async function stopOwnedIsolatedRunner() { await readIsolatedAppDataSentinel(); const endpointPath = path.join( state.isolatedRunner.appDataDir, runnerEndpointFileName, ); const endpointMetadata = await fs.lstat(endpointPath).catch((error) => { if (error?.code === 'ENOENT') return null; throw error; }); if (!endpointMetadata) { assert( state.isolatedRunner.current || !state.isolatedRunner.launchAttempted, 'isolated-owned-runner-endpoint-missing-before-stable-claim', ); await stopClaimedOwnedRunnerWithoutEndpoint(); return; } assert( endpointMetadata.isFile() && !endpointMetadata.isSymbolicLink(), 'isolated-owned-runner-endpoint-not-regular-file', ); const endpoint = await readJson(endpointPath); const status = await readRunnerStatus(); if (status?.running !== true) { assert( !isProcessAlive(Number(endpoint.pid)), 'isolated-owned-runner-live-pid-without-identity', ); await closeOwnedRunnerKillHandle(state.isolatedRunner.current?.killHandle); state.isolatedRunner.current = null; return; } await claimOwnedRunner(status); await killRunnerOnce(); } export async function verifyIsolatedSuiteConfigLinksUnchanged() { for (const link of state.isolatedRunner.configLinks) { const [sourceMetadata, linkedMetadata, sourceContent, linkedContent] = await Promise.all([ fs.lstat(link.sourcePath), fs.lstat(link.linkedPath), fs.readFile(link.sourcePath), fs.readFile(link.linkedPath), ]); const sourceHash = createHash('sha256').update(sourceContent).digest('hex'); const linkedHash = createHash('sha256').update(linkedContent).digest('hex'); const replicaIdentityValid = link.storageMode === 'private-copy' ? linkedMetadata.dev === link.linkedDev && linkedMetadata.ino === link.linkedIno && (linkedMetadata.dev !== sourceMetadata.dev || linkedMetadata.ino !== sourceMetadata.ino) && (process.platform === 'win32' || (linkedMetadata.mode & 0o077) === 0) : linkedMetadata.dev === link.dev && linkedMetadata.ino === link.ino; const sourceMetadataStable = sourceMetadata.mode === link.sourceMode && sourceMetadata.size === link.sourceSize && sourceMetadata.mtimeMs === link.sourceMtimeMs && (link.storageMode !== 'private-copy' || (sourceMetadata.ctimeMs === link.sourceCtimeMs && sourceMetadata.nlink === link.nlink)); assert( sourceMetadata.isFile() && !sourceMetadata.isSymbolicLink() && linkedMetadata.isFile() && !linkedMetadata.isSymbolicLink() && sourceMetadata.dev === link.dev && sourceMetadata.ino === link.ino && sourceMetadataStable && replicaIdentityValid && sourceHash === link.sha256 && linkedHash === link.sha256, 'isolated-source-config-changed-during-suite', ); } const fixture = state.isolatedRunner; if ( fixture.platformSessionFixtureSourcePath && fixture.platformSessionFixturePath && fixture.platformSessionFixtureSha256 ) { const [sourceMetadata, isolatedMetadata, sourceBytes, isolatedBytes] = await Promise.all([ fs.lstat(fixture.platformSessionFixtureSourcePath), fs.lstat(fixture.platformSessionFixturePath), fs.readFile(fixture.platformSessionFixtureSourcePath), fs.readFile(fixture.platformSessionFixturePath), ]); assert( sourceMetadata.isFile() && !sourceMetadata.isSymbolicLink() && isolatedMetadata.isFile() && !isolatedMetadata.isSymbolicLink() && createHash('sha256').update(sourceBytes).digest('hex') === fixture.platformSessionFixtureSha256 && createHash('sha256').update(isolatedBytes).digest('hex') === fixture.platformSessionFixtureSha256, 'supervisor-autonomous-playable-platform-session-fixture-changed', ); } } export async function verifySourceConfigLinkCountsRestored() { for (const link of state.isolatedRunner.configLinks) { const metadata = await fs.lstat(link.sourcePath); assert( metadata.isFile() && !metadata.isSymbolicLink() && metadata.dev === link.dev && metadata.ino === link.ino && metadata.nlink === link.nlink, 'isolated-source-config-link-count-not-restored', ); } } export async function removeIsolatedSuiteAppData() { await readIsolatedAppDataSentinel(); const profile = isolatedSuiteAppDataProfile(); const [sourceConfigDir, appDataDir] = await Promise.all([ fs.realpath(state.options.configDir), fs.realpath(state.isolatedRunner.appDataDir), ]); const cleanupPathValid = isolatedSuiteUsesSiblingAppData() ? !isPathInside(sourceConfigDir, appDataDir) && path.dirname(appDataDir) === path.dirname(sourceConfigDir) : isPathInside(sourceConfigDir, appDataDir); assert( cleanupPathValid && path.basename(appDataDir).startsWith(profile.prefix), 'isolated-appdata-cleanup-path-invalid', ); let ownershipError = null; try { await verifySourceAppDataDirectoryUntouched(); await verifyIsolatedSuiteConfigLinksUnchanged(); await verifySourceRunnerEndpointUnchanged(); } catch (error) { ownershipError = error; } try { await fs.rm(appDataDir, { recursive: true, force: false }); } finally { if (state.isolatedRunner.platformSessionFixtureSourcePath) { const previous = state.isolatedRunner.platformSessionFixturePreviousEnv; if (previous === undefined) { delete process.env[platformSessionFixtureEnv]; } else { process.env[platformSessionFixtureEnv] = previous; } } state.isolatedRunner.platformSessionFixturePath = null; state.isolatedRunner.platformSessionFixtureSourcePath = null; state.isolatedRunner.platformSessionFixtureSha256 = null; state.isolatedRunner.platformSessionFixturePreviousEnv = undefined; } state.runtimeConfigDir = state.options.configDir; try { await verifySourceConfigLinkCountsRestored(); state.isolatedRunner.sourceConfigLinksVerified = true; } catch (error) { ownershipError ??= error; } if (ownershipError) throw ownershipError; return true; }