@agimon-ai/doompi-team 0.0.1-alpha.13 → 0.0.1-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../adapters/atomicJson.cjs`);require(`../../../shared/atomicJson.cjs`);const n=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),r=require(`../../../adapters/process/env.cjs`),i=require(`../../../container/types.cjs`),a=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs`),o=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs`),s=require(`../../intercom/supervisorControlChannel.cjs`),c=require(`../../../shared/paths.cjs`),l=require(`./statusWriter.cjs`),u=require(`../shared/runtimeRegistry.cjs`),d=require(`../../../adapters/asyncJobTracker.cjs`);require(`./asyncJobTracker.cjs`);const f=require(`../../intercom/nativeTeamChannel.cjs`),p=require(`../../../shared/artifacts.cjs`),m=require(`../../../adapters/runRegistry.cjs`);require(`../registry/runRegistry.cjs`);const h=require(`../shared/claudeFableProfile.cjs`),g=require(`../shared/piArgs.cjs`),_=require(`./spawnHandshake.cjs`);let v=require(`node:fs`);v=e.__toESM(v,1);let y=require(`node:path`);y=e.__toESM(y,1);let b=require(`@agimon-ai/doompi-config`),x=require(`inversify`),S=require(`node:child_process`),C=require(`node:url`);function w(e){return y.join(c.currentRunsDir(),e)}function T(e){return y.join(w(e),`handshake.json`)}function E(e){return y.join(w(e),`fable-profile-result.json`)}function D(e){return y.join(w(e),`system-prompt.md`)}function O(e,t){let n=y.dirname((0,C.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)),r=y.resolve(n,`../..`),i=y.join(r,e,`${t}.mjs`);if(v.existsSync(i))return i;let a=y.join(r,e,`${t}.ts`);if(v.existsSync(a))return a;throw Error(`Doom Team runner '${t}' is unavailable at '${i}' or '${a}'.`)}function k(){return O(`runs`,`sdkRunnerEntry`)}function A(){return O(`runs/background`,`cliRunnerEntry`)}let j=class{constructor(e,t){this.jobTracker=e,this.terminalPersistence=t}defaultHandshakeTimeoutMs=1e4;now(){return Date.now()}readCurrentTeamRoot(){return f.readNativeTeamRootFromEnvironment()}createStatusWriter(){return new l.CoalescedStatusWriter}createSpawnHandshake(){return new _.SpawnHandshake}writeLaunchConfig(e,n){t.writePrivateAtomicJson(e,n)}removeLaunchConfig(e){v.rmSync(e,{force:!0})}readRunState(e){try{let t=JSON.parse(v.readFileSync(y.join(w(e),l.STATUS_FILE_NAME),`utf8`));return typeof t.state==`string`?t.state:void 0}catch{return}}buildLaunchArgs(e){return g.buildPiArgs(e)}spawnChild(e,t,n){let i=n.env[r.SUBAGENT_RUN_ID_ENV],a,o;if(i){let e=w(i);v.mkdirSync(e,{recursive:!0}),a=v.openSync(y.join(e,`runner.stdout.log`),`a`,384);try{o=v.openSync(y.join(e,`runner.stderr.log`),`a`,384)}catch(e){throw v.closeSync(a),Error(`Could not open runner stderr for '${i}'.`,{cause:e})}}let s;try{s=(0,S.spawn)(e,t,{cwd:n.cwd,detached:!0,stdio:[`ignore`,a??`ignore`,o??`ignore`],windowsHide:!0,env:n.env})}finally{a!==void 0&&v.closeSync(a),o!==void 0&&v.closeSync(o)}return s.unref(),{pid:s.pid,onError:e=>s.on(`error`,e),onExit:e=>s.on(`exit`,e)}}async spawn(e){let{runId:t,agent:n,task:r,cwd:i}=e,a=e.fanout?e.childIndex:void 0,o=T(t),l=c.currentRunConfigPath(t),g=e.artifacts===!1?void 0:p.getArtifactPaths(p.getArtifactsDir(e.parentSessionFile??null,i,e.artifactDir??`session`),t,n).transcriptPath,_=w(t),x=e.childIndex??0,S=this.readCurrentTeamRoot(),{args:C,env:O,sdk:j}=this.buildLaunchArgs({...e.piArgs,runId:t,task:r,childIndex:e.childIndex,steerInboxDir:s.stepSteerInboxDir(_,x),steerCapabilityPath:s.steerCapabilityPath(_,x),steerAckDir:s.steerAcksDir(_,x),teamToolEnabled:!!S});(0,b.createHarnessSession)({...(0,b.getHarnessState)(),childExtensions:j.extensions},{directory:_,environment:O,unclaimed:!0});let M={...process.env,...O,...c.sessionScopeEnvironment(c.requireCurrentSessionScope()),...S?f.nativeTeamRootEnvironment(S):{}},N;if(u.isPiRuntime(e.runtime)&&j.systemPrompt)try{v.mkdirSync(_,{recursive:!0});let e=D(t);v.writeFileSync(e,j.systemPrompt,{mode:384}),N=e}catch{N=void 0}let P=this.now(),F=this.createStatusWriter();F.open(t,{version:1,runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,...e.sensitiveTask?{}:{task:r},cwd:i,...e.piArgs.model?{model:e.piArgs.model}:{},...e.inlineAgent?{inlineAgent:e.inlineAgent}:{},state:`queued`,startedAt:P,lastUpdate:P,...a===void 0?{}:{fanoutIndex:a},...e.internal?{internal:!0}:{},...u.isPiRuntime(e.runtime)?{}:{runtime:e.runtime},...g?{transcriptPath:g}:{},...N?{systemPromptPath:N}:{},...N&&j.systemPromptMode?{systemPromptMode:j.systemPromptMode}:{},...j.tools?{tools:j.tools}:{},...j.excludeTools?.length?{excludeTools:j.excludeTools}:{},...j.noTools?{noTools:!0}:{}}),u.isPiRuntime(e.runtime)&&this.writeLaunchConfig(l,{runId:t,agent:n,task:r,cwd:i,childIndex:e.childIndex,handshakePath:o,sdk:j,...g?{transcriptPath:g}:{},args:C,env:M});let I,L=e=>{throw this.removeLaunchConfig(l),I&&h.cleanupClaudeFableLaunch(I),v.rmSync(E(t),{force:!0}),F.updateSync(t=>{t.state=`failed`,t.error=e,t.lastUpdate=this.now()}),Error(e)},R=u.isPiRuntime(e.runtime);if(!R){let a=e.piArgs.capabilityCeiling;if(a&&(!e.externalProfile||!a.allowedExternalProfiles.includes(e.externalProfile)))return L(`External profile '${e.externalProfile??`none`}' is denied by the active capability ceiling.`);if(e.externalProfile!==void 0&&e.externalProfile!==h.CLAUDE_FABLE_PROFILE)return L(`External profile '${e.externalProfile}' is not trusted.`);if(e.externalProfile===h.CLAUDE_FABLE_PROFILE){if(e.runtime!==`claude`||e.piArgs.model!==`fable`)return L(`The Fable profile requires runtime claude and model fable.`);if(e.inlineAgent||e.piArgs.sessionFile||e.piArgs.parentSessionId)return L(`The Fable profile requires a fresh isolated context.`);I=h.prepareClaudeFableLaunch({runId:t,prompt:r,repositoryCwd:i,privateRoot:_}),this.writeLaunchConfig(l,{runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,runtime:`claude`,command:I.command,args:I.args,cwd:I.cwd,env:I.env,profile:I.profile,stdinPath:I.stdinPath,cleanupPaths:I.cleanupPaths,profileResultPath:E(t),internal:e.internal===!0,handshakePath:o,resultPath:y.join(c.currentResultsDir(),`${t}.json`)})}else this.writeLaunchConfig(l,{runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,...u.resolveRuntimeLaunch(e.runtime,e.runtimes??{},{prompt:r,model:e.piArgs.model,cwd:i}),cwd:i,env:O,handshakePath:o,resultPath:y.join(c.currentResultsDir(),`${t}.json`)})}let{pid:z,onError:B,onExit:V}=this.spawnChild(process.execPath,[R?k():A()],{cwd:i,env:M});if(z===void 0)return L(`Async spawn for '${n}' (run '${t}') did not produce a pid.`);let H;B(e=>{H=e}),m.registerRun(c.requireCurrentSessionScope(),{runId:t,pid:z,agent:n,runtime:e.runtime??`pi`,startedAt:P}),this.terminalPersistence.trackChild(z),V((e,n)=>{this.terminalPersistence.untrackChild(z),m.releaseRun(c.requireCurrentSessionScope(),t),!(e===0&&n===null)&&(F.updateSync(t=>{t.state&&d.TERMINAL_ASYNC_JOB_STATES.has(t.state)||(t.state=`failed`,t.error=`SDK runner process ${z} exited unexpectedly (${n??`code ${e??`unknown`}`}).`,t.lastUpdate=this.now(),t.endedAt=this.now())}),this.jobTracker.track(t))});let U=await this.createSpawnHandshake().waitForHandshake({path:o,timeoutMs:e.handshakeTimeoutMs??this.defaultHandshakeTimeoutMs}).promise;if(U.status!==`signalled`)return this.terminalPersistence.untrackChild(z),L(U.status===`failed`?U.error:H?.message??`Timed out waiting for '${n}' (run '${t}') to start.`);this.removeLaunchConfig(l);let W=this.readRunState(t);return(!W||!d.TERMINAL_ASYNC_JOB_STATES.has(W))&&F.updateSync(e=>{e.state=`running`,e.lastUpdate=this.now()}),this.jobTracker.track(t),{runId:t,pid:z}}};j=n.__decorate([(0,x.injectable)(),o.__decorateParam(0,(0,x.inject)(i.DOOM_TEAM_TYPES.AsyncJobTracker)),o.__decorateParam(1,(0,x.inject)(i.DOOM_TEAM_TYPES.TerminalPersistenceService)),a.__decorateMetadata(`design:paramtypes`,[Object,Object])],j),Object.defineProperty(exports,`AsyncSubagentSpawner`,{enumerable:!0,get:function(){return j}}),exports.fableProfileResultPathFor=E,exports.handshakePathFor=T,exports.runDirFor=w;
1
+ const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../adapters/atomicJson.cjs`);require(`../../../shared/atomicJson.cjs`);const n=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.cjs`),r=require(`../../../adapters/process/env.cjs`),i=require(`../../../container/types.cjs`),a=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.cjs`),o=require(`../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.cjs`),s=require(`../../intercom/supervisorControlChannel.cjs`),c=require(`../../../shared/paths.cjs`),l=require(`./statusWriter.cjs`),u=require(`../shared/runtimeRegistry.cjs`),d=require(`../../../adapters/asyncJobTracker.cjs`);require(`./asyncJobTracker.cjs`);const f=require(`../../intercom/nativeTeamChannel.cjs`),p=require(`../../../shared/artifacts.cjs`),m=require(`../../../adapters/runRegistry.cjs`);require(`../registry/runRegistry.cjs`);const h=require(`../shared/claudeFableProfile.cjs`),g=require(`../shared/piArgs.cjs`),_=require(`./spawnHandshake.cjs`);let v=require(`node:fs`);v=e.__toESM(v,1);let y=require(`node:path`);y=e.__toESM(y,1);let b=require(`@agimon-ai/doompi-config`),x=require(`inversify`),S=require(`@agimon-ai/doompi-extension-contracts/child-process`),C=require(`node:child_process`),w=require(`node:url`);function T(e){return y.join(c.currentRunsDir(),e)}function E(e){return y.join(T(e),`handshake.json`)}function D(e){return y.join(T(e),`fable-profile-result.json`)}function O(e){return y.join(T(e),`system-prompt.md`)}function k(e,t){let n=y.dirname((0,w.fileURLToPath)(require(`url`).pathToFileURL(__filename).href)),r=y.resolve(n,`../..`),i=y.join(r,e,`${t}.mjs`);if(v.existsSync(i))return i;let a=y.join(r,e,`${t}.ts`);if(v.existsSync(a))return a;throw Error(`Doom Team runner '${t}' is unavailable at '${i}' or '${a}'.`)}function A(){return k(`runs`,`sdkRunnerEntry`)}function j(){return k(`runs/background`,`cliRunnerEntry`)}let M=class{constructor(e,t){this.jobTracker=e,this.terminalPersistence=t}defaultHandshakeTimeoutMs=1e4;now(){return Date.now()}readCurrentTeamRoot(){return f.readNativeTeamRootFromEnvironment()}createStatusWriter(){return new l.CoalescedStatusWriter}createSpawnHandshake(){return new _.SpawnHandshake}writeLaunchConfig(e,n){t.writePrivateAtomicJson(e,n)}removeLaunchConfig(e){v.rmSync(e,{force:!0})}readRunState(e){try{let t=JSON.parse(v.readFileSync(y.join(T(e),l.STATUS_FILE_NAME),`utf8`));return typeof t.state==`string`?t.state:void 0}catch{return}}buildLaunchArgs(e){return g.buildPiArgs(e)}spawnChild(e,t,n){let i=n.env[r.SUBAGENT_RUN_ID_ENV],a,o;if(i){let e=T(i);v.mkdirSync(e,{recursive:!0}),a=v.openSync(y.join(e,`runner.stdout.log`),`a`,384);try{o=v.openSync(y.join(e,`runner.stderr.log`),`a`,384)}catch(e){throw v.closeSync(a),Error(`Could not open runner stderr for '${i}'.`,{cause:e})}}let s;try{s=(0,C.spawn)(e,t,{cwd:n.cwd,detached:!0,stdio:[`ignore`,a??`ignore`,o??`ignore`],windowsHide:!0,env:n.env})}finally{a!==void 0&&v.closeSync(a),o!==void 0&&v.closeSync(o)}return s.unref(),{pid:s.pid,onError:e=>s.on(`error`,e),onExit:e=>s.on(`exit`,e)}}async spawn(e){let{runId:t,agent:n,task:r,cwd:i}=e,a=e.fanout?e.childIndex:void 0,o=E(t),l=c.currentRunConfigPath(t),g=e.artifacts===!1?void 0:p.getArtifactPaths(p.getArtifactsDir(e.parentSessionFile??null,i,e.artifactDir??`session`),t,n).transcriptPath,_=T(t),x=e.childIndex??0,C=this.readCurrentTeamRoot(),{args:w,env:k,sdk:M}=this.buildLaunchArgs({...e.piArgs,runId:t,task:r,childIndex:e.childIndex,steerInboxDir:s.stepSteerInboxDir(_,x),steerCapabilityPath:s.steerCapabilityPath(_,x),steerAckDir:s.steerAcksDir(_,x),teamToolEnabled:!!C});(0,b.createHarnessSession)({...(0,b.getHarnessState)(),childExtensions:M.extensions},{directory:_,environment:k,unclaimed:!0});let N={...process.env,...k,...c.sessionScopeEnvironment(c.requireCurrentSessionScope()),...C?f.nativeTeamRootEnvironment(C):{}};u.isPiRuntime(e.runtime)&&(M.extensionsProvidedExternally?N[S.DOOMPI_EXTENSIONS_PROVIDED_ENV]=`1`:delete N[S.DOOMPI_EXTENSIONS_PROVIDED_ENV]);let P;if(u.isPiRuntime(e.runtime)&&M.systemPrompt)try{v.mkdirSync(_,{recursive:!0});let e=O(t);v.writeFileSync(e,M.systemPrompt,{mode:384}),P=e}catch{P=void 0}let F=this.now(),I=this.createStatusWriter();I.open(t,{version:1,runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,...e.sensitiveTask?{}:{task:r},cwd:i,...e.piArgs.model?{model:e.piArgs.model}:{},...e.inlineAgent?{inlineAgent:e.inlineAgent}:{},state:`queued`,startedAt:F,lastUpdate:F,...a===void 0?{}:{fanoutIndex:a},...e.internal?{internal:!0}:{},...u.isPiRuntime(e.runtime)?{}:{runtime:e.runtime},...g?{transcriptPath:g}:{},...P?{systemPromptPath:P}:{},...P&&M.systemPromptMode?{systemPromptMode:M.systemPromptMode}:{},...M.tools?{tools:M.tools}:{},...M.excludeTools?.length?{excludeTools:M.excludeTools}:{},...M.noTools?{noTools:!0}:{}}),u.isPiRuntime(e.runtime)&&this.writeLaunchConfig(l,{runId:t,agent:n,task:r,cwd:i,childIndex:e.childIndex,handshakePath:o,sdk:M,...g?{transcriptPath:g}:{},args:w,env:N});let L,R=e=>{throw this.removeLaunchConfig(l),L&&h.cleanupClaudeFableLaunch(L),v.rmSync(D(t),{force:!0}),I.updateSync(t=>{t.state=`failed`,t.error=e,t.lastUpdate=this.now()}),Error(e)},z=u.isPiRuntime(e.runtime);if(!z){let a=e.piArgs.capabilityCeiling;if(a&&(!e.externalProfile||!a.allowedExternalProfiles.includes(e.externalProfile)))return R(`External profile '${e.externalProfile??`none`}' is denied by the active capability ceiling.`);if(e.externalProfile!==void 0&&e.externalProfile!==h.CLAUDE_FABLE_PROFILE)return R(`External profile '${e.externalProfile}' is not trusted.`);if(e.externalProfile===h.CLAUDE_FABLE_PROFILE){if(e.runtime!==`claude`||e.piArgs.model!==`fable`)return R(`The Fable profile requires runtime claude and model fable.`);if(e.inlineAgent||e.piArgs.sessionFile||e.piArgs.parentSessionId)return R(`The Fable profile requires a fresh isolated context.`);L=h.prepareClaudeFableLaunch({runId:t,prompt:r,repositoryCwd:i,privateRoot:_}),this.writeLaunchConfig(l,{runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,runtime:`claude`,command:L.command,args:L.args,cwd:L.cwd,env:L.env,profile:L.profile,stdinPath:L.stdinPath,cleanupPaths:L.cleanupPaths,profileResultPath:D(t),internal:e.internal===!0,handshakePath:o,resultPath:y.join(c.currentResultsDir(),`${t}.json`)})}else this.writeLaunchConfig(l,{runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,...u.resolveRuntimeLaunch(e.runtime,e.runtimes??{},{prompt:r,model:e.piArgs.model,cwd:i}),cwd:i,env:k,handshakePath:o,resultPath:y.join(c.currentResultsDir(),`${t}.json`)})}let{pid:B,onError:V,onExit:H}=this.spawnChild(process.execPath,[z?A():j()],{cwd:i,env:N});if(B===void 0)return R(`Async spawn for '${n}' (run '${t}') did not produce a pid.`);let U;V(e=>{U=e}),m.registerRun(c.requireCurrentSessionScope(),{runId:t,pid:B,agent:n,runtime:e.runtime??`pi`,startedAt:F}),this.terminalPersistence.trackChild(B),H((e,n)=>{this.terminalPersistence.untrackChild(B),m.releaseRun(c.requireCurrentSessionScope(),t),!(e===0&&n===null)&&(I.updateSync(t=>{t.state&&d.TERMINAL_ASYNC_JOB_STATES.has(t.state)||(t.state=`failed`,t.error=`SDK runner process ${B} exited unexpectedly (${n??`code ${e??`unknown`}`}).`,t.lastUpdate=this.now(),t.endedAt=this.now())}),this.jobTracker.track(t))});let W=await this.createSpawnHandshake().waitForHandshake({path:o,timeoutMs:e.handshakeTimeoutMs??this.defaultHandshakeTimeoutMs}).promise;if(W.status!==`signalled`)return this.terminalPersistence.untrackChild(B),R(W.status===`failed`?W.error:U?.message??`Timed out waiting for '${n}' (run '${t}') to start.`);this.removeLaunchConfig(l);let G=this.readRunState(t);return(!G||!d.TERMINAL_ASYNC_JOB_STATES.has(G))&&I.updateSync(e=>{e.state=`running`,e.lastUpdate=this.now()}),this.jobTracker.track(t),{runId:t,pid:B}}};M=n.__decorate([(0,x.injectable)(),o.__decorateParam(0,(0,x.inject)(i.DOOM_TEAM_TYPES.AsyncJobTracker)),o.__decorateParam(1,(0,x.inject)(i.DOOM_TEAM_TYPES.TerminalPersistenceService)),a.__decorateMetadata(`design:paramtypes`,[Object,Object])],M),Object.defineProperty(exports,`AsyncSubagentSpawner`,{enumerable:!0,get:function(){return M}}),exports.fableProfileResultPathFor=D,exports.handshakePathFor=E,exports.runDirFor=T;
2
2
  //# sourceMappingURL=asyncExecution.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncExecution.cjs","names":["path","currentRunsDir","fs","readNativeTeamRootFromEnvironment","CoalescedStatusWriter","SpawnHandshake","STATUS_FILE_NAME","buildPiArgs","SUBAGENT_RUN_ID_ENV","currentRunConfigPath","getArtifactPaths","getArtifactsDir","stepSteerInboxDir","steerCapabilityPath","steerAcksDir","sessionScopeEnvironment","requireCurrentSessionScope","nativeTeamRootEnvironment","isPiRuntime","CLAUDE_FABLE_PROFILE","prepareClaudeFableLaunch","currentResultsDir","resolveRuntimeLaunch","TERMINAL_ASYNC_JOB_STATES","DOOM_TEAM_TYPES"],"sources":["../../../../src/services/runs/background/asyncExecution.ts"],"sourcesContent":["/**\n * The async-only spawn executor: launches exactly one subagent child process\n * and returns a run handle immediately, without waiting for the run to\n * finish.\n *\n * SCOPE - PARENT SIDE ONLY, SINGLE SPAWN:\n * `doom-pi-subagents/src/runs/background/asyncExecution.ts` is 1698 lines\n * doing far more than spawning: chain/parallel-group orchestration (building\n * `AgentConfig[]` sequences, dynamic fan-out expansion, workflow-graph\n * snapshots), session revival (a 3-way ready/ack/proceed handshake distinct\n * from the plain started-signal this module uses), and the foreground\n * (synchronous, blocking) code path. None of that is ported here:\n * - Chain/fan-out ORCHESTRATION (deciding how many children to spawn, in\n * what order, from what step list) is `port-steering`'s concurrent work on\n * the resolved-step and workflow-graph types. This module is the\n * PRIMITIVE that orchestration calls once per child - `childIndex` and\n * `fanout` arrive as inputs, already decided by the caller, not derived\n * here (see the child-index section below for why that is safe)\n * - Session revival (`revivalLease`, the 3-way ready/ack/proceed dance at\n * predecessor lines 498-509) is a materially different operation - resuming\n * an existing transcript, not spawning a fresh child - and is not ported.\n * `session-lease.ts` already exists in this package for whoever builds that\n * path later\n * - The foreground (synchronous) branch is dropped entirely, per the async-\n * only requirement below; there is no `async` parameter, only this\n *\n * WHAT THIS DOES NOT BUILD (compose, don't reimplement):\n * `CoalescedStatusWriter`, `TerminalPersistenceService`, `AsyncJobTracker`,\n * `SpawnHandshake`, `ResultWatcher`, `CompletionNotifier` all already exist\n * in this directory and are used here, not reimplemented. In particular, the\n * predecessor's process-exit tracking is reduced to the child handle's exact\n * exit signal: an abnormal exit marks that run failed immediately, while the\n * crash-marker path (`TerminalPersistenceService` +\n * `ProcessTerminalInspector`) remains the recovery path after parent loss.\n * `trackChild(pid)` on the caller's own already-active\n * `TerminalPersistenceService` is what makes a spawned child get killed if\n * the run that spawned it dies, which is the same guarantee the predecessor\n * used its own mechanism for.\n *\n * SDK RUNNER BOUNDARY:\n * `sdkRunnerEntry.ts` is the detached child process target. It reads the\n * launch config, creates the child through Pi's SDK, and loads\n * `subagent-prompt-runtime-entry` as an explicit child extension. That\n * extension owns the handshake, status, steering, and terminal result\n * lifecycle through the existing runner services. The real Pi CLI remains\n * the parent host used by system tests and DoomPi, not the child launcher.\n *\n * WHY SpawnHandshake, NOT `Atomics.wait`:\n * The predecessor blocks the parent's event loop for up to 10 seconds per\n * spawn (`asyncExecution.ts:376-380`, `Atomics.wait` on a shared buffer).\n * That codepath only actually ran for session revival there (a fresh spawn\n * had no `startupPath` and returned immediately) - but this package has no\n * synchronous/foreground path left to hide the cost behind, so a busy-wait\n * would block every single async spawn, not just revivals. `SpawnHandshake`\n * (already in this directory) replaces it: a real `fs.watch` on the\n * handshake file's directory, with a short poll as the safety net, and it\n * never blocks the event loop.\n *\n * LAUNCH CONFIG FILE LIFECYCLE (fix):\n * The predecessor writes the launch config with `fs.writeFileSync(cfgPath,\n * ...)` - no `mode` option, so it lands at the umask default - and never\n * unlinks it on any path, successful or failed\n * (`asyncExecution.ts:491-495`). Both are fixed here: `writeLaunchConfig`\n * writes 0600 (`writePrivateAtomicJson`, already in this package), and\n * `removeLaunchConfig` runs on every exit path - success (once the handshake\n * confirms the child has read it), and every failure (no pid, spawn error,\n * handshake failure or timeout) - so nothing is ever left behind holding the\n * full task text and resolved secrets/tokens at world-readable-adjacent\n * permissions.\n *\n * CHILD INDEX (fix - `DeliverableGuard` had no way to get one):\n * `DeliverableGuard.deliverNudge` requires an explicit `nudgeTarget` of\n * `{kind:'child', index}` for a fan-out run specifically so it cannot\n * broadcast a nudge to every sibling over one child's missing deliverable\n * (see that module's header). Nothing in this package maps a team member id\n * to a fan-out child index (verified: neither `nativeTeamChannel.ts` nor\n * `parallel-groups.ts` does this). This module needs no such mapping either\n * - `childIndex` is simply which spawn call this is, a fact the caller (a\n * single spawn, or a fan-out loop calling this once per child) already knows\n * without looking anything up, exactly like a flat index into\n * `status.steps[]` is decided by whoever builds that array. `nudgeTarget` is\n * derived from `childIndex`/`fanout` and persisted onto the run's own\n * `status.json` (`nudgeTarget` field) for whatever later evaluates\n * `DeliverableGuardInput` to read back, rather than re-derived at that point.\n *\n * DELIVERABLE EXPECTATION (fix - `DeliverableGuard` had no policy):\n * Derived here, at the one point this module already has the task text:\n * `classifyTaskMutationIntent` (used AS-IS, per its own header's warning\n * against widening its vocabulary for a third caller) distinguishes\n * `'implementation'` intent from `'read-only'`/`'unknown'`. Implementation\n * intent gets a real expectation (`{kind:'summary'}` by default - the\n * minimum a run can be asked to prove it did something, when no more\n * specific expectation like a structured-output schema or artifact path was\n * supplied); read-only or unknown intent gets none at all. This is\n * deliberately NOT `{kind:'summary'}` unconditionally: a guard that flags\n * every research run for an empty summary gets ignored, and an ignored guard\n * misses the run that actually needed it.\n *\n * TEAM MEMBERSHIP ENV FORWARDING (fix - a spawned child had no way to join\n * the team):\n * If THIS process (the one calling `spawn()`) is itself part of a native\n * team - `readNativeTeamRootFromEnvironment()` resolves a root context off\n * its own `process.env` - the same root vars\n * (`nativeTeamRootEnvironment(root)`, already in `nativeTeamChannel.ts`,\n * not reimplemented here) are folded into the child's env alongside\n * whatever `buildPiArgs` produced. Only the ROOT vars travel this way, never\n * a member id/token: those are per-member secrets the CHILD mints for\n * itself when it registers (see `runnerBootstrap.ts` and\n * `runnerTeamMembership.ts`), not something safe to hand down from the\n * parent's own identity. When this process has no team root (a standalone\n * spawn outside any team), nothing is forwarded and the child simply has\n * none to register against.\n */\n\nimport { spawn } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { createHarnessSession, getHarnessState } from '@agimon-ai/doompi-config';\nimport type { InlineAgent } from '@agimon-ai/doompi-extension-contracts/subagent-tool';\nimport { inject, injectable } from 'inversify';\nimport { PI_RUNTIME_NAME, SUBAGENT_RUN_ID_ENV } from '../../../adapters/process/env.ts';\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport { getArtifactPaths, getArtifactsDir } from '../../../shared/artifacts.ts';\nimport { writePrivateAtomicJson } from '../../../shared/atomicJson.ts';\nimport {\n currentResultsDir,\n currentRunConfigPath,\n currentRunsDir,\n requireCurrentSessionScope,\n sessionScopeEnvironment,\n} from '../../../shared/paths.ts';\nimport type { ActivityState, ArtifactDirPreference } from '../../../types/index.ts';\nimport {\n nativeTeamRootEnvironment,\n readNativeTeamRootFromEnvironment,\n type TeamRootContext,\n} from '../../intercom/nativeTeamChannel.ts';\nimport { steerAcksDir, steerCapabilityPath, stepSteerInboxDir } from '../../intercom/supervisorControlChannel.ts';\nimport { registerRun, releaseRun } from '../registry/runRegistry.ts';\nimport {\n CLAUDE_FABLE_PROFILE,\n type ClaudeFableLaunch,\n cleanupClaudeFableLaunch,\n prepareClaudeFableLaunch,\n} from '../shared/claudeFableProfile.ts';\nimport {\n type BuildPiArgsInput,\n type BuildPiArgsResult,\n buildPiArgs,\n type PiSdkLaunchSettings,\n} from '../shared/piArgs.ts';\nimport { isPiRuntime, type RuntimeTable, resolveRuntimeLaunch } from '../shared/runtimeRegistry.ts';\nimport { type IAsyncJobTracker, TERMINAL_ASYNC_JOB_STATES } from './asyncJobTracker.ts';\nimport { type ISpawnHandshake, SpawnHandshake } from './spawnHandshake.ts';\nimport {\n CoalescedStatusWriter,\n type ICoalescedStatusWriter,\n STATUS_FILE_NAME,\n type StatusWithRecentEntries,\n} from './statusWriter.ts';\nimport type { ITerminalPersistenceService } from './terminalPersistence.ts';\n\n/** Leaf name of the started-signal file `SpawnHandshake` watches for, under `currentRunsDir()/<runId>/`. Exported so the child-side runner can compose the same convention rather than duplicate it. */\nexport const HANDSHAKE_FILE_NAME = 'handshake.json';\nexport const RUNNER_STDOUT_FILE_NAME = 'runner.stdout.log';\nexport const RUNNER_STDERR_FILE_NAME = 'runner.stderr.log';\nexport const FABLE_PROFILE_RESULT_FILE_NAME = 'fable-profile-result.json';\n/**\n * The effective system prompt this run launched with, kept for the fleet\n * inspector's agent view.\n *\n * A SIDECAR RATHER THAN A FIELD ON status.json: `CoalescedStatusWriter`\n * rewrites the whole status file on every coalesced flush, and a system prompt\n * is kilobytes of text that never changes after spawn. Inlining it would put\n * that payload through every status write for the life of the run. This file\n * is written once, next to the status it belongs to.\n *\n * Written 0600 for the same reason `piArgs.ts` writes its prompt file that\n * way: a resolved prompt can carry inherited project context.\n */\nexport const SYSTEM_PROMPT_FILE_NAME = 'system-prompt.md';\nconst SYSTEM_PROMPT_FILE_MODE = 0o600;\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;\n\n/**\n * `state`/`steps`/`parallelGroups`/`chainStepCount`/`workflowGraph`/\n * `pendingAppends` are declared as optional-and-widened, rather than a\n * runner-owned status type, so this SAME status shape is what\n * `chain-append.ts`'s `applyChainAppendRequest` (which expects\n * `AppendableChainStatus`, an object with exactly these optional fields) can\n * mutate directly - one status type for the whole spawn-through-finalize\n * lifecycle, not a parent-side one and a child-side one that have to be kept\n * in sync by hand.\n */\nexport interface AsyncRunStatus extends StatusWithRecentEntries {\n /** Version 1 is emitted by current writers; absent is accepted only by the compatibility reader. */\n version?: 1;\n runId: string;\n operationId?: string;\n agent: string;\n /** Original task text required to restore a suspended run. */\n task?: string;\n cwd?: string;\n /** Resolved model selected for this run, when one was requested. */\n model?: string;\n /** One-shot agent definition required to restore an inline agent. */\n inlineAgent?: InlineAgent;\n state: 'queued' | 'running' | 'complete' | 'completed' | 'failed' | 'paused' | 'stopped';\n startedAt: number;\n lastUpdate: number;\n endedAt?: number;\n /** This run's position among its fan-out siblings, when it has any. Absent for a standalone run. */\n fanoutIndex?: number;\n /** Bridge-owned runs are consumed internally and never rendered as generic completion chat. */\n internal?: boolean;\n /** Which runtime executes this run. Absent means the default in-process `pi` path. */\n runtime?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n error?: string;\n /**\n * Set once, at finalize, by `RunnerReporting.mutateTerminalStatus` - the\n * SAME value it puts in the terminal result file, truncated to a bounded\n * length (see that module's header for why). Lets a caller (chain mode,\n * in particular) learn a completed run's own summary through the status\n * file it already reads to learn the run finished, rather than needing a\n * second read of a claimed, single-consumer result file.\n */\n summary?: string;\n /**\n * This run's own Pi session transcript path, written during child bootstrap\n * and retained by `RunnerReporting.mutateTerminalStatus` at finalize. See\n * that module's header for where the value comes from and why it is bounded\n * by omission, not truncation. Absent for a run that either never reported\n * a session file (headless, no persisted transcript) or reported one too\n * long to trust. This is what `action='resume'` reads to know which\n * transcript a later revival should reopen.\n */\n sessionFile?: string;\n /** Child-owned Pi SDK event transcript consumed by the fleet detail pane. */\n transcriptPath?: string;\n /**\n * Sidecar holding this run's effective system prompt, and the resolved tool\n * plan it launched with. Recorded at spawn purely so the fleet inspector can\n * show what an agent was actually told: the launch config that carried them\n * is deleted as soon as the child handshakes, and the agent definition on\n * disk can change while a run is still going.\n *\n * All absent for a non-`pi` runtime, which has no SDK launch settings, and\n * for any run started before this was recorded.\n */\n systemPromptPath?: string;\n systemPromptMode?: 'append' | 'replace';\n /** Effective tool allowlist. Absent means \"whatever the child defaults to\". */\n tools?: string[];\n excludeTools?: string[];\n noTools?: boolean;\n /** Cumulative tokens observed from finalized child messages. */\n tokens?: number;\n /** Most recently started child tool. */\n currentTool?: string;\n /** Number of child tool executions observed so far. */\n toolCount?: number;\n}\n\n/** The parent-written contract a child-side runner reads via `currentRunConfigPath(runId)`. */\nexport interface LaunchConfig {\n runId: string;\n agent: string;\n task: string;\n cwd: string;\n childIndex: number;\n handshakePath: string;\n sdk: PiSdkLaunchSettings;\n transcriptPath?: string;\n /** Retained during the SDK cutover for compatibility diagnostics only. */\n args: string[];\n env: Record<string, string | undefined>;\n}\n\nexport interface AsyncSubagentSpawnInput {\n runId: string;\n operationId?: string;\n agent: string;\n /** One-shot agent definition retained only for explicit suspension restore. */\n inlineAgent?: InlineAgent;\n task: string;\n cwd: string;\n /** Persisted parent transcript used to place child artifacts beside the parent session. */\n parentSessionFile?: string;\n /** This spawn's position among its siblings. 0 for a standalone (non-fan-out) run. */\n childIndex: number;\n /** Whether this spawn is one of several children under a fan-out parent. */\n fanout: boolean;\n /** Everything `buildPiArgs` needs, minus what this spawner supplies itself (`runId`/`task`/`childIndex`). */\n piArgs: Omit<BuildPiArgsInput, 'runId' | 'task' | 'childIndex'>;\n /** False disables the child event transcript. Defaults to enabled. */\n artifacts?: boolean;\n /** Artifact root policy inherited from the parent Doom Team config. */\n artifactDir?: ArtifactDirPreference;\n handshakeTimeoutMs?: number;\n /**\n * Which runtime executes this run. Defaults to the in-process Pi SDK path.\n * Anything else is an external CLI resolved through `runtimeRegistry.ts`.\n */\n runtime?: string;\n /** Fixed trusted external profile. Only Doom Team internal bridges may set this. */\n externalProfile?: string;\n /** Prevent sensitive task text from being persisted in status.json. */\n sensitiveTask?: boolean;\n /** Suppress the generic completion notifier for an internal bridge run. */\n internal?: boolean;\n /** Runtimes available to this spawn, already merged with the shipped defaults. */\n runtimes?: RuntimeTable;\n}\n\nexport interface AsyncSubagentSpawnResult {\n runId: string;\n pid: number;\n}\n\nexport interface IAsyncSubagentSpawner {\n /** Spawn one child and return once it has confirmed it started. Throws on any failure to reach that point. */\n spawn(input: AsyncSubagentSpawnInput): Promise<AsyncSubagentSpawnResult>;\n}\n\n/** `currentRunsDir()/<runId>`. Exported so the child-side runner resolves the same directory, not a parallel one. */\nexport function runDirFor(runId: string): string {\n return path.join(currentRunsDir(), runId);\n}\n\n/** Exported so the child-side runner writes its ready/error signal at the exact path this parent half waits on. */\nexport function handshakePathFor(runId: string): string {\n return path.join(runDirFor(runId), HANDSHAKE_FILE_NAME);\n}\n\nexport function fableProfileResultPathFor(runId: string): string {\n return path.join(runDirFor(runId), FABLE_PROFILE_RESULT_FILE_NAME);\n}\n\n/** Where `spawn()` records this run's effective system prompt. See `SYSTEM_PROMPT_FILE_NAME`. */\nexport function systemPromptPathFor(runId: string): string {\n return path.join(runDirFor(runId), SYSTEM_PROMPT_FILE_NAME);\n}\n\n/**\n * Locate a child entry point on disk, built or source.\n *\n * The child is a separate process loading this by path, so the path has to be\n * the one that exists now: `.mjs` under `dist` when this bundle is built, the\n * original source file when it is not. `relativeDir` is where the entry sits\n * relative to `src/` - the SDK runner is one level up from this module, the\n * CLI runner is beside it.\n */\nfunction resolveRunnerEntry(relativeDir: string, stem: string): string {\n const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));\n const packageRoot = path.resolve(moduleDirectory, '../..');\n const builtCandidate = path.join(packageRoot, relativeDir, `${stem}.mjs`);\n if (fs.existsSync(builtCandidate)) return builtCandidate;\n const sourceCandidate = path.join(packageRoot, relativeDir, `${stem}.ts`);\n if (fs.existsSync(sourceCandidate)) return sourceCandidate;\n throw new Error(`Doom Team runner '${stem}' is unavailable at '${builtCandidate}' or '${sourceCandidate}'.`);\n}\n\nfunction resolveSdkRunnerEntry(): string {\n return resolveRunnerEntry('runs', 'sdkRunnerEntry');\n}\n\nfunction resolveCliRunnerEntry(): string {\n return resolveRunnerEntry('runs/background', 'cliRunnerEntry');\n}\n\n@injectable()\nexport class AsyncSubagentSpawner implements IAsyncSubagentSpawner {\n constructor(\n @inject(DOOM_TEAM_TYPES.AsyncJobTracker) private readonly jobTracker: IAsyncJobTracker,\n @inject(DOOM_TEAM_TYPES.TerminalPersistenceService)\n private readonly terminalPersistence: ITerminalPersistenceService,\n ) {}\n\n /**\n * Seam for tests, as a protected member rather than a constructor\n * argument: inversify cannot resolve a primitive. See `pollScheduler.ts`\n * for the same pattern.\n */\n protected readonly defaultHandshakeTimeoutMs: number = DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n /**\n * This process's own team root context, read fresh per spawn. Protected\n * so a test can supply one without writing real team files to disk - see\n * the module header's TEAM MEMBERSHIP ENV FORWARDING section for why this\n * is read from `process.env` rather than accepted as a constructor\n * argument (inversify cannot resolve a primitive either way).\n */\n protected readCurrentTeamRoot(): TeamRootContext | undefined {\n return readNativeTeamRootFromEnvironment();\n }\n\n /**\n * A fresh writer per spawn, deliberately never the container's shared\n * singleton - the same reasoning as `StaleRunReconciler.createStatusWriter`\n * (see that class's header): the singleton may simultaneously be the live\n * write path for a run THIS process is itself running, and bootstrapping\n * someone else's (the spawned child's) initial status is a different\n * writer's job.\n */\n protected createStatusWriter(): ICoalescedStatusWriter<AsyncRunStatus> {\n return new CoalescedStatusWriter<AsyncRunStatus>();\n }\n\n /**\n * A fresh handshake wait per spawn. `SpawnHandshake` is bound transient in\n * the container for exactly this reason (each `waitForHandshake()` owns\n * its own timers and watcher for exactly one spawn); resolving it via\n * constructor injection would hand every spawn the same, already-used\n * instance, so this seam constructs one directly instead.\n */\n protected createSpawnHandshake(): ISpawnHandshake {\n return new SpawnHandshake();\n }\n\n protected writeLaunchConfig(filePath: string, config: object): void {\n writePrivateAtomicJson(filePath, config);\n }\n\n protected removeLaunchConfig(filePath: string): void {\n fs.rmSync(filePath, { force: true });\n }\n\n protected readRunState(runId: string): string | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(runDirFor(runId), STATUS_FILE_NAME), 'utf8')) as {\n state?: unknown;\n };\n return typeof parsed.state === 'string' ? parsed.state : undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Resolves the child's CLI args/env. Protected so a test can bypass\n * `buildPiArgs`'s own real filesystem resolution (it locates a runtime\n * extension script on disk via `resolvePiLaunchToolPlan`, which is\n * environment-dependent and not something a unit test for THIS module's\n * orchestration logic should depend on).\n */\n protected buildLaunchArgs(input: BuildPiArgsInput): BuildPiArgsResult {\n return buildPiArgs(input);\n }\n\n /**\n * The actual `child_process.spawn`. Detached and unref'd immediately. Output\n * goes to per-run files so a startup failure is diagnosable without keeping\n * parent-owned pipes alive.\n */\n protected spawnChild(\n command: string,\n args: string[],\n options: { cwd: string; env: NodeJS.ProcessEnv },\n ): {\n pid: number | undefined;\n onError: (handler: (error: Error) => void) => void;\n onExit: (handler: (code: number | null, signal: NodeJS.Signals | null) => void) => void;\n } {\n const runId = options.env[SUBAGENT_RUN_ID_ENV];\n let stdoutFd: number | undefined;\n let stderrFd: number | undefined;\n if (runId) {\n const runDir = runDirFor(runId);\n fs.mkdirSync(runDir, { recursive: true });\n stdoutFd = fs.openSync(path.join(runDir, RUNNER_STDOUT_FILE_NAME), 'a', 0o600);\n try {\n stderrFd = fs.openSync(path.join(runDir, RUNNER_STDERR_FILE_NAME), 'a', 0o600);\n } catch (error) {\n fs.closeSync(stdoutFd);\n throw new Error(`Could not open runner stderr for '${runId}'.`, { cause: error });\n }\n }\n let child: ReturnType<typeof spawn>;\n try {\n child = spawn(command, args, {\n cwd: options.cwd,\n detached: true,\n stdio: ['ignore', stdoutFd ?? 'ignore', stderrFd ?? 'ignore'],\n windowsHide: true,\n env: options.env,\n });\n } finally {\n if (stdoutFd !== undefined) fs.closeSync(stdoutFd);\n if (stderrFd !== undefined) fs.closeSync(stderrFd);\n }\n child.unref();\n return {\n pid: child.pid,\n onError: (handler) => child.on('error', handler),\n onExit: (handler) => child.on('exit', handler),\n };\n }\n\n async spawn(input: AsyncSubagentSpawnInput): Promise<AsyncSubagentSpawnResult> {\n const { runId, agent, task, cwd } = input;\n const fanoutIndex = input.fanout ? input.childIndex : undefined;\n const handshakePath = handshakePathFor(runId);\n const configPath = currentRunConfigPath(runId);\n const transcriptPath =\n input.artifacts === false\n ? undefined\n : getArtifactPaths(\n getArtifactsDir(input.parentSessionFile ?? null, cwd, input.artifactDir ?? 'session'),\n runId,\n agent,\n ).transcriptPath;\n\n const runDir = runDirFor(runId);\n const steerIndex = input.childIndex ?? 0;\n const teamRoot = this.readCurrentTeamRoot();\n const {\n args,\n env: builtEnv,\n sdk,\n } = this.buildLaunchArgs({\n ...input.piArgs,\n runId,\n task,\n childIndex: input.childIndex,\n steerInboxDir: stepSteerInboxDir(runDir, steerIndex),\n steerCapabilityPath: steerCapabilityPath(runDir, steerIndex),\n steerAckDir: steerAcksDir(runDir, steerIndex),\n teamToolEnabled: Boolean(teamRoot),\n });\n // The child gets its own harness state, in this run's directory, which this\n // package owns. Sharing the parent's file would let a detached child rewrite\n // a session it does not own, and pointing at the parent's directory would\n // lose the file the moment the parent cleaned up its own session: a child\n // is detached and can outlive it. Pi 0.84 SDK launches consume the resolved\n // extension list directly, so preserve that same list in the child harness\n // snapshot instead of projecting the parent's stale childExtensions value.\n createHarnessSession(\n { ...getHarnessState(), childExtensions: sdk.extensions },\n { directory: runDir, environment: builtEnv, unclaimed: true },\n );\n const env = {\n ...process.env,\n ...builtEnv,\n // The child resolves the same filesystem tree from this, independently of\n // whether it also joins a team.\n ...sessionScopeEnvironment(requireCurrentSessionScope()),\n ...(teamRoot ? nativeTeamRootEnvironment(teamRoot) : {}),\n };\n\n // Recorded before open() so the status never advertises a sidecar that is\n // not on disk yet. A failure here is not worth failing a spawn over: the\n // prompt is an inspection aid, so the run proceeds without it.\n let systemPromptPath: string | undefined;\n if (isPiRuntime(input.runtime) && sdk.systemPrompt) {\n try {\n fs.mkdirSync(runDir, { recursive: true });\n const target = systemPromptPathFor(runId);\n fs.writeFileSync(target, sdk.systemPrompt, { mode: SYSTEM_PROMPT_FILE_MODE });\n systemPromptPath = target;\n } catch {\n systemPromptPath = undefined;\n }\n }\n\n const now = this.now();\n const statusWriter = this.createStatusWriter();\n statusWriter.open(runId, {\n version: 1,\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n ...(input.sensitiveTask ? {} : { task }),\n cwd,\n ...(input.piArgs.model ? { model: input.piArgs.model } : {}),\n ...(input.inlineAgent ? { inlineAgent: input.inlineAgent } : {}),\n state: 'queued',\n startedAt: now,\n lastUpdate: now,\n ...(fanoutIndex === undefined ? {} : { fanoutIndex }),\n ...(input.internal ? { internal: true } : {}),\n ...(isPiRuntime(input.runtime) ? {} : { runtime: input.runtime }),\n ...(transcriptPath ? { transcriptPath } : {}),\n ...(systemPromptPath ? { systemPromptPath } : {}),\n ...(systemPromptPath && sdk.systemPromptMode ? { systemPromptMode: sdk.systemPromptMode } : {}),\n ...(sdk.tools ? { tools: sdk.tools } : {}),\n ...(sdk.excludeTools?.length ? { excludeTools: sdk.excludeTools } : {}),\n ...(sdk.noTools ? { noTools: true } : {}),\n });\n\n if (isPiRuntime(input.runtime)) {\n this.writeLaunchConfig(configPath, {\n runId,\n agent,\n task,\n cwd,\n childIndex: input.childIndex,\n handshakePath,\n sdk,\n ...(transcriptPath ? { transcriptPath } : {}),\n args,\n env,\n });\n }\n\n let fableLaunch: ClaudeFableLaunch | undefined;\n const fail = (message: string): never => {\n this.removeLaunchConfig(configPath);\n if (fableLaunch) cleanupClaudeFableLaunch(fableLaunch);\n fs.rmSync(fableProfileResultPathFor(runId), { force: true });\n statusWriter.updateSync((status) => {\n status.state = 'failed';\n status.error = message;\n status.lastUpdate = this.now();\n });\n throw new Error(message);\n };\n\n // One branch, and only one: `pi` runs in-process through the SDK runner and\n // gets the team channel, steering and structured output that go with it.\n // Every other runtime is an external CLI, spawned through the generic\n // runner, which deliberately knows nothing about the vendor.\n const usePi = isPiRuntime(input.runtime);\n if (!usePi) {\n const ceiling = input.piArgs.capabilityCeiling;\n if (ceiling && (!input.externalProfile || !ceiling.allowedExternalProfiles.includes(input.externalProfile))) {\n return fail(\n `External profile '${input.externalProfile ?? 'none'}' is denied by the active capability ceiling.`,\n );\n }\n if (input.externalProfile !== undefined && input.externalProfile !== CLAUDE_FABLE_PROFILE) {\n return fail(`External profile '${input.externalProfile}' is not trusted.`);\n }\n if (input.externalProfile === CLAUDE_FABLE_PROFILE) {\n if (input.runtime !== 'claude' || input.piArgs.model !== 'fable') {\n return fail('The Fable profile requires runtime claude and model fable.');\n }\n if (input.inlineAgent || input.piArgs.sessionFile || input.piArgs.parentSessionId) {\n return fail('The Fable profile requires a fresh isolated context.');\n }\n fableLaunch = prepareClaudeFableLaunch({\n runId,\n prompt: task,\n repositoryCwd: cwd,\n privateRoot: runDir,\n });\n this.writeLaunchConfig(configPath, {\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n runtime: 'claude',\n command: fableLaunch.command,\n args: fableLaunch.args,\n cwd: fableLaunch.cwd,\n env: fableLaunch.env,\n profile: fableLaunch.profile,\n stdinPath: fableLaunch.stdinPath,\n cleanupPaths: fableLaunch.cleanupPaths,\n profileResultPath: fableProfileResultPathFor(runId),\n internal: input.internal === true,\n handshakePath,\n resultPath: path.join(currentResultsDir(), `${runId}.json`),\n });\n } else {\n this.writeLaunchConfig(configPath, {\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n ...resolveRuntimeLaunch(input.runtime as string, input.runtimes ?? {}, {\n prompt: task,\n model: input.piArgs.model,\n cwd,\n }),\n cwd,\n env: builtEnv as Record<string, string>,\n handshakePath,\n resultPath: path.join(currentResultsDir(), `${runId}.json`),\n });\n }\n }\n\n const { pid, onError, onExit } = this.spawnChild(\n process.execPath,\n [usePi ? resolveSdkRunnerEntry() : resolveCliRunnerEntry()],\n { cwd, env: env as NodeJS.ProcessEnv },\n );\n if (pid === undefined) {\n return fail(`Async spawn for '${agent}' (run '${runId}') did not produce a pid.`);\n }\n let spawnError: Error | undefined;\n onError((error) => {\n spawnError = error;\n });\n\n // Recorded before the handshake: a parent that dies between spawn and\n // handshake must still leave a record a later sweep can act on.\n registerRun(requireCurrentSessionScope(), {\n runId,\n pid,\n agent,\n runtime: input.runtime ?? PI_RUNTIME_NAME,\n startedAt: now,\n });\n this.terminalPersistence.trackChild(pid);\n onExit((code, signal) => {\n this.terminalPersistence.untrackChild(pid);\n releaseRun(requireCurrentSessionScope(), runId);\n if (code === 0 && signal === null) return;\n statusWriter.updateSync((status) => {\n if (status.state && TERMINAL_ASYNC_JOB_STATES.has(status.state)) return;\n status.state = 'failed';\n status.error = `SDK runner process ${pid} exited unexpectedly (${signal ?? `code ${code ?? 'unknown'}`}).`;\n status.lastUpdate = this.now();\n status.endedAt = this.now();\n });\n this.jobTracker.track(runId);\n });\n\n const handshake = this.createSpawnHandshake();\n const outcome = await handshake.waitForHandshake({\n path: handshakePath,\n timeoutMs: input.handshakeTimeoutMs ?? this.defaultHandshakeTimeoutMs,\n }).promise;\n\n if (outcome.status !== 'signalled') {\n this.terminalPersistence.untrackChild(pid);\n const message =\n outcome.status === 'failed'\n ? outcome.error\n : (spawnError?.message ?? `Timed out waiting for '${agent}' (run '${runId}') to start.`);\n return fail(message);\n }\n\n // The child has confirmed it read the launch config; the parent's copy\n // is no longer needed (see the module header's LAUNCH CONFIG FILE\n // LIFECYCLE section).\n this.removeLaunchConfig(configPath);\n const observedState = this.readRunState(runId);\n if (!observedState || !TERMINAL_ASYNC_JOB_STATES.has(observedState)) {\n statusWriter.updateSync((status) => {\n status.state = 'running';\n status.lastUpdate = this.now();\n });\n }\n this.jobTracker.track(runId);\n\n return { runId, pid };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"asyncExecution.cjs","names":["path","currentRunsDir","fs","readNativeTeamRootFromEnvironment","CoalescedStatusWriter","SpawnHandshake","STATUS_FILE_NAME","buildPiArgs","SUBAGENT_RUN_ID_ENV","currentRunConfigPath","getArtifactPaths","getArtifactsDir","stepSteerInboxDir","steerCapabilityPath","steerAcksDir","sessionScopeEnvironment","requireCurrentSessionScope","nativeTeamRootEnvironment","isPiRuntime","DOOMPI_EXTENSIONS_PROVIDED_ENV","CLAUDE_FABLE_PROFILE","prepareClaudeFableLaunch","currentResultsDir","resolveRuntimeLaunch","TERMINAL_ASYNC_JOB_STATES","DOOM_TEAM_TYPES"],"sources":["../../../../src/services/runs/background/asyncExecution.ts"],"sourcesContent":["/**\n * The async-only spawn executor: launches exactly one subagent child process\n * and returns a run handle immediately, without waiting for the run to\n * finish.\n *\n * SCOPE - PARENT SIDE ONLY, SINGLE SPAWN:\n * `doom-pi-subagents/src/runs/background/asyncExecution.ts` is 1698 lines\n * doing far more than spawning: chain/parallel-group orchestration (building\n * `AgentConfig[]` sequences, dynamic fan-out expansion, workflow-graph\n * snapshots), session revival (a 3-way ready/ack/proceed handshake distinct\n * from the plain started-signal this module uses), and the foreground\n * (synchronous, blocking) code path. None of that is ported here:\n * - Chain/fan-out ORCHESTRATION (deciding how many children to spawn, in\n * what order, from what step list) is `port-steering`'s concurrent work on\n * the resolved-step and workflow-graph types. This module is the\n * PRIMITIVE that orchestration calls once per child - `childIndex` and\n * `fanout` arrive as inputs, already decided by the caller, not derived\n * here (see the child-index section below for why that is safe)\n * - Session revival (`revivalLease`, the 3-way ready/ack/proceed dance at\n * predecessor lines 498-509) is a materially different operation - resuming\n * an existing transcript, not spawning a fresh child - and is not ported.\n * `session-lease.ts` already exists in this package for whoever builds that\n * path later\n * - The foreground (synchronous) branch is dropped entirely, per the async-\n * only requirement below; there is no `async` parameter, only this\n *\n * WHAT THIS DOES NOT BUILD (compose, don't reimplement):\n * `CoalescedStatusWriter`, `TerminalPersistenceService`, `AsyncJobTracker`,\n * `SpawnHandshake`, `ResultWatcher`, `CompletionNotifier` all already exist\n * in this directory and are used here, not reimplemented. In particular, the\n * predecessor's process-exit tracking is reduced to the child handle's exact\n * exit signal: an abnormal exit marks that run failed immediately, while the\n * crash-marker path (`TerminalPersistenceService` +\n * `ProcessTerminalInspector`) remains the recovery path after parent loss.\n * `trackChild(pid)` on the caller's own already-active\n * `TerminalPersistenceService` is what makes a spawned child get killed if\n * the run that spawned it dies, which is the same guarantee the predecessor\n * used its own mechanism for.\n *\n * SDK RUNNER BOUNDARY:\n * `sdkRunnerEntry.ts` is the detached child process target. It reads the\n * launch config, creates the child through Pi's SDK, and loads\n * `subagent-prompt-runtime-entry` as an explicit child extension. That\n * extension owns the handshake, status, steering, and terminal result\n * lifecycle through the existing runner services. The real Pi CLI remains\n * the parent host used by system tests and DoomPi, not the child launcher.\n *\n * WHY SpawnHandshake, NOT `Atomics.wait`:\n * The predecessor blocks the parent's event loop for up to 10 seconds per\n * spawn (`asyncExecution.ts:376-380`, `Atomics.wait` on a shared buffer).\n * That codepath only actually ran for session revival there (a fresh spawn\n * had no `startupPath` and returned immediately) - but this package has no\n * synchronous/foreground path left to hide the cost behind, so a busy-wait\n * would block every single async spawn, not just revivals. `SpawnHandshake`\n * (already in this directory) replaces it: a real `fs.watch` on the\n * handshake file's directory, with a short poll as the safety net, and it\n * never blocks the event loop.\n *\n * LAUNCH CONFIG FILE LIFECYCLE (fix):\n * The predecessor writes the launch config with `fs.writeFileSync(cfgPath,\n * ...)` - no `mode` option, so it lands at the umask default - and never\n * unlinks it on any path, successful or failed\n * (`asyncExecution.ts:491-495`). Both are fixed here: `writeLaunchConfig`\n * writes 0600 (`writePrivateAtomicJson`, already in this package), and\n * `removeLaunchConfig` runs on every exit path - success (once the handshake\n * confirms the child has read it), and every failure (no pid, spawn error,\n * handshake failure or timeout) - so nothing is ever left behind holding the\n * full task text and resolved secrets/tokens at world-readable-adjacent\n * permissions.\n *\n * CHILD INDEX (fix - `DeliverableGuard` had no way to get one):\n * `DeliverableGuard.deliverNudge` requires an explicit `nudgeTarget` of\n * `{kind:'child', index}` for a fan-out run specifically so it cannot\n * broadcast a nudge to every sibling over one child's missing deliverable\n * (see that module's header). Nothing in this package maps a team member id\n * to a fan-out child index (verified: neither `nativeTeamChannel.ts` nor\n * `parallel-groups.ts` does this). This module needs no such mapping either\n * - `childIndex` is simply which spawn call this is, a fact the caller (a\n * single spawn, or a fan-out loop calling this once per child) already knows\n * without looking anything up, exactly like a flat index into\n * `status.steps[]` is decided by whoever builds that array. `nudgeTarget` is\n * derived from `childIndex`/`fanout` and persisted onto the run's own\n * `status.json` (`nudgeTarget` field) for whatever later evaluates\n * `DeliverableGuardInput` to read back, rather than re-derived at that point.\n *\n * DELIVERABLE EXPECTATION (fix - `DeliverableGuard` had no policy):\n * Derived here, at the one point this module already has the task text:\n * `classifyTaskMutationIntent` (used AS-IS, per its own header's warning\n * against widening its vocabulary for a third caller) distinguishes\n * `'implementation'` intent from `'read-only'`/`'unknown'`. Implementation\n * intent gets a real expectation (`{kind:'summary'}` by default - the\n * minimum a run can be asked to prove it did something, when no more\n * specific expectation like a structured-output schema or artifact path was\n * supplied); read-only or unknown intent gets none at all. This is\n * deliberately NOT `{kind:'summary'}` unconditionally: a guard that flags\n * every research run for an empty summary gets ignored, and an ignored guard\n * misses the run that actually needed it.\n *\n * TEAM MEMBERSHIP ENV FORWARDING (fix - a spawned child had no way to join\n * the team):\n * If THIS process (the one calling `spawn()`) is itself part of a native\n * team - `readNativeTeamRootFromEnvironment()` resolves a root context off\n * its own `process.env` - the same root vars\n * (`nativeTeamRootEnvironment(root)`, already in `nativeTeamChannel.ts`,\n * not reimplemented here) are folded into the child's env alongside\n * whatever `buildPiArgs` produced. Only the ROOT vars travel this way, never\n * a member id/token: those are per-member secrets the CHILD mints for\n * itself when it registers (see `runnerBootstrap.ts` and\n * `runnerTeamMembership.ts`), not something safe to hand down from the\n * parent's own identity. When this process has no team root (a standalone\n * spawn outside any team), nothing is forwarded and the child simply has\n * none to register against.\n */\n\nimport { spawn } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { createHarnessSession, getHarnessState } from '@agimon-ai/doompi-config';\nimport { DOOMPI_EXTENSIONS_PROVIDED_ENV } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport type { InlineAgent } from '@agimon-ai/doompi-extension-contracts/subagent-tool';\nimport { inject, injectable } from 'inversify';\nimport { PI_RUNTIME_NAME, SUBAGENT_RUN_ID_ENV } from '../../../adapters/process/env.ts';\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport { getArtifactPaths, getArtifactsDir } from '../../../shared/artifacts.ts';\nimport { writePrivateAtomicJson } from '../../../shared/atomicJson.ts';\nimport {\n currentResultsDir,\n currentRunConfigPath,\n currentRunsDir,\n requireCurrentSessionScope,\n sessionScopeEnvironment,\n} from '../../../shared/paths.ts';\nimport type { ActivityState, ArtifactDirPreference } from '../../../types/index.ts';\nimport {\n nativeTeamRootEnvironment,\n readNativeTeamRootFromEnvironment,\n type TeamRootContext,\n} from '../../intercom/nativeTeamChannel.ts';\nimport { steerAcksDir, steerCapabilityPath, stepSteerInboxDir } from '../../intercom/supervisorControlChannel.ts';\nimport { registerRun, releaseRun } from '../registry/runRegistry.ts';\nimport {\n CLAUDE_FABLE_PROFILE,\n type ClaudeFableLaunch,\n cleanupClaudeFableLaunch,\n prepareClaudeFableLaunch,\n} from '../shared/claudeFableProfile.ts';\nimport {\n type BuildPiArgsInput,\n type BuildPiArgsResult,\n buildPiArgs,\n type PiSdkLaunchSettings,\n} from '../shared/piArgs.ts';\nimport { isPiRuntime, type RuntimeTable, resolveRuntimeLaunch } from '../shared/runtimeRegistry.ts';\nimport { type IAsyncJobTracker, TERMINAL_ASYNC_JOB_STATES } from './asyncJobTracker.ts';\nimport { type ISpawnHandshake, SpawnHandshake } from './spawnHandshake.ts';\nimport {\n CoalescedStatusWriter,\n type ICoalescedStatusWriter,\n STATUS_FILE_NAME,\n type StatusWithRecentEntries,\n} from './statusWriter.ts';\nimport type { ITerminalPersistenceService } from './terminalPersistence.ts';\n\n/** Leaf name of the started-signal file `SpawnHandshake` watches for, under `currentRunsDir()/<runId>/`. Exported so the child-side runner can compose the same convention rather than duplicate it. */\nexport const HANDSHAKE_FILE_NAME = 'handshake.json';\nexport const RUNNER_STDOUT_FILE_NAME = 'runner.stdout.log';\nexport const RUNNER_STDERR_FILE_NAME = 'runner.stderr.log';\nexport const FABLE_PROFILE_RESULT_FILE_NAME = 'fable-profile-result.json';\n/**\n * The effective system prompt this run launched with, kept for the fleet\n * inspector's agent view.\n *\n * A SIDECAR RATHER THAN A FIELD ON status.json: `CoalescedStatusWriter`\n * rewrites the whole status file on every coalesced flush, and a system prompt\n * is kilobytes of text that never changes after spawn. Inlining it would put\n * that payload through every status write for the life of the run. This file\n * is written once, next to the status it belongs to.\n *\n * Written 0600 for the same reason `piArgs.ts` writes its prompt file that\n * way: a resolved prompt can carry inherited project context.\n */\nexport const SYSTEM_PROMPT_FILE_NAME = 'system-prompt.md';\nconst SYSTEM_PROMPT_FILE_MODE = 0o600;\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;\n\n/**\n * `state`/`steps`/`parallelGroups`/`chainStepCount`/`workflowGraph`/\n * `pendingAppends` are declared as optional-and-widened, rather than a\n * runner-owned status type, so this SAME status shape is what\n * `chain-append.ts`'s `applyChainAppendRequest` (which expects\n * `AppendableChainStatus`, an object with exactly these optional fields) can\n * mutate directly - one status type for the whole spawn-through-finalize\n * lifecycle, not a parent-side one and a child-side one that have to be kept\n * in sync by hand.\n */\nexport interface AsyncRunStatus extends StatusWithRecentEntries {\n /** Version 1 is emitted by current writers; absent is accepted only by the compatibility reader. */\n version?: 1;\n runId: string;\n operationId?: string;\n agent: string;\n /** Original task text required to restore a suspended run. */\n task?: string;\n cwd?: string;\n /** Resolved model selected for this run, when one was requested. */\n model?: string;\n /** One-shot agent definition required to restore an inline agent. */\n inlineAgent?: InlineAgent;\n state: 'queued' | 'running' | 'complete' | 'completed' | 'failed' | 'paused' | 'stopped';\n startedAt: number;\n lastUpdate: number;\n endedAt?: number;\n /** This run's position among its fan-out siblings, when it has any. Absent for a standalone run. */\n fanoutIndex?: number;\n /** Bridge-owned runs are consumed internally and never rendered as generic completion chat. */\n internal?: boolean;\n /** Which runtime executes this run. Absent means the default in-process `pi` path. */\n runtime?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n error?: string;\n /**\n * Set once, at finalize, by `RunnerReporting.mutateTerminalStatus` - the\n * SAME value it puts in the terminal result file, truncated to a bounded\n * length (see that module's header for why). Lets a caller (chain mode,\n * in particular) learn a completed run's own summary through the status\n * file it already reads to learn the run finished, rather than needing a\n * second read of a claimed, single-consumer result file.\n */\n summary?: string;\n /**\n * This run's own Pi session transcript path, written during child bootstrap\n * and retained by `RunnerReporting.mutateTerminalStatus` at finalize. See\n * that module's header for where the value comes from and why it is bounded\n * by omission, not truncation. Absent for a run that either never reported\n * a session file (headless, no persisted transcript) or reported one too\n * long to trust. This is what `action='resume'` reads to know which\n * transcript a later revival should reopen.\n */\n sessionFile?: string;\n /** Child-owned Pi SDK event transcript consumed by the fleet detail pane. */\n transcriptPath?: string;\n /**\n * Sidecar holding this run's effective system prompt, and the resolved tool\n * plan it launched with. Recorded at spawn purely so the fleet inspector can\n * show what an agent was actually told: the launch config that carried them\n * is deleted as soon as the child handshakes, and the agent definition on\n * disk can change while a run is still going.\n *\n * All absent for a non-`pi` runtime, which has no SDK launch settings, and\n * for any run started before this was recorded.\n */\n systemPromptPath?: string;\n systemPromptMode?: 'append' | 'replace';\n /** Effective tool allowlist. Absent means \"whatever the child defaults to\". */\n tools?: string[];\n excludeTools?: string[];\n noTools?: boolean;\n /** Cumulative tokens observed from finalized child messages. */\n tokens?: number;\n /** Most recently started child tool. */\n currentTool?: string;\n /** Number of child tool executions observed so far. */\n toolCount?: number;\n}\n\n/** The parent-written contract a child-side runner reads via `currentRunConfigPath(runId)`. */\nexport interface LaunchConfig {\n runId: string;\n agent: string;\n task: string;\n cwd: string;\n childIndex: number;\n handshakePath: string;\n sdk: PiSdkLaunchSettings;\n transcriptPath?: string;\n /** Retained during the SDK cutover for compatibility diagnostics only. */\n args: string[];\n env: Record<string, string | undefined>;\n}\n\nexport interface AsyncSubagentSpawnInput {\n runId: string;\n operationId?: string;\n agent: string;\n /** One-shot agent definition retained only for explicit suspension restore. */\n inlineAgent?: InlineAgent;\n task: string;\n cwd: string;\n /** Persisted parent transcript used to place child artifacts beside the parent session. */\n parentSessionFile?: string;\n /** This spawn's position among its siblings. 0 for a standalone (non-fan-out) run. */\n childIndex: number;\n /** Whether this spawn is one of several children under a fan-out parent. */\n fanout: boolean;\n /** Everything `buildPiArgs` needs, minus what this spawner supplies itself (`runId`/`task`/`childIndex`). */\n piArgs: Omit<BuildPiArgsInput, 'runId' | 'task' | 'childIndex'>;\n /** False disables the child event transcript. Defaults to enabled. */\n artifacts?: boolean;\n /** Artifact root policy inherited from the parent Doom Team config. */\n artifactDir?: ArtifactDirPreference;\n handshakeTimeoutMs?: number;\n /**\n * Which runtime executes this run. Defaults to the in-process Pi SDK path.\n * Anything else is an external CLI resolved through `runtimeRegistry.ts`.\n */\n runtime?: string;\n /** Fixed trusted external profile. Only Doom Team internal bridges may set this. */\n externalProfile?: string;\n /** Prevent sensitive task text from being persisted in status.json. */\n sensitiveTask?: boolean;\n /** Suppress the generic completion notifier for an internal bridge run. */\n internal?: boolean;\n /** Runtimes available to this spawn, already merged with the shipped defaults. */\n runtimes?: RuntimeTable;\n}\n\nexport interface AsyncSubagentSpawnResult {\n runId: string;\n pid: number;\n}\n\nexport interface IAsyncSubagentSpawner {\n /** Spawn one child and return once it has confirmed it started. Throws on any failure to reach that point. */\n spawn(input: AsyncSubagentSpawnInput): Promise<AsyncSubagentSpawnResult>;\n}\n\n/** `currentRunsDir()/<runId>`. Exported so the child-side runner resolves the same directory, not a parallel one. */\nexport function runDirFor(runId: string): string {\n return path.join(currentRunsDir(), runId);\n}\n\n/** Exported so the child-side runner writes its ready/error signal at the exact path this parent half waits on. */\nexport function handshakePathFor(runId: string): string {\n return path.join(runDirFor(runId), HANDSHAKE_FILE_NAME);\n}\n\nexport function fableProfileResultPathFor(runId: string): string {\n return path.join(runDirFor(runId), FABLE_PROFILE_RESULT_FILE_NAME);\n}\n\n/** Where `spawn()` records this run's effective system prompt. See `SYSTEM_PROMPT_FILE_NAME`. */\nexport function systemPromptPathFor(runId: string): string {\n return path.join(runDirFor(runId), SYSTEM_PROMPT_FILE_NAME);\n}\n\n/**\n * Locate a child entry point on disk, built or source.\n *\n * The child is a separate process loading this by path, so the path has to be\n * the one that exists now: `.mjs` under `dist` when this bundle is built, the\n * original source file when it is not. `relativeDir` is where the entry sits\n * relative to `src/` - the SDK runner is one level up from this module, the\n * CLI runner is beside it.\n */\nfunction resolveRunnerEntry(relativeDir: string, stem: string): string {\n const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));\n const packageRoot = path.resolve(moduleDirectory, '../..');\n const builtCandidate = path.join(packageRoot, relativeDir, `${stem}.mjs`);\n if (fs.existsSync(builtCandidate)) return builtCandidate;\n const sourceCandidate = path.join(packageRoot, relativeDir, `${stem}.ts`);\n if (fs.existsSync(sourceCandidate)) return sourceCandidate;\n throw new Error(`Doom Team runner '${stem}' is unavailable at '${builtCandidate}' or '${sourceCandidate}'.`);\n}\n\nfunction resolveSdkRunnerEntry(): string {\n return resolveRunnerEntry('runs', 'sdkRunnerEntry');\n}\n\nfunction resolveCliRunnerEntry(): string {\n return resolveRunnerEntry('runs/background', 'cliRunnerEntry');\n}\n\n@injectable()\nexport class AsyncSubagentSpawner implements IAsyncSubagentSpawner {\n constructor(\n @inject(DOOM_TEAM_TYPES.AsyncJobTracker) private readonly jobTracker: IAsyncJobTracker,\n @inject(DOOM_TEAM_TYPES.TerminalPersistenceService)\n private readonly terminalPersistence: ITerminalPersistenceService,\n ) {}\n\n /**\n * Seam for tests, as a protected member rather than a constructor\n * argument: inversify cannot resolve a primitive. See `pollScheduler.ts`\n * for the same pattern.\n */\n protected readonly defaultHandshakeTimeoutMs: number = DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n /**\n * This process's own team root context, read fresh per spawn. Protected\n * so a test can supply one without writing real team files to disk - see\n * the module header's TEAM MEMBERSHIP ENV FORWARDING section for why this\n * is read from `process.env` rather than accepted as a constructor\n * argument (inversify cannot resolve a primitive either way).\n */\n protected readCurrentTeamRoot(): TeamRootContext | undefined {\n return readNativeTeamRootFromEnvironment();\n }\n\n /**\n * A fresh writer per spawn, deliberately never the container's shared\n * singleton - the same reasoning as `StaleRunReconciler.createStatusWriter`\n * (see that class's header): the singleton may simultaneously be the live\n * write path for a run THIS process is itself running, and bootstrapping\n * someone else's (the spawned child's) initial status is a different\n * writer's job.\n */\n protected createStatusWriter(): ICoalescedStatusWriter<AsyncRunStatus> {\n return new CoalescedStatusWriter<AsyncRunStatus>();\n }\n\n /**\n * A fresh handshake wait per spawn. `SpawnHandshake` is bound transient in\n * the container for exactly this reason (each `waitForHandshake()` owns\n * its own timers and watcher for exactly one spawn); resolving it via\n * constructor injection would hand every spawn the same, already-used\n * instance, so this seam constructs one directly instead.\n */\n protected createSpawnHandshake(): ISpawnHandshake {\n return new SpawnHandshake();\n }\n\n protected writeLaunchConfig(filePath: string, config: object): void {\n writePrivateAtomicJson(filePath, config);\n }\n\n protected removeLaunchConfig(filePath: string): void {\n fs.rmSync(filePath, { force: true });\n }\n\n protected readRunState(runId: string): string | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(runDirFor(runId), STATUS_FILE_NAME), 'utf8')) as {\n state?: unknown;\n };\n return typeof parsed.state === 'string' ? parsed.state : undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Resolves the child's CLI args/env. Protected so a test can bypass\n * `buildPiArgs`'s own real filesystem resolution (it locates a runtime\n * extension script on disk via `resolvePiLaunchToolPlan`, which is\n * environment-dependent and not something a unit test for THIS module's\n * orchestration logic should depend on).\n */\n protected buildLaunchArgs(input: BuildPiArgsInput): BuildPiArgsResult {\n return buildPiArgs(input);\n }\n\n /**\n * The actual `child_process.spawn`. Detached and unref'd immediately. Output\n * goes to per-run files so a startup failure is diagnosable without keeping\n * parent-owned pipes alive.\n */\n protected spawnChild(\n command: string,\n args: string[],\n options: { cwd: string; env: NodeJS.ProcessEnv },\n ): {\n pid: number | undefined;\n onError: (handler: (error: Error) => void) => void;\n onExit: (handler: (code: number | null, signal: NodeJS.Signals | null) => void) => void;\n } {\n const runId = options.env[SUBAGENT_RUN_ID_ENV];\n let stdoutFd: number | undefined;\n let stderrFd: number | undefined;\n if (runId) {\n const runDir = runDirFor(runId);\n fs.mkdirSync(runDir, { recursive: true });\n stdoutFd = fs.openSync(path.join(runDir, RUNNER_STDOUT_FILE_NAME), 'a', 0o600);\n try {\n stderrFd = fs.openSync(path.join(runDir, RUNNER_STDERR_FILE_NAME), 'a', 0o600);\n } catch (error) {\n fs.closeSync(stdoutFd);\n throw new Error(`Could not open runner stderr for '${runId}'.`, { cause: error });\n }\n }\n let child: ReturnType<typeof spawn>;\n try {\n child = spawn(command, args, {\n cwd: options.cwd,\n detached: true,\n stdio: ['ignore', stdoutFd ?? 'ignore', stderrFd ?? 'ignore'],\n windowsHide: true,\n env: options.env,\n });\n } finally {\n if (stdoutFd !== undefined) fs.closeSync(stdoutFd);\n if (stderrFd !== undefined) fs.closeSync(stderrFd);\n }\n child.unref();\n return {\n pid: child.pid,\n onError: (handler) => child.on('error', handler),\n onExit: (handler) => child.on('exit', handler),\n };\n }\n\n async spawn(input: AsyncSubagentSpawnInput): Promise<AsyncSubagentSpawnResult> {\n const { runId, agent, task, cwd } = input;\n const fanoutIndex = input.fanout ? input.childIndex : undefined;\n const handshakePath = handshakePathFor(runId);\n const configPath = currentRunConfigPath(runId);\n const transcriptPath =\n input.artifacts === false\n ? undefined\n : getArtifactPaths(\n getArtifactsDir(input.parentSessionFile ?? null, cwd, input.artifactDir ?? 'session'),\n runId,\n agent,\n ).transcriptPath;\n\n const runDir = runDirFor(runId);\n const steerIndex = input.childIndex ?? 0;\n const teamRoot = this.readCurrentTeamRoot();\n const {\n args,\n env: builtEnv,\n sdk,\n } = this.buildLaunchArgs({\n ...input.piArgs,\n runId,\n task,\n childIndex: input.childIndex,\n steerInboxDir: stepSteerInboxDir(runDir, steerIndex),\n steerCapabilityPath: steerCapabilityPath(runDir, steerIndex),\n steerAckDir: steerAcksDir(runDir, steerIndex),\n teamToolEnabled: Boolean(teamRoot),\n });\n // The child gets its own harness state, in this run's directory, which this\n // package owns. Sharing the parent's file would let a detached child rewrite\n // a session it does not own, and pointing at the parent's directory would\n // lose the file the moment the parent cleaned up its own session: a child\n // is detached and can outlive it. Pi 0.84 SDK launches consume the resolved\n // extension list directly, so preserve that same list in the child harness\n // snapshot instead of projecting the parent's stale childExtensions value.\n createHarnessSession(\n { ...getHarnessState(), childExtensions: sdk.extensions },\n { directory: runDir, environment: builtEnv, unclaimed: true },\n );\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n ...builtEnv,\n // The child resolves the same filesystem tree from this, independently of\n // whether it also joins a team.\n ...sessionScopeEnvironment(requireCurrentSessionScope()),\n ...(teamRoot ? nativeTeamRootEnvironment(teamRoot) : {}),\n };\n if (isPiRuntime(input.runtime)) {\n if (sdk.extensionsProvidedExternally) env[DOOMPI_EXTENSIONS_PROVIDED_ENV] = '1';\n else delete env[DOOMPI_EXTENSIONS_PROVIDED_ENV];\n }\n\n // Recorded before open() so the status never advertises a sidecar that is\n // not on disk yet. A failure here is not worth failing a spawn over: the\n // prompt is an inspection aid, so the run proceeds without it.\n let systemPromptPath: string | undefined;\n if (isPiRuntime(input.runtime) && sdk.systemPrompt) {\n try {\n fs.mkdirSync(runDir, { recursive: true });\n const target = systemPromptPathFor(runId);\n fs.writeFileSync(target, sdk.systemPrompt, { mode: SYSTEM_PROMPT_FILE_MODE });\n systemPromptPath = target;\n } catch {\n systemPromptPath = undefined;\n }\n }\n\n const now = this.now();\n const statusWriter = this.createStatusWriter();\n statusWriter.open(runId, {\n version: 1,\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n ...(input.sensitiveTask ? {} : { task }),\n cwd,\n ...(input.piArgs.model ? { model: input.piArgs.model } : {}),\n ...(input.inlineAgent ? { inlineAgent: input.inlineAgent } : {}),\n state: 'queued',\n startedAt: now,\n lastUpdate: now,\n ...(fanoutIndex === undefined ? {} : { fanoutIndex }),\n ...(input.internal ? { internal: true } : {}),\n ...(isPiRuntime(input.runtime) ? {} : { runtime: input.runtime }),\n ...(transcriptPath ? { transcriptPath } : {}),\n ...(systemPromptPath ? { systemPromptPath } : {}),\n ...(systemPromptPath && sdk.systemPromptMode ? { systemPromptMode: sdk.systemPromptMode } : {}),\n ...(sdk.tools ? { tools: sdk.tools } : {}),\n ...(sdk.excludeTools?.length ? { excludeTools: sdk.excludeTools } : {}),\n ...(sdk.noTools ? { noTools: true } : {}),\n });\n\n if (isPiRuntime(input.runtime)) {\n this.writeLaunchConfig(configPath, {\n runId,\n agent,\n task,\n cwd,\n childIndex: input.childIndex,\n handshakePath,\n sdk,\n ...(transcriptPath ? { transcriptPath } : {}),\n args,\n env,\n });\n }\n\n let fableLaunch: ClaudeFableLaunch | undefined;\n const fail = (message: string): never => {\n this.removeLaunchConfig(configPath);\n if (fableLaunch) cleanupClaudeFableLaunch(fableLaunch);\n fs.rmSync(fableProfileResultPathFor(runId), { force: true });\n statusWriter.updateSync((status) => {\n status.state = 'failed';\n status.error = message;\n status.lastUpdate = this.now();\n });\n throw new Error(message);\n };\n\n // One branch, and only one: `pi` runs in-process through the SDK runner and\n // gets the team channel, steering and structured output that go with it.\n // Every other runtime is an external CLI, spawned through the generic\n // runner, which deliberately knows nothing about the vendor.\n const usePi = isPiRuntime(input.runtime);\n if (!usePi) {\n const ceiling = input.piArgs.capabilityCeiling;\n if (ceiling && (!input.externalProfile || !ceiling.allowedExternalProfiles.includes(input.externalProfile))) {\n return fail(\n `External profile '${input.externalProfile ?? 'none'}' is denied by the active capability ceiling.`,\n );\n }\n if (input.externalProfile !== undefined && input.externalProfile !== CLAUDE_FABLE_PROFILE) {\n return fail(`External profile '${input.externalProfile}' is not trusted.`);\n }\n if (input.externalProfile === CLAUDE_FABLE_PROFILE) {\n if (input.runtime !== 'claude' || input.piArgs.model !== 'fable') {\n return fail('The Fable profile requires runtime claude and model fable.');\n }\n if (input.inlineAgent || input.piArgs.sessionFile || input.piArgs.parentSessionId) {\n return fail('The Fable profile requires a fresh isolated context.');\n }\n fableLaunch = prepareClaudeFableLaunch({\n runId,\n prompt: task,\n repositoryCwd: cwd,\n privateRoot: runDir,\n });\n this.writeLaunchConfig(configPath, {\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n runtime: 'claude',\n command: fableLaunch.command,\n args: fableLaunch.args,\n cwd: fableLaunch.cwd,\n env: fableLaunch.env,\n profile: fableLaunch.profile,\n stdinPath: fableLaunch.stdinPath,\n cleanupPaths: fableLaunch.cleanupPaths,\n profileResultPath: fableProfileResultPathFor(runId),\n internal: input.internal === true,\n handshakePath,\n resultPath: path.join(currentResultsDir(), `${runId}.json`),\n });\n } else {\n this.writeLaunchConfig(configPath, {\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n ...resolveRuntimeLaunch(input.runtime as string, input.runtimes ?? {}, {\n prompt: task,\n model: input.piArgs.model,\n cwd,\n }),\n cwd,\n env: builtEnv as Record<string, string>,\n handshakePath,\n resultPath: path.join(currentResultsDir(), `${runId}.json`),\n });\n }\n }\n\n const { pid, onError, onExit } = this.spawnChild(\n process.execPath,\n [usePi ? resolveSdkRunnerEntry() : resolveCliRunnerEntry()],\n { cwd, env: env as NodeJS.ProcessEnv },\n );\n if (pid === undefined) {\n return fail(`Async spawn for '${agent}' (run '${runId}') did not produce a pid.`);\n }\n let spawnError: Error | undefined;\n onError((error) => {\n spawnError = error;\n });\n\n // Recorded before the handshake: a parent that dies between spawn and\n // handshake must still leave a record a later sweep can act on.\n registerRun(requireCurrentSessionScope(), {\n runId,\n pid,\n agent,\n runtime: input.runtime ?? PI_RUNTIME_NAME,\n startedAt: now,\n });\n this.terminalPersistence.trackChild(pid);\n onExit((code, signal) => {\n this.terminalPersistence.untrackChild(pid);\n releaseRun(requireCurrentSessionScope(), runId);\n if (code === 0 && signal === null) return;\n statusWriter.updateSync((status) => {\n if (status.state && TERMINAL_ASYNC_JOB_STATES.has(status.state)) return;\n status.state = 'failed';\n status.error = `SDK runner process ${pid} exited unexpectedly (${signal ?? `code ${code ?? 'unknown'}`}).`;\n status.lastUpdate = this.now();\n status.endedAt = this.now();\n });\n this.jobTracker.track(runId);\n });\n\n const handshake = this.createSpawnHandshake();\n const outcome = await handshake.waitForHandshake({\n path: handshakePath,\n timeoutMs: input.handshakeTimeoutMs ?? this.defaultHandshakeTimeoutMs,\n }).promise;\n\n if (outcome.status !== 'signalled') {\n this.terminalPersistence.untrackChild(pid);\n const message =\n outcome.status === 'failed'\n ? outcome.error\n : (spawnError?.message ?? `Timed out waiting for '${agent}' (run '${runId}') to start.`);\n return fail(message);\n }\n\n // The child has confirmed it read the launch config; the parent's copy\n // is no longer needed (see the module header's LAUNCH CONFIG FILE\n // LIFECYCLE section).\n this.removeLaunchConfig(configPath);\n const observedState = this.readRunState(runId);\n if (!observedState || !TERMINAL_ASYNC_JOB_STATES.has(observedState)) {\n statusWriter.updateSync((status) => {\n status.state = 'running';\n status.lastUpdate = this.now();\n });\n }\n this.jobTracker.track(runId);\n\n return { runId, pid };\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{writePrivateAtomicJson as t}from"../../../adapters/atomicJson.mjs";import{init_atomicJson as n}from"../../../shared/atomicJson.mjs";import{__decorate as r,init_decorate as i}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{SUBAGENT_RUN_ID_ENV as a,init_env as o}from"../../../adapters/process/env.mjs";import{DOOM_TEAM_TYPES as s,init_types as c}from"../../../container/types.mjs";import{__decorateMetadata as l,init_decorateMetadata as u}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as d,init_decorateParam as f}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{init_supervisorControlChannel as p,steerAcksDir as ee,steerCapabilityPath as te,stepSteerInboxDir as ne}from"../../intercom/supervisorControlChannel.mjs";import{currentResultsDir as m,currentRunConfigPath as re,currentRunsDir as h,init_paths as g,requireCurrentSessionScope as _,sessionScopeEnvironment as ie}from"../../../shared/paths.mjs";import{CoalescedStatusWriter as v,STATUS_FILE_NAME as y,init_statusWriter as b}from"./statusWriter.mjs";import{init_runtimeRegistry as x,isPiRuntime as S,resolveRuntimeLaunch as C}from"../shared/runtimeRegistry.mjs";import{TERMINAL_ASYNC_JOB_STATES as w}from"../../../adapters/asyncJobTracker.mjs";import{init_asyncJobTracker as T}from"./asyncJobTracker.mjs";import{init_nativeTeamChannel as E,nativeTeamRootEnvironment as ae,readNativeTeamRootFromEnvironment as D}from"../../intercom/nativeTeamChannel.mjs";import{getArtifactPaths as O,getArtifactsDir as oe,init_artifacts as k}from"../../../shared/artifacts.mjs";import{registerRun as A,releaseRun as se}from"../../../adapters/runRegistry.mjs";import{init_runRegistry as j}from"../registry/runRegistry.mjs";import{CLAUDE_FABLE_PROFILE as M,cleanupClaudeFableLaunch as ce,init_claudeFableProfile as N,prepareClaudeFableLaunch as le}from"../shared/claudeFableProfile.mjs";import{buildPiArgs as P,init_piArgs as F}from"../shared/piArgs.mjs";import{SpawnHandshake as ue,init_spawnHandshake as de}from"./spawnHandshake.mjs";import*as I from"node:fs";import*as L from"node:path";import{createHarnessSession as fe,getHarnessState as R}from"@agimon-ai/doompi-config";import{inject as z,injectable as B}from"inversify";import{spawn as pe}from"node:child_process";import{fileURLToPath as me}from"node:url";function V(e){return L.join(h(),e)}function H(e){return L.join(V(e),G)}function U(e){return L.join(V(e),J)}function he(e){return L.join(V(e),Y)}function W(e,t){let n=L.dirname(me(import.meta.url)),r=L.resolve(n,`../..`),i=L.join(r,e,`${t}.mjs`);if(I.existsSync(i))return i;let a=L.join(r,e,`${t}.ts`);if(I.existsSync(a))return a;throw Error(`Doom Team runner '${t}' is unavailable at '${i}' or '${a}'.`)}function ge(){return W(`runs`,`sdkRunnerEntry`)}function _e(){return W(`runs/background`,`cliRunnerEntry`)}var G,K,q,J,Y,X,Z,Q,$=e((()=>{o(),c(),k(),n(),g(),E(),p(),j(),N(),F(),x(),T(),de(),b(),u(),f(),i(),G=`handshake.json`,K=`runner.stdout.log`,q=`runner.stderr.log`,J=`fable-profile-result.json`,Y=`system-prompt.md`,X=384,Z=1e4,Q=class{constructor(e,t){this.jobTracker=e,this.terminalPersistence=t}defaultHandshakeTimeoutMs=Z;now(){return Date.now()}readCurrentTeamRoot(){return D()}createStatusWriter(){return new v}createSpawnHandshake(){return new ue}writeLaunchConfig(e,n){t(e,n)}removeLaunchConfig(e){I.rmSync(e,{force:!0})}readRunState(e){try{let t=JSON.parse(I.readFileSync(L.join(V(e),y),`utf8`));return typeof t.state==`string`?t.state:void 0}catch{return}}buildLaunchArgs(e){return P(e)}spawnChild(e,t,n){let r=n.env[a],i,o;if(r){let e=V(r);I.mkdirSync(e,{recursive:!0}),i=I.openSync(L.join(e,K),`a`,384);try{o=I.openSync(L.join(e,q),`a`,384)}catch(e){throw I.closeSync(i),Error(`Could not open runner stderr for '${r}'.`,{cause:e})}}let s;try{s=pe(e,t,{cwd:n.cwd,detached:!0,stdio:[`ignore`,i??`ignore`,o??`ignore`],windowsHide:!0,env:n.env})}finally{i!==void 0&&I.closeSync(i),o!==void 0&&I.closeSync(o)}return s.unref(),{pid:s.pid,onError:e=>s.on(`error`,e),onExit:e=>s.on(`exit`,e)}}async spawn(e){let{runId:t,agent:n,task:r,cwd:i}=e,a=e.fanout?e.childIndex:void 0,o=H(t),s=re(t),c=e.artifacts===!1?void 0:O(oe(e.parentSessionFile??null,i,e.artifactDir??`session`),t,n).transcriptPath,l=V(t),u=e.childIndex??0,d=this.readCurrentTeamRoot(),{args:f,env:p,sdk:h}=this.buildLaunchArgs({...e.piArgs,runId:t,task:r,childIndex:e.childIndex,steerInboxDir:ne(l,u),steerCapabilityPath:te(l,u),steerAckDir:ee(l,u),teamToolEnabled:!!d});fe({...R(),childExtensions:h.extensions},{directory:l,environment:p,unclaimed:!0});let g={...process.env,...p,...ie(_()),...d?ae(d):{}},v;if(S(e.runtime)&&h.systemPrompt)try{I.mkdirSync(l,{recursive:!0});let e=he(t);I.writeFileSync(e,h.systemPrompt,{mode:X}),v=e}catch{v=void 0}let y=this.now(),b=this.createStatusWriter();b.open(t,{version:1,runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,...e.sensitiveTask?{}:{task:r},cwd:i,...e.piArgs.model?{model:e.piArgs.model}:{},...e.inlineAgent?{inlineAgent:e.inlineAgent}:{},state:`queued`,startedAt:y,lastUpdate:y,...a===void 0?{}:{fanoutIndex:a},...e.internal?{internal:!0}:{},...S(e.runtime)?{}:{runtime:e.runtime},...c?{transcriptPath:c}:{},...v?{systemPromptPath:v}:{},...v&&h.systemPromptMode?{systemPromptMode:h.systemPromptMode}:{},...h.tools?{tools:h.tools}:{},...h.excludeTools?.length?{excludeTools:h.excludeTools}:{},...h.noTools?{noTools:!0}:{}}),S(e.runtime)&&this.writeLaunchConfig(s,{runId:t,agent:n,task:r,cwd:i,childIndex:e.childIndex,handshakePath:o,sdk:h,...c?{transcriptPath:c}:{},args:f,env:g});let x,T=e=>{throw this.removeLaunchConfig(s),x&&ce(x),I.rmSync(U(t),{force:!0}),b.updateSync(t=>{t.state=`failed`,t.error=e,t.lastUpdate=this.now()}),Error(e)},E=S(e.runtime);if(!E){let a=e.piArgs.capabilityCeiling;if(a&&(!e.externalProfile||!a.allowedExternalProfiles.includes(e.externalProfile)))return T(`External profile '${e.externalProfile??`none`}' is denied by the active capability ceiling.`);if(e.externalProfile!==void 0&&e.externalProfile!==M)return T(`External profile '${e.externalProfile}' is not trusted.`);if(e.externalProfile===M){if(e.runtime!==`claude`||e.piArgs.model!==`fable`)return T(`The Fable profile requires runtime claude and model fable.`);if(e.inlineAgent||e.piArgs.sessionFile||e.piArgs.parentSessionId)return T(`The Fable profile requires a fresh isolated context.`);x=le({runId:t,prompt:r,repositoryCwd:i,privateRoot:l}),this.writeLaunchConfig(s,{runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,runtime:`claude`,command:x.command,args:x.args,cwd:x.cwd,env:x.env,profile:x.profile,stdinPath:x.stdinPath,cleanupPaths:x.cleanupPaths,profileResultPath:U(t),internal:e.internal===!0,handshakePath:o,resultPath:L.join(m(),`${t}.json`)})}else this.writeLaunchConfig(s,{runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,...C(e.runtime,e.runtimes??{},{prompt:r,model:e.piArgs.model,cwd:i}),cwd:i,env:p,handshakePath:o,resultPath:L.join(m(),`${t}.json`)})}let{pid:D,onError:k,onExit:j}=this.spawnChild(process.execPath,[E?ge():_e()],{cwd:i,env:g});if(D===void 0)return T(`Async spawn for '${n}' (run '${t}') did not produce a pid.`);let N;k(e=>{N=e}),A(_(),{runId:t,pid:D,agent:n,runtime:e.runtime??`pi`,startedAt:y}),this.terminalPersistence.trackChild(D),j((e,n)=>{this.terminalPersistence.untrackChild(D),se(_(),t),!(e===0&&n===null)&&(b.updateSync(t=>{t.state&&w.has(t.state)||(t.state=`failed`,t.error=`SDK runner process ${D} exited unexpectedly (${n??`code ${e??`unknown`}`}).`,t.lastUpdate=this.now(),t.endedAt=this.now())}),this.jobTracker.track(t))});let P=await this.createSpawnHandshake().waitForHandshake({path:o,timeoutMs:e.handshakeTimeoutMs??this.defaultHandshakeTimeoutMs}).promise;if(P.status!==`signalled`)return this.terminalPersistence.untrackChild(D),T(P.status===`failed`?P.error:N?.message??`Timed out waiting for '${n}' (run '${t}') to start.`);this.removeLaunchConfig(s);let F=this.readRunState(t);return(!F||!w.has(F))&&b.updateSync(e=>{e.state=`running`,e.lastUpdate=this.now()}),this.jobTracker.track(t),{runId:t,pid:D}}},Q=r([B(),d(0,z(s.AsyncJobTracker)),d(1,z(s.TerminalPersistenceService)),l(`design:paramtypes`,[Object,Object])],Q)}));$();export{Q as AsyncSubagentSpawner,U as fableProfileResultPathFor,H as handshakePathFor,$ as init_asyncExecution,V as runDirFor};
1
+ import{__esmMin as e}from"../../../_virtual/_rolldown/runtime.mjs";import{writePrivateAtomicJson as t}from"../../../adapters/atomicJson.mjs";import{init_atomicJson as n}from"../../../shared/atomicJson.mjs";import{__decorate as r,init_decorate as i}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.mjs";import{SUBAGENT_RUN_ID_ENV as a,init_env as o}from"../../../adapters/process/env.mjs";import{DOOM_TEAM_TYPES as s,init_types as c}from"../../../container/types.mjs";import{__decorateMetadata as l,init_decorateMetadata as u}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateMetadata.mjs";import{__decorateParam as d,init_decorateParam as f}from"../../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorateParam.mjs";import{init_supervisorControlChannel as p,steerAcksDir as ee,steerCapabilityPath as te,stepSteerInboxDir as m}from"../../intercom/supervisorControlChannel.mjs";import{currentResultsDir as h,currentRunConfigPath as ne,currentRunsDir as g,init_paths as _,requireCurrentSessionScope as v,sessionScopeEnvironment as re}from"../../../shared/paths.mjs";import{CoalescedStatusWriter as y,STATUS_FILE_NAME as b,init_statusWriter as x}from"./statusWriter.mjs";import{init_runtimeRegistry as S,isPiRuntime as C,resolveRuntimeLaunch as ie}from"../shared/runtimeRegistry.mjs";import{TERMINAL_ASYNC_JOB_STATES as w}from"../../../adapters/asyncJobTracker.mjs";import{init_asyncJobTracker as T}from"./asyncJobTracker.mjs";import{init_nativeTeamChannel as E,nativeTeamRootEnvironment as ae,readNativeTeamRootFromEnvironment as D}from"../../intercom/nativeTeamChannel.mjs";import{getArtifactPaths as O,getArtifactsDir as oe,init_artifacts as k}from"../../../shared/artifacts.mjs";import{registerRun as A,releaseRun as se}from"../../../adapters/runRegistry.mjs";import{init_runRegistry as j}from"../registry/runRegistry.mjs";import{CLAUDE_FABLE_PROFILE as M,cleanupClaudeFableLaunch as ce,init_claudeFableProfile as N,prepareClaudeFableLaunch as le}from"../shared/claudeFableProfile.mjs";import{buildPiArgs as P,init_piArgs as F}from"../shared/piArgs.mjs";import{SpawnHandshake as ue,init_spawnHandshake as de}from"./spawnHandshake.mjs";import*as I from"node:fs";import*as L from"node:path";import{createHarnessSession as fe,getHarnessState as pe}from"@agimon-ai/doompi-config";import{inject as R,injectable as z}from"inversify";import{DOOMPI_EXTENSIONS_PROVIDED_ENV as B}from"@agimon-ai/doompi-extension-contracts/child-process";import{spawn as me}from"node:child_process";import{fileURLToPath as he}from"node:url";function V(e){return L.join(g(),e)}function H(e){return L.join(V(e),G)}function U(e){return L.join(V(e),J)}function ge(e){return L.join(V(e),Y)}function W(e,t){let n=L.dirname(he(import.meta.url)),r=L.resolve(n,`../..`),i=L.join(r,e,`${t}.mjs`);if(I.existsSync(i))return i;let a=L.join(r,e,`${t}.ts`);if(I.existsSync(a))return a;throw Error(`Doom Team runner '${t}' is unavailable at '${i}' or '${a}'.`)}function _e(){return W(`runs`,`sdkRunnerEntry`)}function ve(){return W(`runs/background`,`cliRunnerEntry`)}var G,K,q,J,Y,X,Z,Q,$=e((()=>{o(),c(),k(),n(),_(),E(),p(),j(),N(),F(),S(),T(),de(),x(),u(),f(),i(),G=`handshake.json`,K=`runner.stdout.log`,q=`runner.stderr.log`,J=`fable-profile-result.json`,Y=`system-prompt.md`,X=384,Z=1e4,Q=class{constructor(e,t){this.jobTracker=e,this.terminalPersistence=t}defaultHandshakeTimeoutMs=Z;now(){return Date.now()}readCurrentTeamRoot(){return D()}createStatusWriter(){return new y}createSpawnHandshake(){return new ue}writeLaunchConfig(e,n){t(e,n)}removeLaunchConfig(e){I.rmSync(e,{force:!0})}readRunState(e){try{let t=JSON.parse(I.readFileSync(L.join(V(e),b),`utf8`));return typeof t.state==`string`?t.state:void 0}catch{return}}buildLaunchArgs(e){return P(e)}spawnChild(e,t,n){let r=n.env[a],i,o;if(r){let e=V(r);I.mkdirSync(e,{recursive:!0}),i=I.openSync(L.join(e,K),`a`,384);try{o=I.openSync(L.join(e,q),`a`,384)}catch(e){throw I.closeSync(i),Error(`Could not open runner stderr for '${r}'.`,{cause:e})}}let s;try{s=me(e,t,{cwd:n.cwd,detached:!0,stdio:[`ignore`,i??`ignore`,o??`ignore`],windowsHide:!0,env:n.env})}finally{i!==void 0&&I.closeSync(i),o!==void 0&&I.closeSync(o)}return s.unref(),{pid:s.pid,onError:e=>s.on(`error`,e),onExit:e=>s.on(`exit`,e)}}async spawn(e){let{runId:t,agent:n,task:r,cwd:i}=e,a=e.fanout?e.childIndex:void 0,o=H(t),s=ne(t),c=e.artifacts===!1?void 0:O(oe(e.parentSessionFile??null,i,e.artifactDir??`session`),t,n).transcriptPath,l=V(t),u=e.childIndex??0,d=this.readCurrentTeamRoot(),{args:f,env:p,sdk:g}=this.buildLaunchArgs({...e.piArgs,runId:t,task:r,childIndex:e.childIndex,steerInboxDir:m(l,u),steerCapabilityPath:te(l,u),steerAckDir:ee(l,u),teamToolEnabled:!!d});fe({...pe(),childExtensions:g.extensions},{directory:l,environment:p,unclaimed:!0});let _={...process.env,...p,...re(v()),...d?ae(d):{}};C(e.runtime)&&(g.extensionsProvidedExternally?_[B]=`1`:delete _[B]);let y;if(C(e.runtime)&&g.systemPrompt)try{I.mkdirSync(l,{recursive:!0});let e=ge(t);I.writeFileSync(e,g.systemPrompt,{mode:X}),y=e}catch{y=void 0}let b=this.now(),x=this.createStatusWriter();x.open(t,{version:1,runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,...e.sensitiveTask?{}:{task:r},cwd:i,...e.piArgs.model?{model:e.piArgs.model}:{},...e.inlineAgent?{inlineAgent:e.inlineAgent}:{},state:`queued`,startedAt:b,lastUpdate:b,...a===void 0?{}:{fanoutIndex:a},...e.internal?{internal:!0}:{},...C(e.runtime)?{}:{runtime:e.runtime},...c?{transcriptPath:c}:{},...y?{systemPromptPath:y}:{},...y&&g.systemPromptMode?{systemPromptMode:g.systemPromptMode}:{},...g.tools?{tools:g.tools}:{},...g.excludeTools?.length?{excludeTools:g.excludeTools}:{},...g.noTools?{noTools:!0}:{}}),C(e.runtime)&&this.writeLaunchConfig(s,{runId:t,agent:n,task:r,cwd:i,childIndex:e.childIndex,handshakePath:o,sdk:g,...c?{transcriptPath:c}:{},args:f,env:_});let S,T=e=>{throw this.removeLaunchConfig(s),S&&ce(S),I.rmSync(U(t),{force:!0}),x.updateSync(t=>{t.state=`failed`,t.error=e,t.lastUpdate=this.now()}),Error(e)},E=C(e.runtime);if(!E){let a=e.piArgs.capabilityCeiling;if(a&&(!e.externalProfile||!a.allowedExternalProfiles.includes(e.externalProfile)))return T(`External profile '${e.externalProfile??`none`}' is denied by the active capability ceiling.`);if(e.externalProfile!==void 0&&e.externalProfile!==M)return T(`External profile '${e.externalProfile}' is not trusted.`);if(e.externalProfile===M){if(e.runtime!==`claude`||e.piArgs.model!==`fable`)return T(`The Fable profile requires runtime claude and model fable.`);if(e.inlineAgent||e.piArgs.sessionFile||e.piArgs.parentSessionId)return T(`The Fable profile requires a fresh isolated context.`);S=le({runId:t,prompt:r,repositoryCwd:i,privateRoot:l}),this.writeLaunchConfig(s,{runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,runtime:`claude`,command:S.command,args:S.args,cwd:S.cwd,env:S.env,profile:S.profile,stdinPath:S.stdinPath,cleanupPaths:S.cleanupPaths,profileResultPath:U(t),internal:e.internal===!0,handshakePath:o,resultPath:L.join(h(),`${t}.json`)})}else this.writeLaunchConfig(s,{runId:t,...e.operationId?{operationId:e.operationId}:{},agent:n,...ie(e.runtime,e.runtimes??{},{prompt:r,model:e.piArgs.model,cwd:i}),cwd:i,env:p,handshakePath:o,resultPath:L.join(h(),`${t}.json`)})}let{pid:D,onError:k,onExit:j}=this.spawnChild(process.execPath,[E?_e():ve()],{cwd:i,env:_});if(D===void 0)return T(`Async spawn for '${n}' (run '${t}') did not produce a pid.`);let N;k(e=>{N=e}),A(v(),{runId:t,pid:D,agent:n,runtime:e.runtime??`pi`,startedAt:b}),this.terminalPersistence.trackChild(D),j((e,n)=>{this.terminalPersistence.untrackChild(D),se(v(),t),!(e===0&&n===null)&&(x.updateSync(t=>{t.state&&w.has(t.state)||(t.state=`failed`,t.error=`SDK runner process ${D} exited unexpectedly (${n??`code ${e??`unknown`}`}).`,t.lastUpdate=this.now(),t.endedAt=this.now())}),this.jobTracker.track(t))});let P=await this.createSpawnHandshake().waitForHandshake({path:o,timeoutMs:e.handshakeTimeoutMs??this.defaultHandshakeTimeoutMs}).promise;if(P.status!==`signalled`)return this.terminalPersistence.untrackChild(D),T(P.status===`failed`?P.error:N?.message??`Timed out waiting for '${n}' (run '${t}') to start.`);this.removeLaunchConfig(s);let F=this.readRunState(t);return(!F||!w.has(F))&&x.updateSync(e=>{e.state=`running`,e.lastUpdate=this.now()}),this.jobTracker.track(t),{runId:t,pid:D}}},Q=r([z(),d(0,R(s.AsyncJobTracker)),d(1,R(s.TerminalPersistenceService)),l(`design:paramtypes`,[Object,Object])],Q)}));$();export{Q as AsyncSubagentSpawner,U as fableProfileResultPathFor,H as handshakePathFor,$ as init_asyncExecution,V as runDirFor};
2
2
  //# sourceMappingURL=asyncExecution.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncExecution.mjs","names":[],"sources":["../../../../src/services/runs/background/asyncExecution.ts"],"sourcesContent":["/**\n * The async-only spawn executor: launches exactly one subagent child process\n * and returns a run handle immediately, without waiting for the run to\n * finish.\n *\n * SCOPE - PARENT SIDE ONLY, SINGLE SPAWN:\n * `doom-pi-subagents/src/runs/background/asyncExecution.ts` is 1698 lines\n * doing far more than spawning: chain/parallel-group orchestration (building\n * `AgentConfig[]` sequences, dynamic fan-out expansion, workflow-graph\n * snapshots), session revival (a 3-way ready/ack/proceed handshake distinct\n * from the plain started-signal this module uses), and the foreground\n * (synchronous, blocking) code path. None of that is ported here:\n * - Chain/fan-out ORCHESTRATION (deciding how many children to spawn, in\n * what order, from what step list) is `port-steering`'s concurrent work on\n * the resolved-step and workflow-graph types. This module is the\n * PRIMITIVE that orchestration calls once per child - `childIndex` and\n * `fanout` arrive as inputs, already decided by the caller, not derived\n * here (see the child-index section below for why that is safe)\n * - Session revival (`revivalLease`, the 3-way ready/ack/proceed dance at\n * predecessor lines 498-509) is a materially different operation - resuming\n * an existing transcript, not spawning a fresh child - and is not ported.\n * `session-lease.ts` already exists in this package for whoever builds that\n * path later\n * - The foreground (synchronous) branch is dropped entirely, per the async-\n * only requirement below; there is no `async` parameter, only this\n *\n * WHAT THIS DOES NOT BUILD (compose, don't reimplement):\n * `CoalescedStatusWriter`, `TerminalPersistenceService`, `AsyncJobTracker`,\n * `SpawnHandshake`, `ResultWatcher`, `CompletionNotifier` all already exist\n * in this directory and are used here, not reimplemented. In particular, the\n * predecessor's process-exit tracking is reduced to the child handle's exact\n * exit signal: an abnormal exit marks that run failed immediately, while the\n * crash-marker path (`TerminalPersistenceService` +\n * `ProcessTerminalInspector`) remains the recovery path after parent loss.\n * `trackChild(pid)` on the caller's own already-active\n * `TerminalPersistenceService` is what makes a spawned child get killed if\n * the run that spawned it dies, which is the same guarantee the predecessor\n * used its own mechanism for.\n *\n * SDK RUNNER BOUNDARY:\n * `sdkRunnerEntry.ts` is the detached child process target. It reads the\n * launch config, creates the child through Pi's SDK, and loads\n * `subagent-prompt-runtime-entry` as an explicit child extension. That\n * extension owns the handshake, status, steering, and terminal result\n * lifecycle through the existing runner services. The real Pi CLI remains\n * the parent host used by system tests and DoomPi, not the child launcher.\n *\n * WHY SpawnHandshake, NOT `Atomics.wait`:\n * The predecessor blocks the parent's event loop for up to 10 seconds per\n * spawn (`asyncExecution.ts:376-380`, `Atomics.wait` on a shared buffer).\n * That codepath only actually ran for session revival there (a fresh spawn\n * had no `startupPath` and returned immediately) - but this package has no\n * synchronous/foreground path left to hide the cost behind, so a busy-wait\n * would block every single async spawn, not just revivals. `SpawnHandshake`\n * (already in this directory) replaces it: a real `fs.watch` on the\n * handshake file's directory, with a short poll as the safety net, and it\n * never blocks the event loop.\n *\n * LAUNCH CONFIG FILE LIFECYCLE (fix):\n * The predecessor writes the launch config with `fs.writeFileSync(cfgPath,\n * ...)` - no `mode` option, so it lands at the umask default - and never\n * unlinks it on any path, successful or failed\n * (`asyncExecution.ts:491-495`). Both are fixed here: `writeLaunchConfig`\n * writes 0600 (`writePrivateAtomicJson`, already in this package), and\n * `removeLaunchConfig` runs on every exit path - success (once the handshake\n * confirms the child has read it), and every failure (no pid, spawn error,\n * handshake failure or timeout) - so nothing is ever left behind holding the\n * full task text and resolved secrets/tokens at world-readable-adjacent\n * permissions.\n *\n * CHILD INDEX (fix - `DeliverableGuard` had no way to get one):\n * `DeliverableGuard.deliverNudge` requires an explicit `nudgeTarget` of\n * `{kind:'child', index}` for a fan-out run specifically so it cannot\n * broadcast a nudge to every sibling over one child's missing deliverable\n * (see that module's header). Nothing in this package maps a team member id\n * to a fan-out child index (verified: neither `nativeTeamChannel.ts` nor\n * `parallel-groups.ts` does this). This module needs no such mapping either\n * - `childIndex` is simply which spawn call this is, a fact the caller (a\n * single spawn, or a fan-out loop calling this once per child) already knows\n * without looking anything up, exactly like a flat index into\n * `status.steps[]` is decided by whoever builds that array. `nudgeTarget` is\n * derived from `childIndex`/`fanout` and persisted onto the run's own\n * `status.json` (`nudgeTarget` field) for whatever later evaluates\n * `DeliverableGuardInput` to read back, rather than re-derived at that point.\n *\n * DELIVERABLE EXPECTATION (fix - `DeliverableGuard` had no policy):\n * Derived here, at the one point this module already has the task text:\n * `classifyTaskMutationIntent` (used AS-IS, per its own header's warning\n * against widening its vocabulary for a third caller) distinguishes\n * `'implementation'` intent from `'read-only'`/`'unknown'`. Implementation\n * intent gets a real expectation (`{kind:'summary'}` by default - the\n * minimum a run can be asked to prove it did something, when no more\n * specific expectation like a structured-output schema or artifact path was\n * supplied); read-only or unknown intent gets none at all. This is\n * deliberately NOT `{kind:'summary'}` unconditionally: a guard that flags\n * every research run for an empty summary gets ignored, and an ignored guard\n * misses the run that actually needed it.\n *\n * TEAM MEMBERSHIP ENV FORWARDING (fix - a spawned child had no way to join\n * the team):\n * If THIS process (the one calling `spawn()`) is itself part of a native\n * team - `readNativeTeamRootFromEnvironment()` resolves a root context off\n * its own `process.env` - the same root vars\n * (`nativeTeamRootEnvironment(root)`, already in `nativeTeamChannel.ts`,\n * not reimplemented here) are folded into the child's env alongside\n * whatever `buildPiArgs` produced. Only the ROOT vars travel this way, never\n * a member id/token: those are per-member secrets the CHILD mints for\n * itself when it registers (see `runnerBootstrap.ts` and\n * `runnerTeamMembership.ts`), not something safe to hand down from the\n * parent's own identity. When this process has no team root (a standalone\n * spawn outside any team), nothing is forwarded and the child simply has\n * none to register against.\n */\n\nimport { spawn } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { createHarnessSession, getHarnessState } from '@agimon-ai/doompi-config';\nimport type { InlineAgent } from '@agimon-ai/doompi-extension-contracts/subagent-tool';\nimport { inject, injectable } from 'inversify';\nimport { PI_RUNTIME_NAME, SUBAGENT_RUN_ID_ENV } from '../../../adapters/process/env.ts';\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport { getArtifactPaths, getArtifactsDir } from '../../../shared/artifacts.ts';\nimport { writePrivateAtomicJson } from '../../../shared/atomicJson.ts';\nimport {\n currentResultsDir,\n currentRunConfigPath,\n currentRunsDir,\n requireCurrentSessionScope,\n sessionScopeEnvironment,\n} from '../../../shared/paths.ts';\nimport type { ActivityState, ArtifactDirPreference } from '../../../types/index.ts';\nimport {\n nativeTeamRootEnvironment,\n readNativeTeamRootFromEnvironment,\n type TeamRootContext,\n} from '../../intercom/nativeTeamChannel.ts';\nimport { steerAcksDir, steerCapabilityPath, stepSteerInboxDir } from '../../intercom/supervisorControlChannel.ts';\nimport { registerRun, releaseRun } from '../registry/runRegistry.ts';\nimport {\n CLAUDE_FABLE_PROFILE,\n type ClaudeFableLaunch,\n cleanupClaudeFableLaunch,\n prepareClaudeFableLaunch,\n} from '../shared/claudeFableProfile.ts';\nimport {\n type BuildPiArgsInput,\n type BuildPiArgsResult,\n buildPiArgs,\n type PiSdkLaunchSettings,\n} from '../shared/piArgs.ts';\nimport { isPiRuntime, type RuntimeTable, resolveRuntimeLaunch } from '../shared/runtimeRegistry.ts';\nimport { type IAsyncJobTracker, TERMINAL_ASYNC_JOB_STATES } from './asyncJobTracker.ts';\nimport { type ISpawnHandshake, SpawnHandshake } from './spawnHandshake.ts';\nimport {\n CoalescedStatusWriter,\n type ICoalescedStatusWriter,\n STATUS_FILE_NAME,\n type StatusWithRecentEntries,\n} from './statusWriter.ts';\nimport type { ITerminalPersistenceService } from './terminalPersistence.ts';\n\n/** Leaf name of the started-signal file `SpawnHandshake` watches for, under `currentRunsDir()/<runId>/`. Exported so the child-side runner can compose the same convention rather than duplicate it. */\nexport const HANDSHAKE_FILE_NAME = 'handshake.json';\nexport const RUNNER_STDOUT_FILE_NAME = 'runner.stdout.log';\nexport const RUNNER_STDERR_FILE_NAME = 'runner.stderr.log';\nexport const FABLE_PROFILE_RESULT_FILE_NAME = 'fable-profile-result.json';\n/**\n * The effective system prompt this run launched with, kept for the fleet\n * inspector's agent view.\n *\n * A SIDECAR RATHER THAN A FIELD ON status.json: `CoalescedStatusWriter`\n * rewrites the whole status file on every coalesced flush, and a system prompt\n * is kilobytes of text that never changes after spawn. Inlining it would put\n * that payload through every status write for the life of the run. This file\n * is written once, next to the status it belongs to.\n *\n * Written 0600 for the same reason `piArgs.ts` writes its prompt file that\n * way: a resolved prompt can carry inherited project context.\n */\nexport const SYSTEM_PROMPT_FILE_NAME = 'system-prompt.md';\nconst SYSTEM_PROMPT_FILE_MODE = 0o600;\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;\n\n/**\n * `state`/`steps`/`parallelGroups`/`chainStepCount`/`workflowGraph`/\n * `pendingAppends` are declared as optional-and-widened, rather than a\n * runner-owned status type, so this SAME status shape is what\n * `chain-append.ts`'s `applyChainAppendRequest` (which expects\n * `AppendableChainStatus`, an object with exactly these optional fields) can\n * mutate directly - one status type for the whole spawn-through-finalize\n * lifecycle, not a parent-side one and a child-side one that have to be kept\n * in sync by hand.\n */\nexport interface AsyncRunStatus extends StatusWithRecentEntries {\n /** Version 1 is emitted by current writers; absent is accepted only by the compatibility reader. */\n version?: 1;\n runId: string;\n operationId?: string;\n agent: string;\n /** Original task text required to restore a suspended run. */\n task?: string;\n cwd?: string;\n /** Resolved model selected for this run, when one was requested. */\n model?: string;\n /** One-shot agent definition required to restore an inline agent. */\n inlineAgent?: InlineAgent;\n state: 'queued' | 'running' | 'complete' | 'completed' | 'failed' | 'paused' | 'stopped';\n startedAt: number;\n lastUpdate: number;\n endedAt?: number;\n /** This run's position among its fan-out siblings, when it has any. Absent for a standalone run. */\n fanoutIndex?: number;\n /** Bridge-owned runs are consumed internally and never rendered as generic completion chat. */\n internal?: boolean;\n /** Which runtime executes this run. Absent means the default in-process `pi` path. */\n runtime?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n error?: string;\n /**\n * Set once, at finalize, by `RunnerReporting.mutateTerminalStatus` - the\n * SAME value it puts in the terminal result file, truncated to a bounded\n * length (see that module's header for why). Lets a caller (chain mode,\n * in particular) learn a completed run's own summary through the status\n * file it already reads to learn the run finished, rather than needing a\n * second read of a claimed, single-consumer result file.\n */\n summary?: string;\n /**\n * This run's own Pi session transcript path, written during child bootstrap\n * and retained by `RunnerReporting.mutateTerminalStatus` at finalize. See\n * that module's header for where the value comes from and why it is bounded\n * by omission, not truncation. Absent for a run that either never reported\n * a session file (headless, no persisted transcript) or reported one too\n * long to trust. This is what `action='resume'` reads to know which\n * transcript a later revival should reopen.\n */\n sessionFile?: string;\n /** Child-owned Pi SDK event transcript consumed by the fleet detail pane. */\n transcriptPath?: string;\n /**\n * Sidecar holding this run's effective system prompt, and the resolved tool\n * plan it launched with. Recorded at spawn purely so the fleet inspector can\n * show what an agent was actually told: the launch config that carried them\n * is deleted as soon as the child handshakes, and the agent definition on\n * disk can change while a run is still going.\n *\n * All absent for a non-`pi` runtime, which has no SDK launch settings, and\n * for any run started before this was recorded.\n */\n systemPromptPath?: string;\n systemPromptMode?: 'append' | 'replace';\n /** Effective tool allowlist. Absent means \"whatever the child defaults to\". */\n tools?: string[];\n excludeTools?: string[];\n noTools?: boolean;\n /** Cumulative tokens observed from finalized child messages. */\n tokens?: number;\n /** Most recently started child tool. */\n currentTool?: string;\n /** Number of child tool executions observed so far. */\n toolCount?: number;\n}\n\n/** The parent-written contract a child-side runner reads via `currentRunConfigPath(runId)`. */\nexport interface LaunchConfig {\n runId: string;\n agent: string;\n task: string;\n cwd: string;\n childIndex: number;\n handshakePath: string;\n sdk: PiSdkLaunchSettings;\n transcriptPath?: string;\n /** Retained during the SDK cutover for compatibility diagnostics only. */\n args: string[];\n env: Record<string, string | undefined>;\n}\n\nexport interface AsyncSubagentSpawnInput {\n runId: string;\n operationId?: string;\n agent: string;\n /** One-shot agent definition retained only for explicit suspension restore. */\n inlineAgent?: InlineAgent;\n task: string;\n cwd: string;\n /** Persisted parent transcript used to place child artifacts beside the parent session. */\n parentSessionFile?: string;\n /** This spawn's position among its siblings. 0 for a standalone (non-fan-out) run. */\n childIndex: number;\n /** Whether this spawn is one of several children under a fan-out parent. */\n fanout: boolean;\n /** Everything `buildPiArgs` needs, minus what this spawner supplies itself (`runId`/`task`/`childIndex`). */\n piArgs: Omit<BuildPiArgsInput, 'runId' | 'task' | 'childIndex'>;\n /** False disables the child event transcript. Defaults to enabled. */\n artifacts?: boolean;\n /** Artifact root policy inherited from the parent Doom Team config. */\n artifactDir?: ArtifactDirPreference;\n handshakeTimeoutMs?: number;\n /**\n * Which runtime executes this run. Defaults to the in-process Pi SDK path.\n * Anything else is an external CLI resolved through `runtimeRegistry.ts`.\n */\n runtime?: string;\n /** Fixed trusted external profile. Only Doom Team internal bridges may set this. */\n externalProfile?: string;\n /** Prevent sensitive task text from being persisted in status.json. */\n sensitiveTask?: boolean;\n /** Suppress the generic completion notifier for an internal bridge run. */\n internal?: boolean;\n /** Runtimes available to this spawn, already merged with the shipped defaults. */\n runtimes?: RuntimeTable;\n}\n\nexport interface AsyncSubagentSpawnResult {\n runId: string;\n pid: number;\n}\n\nexport interface IAsyncSubagentSpawner {\n /** Spawn one child and return once it has confirmed it started. Throws on any failure to reach that point. */\n spawn(input: AsyncSubagentSpawnInput): Promise<AsyncSubagentSpawnResult>;\n}\n\n/** `currentRunsDir()/<runId>`. Exported so the child-side runner resolves the same directory, not a parallel one. */\nexport function runDirFor(runId: string): string {\n return path.join(currentRunsDir(), runId);\n}\n\n/** Exported so the child-side runner writes its ready/error signal at the exact path this parent half waits on. */\nexport function handshakePathFor(runId: string): string {\n return path.join(runDirFor(runId), HANDSHAKE_FILE_NAME);\n}\n\nexport function fableProfileResultPathFor(runId: string): string {\n return path.join(runDirFor(runId), FABLE_PROFILE_RESULT_FILE_NAME);\n}\n\n/** Where `spawn()` records this run's effective system prompt. See `SYSTEM_PROMPT_FILE_NAME`. */\nexport function systemPromptPathFor(runId: string): string {\n return path.join(runDirFor(runId), SYSTEM_PROMPT_FILE_NAME);\n}\n\n/**\n * Locate a child entry point on disk, built or source.\n *\n * The child is a separate process loading this by path, so the path has to be\n * the one that exists now: `.mjs` under `dist` when this bundle is built, the\n * original source file when it is not. `relativeDir` is where the entry sits\n * relative to `src/` - the SDK runner is one level up from this module, the\n * CLI runner is beside it.\n */\nfunction resolveRunnerEntry(relativeDir: string, stem: string): string {\n const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));\n const packageRoot = path.resolve(moduleDirectory, '../..');\n const builtCandidate = path.join(packageRoot, relativeDir, `${stem}.mjs`);\n if (fs.existsSync(builtCandidate)) return builtCandidate;\n const sourceCandidate = path.join(packageRoot, relativeDir, `${stem}.ts`);\n if (fs.existsSync(sourceCandidate)) return sourceCandidate;\n throw new Error(`Doom Team runner '${stem}' is unavailable at '${builtCandidate}' or '${sourceCandidate}'.`);\n}\n\nfunction resolveSdkRunnerEntry(): string {\n return resolveRunnerEntry('runs', 'sdkRunnerEntry');\n}\n\nfunction resolveCliRunnerEntry(): string {\n return resolveRunnerEntry('runs/background', 'cliRunnerEntry');\n}\n\n@injectable()\nexport class AsyncSubagentSpawner implements IAsyncSubagentSpawner {\n constructor(\n @inject(DOOM_TEAM_TYPES.AsyncJobTracker) private readonly jobTracker: IAsyncJobTracker,\n @inject(DOOM_TEAM_TYPES.TerminalPersistenceService)\n private readonly terminalPersistence: ITerminalPersistenceService,\n ) {}\n\n /**\n * Seam for tests, as a protected member rather than a constructor\n * argument: inversify cannot resolve a primitive. See `pollScheduler.ts`\n * for the same pattern.\n */\n protected readonly defaultHandshakeTimeoutMs: number = DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n /**\n * This process's own team root context, read fresh per spawn. Protected\n * so a test can supply one without writing real team files to disk - see\n * the module header's TEAM MEMBERSHIP ENV FORWARDING section for why this\n * is read from `process.env` rather than accepted as a constructor\n * argument (inversify cannot resolve a primitive either way).\n */\n protected readCurrentTeamRoot(): TeamRootContext | undefined {\n return readNativeTeamRootFromEnvironment();\n }\n\n /**\n * A fresh writer per spawn, deliberately never the container's shared\n * singleton - the same reasoning as `StaleRunReconciler.createStatusWriter`\n * (see that class's header): the singleton may simultaneously be the live\n * write path for a run THIS process is itself running, and bootstrapping\n * someone else's (the spawned child's) initial status is a different\n * writer's job.\n */\n protected createStatusWriter(): ICoalescedStatusWriter<AsyncRunStatus> {\n return new CoalescedStatusWriter<AsyncRunStatus>();\n }\n\n /**\n * A fresh handshake wait per spawn. `SpawnHandshake` is bound transient in\n * the container for exactly this reason (each `waitForHandshake()` owns\n * its own timers and watcher for exactly one spawn); resolving it via\n * constructor injection would hand every spawn the same, already-used\n * instance, so this seam constructs one directly instead.\n */\n protected createSpawnHandshake(): ISpawnHandshake {\n return new SpawnHandshake();\n }\n\n protected writeLaunchConfig(filePath: string, config: object): void {\n writePrivateAtomicJson(filePath, config);\n }\n\n protected removeLaunchConfig(filePath: string): void {\n fs.rmSync(filePath, { force: true });\n }\n\n protected readRunState(runId: string): string | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(runDirFor(runId), STATUS_FILE_NAME), 'utf8')) as {\n state?: unknown;\n };\n return typeof parsed.state === 'string' ? parsed.state : undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Resolves the child's CLI args/env. Protected so a test can bypass\n * `buildPiArgs`'s own real filesystem resolution (it locates a runtime\n * extension script on disk via `resolvePiLaunchToolPlan`, which is\n * environment-dependent and not something a unit test for THIS module's\n * orchestration logic should depend on).\n */\n protected buildLaunchArgs(input: BuildPiArgsInput): BuildPiArgsResult {\n return buildPiArgs(input);\n }\n\n /**\n * The actual `child_process.spawn`. Detached and unref'd immediately. Output\n * goes to per-run files so a startup failure is diagnosable without keeping\n * parent-owned pipes alive.\n */\n protected spawnChild(\n command: string,\n args: string[],\n options: { cwd: string; env: NodeJS.ProcessEnv },\n ): {\n pid: number | undefined;\n onError: (handler: (error: Error) => void) => void;\n onExit: (handler: (code: number | null, signal: NodeJS.Signals | null) => void) => void;\n } {\n const runId = options.env[SUBAGENT_RUN_ID_ENV];\n let stdoutFd: number | undefined;\n let stderrFd: number | undefined;\n if (runId) {\n const runDir = runDirFor(runId);\n fs.mkdirSync(runDir, { recursive: true });\n stdoutFd = fs.openSync(path.join(runDir, RUNNER_STDOUT_FILE_NAME), 'a', 0o600);\n try {\n stderrFd = fs.openSync(path.join(runDir, RUNNER_STDERR_FILE_NAME), 'a', 0o600);\n } catch (error) {\n fs.closeSync(stdoutFd);\n throw new Error(`Could not open runner stderr for '${runId}'.`, { cause: error });\n }\n }\n let child: ReturnType<typeof spawn>;\n try {\n child = spawn(command, args, {\n cwd: options.cwd,\n detached: true,\n stdio: ['ignore', stdoutFd ?? 'ignore', stderrFd ?? 'ignore'],\n windowsHide: true,\n env: options.env,\n });\n } finally {\n if (stdoutFd !== undefined) fs.closeSync(stdoutFd);\n if (stderrFd !== undefined) fs.closeSync(stderrFd);\n }\n child.unref();\n return {\n pid: child.pid,\n onError: (handler) => child.on('error', handler),\n onExit: (handler) => child.on('exit', handler),\n };\n }\n\n async spawn(input: AsyncSubagentSpawnInput): Promise<AsyncSubagentSpawnResult> {\n const { runId, agent, task, cwd } = input;\n const fanoutIndex = input.fanout ? input.childIndex : undefined;\n const handshakePath = handshakePathFor(runId);\n const configPath = currentRunConfigPath(runId);\n const transcriptPath =\n input.artifacts === false\n ? undefined\n : getArtifactPaths(\n getArtifactsDir(input.parentSessionFile ?? null, cwd, input.artifactDir ?? 'session'),\n runId,\n agent,\n ).transcriptPath;\n\n const runDir = runDirFor(runId);\n const steerIndex = input.childIndex ?? 0;\n const teamRoot = this.readCurrentTeamRoot();\n const {\n args,\n env: builtEnv,\n sdk,\n } = this.buildLaunchArgs({\n ...input.piArgs,\n runId,\n task,\n childIndex: input.childIndex,\n steerInboxDir: stepSteerInboxDir(runDir, steerIndex),\n steerCapabilityPath: steerCapabilityPath(runDir, steerIndex),\n steerAckDir: steerAcksDir(runDir, steerIndex),\n teamToolEnabled: Boolean(teamRoot),\n });\n // The child gets its own harness state, in this run's directory, which this\n // package owns. Sharing the parent's file would let a detached child rewrite\n // a session it does not own, and pointing at the parent's directory would\n // lose the file the moment the parent cleaned up its own session: a child\n // is detached and can outlive it. Pi 0.84 SDK launches consume the resolved\n // extension list directly, so preserve that same list in the child harness\n // snapshot instead of projecting the parent's stale childExtensions value.\n createHarnessSession(\n { ...getHarnessState(), childExtensions: sdk.extensions },\n { directory: runDir, environment: builtEnv, unclaimed: true },\n );\n const env = {\n ...process.env,\n ...builtEnv,\n // The child resolves the same filesystem tree from this, independently of\n // whether it also joins a team.\n ...sessionScopeEnvironment(requireCurrentSessionScope()),\n ...(teamRoot ? nativeTeamRootEnvironment(teamRoot) : {}),\n };\n\n // Recorded before open() so the status never advertises a sidecar that is\n // not on disk yet. A failure here is not worth failing a spawn over: the\n // prompt is an inspection aid, so the run proceeds without it.\n let systemPromptPath: string | undefined;\n if (isPiRuntime(input.runtime) && sdk.systemPrompt) {\n try {\n fs.mkdirSync(runDir, { recursive: true });\n const target = systemPromptPathFor(runId);\n fs.writeFileSync(target, sdk.systemPrompt, { mode: SYSTEM_PROMPT_FILE_MODE });\n systemPromptPath = target;\n } catch {\n systemPromptPath = undefined;\n }\n }\n\n const now = this.now();\n const statusWriter = this.createStatusWriter();\n statusWriter.open(runId, {\n version: 1,\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n ...(input.sensitiveTask ? {} : { task }),\n cwd,\n ...(input.piArgs.model ? { model: input.piArgs.model } : {}),\n ...(input.inlineAgent ? { inlineAgent: input.inlineAgent } : {}),\n state: 'queued',\n startedAt: now,\n lastUpdate: now,\n ...(fanoutIndex === undefined ? {} : { fanoutIndex }),\n ...(input.internal ? { internal: true } : {}),\n ...(isPiRuntime(input.runtime) ? {} : { runtime: input.runtime }),\n ...(transcriptPath ? { transcriptPath } : {}),\n ...(systemPromptPath ? { systemPromptPath } : {}),\n ...(systemPromptPath && sdk.systemPromptMode ? { systemPromptMode: sdk.systemPromptMode } : {}),\n ...(sdk.tools ? { tools: sdk.tools } : {}),\n ...(sdk.excludeTools?.length ? { excludeTools: sdk.excludeTools } : {}),\n ...(sdk.noTools ? { noTools: true } : {}),\n });\n\n if (isPiRuntime(input.runtime)) {\n this.writeLaunchConfig(configPath, {\n runId,\n agent,\n task,\n cwd,\n childIndex: input.childIndex,\n handshakePath,\n sdk,\n ...(transcriptPath ? { transcriptPath } : {}),\n args,\n env,\n });\n }\n\n let fableLaunch: ClaudeFableLaunch | undefined;\n const fail = (message: string): never => {\n this.removeLaunchConfig(configPath);\n if (fableLaunch) cleanupClaudeFableLaunch(fableLaunch);\n fs.rmSync(fableProfileResultPathFor(runId), { force: true });\n statusWriter.updateSync((status) => {\n status.state = 'failed';\n status.error = message;\n status.lastUpdate = this.now();\n });\n throw new Error(message);\n };\n\n // One branch, and only one: `pi` runs in-process through the SDK runner and\n // gets the team channel, steering and structured output that go with it.\n // Every other runtime is an external CLI, spawned through the generic\n // runner, which deliberately knows nothing about the vendor.\n const usePi = isPiRuntime(input.runtime);\n if (!usePi) {\n const ceiling = input.piArgs.capabilityCeiling;\n if (ceiling && (!input.externalProfile || !ceiling.allowedExternalProfiles.includes(input.externalProfile))) {\n return fail(\n `External profile '${input.externalProfile ?? 'none'}' is denied by the active capability ceiling.`,\n );\n }\n if (input.externalProfile !== undefined && input.externalProfile !== CLAUDE_FABLE_PROFILE) {\n return fail(`External profile '${input.externalProfile}' is not trusted.`);\n }\n if (input.externalProfile === CLAUDE_FABLE_PROFILE) {\n if (input.runtime !== 'claude' || input.piArgs.model !== 'fable') {\n return fail('The Fable profile requires runtime claude and model fable.');\n }\n if (input.inlineAgent || input.piArgs.sessionFile || input.piArgs.parentSessionId) {\n return fail('The Fable profile requires a fresh isolated context.');\n }\n fableLaunch = prepareClaudeFableLaunch({\n runId,\n prompt: task,\n repositoryCwd: cwd,\n privateRoot: runDir,\n });\n this.writeLaunchConfig(configPath, {\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n runtime: 'claude',\n command: fableLaunch.command,\n args: fableLaunch.args,\n cwd: fableLaunch.cwd,\n env: fableLaunch.env,\n profile: fableLaunch.profile,\n stdinPath: fableLaunch.stdinPath,\n cleanupPaths: fableLaunch.cleanupPaths,\n profileResultPath: fableProfileResultPathFor(runId),\n internal: input.internal === true,\n handshakePath,\n resultPath: path.join(currentResultsDir(), `${runId}.json`),\n });\n } else {\n this.writeLaunchConfig(configPath, {\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n ...resolveRuntimeLaunch(input.runtime as string, input.runtimes ?? {}, {\n prompt: task,\n model: input.piArgs.model,\n cwd,\n }),\n cwd,\n env: builtEnv as Record<string, string>,\n handshakePath,\n resultPath: path.join(currentResultsDir(), `${runId}.json`),\n });\n }\n }\n\n const { pid, onError, onExit } = this.spawnChild(\n process.execPath,\n [usePi ? resolveSdkRunnerEntry() : resolveCliRunnerEntry()],\n { cwd, env: env as NodeJS.ProcessEnv },\n );\n if (pid === undefined) {\n return fail(`Async spawn for '${agent}' (run '${runId}') did not produce a pid.`);\n }\n let spawnError: Error | undefined;\n onError((error) => {\n spawnError = error;\n });\n\n // Recorded before the handshake: a parent that dies between spawn and\n // handshake must still leave a record a later sweep can act on.\n registerRun(requireCurrentSessionScope(), {\n runId,\n pid,\n agent,\n runtime: input.runtime ?? PI_RUNTIME_NAME,\n startedAt: now,\n });\n this.terminalPersistence.trackChild(pid);\n onExit((code, signal) => {\n this.terminalPersistence.untrackChild(pid);\n releaseRun(requireCurrentSessionScope(), runId);\n if (code === 0 && signal === null) return;\n statusWriter.updateSync((status) => {\n if (status.state && TERMINAL_ASYNC_JOB_STATES.has(status.state)) return;\n status.state = 'failed';\n status.error = `SDK runner process ${pid} exited unexpectedly (${signal ?? `code ${code ?? 'unknown'}`}).`;\n status.lastUpdate = this.now();\n status.endedAt = this.now();\n });\n this.jobTracker.track(runId);\n });\n\n const handshake = this.createSpawnHandshake();\n const outcome = await handshake.waitForHandshake({\n path: handshakePath,\n timeoutMs: input.handshakeTimeoutMs ?? this.defaultHandshakeTimeoutMs,\n }).promise;\n\n if (outcome.status !== 'signalled') {\n this.terminalPersistence.untrackChild(pid);\n const message =\n outcome.status === 'failed'\n ? outcome.error\n : (spawnError?.message ?? `Timed out waiting for '${agent}' (run '${runId}') to start.`);\n return fail(message);\n }\n\n // The child has confirmed it read the launch config; the parent's copy\n // is no longer needed (see the module header's LAUNCH CONFIG FILE\n // LIFECYCLE section).\n this.removeLaunchConfig(configPath);\n const observedState = this.readRunState(runId);\n if (!observedState || !TERMINAL_ASYNC_JOB_STATES.has(observedState)) {\n statusWriter.updateSync((status) => {\n status.state = 'running';\n status.lastUpdate = this.now();\n });\n }\n this.jobTracker.track(runId);\n\n return { runId, pid };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"asyncExecution.mjs","names":[],"sources":["../../../../src/services/runs/background/asyncExecution.ts"],"sourcesContent":["/**\n * The async-only spawn executor: launches exactly one subagent child process\n * and returns a run handle immediately, without waiting for the run to\n * finish.\n *\n * SCOPE - PARENT SIDE ONLY, SINGLE SPAWN:\n * `doom-pi-subagents/src/runs/background/asyncExecution.ts` is 1698 lines\n * doing far more than spawning: chain/parallel-group orchestration (building\n * `AgentConfig[]` sequences, dynamic fan-out expansion, workflow-graph\n * snapshots), session revival (a 3-way ready/ack/proceed handshake distinct\n * from the plain started-signal this module uses), and the foreground\n * (synchronous, blocking) code path. None of that is ported here:\n * - Chain/fan-out ORCHESTRATION (deciding how many children to spawn, in\n * what order, from what step list) is `port-steering`'s concurrent work on\n * the resolved-step and workflow-graph types. This module is the\n * PRIMITIVE that orchestration calls once per child - `childIndex` and\n * `fanout` arrive as inputs, already decided by the caller, not derived\n * here (see the child-index section below for why that is safe)\n * - Session revival (`revivalLease`, the 3-way ready/ack/proceed dance at\n * predecessor lines 498-509) is a materially different operation - resuming\n * an existing transcript, not spawning a fresh child - and is not ported.\n * `session-lease.ts` already exists in this package for whoever builds that\n * path later\n * - The foreground (synchronous) branch is dropped entirely, per the async-\n * only requirement below; there is no `async` parameter, only this\n *\n * WHAT THIS DOES NOT BUILD (compose, don't reimplement):\n * `CoalescedStatusWriter`, `TerminalPersistenceService`, `AsyncJobTracker`,\n * `SpawnHandshake`, `ResultWatcher`, `CompletionNotifier` all already exist\n * in this directory and are used here, not reimplemented. In particular, the\n * predecessor's process-exit tracking is reduced to the child handle's exact\n * exit signal: an abnormal exit marks that run failed immediately, while the\n * crash-marker path (`TerminalPersistenceService` +\n * `ProcessTerminalInspector`) remains the recovery path after parent loss.\n * `trackChild(pid)` on the caller's own already-active\n * `TerminalPersistenceService` is what makes a spawned child get killed if\n * the run that spawned it dies, which is the same guarantee the predecessor\n * used its own mechanism for.\n *\n * SDK RUNNER BOUNDARY:\n * `sdkRunnerEntry.ts` is the detached child process target. It reads the\n * launch config, creates the child through Pi's SDK, and loads\n * `subagent-prompt-runtime-entry` as an explicit child extension. That\n * extension owns the handshake, status, steering, and terminal result\n * lifecycle through the existing runner services. The real Pi CLI remains\n * the parent host used by system tests and DoomPi, not the child launcher.\n *\n * WHY SpawnHandshake, NOT `Atomics.wait`:\n * The predecessor blocks the parent's event loop for up to 10 seconds per\n * spawn (`asyncExecution.ts:376-380`, `Atomics.wait` on a shared buffer).\n * That codepath only actually ran for session revival there (a fresh spawn\n * had no `startupPath` and returned immediately) - but this package has no\n * synchronous/foreground path left to hide the cost behind, so a busy-wait\n * would block every single async spawn, not just revivals. `SpawnHandshake`\n * (already in this directory) replaces it: a real `fs.watch` on the\n * handshake file's directory, with a short poll as the safety net, and it\n * never blocks the event loop.\n *\n * LAUNCH CONFIG FILE LIFECYCLE (fix):\n * The predecessor writes the launch config with `fs.writeFileSync(cfgPath,\n * ...)` - no `mode` option, so it lands at the umask default - and never\n * unlinks it on any path, successful or failed\n * (`asyncExecution.ts:491-495`). Both are fixed here: `writeLaunchConfig`\n * writes 0600 (`writePrivateAtomicJson`, already in this package), and\n * `removeLaunchConfig` runs on every exit path - success (once the handshake\n * confirms the child has read it), and every failure (no pid, spawn error,\n * handshake failure or timeout) - so nothing is ever left behind holding the\n * full task text and resolved secrets/tokens at world-readable-adjacent\n * permissions.\n *\n * CHILD INDEX (fix - `DeliverableGuard` had no way to get one):\n * `DeliverableGuard.deliverNudge` requires an explicit `nudgeTarget` of\n * `{kind:'child', index}` for a fan-out run specifically so it cannot\n * broadcast a nudge to every sibling over one child's missing deliverable\n * (see that module's header). Nothing in this package maps a team member id\n * to a fan-out child index (verified: neither `nativeTeamChannel.ts` nor\n * `parallel-groups.ts` does this). This module needs no such mapping either\n * - `childIndex` is simply which spawn call this is, a fact the caller (a\n * single spawn, or a fan-out loop calling this once per child) already knows\n * without looking anything up, exactly like a flat index into\n * `status.steps[]` is decided by whoever builds that array. `nudgeTarget` is\n * derived from `childIndex`/`fanout` and persisted onto the run's own\n * `status.json` (`nudgeTarget` field) for whatever later evaluates\n * `DeliverableGuardInput` to read back, rather than re-derived at that point.\n *\n * DELIVERABLE EXPECTATION (fix - `DeliverableGuard` had no policy):\n * Derived here, at the one point this module already has the task text:\n * `classifyTaskMutationIntent` (used AS-IS, per its own header's warning\n * against widening its vocabulary for a third caller) distinguishes\n * `'implementation'` intent from `'read-only'`/`'unknown'`. Implementation\n * intent gets a real expectation (`{kind:'summary'}` by default - the\n * minimum a run can be asked to prove it did something, when no more\n * specific expectation like a structured-output schema or artifact path was\n * supplied); read-only or unknown intent gets none at all. This is\n * deliberately NOT `{kind:'summary'}` unconditionally: a guard that flags\n * every research run for an empty summary gets ignored, and an ignored guard\n * misses the run that actually needed it.\n *\n * TEAM MEMBERSHIP ENV FORWARDING (fix - a spawned child had no way to join\n * the team):\n * If THIS process (the one calling `spawn()`) is itself part of a native\n * team - `readNativeTeamRootFromEnvironment()` resolves a root context off\n * its own `process.env` - the same root vars\n * (`nativeTeamRootEnvironment(root)`, already in `nativeTeamChannel.ts`,\n * not reimplemented here) are folded into the child's env alongside\n * whatever `buildPiArgs` produced. Only the ROOT vars travel this way, never\n * a member id/token: those are per-member secrets the CHILD mints for\n * itself when it registers (see `runnerBootstrap.ts` and\n * `runnerTeamMembership.ts`), not something safe to hand down from the\n * parent's own identity. When this process has no team root (a standalone\n * spawn outside any team), nothing is forwarded and the child simply has\n * none to register against.\n */\n\nimport { spawn } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { createHarnessSession, getHarnessState } from '@agimon-ai/doompi-config';\nimport { DOOMPI_EXTENSIONS_PROVIDED_ENV } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport type { InlineAgent } from '@agimon-ai/doompi-extension-contracts/subagent-tool';\nimport { inject, injectable } from 'inversify';\nimport { PI_RUNTIME_NAME, SUBAGENT_RUN_ID_ENV } from '../../../adapters/process/env.ts';\nimport { DOOM_TEAM_TYPES } from '../../../container/types.ts';\nimport { getArtifactPaths, getArtifactsDir } from '../../../shared/artifacts.ts';\nimport { writePrivateAtomicJson } from '../../../shared/atomicJson.ts';\nimport {\n currentResultsDir,\n currentRunConfigPath,\n currentRunsDir,\n requireCurrentSessionScope,\n sessionScopeEnvironment,\n} from '../../../shared/paths.ts';\nimport type { ActivityState, ArtifactDirPreference } from '../../../types/index.ts';\nimport {\n nativeTeamRootEnvironment,\n readNativeTeamRootFromEnvironment,\n type TeamRootContext,\n} from '../../intercom/nativeTeamChannel.ts';\nimport { steerAcksDir, steerCapabilityPath, stepSteerInboxDir } from '../../intercom/supervisorControlChannel.ts';\nimport { registerRun, releaseRun } from '../registry/runRegistry.ts';\nimport {\n CLAUDE_FABLE_PROFILE,\n type ClaudeFableLaunch,\n cleanupClaudeFableLaunch,\n prepareClaudeFableLaunch,\n} from '../shared/claudeFableProfile.ts';\nimport {\n type BuildPiArgsInput,\n type BuildPiArgsResult,\n buildPiArgs,\n type PiSdkLaunchSettings,\n} from '../shared/piArgs.ts';\nimport { isPiRuntime, type RuntimeTable, resolveRuntimeLaunch } from '../shared/runtimeRegistry.ts';\nimport { type IAsyncJobTracker, TERMINAL_ASYNC_JOB_STATES } from './asyncJobTracker.ts';\nimport { type ISpawnHandshake, SpawnHandshake } from './spawnHandshake.ts';\nimport {\n CoalescedStatusWriter,\n type ICoalescedStatusWriter,\n STATUS_FILE_NAME,\n type StatusWithRecentEntries,\n} from './statusWriter.ts';\nimport type { ITerminalPersistenceService } from './terminalPersistence.ts';\n\n/** Leaf name of the started-signal file `SpawnHandshake` watches for, under `currentRunsDir()/<runId>/`. Exported so the child-side runner can compose the same convention rather than duplicate it. */\nexport const HANDSHAKE_FILE_NAME = 'handshake.json';\nexport const RUNNER_STDOUT_FILE_NAME = 'runner.stdout.log';\nexport const RUNNER_STDERR_FILE_NAME = 'runner.stderr.log';\nexport const FABLE_PROFILE_RESULT_FILE_NAME = 'fable-profile-result.json';\n/**\n * The effective system prompt this run launched with, kept for the fleet\n * inspector's agent view.\n *\n * A SIDECAR RATHER THAN A FIELD ON status.json: `CoalescedStatusWriter`\n * rewrites the whole status file on every coalesced flush, and a system prompt\n * is kilobytes of text that never changes after spawn. Inlining it would put\n * that payload through every status write for the life of the run. This file\n * is written once, next to the status it belongs to.\n *\n * Written 0600 for the same reason `piArgs.ts` writes its prompt file that\n * way: a resolved prompt can carry inherited project context.\n */\nexport const SYSTEM_PROMPT_FILE_NAME = 'system-prompt.md';\nconst SYSTEM_PROMPT_FILE_MODE = 0o600;\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;\n\n/**\n * `state`/`steps`/`parallelGroups`/`chainStepCount`/`workflowGraph`/\n * `pendingAppends` are declared as optional-and-widened, rather than a\n * runner-owned status type, so this SAME status shape is what\n * `chain-append.ts`'s `applyChainAppendRequest` (which expects\n * `AppendableChainStatus`, an object with exactly these optional fields) can\n * mutate directly - one status type for the whole spawn-through-finalize\n * lifecycle, not a parent-side one and a child-side one that have to be kept\n * in sync by hand.\n */\nexport interface AsyncRunStatus extends StatusWithRecentEntries {\n /** Version 1 is emitted by current writers; absent is accepted only by the compatibility reader. */\n version?: 1;\n runId: string;\n operationId?: string;\n agent: string;\n /** Original task text required to restore a suspended run. */\n task?: string;\n cwd?: string;\n /** Resolved model selected for this run, when one was requested. */\n model?: string;\n /** One-shot agent definition required to restore an inline agent. */\n inlineAgent?: InlineAgent;\n state: 'queued' | 'running' | 'complete' | 'completed' | 'failed' | 'paused' | 'stopped';\n startedAt: number;\n lastUpdate: number;\n endedAt?: number;\n /** This run's position among its fan-out siblings, when it has any. Absent for a standalone run. */\n fanoutIndex?: number;\n /** Bridge-owned runs are consumed internally and never rendered as generic completion chat. */\n internal?: boolean;\n /** Which runtime executes this run. Absent means the default in-process `pi` path. */\n runtime?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n error?: string;\n /**\n * Set once, at finalize, by `RunnerReporting.mutateTerminalStatus` - the\n * SAME value it puts in the terminal result file, truncated to a bounded\n * length (see that module's header for why). Lets a caller (chain mode,\n * in particular) learn a completed run's own summary through the status\n * file it already reads to learn the run finished, rather than needing a\n * second read of a claimed, single-consumer result file.\n */\n summary?: string;\n /**\n * This run's own Pi session transcript path, written during child bootstrap\n * and retained by `RunnerReporting.mutateTerminalStatus` at finalize. See\n * that module's header for where the value comes from and why it is bounded\n * by omission, not truncation. Absent for a run that either never reported\n * a session file (headless, no persisted transcript) or reported one too\n * long to trust. This is what `action='resume'` reads to know which\n * transcript a later revival should reopen.\n */\n sessionFile?: string;\n /** Child-owned Pi SDK event transcript consumed by the fleet detail pane. */\n transcriptPath?: string;\n /**\n * Sidecar holding this run's effective system prompt, and the resolved tool\n * plan it launched with. Recorded at spawn purely so the fleet inspector can\n * show what an agent was actually told: the launch config that carried them\n * is deleted as soon as the child handshakes, and the agent definition on\n * disk can change while a run is still going.\n *\n * All absent for a non-`pi` runtime, which has no SDK launch settings, and\n * for any run started before this was recorded.\n */\n systemPromptPath?: string;\n systemPromptMode?: 'append' | 'replace';\n /** Effective tool allowlist. Absent means \"whatever the child defaults to\". */\n tools?: string[];\n excludeTools?: string[];\n noTools?: boolean;\n /** Cumulative tokens observed from finalized child messages. */\n tokens?: number;\n /** Most recently started child tool. */\n currentTool?: string;\n /** Number of child tool executions observed so far. */\n toolCount?: number;\n}\n\n/** The parent-written contract a child-side runner reads via `currentRunConfigPath(runId)`. */\nexport interface LaunchConfig {\n runId: string;\n agent: string;\n task: string;\n cwd: string;\n childIndex: number;\n handshakePath: string;\n sdk: PiSdkLaunchSettings;\n transcriptPath?: string;\n /** Retained during the SDK cutover for compatibility diagnostics only. */\n args: string[];\n env: Record<string, string | undefined>;\n}\n\nexport interface AsyncSubagentSpawnInput {\n runId: string;\n operationId?: string;\n agent: string;\n /** One-shot agent definition retained only for explicit suspension restore. */\n inlineAgent?: InlineAgent;\n task: string;\n cwd: string;\n /** Persisted parent transcript used to place child artifacts beside the parent session. */\n parentSessionFile?: string;\n /** This spawn's position among its siblings. 0 for a standalone (non-fan-out) run. */\n childIndex: number;\n /** Whether this spawn is one of several children under a fan-out parent. */\n fanout: boolean;\n /** Everything `buildPiArgs` needs, minus what this spawner supplies itself (`runId`/`task`/`childIndex`). */\n piArgs: Omit<BuildPiArgsInput, 'runId' | 'task' | 'childIndex'>;\n /** False disables the child event transcript. Defaults to enabled. */\n artifacts?: boolean;\n /** Artifact root policy inherited from the parent Doom Team config. */\n artifactDir?: ArtifactDirPreference;\n handshakeTimeoutMs?: number;\n /**\n * Which runtime executes this run. Defaults to the in-process Pi SDK path.\n * Anything else is an external CLI resolved through `runtimeRegistry.ts`.\n */\n runtime?: string;\n /** Fixed trusted external profile. Only Doom Team internal bridges may set this. */\n externalProfile?: string;\n /** Prevent sensitive task text from being persisted in status.json. */\n sensitiveTask?: boolean;\n /** Suppress the generic completion notifier for an internal bridge run. */\n internal?: boolean;\n /** Runtimes available to this spawn, already merged with the shipped defaults. */\n runtimes?: RuntimeTable;\n}\n\nexport interface AsyncSubagentSpawnResult {\n runId: string;\n pid: number;\n}\n\nexport interface IAsyncSubagentSpawner {\n /** Spawn one child and return once it has confirmed it started. Throws on any failure to reach that point. */\n spawn(input: AsyncSubagentSpawnInput): Promise<AsyncSubagentSpawnResult>;\n}\n\n/** `currentRunsDir()/<runId>`. Exported so the child-side runner resolves the same directory, not a parallel one. */\nexport function runDirFor(runId: string): string {\n return path.join(currentRunsDir(), runId);\n}\n\n/** Exported so the child-side runner writes its ready/error signal at the exact path this parent half waits on. */\nexport function handshakePathFor(runId: string): string {\n return path.join(runDirFor(runId), HANDSHAKE_FILE_NAME);\n}\n\nexport function fableProfileResultPathFor(runId: string): string {\n return path.join(runDirFor(runId), FABLE_PROFILE_RESULT_FILE_NAME);\n}\n\n/** Where `spawn()` records this run's effective system prompt. See `SYSTEM_PROMPT_FILE_NAME`. */\nexport function systemPromptPathFor(runId: string): string {\n return path.join(runDirFor(runId), SYSTEM_PROMPT_FILE_NAME);\n}\n\n/**\n * Locate a child entry point on disk, built or source.\n *\n * The child is a separate process loading this by path, so the path has to be\n * the one that exists now: `.mjs` under `dist` when this bundle is built, the\n * original source file when it is not. `relativeDir` is where the entry sits\n * relative to `src/` - the SDK runner is one level up from this module, the\n * CLI runner is beside it.\n */\nfunction resolveRunnerEntry(relativeDir: string, stem: string): string {\n const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));\n const packageRoot = path.resolve(moduleDirectory, '../..');\n const builtCandidate = path.join(packageRoot, relativeDir, `${stem}.mjs`);\n if (fs.existsSync(builtCandidate)) return builtCandidate;\n const sourceCandidate = path.join(packageRoot, relativeDir, `${stem}.ts`);\n if (fs.existsSync(sourceCandidate)) return sourceCandidate;\n throw new Error(`Doom Team runner '${stem}' is unavailable at '${builtCandidate}' or '${sourceCandidate}'.`);\n}\n\nfunction resolveSdkRunnerEntry(): string {\n return resolveRunnerEntry('runs', 'sdkRunnerEntry');\n}\n\nfunction resolveCliRunnerEntry(): string {\n return resolveRunnerEntry('runs/background', 'cliRunnerEntry');\n}\n\n@injectable()\nexport class AsyncSubagentSpawner implements IAsyncSubagentSpawner {\n constructor(\n @inject(DOOM_TEAM_TYPES.AsyncJobTracker) private readonly jobTracker: IAsyncJobTracker,\n @inject(DOOM_TEAM_TYPES.TerminalPersistenceService)\n private readonly terminalPersistence: ITerminalPersistenceService,\n ) {}\n\n /**\n * Seam for tests, as a protected member rather than a constructor\n * argument: inversify cannot resolve a primitive. See `pollScheduler.ts`\n * for the same pattern.\n */\n protected readonly defaultHandshakeTimeoutMs: number = DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n /**\n * This process's own team root context, read fresh per spawn. Protected\n * so a test can supply one without writing real team files to disk - see\n * the module header's TEAM MEMBERSHIP ENV FORWARDING section for why this\n * is read from `process.env` rather than accepted as a constructor\n * argument (inversify cannot resolve a primitive either way).\n */\n protected readCurrentTeamRoot(): TeamRootContext | undefined {\n return readNativeTeamRootFromEnvironment();\n }\n\n /**\n * A fresh writer per spawn, deliberately never the container's shared\n * singleton - the same reasoning as `StaleRunReconciler.createStatusWriter`\n * (see that class's header): the singleton may simultaneously be the live\n * write path for a run THIS process is itself running, and bootstrapping\n * someone else's (the spawned child's) initial status is a different\n * writer's job.\n */\n protected createStatusWriter(): ICoalescedStatusWriter<AsyncRunStatus> {\n return new CoalescedStatusWriter<AsyncRunStatus>();\n }\n\n /**\n * A fresh handshake wait per spawn. `SpawnHandshake` is bound transient in\n * the container for exactly this reason (each `waitForHandshake()` owns\n * its own timers and watcher for exactly one spawn); resolving it via\n * constructor injection would hand every spawn the same, already-used\n * instance, so this seam constructs one directly instead.\n */\n protected createSpawnHandshake(): ISpawnHandshake {\n return new SpawnHandshake();\n }\n\n protected writeLaunchConfig(filePath: string, config: object): void {\n writePrivateAtomicJson(filePath, config);\n }\n\n protected removeLaunchConfig(filePath: string): void {\n fs.rmSync(filePath, { force: true });\n }\n\n protected readRunState(runId: string): string | undefined {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(runDirFor(runId), STATUS_FILE_NAME), 'utf8')) as {\n state?: unknown;\n };\n return typeof parsed.state === 'string' ? parsed.state : undefined;\n } catch {\n return undefined;\n }\n }\n\n /**\n * Resolves the child's CLI args/env. Protected so a test can bypass\n * `buildPiArgs`'s own real filesystem resolution (it locates a runtime\n * extension script on disk via `resolvePiLaunchToolPlan`, which is\n * environment-dependent and not something a unit test for THIS module's\n * orchestration logic should depend on).\n */\n protected buildLaunchArgs(input: BuildPiArgsInput): BuildPiArgsResult {\n return buildPiArgs(input);\n }\n\n /**\n * The actual `child_process.spawn`. Detached and unref'd immediately. Output\n * goes to per-run files so a startup failure is diagnosable without keeping\n * parent-owned pipes alive.\n */\n protected spawnChild(\n command: string,\n args: string[],\n options: { cwd: string; env: NodeJS.ProcessEnv },\n ): {\n pid: number | undefined;\n onError: (handler: (error: Error) => void) => void;\n onExit: (handler: (code: number | null, signal: NodeJS.Signals | null) => void) => void;\n } {\n const runId = options.env[SUBAGENT_RUN_ID_ENV];\n let stdoutFd: number | undefined;\n let stderrFd: number | undefined;\n if (runId) {\n const runDir = runDirFor(runId);\n fs.mkdirSync(runDir, { recursive: true });\n stdoutFd = fs.openSync(path.join(runDir, RUNNER_STDOUT_FILE_NAME), 'a', 0o600);\n try {\n stderrFd = fs.openSync(path.join(runDir, RUNNER_STDERR_FILE_NAME), 'a', 0o600);\n } catch (error) {\n fs.closeSync(stdoutFd);\n throw new Error(`Could not open runner stderr for '${runId}'.`, { cause: error });\n }\n }\n let child: ReturnType<typeof spawn>;\n try {\n child = spawn(command, args, {\n cwd: options.cwd,\n detached: true,\n stdio: ['ignore', stdoutFd ?? 'ignore', stderrFd ?? 'ignore'],\n windowsHide: true,\n env: options.env,\n });\n } finally {\n if (stdoutFd !== undefined) fs.closeSync(stdoutFd);\n if (stderrFd !== undefined) fs.closeSync(stderrFd);\n }\n child.unref();\n return {\n pid: child.pid,\n onError: (handler) => child.on('error', handler),\n onExit: (handler) => child.on('exit', handler),\n };\n }\n\n async spawn(input: AsyncSubagentSpawnInput): Promise<AsyncSubagentSpawnResult> {\n const { runId, agent, task, cwd } = input;\n const fanoutIndex = input.fanout ? input.childIndex : undefined;\n const handshakePath = handshakePathFor(runId);\n const configPath = currentRunConfigPath(runId);\n const transcriptPath =\n input.artifacts === false\n ? undefined\n : getArtifactPaths(\n getArtifactsDir(input.parentSessionFile ?? null, cwd, input.artifactDir ?? 'session'),\n runId,\n agent,\n ).transcriptPath;\n\n const runDir = runDirFor(runId);\n const steerIndex = input.childIndex ?? 0;\n const teamRoot = this.readCurrentTeamRoot();\n const {\n args,\n env: builtEnv,\n sdk,\n } = this.buildLaunchArgs({\n ...input.piArgs,\n runId,\n task,\n childIndex: input.childIndex,\n steerInboxDir: stepSteerInboxDir(runDir, steerIndex),\n steerCapabilityPath: steerCapabilityPath(runDir, steerIndex),\n steerAckDir: steerAcksDir(runDir, steerIndex),\n teamToolEnabled: Boolean(teamRoot),\n });\n // The child gets its own harness state, in this run's directory, which this\n // package owns. Sharing the parent's file would let a detached child rewrite\n // a session it does not own, and pointing at the parent's directory would\n // lose the file the moment the parent cleaned up its own session: a child\n // is detached and can outlive it. Pi 0.84 SDK launches consume the resolved\n // extension list directly, so preserve that same list in the child harness\n // snapshot instead of projecting the parent's stale childExtensions value.\n createHarnessSession(\n { ...getHarnessState(), childExtensions: sdk.extensions },\n { directory: runDir, environment: builtEnv, unclaimed: true },\n );\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n ...builtEnv,\n // The child resolves the same filesystem tree from this, independently of\n // whether it also joins a team.\n ...sessionScopeEnvironment(requireCurrentSessionScope()),\n ...(teamRoot ? nativeTeamRootEnvironment(teamRoot) : {}),\n };\n if (isPiRuntime(input.runtime)) {\n if (sdk.extensionsProvidedExternally) env[DOOMPI_EXTENSIONS_PROVIDED_ENV] = '1';\n else delete env[DOOMPI_EXTENSIONS_PROVIDED_ENV];\n }\n\n // Recorded before open() so the status never advertises a sidecar that is\n // not on disk yet. A failure here is not worth failing a spawn over: the\n // prompt is an inspection aid, so the run proceeds without it.\n let systemPromptPath: string | undefined;\n if (isPiRuntime(input.runtime) && sdk.systemPrompt) {\n try {\n fs.mkdirSync(runDir, { recursive: true });\n const target = systemPromptPathFor(runId);\n fs.writeFileSync(target, sdk.systemPrompt, { mode: SYSTEM_PROMPT_FILE_MODE });\n systemPromptPath = target;\n } catch {\n systemPromptPath = undefined;\n }\n }\n\n const now = this.now();\n const statusWriter = this.createStatusWriter();\n statusWriter.open(runId, {\n version: 1,\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n ...(input.sensitiveTask ? {} : { task }),\n cwd,\n ...(input.piArgs.model ? { model: input.piArgs.model } : {}),\n ...(input.inlineAgent ? { inlineAgent: input.inlineAgent } : {}),\n state: 'queued',\n startedAt: now,\n lastUpdate: now,\n ...(fanoutIndex === undefined ? {} : { fanoutIndex }),\n ...(input.internal ? { internal: true } : {}),\n ...(isPiRuntime(input.runtime) ? {} : { runtime: input.runtime }),\n ...(transcriptPath ? { transcriptPath } : {}),\n ...(systemPromptPath ? { systemPromptPath } : {}),\n ...(systemPromptPath && sdk.systemPromptMode ? { systemPromptMode: sdk.systemPromptMode } : {}),\n ...(sdk.tools ? { tools: sdk.tools } : {}),\n ...(sdk.excludeTools?.length ? { excludeTools: sdk.excludeTools } : {}),\n ...(sdk.noTools ? { noTools: true } : {}),\n });\n\n if (isPiRuntime(input.runtime)) {\n this.writeLaunchConfig(configPath, {\n runId,\n agent,\n task,\n cwd,\n childIndex: input.childIndex,\n handshakePath,\n sdk,\n ...(transcriptPath ? { transcriptPath } : {}),\n args,\n env,\n });\n }\n\n let fableLaunch: ClaudeFableLaunch | undefined;\n const fail = (message: string): never => {\n this.removeLaunchConfig(configPath);\n if (fableLaunch) cleanupClaudeFableLaunch(fableLaunch);\n fs.rmSync(fableProfileResultPathFor(runId), { force: true });\n statusWriter.updateSync((status) => {\n status.state = 'failed';\n status.error = message;\n status.lastUpdate = this.now();\n });\n throw new Error(message);\n };\n\n // One branch, and only one: `pi` runs in-process through the SDK runner and\n // gets the team channel, steering and structured output that go with it.\n // Every other runtime is an external CLI, spawned through the generic\n // runner, which deliberately knows nothing about the vendor.\n const usePi = isPiRuntime(input.runtime);\n if (!usePi) {\n const ceiling = input.piArgs.capabilityCeiling;\n if (ceiling && (!input.externalProfile || !ceiling.allowedExternalProfiles.includes(input.externalProfile))) {\n return fail(\n `External profile '${input.externalProfile ?? 'none'}' is denied by the active capability ceiling.`,\n );\n }\n if (input.externalProfile !== undefined && input.externalProfile !== CLAUDE_FABLE_PROFILE) {\n return fail(`External profile '${input.externalProfile}' is not trusted.`);\n }\n if (input.externalProfile === CLAUDE_FABLE_PROFILE) {\n if (input.runtime !== 'claude' || input.piArgs.model !== 'fable') {\n return fail('The Fable profile requires runtime claude and model fable.');\n }\n if (input.inlineAgent || input.piArgs.sessionFile || input.piArgs.parentSessionId) {\n return fail('The Fable profile requires a fresh isolated context.');\n }\n fableLaunch = prepareClaudeFableLaunch({\n runId,\n prompt: task,\n repositoryCwd: cwd,\n privateRoot: runDir,\n });\n this.writeLaunchConfig(configPath, {\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n runtime: 'claude',\n command: fableLaunch.command,\n args: fableLaunch.args,\n cwd: fableLaunch.cwd,\n env: fableLaunch.env,\n profile: fableLaunch.profile,\n stdinPath: fableLaunch.stdinPath,\n cleanupPaths: fableLaunch.cleanupPaths,\n profileResultPath: fableProfileResultPathFor(runId),\n internal: input.internal === true,\n handshakePath,\n resultPath: path.join(currentResultsDir(), `${runId}.json`),\n });\n } else {\n this.writeLaunchConfig(configPath, {\n runId,\n ...(input.operationId ? { operationId: input.operationId } : {}),\n agent,\n ...resolveRuntimeLaunch(input.runtime as string, input.runtimes ?? {}, {\n prompt: task,\n model: input.piArgs.model,\n cwd,\n }),\n cwd,\n env: builtEnv as Record<string, string>,\n handshakePath,\n resultPath: path.join(currentResultsDir(), `${runId}.json`),\n });\n }\n }\n\n const { pid, onError, onExit } = this.spawnChild(\n process.execPath,\n [usePi ? resolveSdkRunnerEntry() : resolveCliRunnerEntry()],\n { cwd, env: env as NodeJS.ProcessEnv },\n );\n if (pid === undefined) {\n return fail(`Async spawn for '${agent}' (run '${runId}') did not produce a pid.`);\n }\n let spawnError: Error | undefined;\n onError((error) => {\n spawnError = error;\n });\n\n // Recorded before the handshake: a parent that dies between spawn and\n // handshake must still leave a record a later sweep can act on.\n registerRun(requireCurrentSessionScope(), {\n runId,\n pid,\n agent,\n runtime: input.runtime ?? PI_RUNTIME_NAME,\n startedAt: now,\n });\n this.terminalPersistence.trackChild(pid);\n onExit((code, signal) => {\n this.terminalPersistence.untrackChild(pid);\n releaseRun(requireCurrentSessionScope(), runId);\n if (code === 0 && signal === null) return;\n statusWriter.updateSync((status) => {\n if (status.state && TERMINAL_ASYNC_JOB_STATES.has(status.state)) return;\n status.state = 'failed';\n status.error = `SDK runner process ${pid} exited unexpectedly (${signal ?? `code ${code ?? 'unknown'}`}).`;\n status.lastUpdate = this.now();\n status.endedAt = this.now();\n });\n this.jobTracker.track(runId);\n });\n\n const handshake = this.createSpawnHandshake();\n const outcome = await handshake.waitForHandshake({\n path: handshakePath,\n timeoutMs: input.handshakeTimeoutMs ?? this.defaultHandshakeTimeoutMs,\n }).promise;\n\n if (outcome.status !== 'signalled') {\n this.terminalPersistence.untrackChild(pid);\n const message =\n outcome.status === 'failed'\n ? outcome.error\n : (spawnError?.message ?? `Timed out waiting for '${agent}' (run '${runId}') to start.`);\n return fail(message);\n }\n\n // The child has confirmed it read the launch config; the parent's copy\n // is no longer needed (see the module header's LAUNCH CONFIG FILE\n // LIFECYCLE section).\n this.removeLaunchConfig(configPath);\n const observedState = this.readRunState(runId);\n if (!observedState || !TERMINAL_ASYNC_JOB_STATES.has(observedState)) {\n statusWriter.updateSync((status) => {\n status.state = 'running';\n status.lastUpdate = this.now();\n });\n }\n this.jobTracker.track(runId);\n\n return { runId, pid };\n }\n}\n"],"mappings":"+/EAyUA,SAAgB,EAAU,EAAuB,CAC/C,OAAO,EAAK,KAAK,GAAgB,CAAE,EAAM,CAI3C,SAAgB,EAAiB,EAAuB,CACtD,OAAO,EAAK,KAAK,EAAU,EAAM,CAAE,EAAoB,CAGzD,SAAgB,EAA0B,EAAuB,CAC/D,OAAO,EAAK,KAAK,EAAU,EAAM,CAAE,EAA+B,CAIpE,SAAgB,GAAoB,EAAuB,CACzD,OAAO,EAAK,KAAK,EAAU,EAAM,CAAE,EAAwB,CAY7D,SAAS,EAAmB,EAAqB,EAAsB,CACrE,IAAM,EAAkB,EAAK,QAAQ,GAAc,OAAO,KAAK,IAAI,CAAC,CAC9D,EAAc,EAAK,QAAQ,EAAiB,QAAQ,CACpD,EAAiB,EAAK,KAAK,EAAa,EAAa,GAAG,EAAK,MAAM,CACzE,GAAI,EAAG,WAAW,EAAe,CAAE,OAAO,EAC1C,IAAM,EAAkB,EAAK,KAAK,EAAa,EAAa,GAAG,EAAK,KAAK,CACzE,GAAI,EAAG,WAAW,EAAgB,CAAE,OAAO,EAC3C,MAAU,MAAM,qBAAqB,EAAK,uBAAuB,EAAe,QAAQ,EAAgB,IAAI,CAG9G,SAAS,IAAgC,CACvC,OAAO,EAAmB,OAAQ,iBAAiB,CAGrD,SAAS,IAAgC,CACvC,OAAO,EAAmB,kBAAmB,iBAAiB,kCAzPwB,IAC1B,IACmB,IACV,IAOrC,IAMW,IACqE,IAC7C,IAM5B,IAMZ,IACuE,IACZ,KACb,IAMhD,aAId,EAAsB,iBACtB,EAA0B,oBAC1B,EAA0B,oBAC1B,EAAiC,4BAcjC,EAA0B,mBACjC,EAA0B,IAC1B,EAA+B,MA+L9B,KAA4D,CACjE,YACE,EACA,EAEA,CAH0D,KAAA,WAAA,EAEzC,KAAA,oBAAA,EAQnB,0BAAuD,EAEvD,KAAwB,CACtB,OAAO,KAAK,KAAK,CAUnB,qBAA6D,CAC3D,OAAO,GAAmC,CAW5C,oBAAuE,CACrE,OAAO,IAAI,EAUb,sBAAkD,CAChD,OAAO,IAAI,GAGb,kBAA4B,EAAkB,EAAsB,CAClE,EAAuB,EAAU,EAAO,CAG1C,mBAA6B,EAAwB,CACnD,EAAG,OAAO,EAAU,CAAE,MAAO,GAAM,CAAC,CAGtC,aAAuB,EAAmC,CACxD,GAAI,CACF,IAAM,EAAS,KAAK,MAAM,EAAG,aAAa,EAAK,KAAK,EAAU,EAAM,CAAE,EAAiB,CAAE,OAAO,CAAC,CAGjG,OAAO,OAAO,EAAO,OAAU,SAAW,EAAO,MAAQ,IAAA,QACnD,CACN,QAWJ,gBAA0B,EAA4C,CACpE,OAAO,EAAY,EAAM,CAQ3B,WACE,EACA,EACA,EAKA,CACA,IAAM,EAAQ,EAAQ,IAAI,GACtB,EACA,EACJ,GAAI,EAAO,CACT,IAAM,EAAS,EAAU,EAAM,CAC/B,EAAG,UAAU,EAAQ,CAAE,UAAW,GAAM,CAAC,CACzC,EAAW,EAAG,SAAS,EAAK,KAAK,EAAQ,EAAwB,CAAE,IAAK,IAAM,CAC9E,GAAI,CACF,EAAW,EAAG,SAAS,EAAK,KAAK,EAAQ,EAAwB,CAAE,IAAK,IAAM,OACvE,EAAO,CAEd,MADA,EAAG,UAAU,EAAS,CACZ,MAAM,qCAAqC,EAAM,IAAK,CAAE,MAAO,EAAO,CAAC,EAGrF,IAAI,EACJ,GAAI,CACF,EAAQ,GAAM,EAAS,EAAM,CAC3B,IAAK,EAAQ,IACb,SAAU,GACV,MAAO,CAAC,SAAU,GAAY,SAAU,GAAY,SAAS,CAC7D,YAAa,GACb,IAAK,EAAQ,IACd,CAAC,QACM,CACJ,IAAa,IAAA,IAAW,EAAG,UAAU,EAAS,CAC9C,IAAa,IAAA,IAAW,EAAG,UAAU,EAAS,CAGpD,OADA,EAAM,OAAO,CACN,CACL,IAAK,EAAM,IACX,QAAU,GAAY,EAAM,GAAG,QAAS,EAAQ,CAChD,OAAS,GAAY,EAAM,GAAG,OAAQ,EAAQ,CAC/C,CAGH,MAAM,MAAM,EAAmE,CAC7E,GAAM,CAAE,QAAO,QAAO,OAAM,OAAQ,EAC9B,EAAc,EAAM,OAAS,EAAM,WAAa,IAAA,GAChD,EAAgB,EAAiB,EAAM,CACvC,EAAa,GAAqB,EAAM,CACxC,EACJ,EAAM,YAAc,GAChB,IAAA,GACA,EACE,GAAgB,EAAM,mBAAqB,KAAM,EAAK,EAAM,aAAe,UAAU,CACrF,EACA,EACD,CAAC,eAEF,EAAS,EAAU,EAAM,CACzB,EAAa,EAAM,YAAc,EACjC,EAAW,KAAK,qBAAqB,CACrC,CACJ,OACA,IAAK,EACL,OACE,KAAK,gBAAgB,CACvB,GAAG,EAAM,OACT,QACA,OACA,WAAY,EAAM,WAClB,cAAe,EAAkB,EAAQ,EAAW,CACpD,oBAAqB,GAAoB,EAAQ,EAAW,CAC5D,YAAa,GAAa,EAAQ,EAAW,CAC7C,gBAAiB,EAAQ,EAC1B,CAAC,CAQF,GACE,CAAE,GAAG,IAAiB,CAAE,gBAAiB,EAAI,WAAY,CACzD,CAAE,UAAW,EAAQ,YAAa,EAAU,UAAW,GAAM,CAC9D,CACD,IAAM,EAAyB,CAC7B,GAAG,QAAQ,IACX,GAAG,EAGH,GAAG,GAAwB,GAA4B,CAAC,CACxD,GAAI,EAAW,GAA0B,EAAS,CAAG,EAAE,CACxD,CACG,EAAY,EAAM,QAAQ,GACxB,EAAI,6BAA8B,EAAI,GAAkC,IACvE,OAAO,EAAI,IAMlB,IAAI,EACJ,GAAI,EAAY,EAAM,QAAQ,EAAI,EAAI,aACpC,GAAI,CACF,EAAG,UAAU,EAAQ,CAAE,UAAW,GAAM,CAAC,CACzC,IAAM,EAAS,GAAoB,EAAM,CACzC,EAAG,cAAc,EAAQ,EAAI,aAAc,CAAE,KAAM,EAAyB,CAAC,CAC7E,EAAmB,OACb,CACN,EAAmB,IAAA,GAIvB,IAAM,EAAM,KAAK,KAAK,CAChB,EAAe,KAAK,oBAAoB,CAC9C,EAAa,KAAK,EAAO,CACvB,QAAS,EACT,QACA,GAAI,EAAM,YAAc,CAAE,YAAa,EAAM,YAAa,CAAG,EAAE,CAC/D,QACA,GAAI,EAAM,cAAgB,EAAE,CAAG,CAAE,OAAM,CACvC,MACA,GAAI,EAAM,OAAO,MAAQ,CAAE,MAAO,EAAM,OAAO,MAAO,CAAG,EAAE,CAC3D,GAAI,EAAM,YAAc,CAAE,YAAa,EAAM,YAAa,CAAG,EAAE,CAC/D,MAAO,SACP,UAAW,EACX,WAAY,EACZ,GAAI,IAAgB,IAAA,GAAY,EAAE,CAAG,CAAE,cAAa,CACpD,GAAI,EAAM,SAAW,CAAE,SAAU,GAAM,CAAG,EAAE,CAC5C,GAAI,EAAY,EAAM,QAAQ,CAAG,EAAE,CAAG,CAAE,QAAS,EAAM,QAAS,CAChE,GAAI,EAAiB,CAAE,iBAAgB,CAAG,EAAE,CAC5C,GAAI,EAAmB,CAAE,mBAAkB,CAAG,EAAE,CAChD,GAAI,GAAoB,EAAI,iBAAmB,CAAE,iBAAkB,EAAI,iBAAkB,CAAG,EAAE,CAC9F,GAAI,EAAI,MAAQ,CAAE,MAAO,EAAI,MAAO,CAAG,EAAE,CACzC,GAAI,EAAI,cAAc,OAAS,CAAE,aAAc,EAAI,aAAc,CAAG,EAAE,CACtE,GAAI,EAAI,QAAU,CAAE,QAAS,GAAM,CAAG,EAAE,CACzC,CAAC,CAEE,EAAY,EAAM,QAAQ,EAC5B,KAAK,kBAAkB,EAAY,CACjC,QACA,QACA,OACA,MACA,WAAY,EAAM,WAClB,gBACA,MACA,GAAI,EAAiB,CAAE,iBAAgB,CAAG,EAAE,CAC5C,OACA,MACD,CAAC,CAGJ,IAAI,EACE,EAAQ,GAA2B,CASvC,MARA,KAAK,mBAAmB,EAAW,CAC/B,GAAa,GAAyB,EAAY,CACtD,EAAG,OAAO,EAA0B,EAAM,CAAE,CAAE,MAAO,GAAM,CAAC,CAC5D,EAAa,WAAY,GAAW,CAClC,EAAO,MAAQ,SACf,EAAO,MAAQ,EACf,EAAO,WAAa,KAAK,KAAK,EAC9B,CACQ,MAAM,EAAQ,EAOpB,EAAQ,EAAY,EAAM,QAAQ,CACxC,GAAI,CAAC,EAAO,CACV,IAAM,EAAU,EAAM,OAAO,kBAC7B,GAAI,IAAY,CAAC,EAAM,iBAAmB,CAAC,EAAQ,wBAAwB,SAAS,EAAM,gBAAgB,EACxG,OAAO,EACL,qBAAqB,EAAM,iBAAmB,OAAO,+CACtD,CAEH,GAAI,EAAM,kBAAoB,IAAA,IAAa,EAAM,kBAAoB,EACnE,OAAO,EAAK,qBAAqB,EAAM,gBAAgB,mBAAmB,CAE5E,GAAI,EAAM,kBAAoB,EAAsB,CAClD,GAAI,EAAM,UAAY,UAAY,EAAM,OAAO,QAAU,QACvD,OAAO,EAAK,6DAA6D,CAE3E,GAAI,EAAM,aAAe,EAAM,OAAO,aAAe,EAAM,OAAO,gBAChE,OAAO,EAAK,uDAAuD,CAErE,EAAc,GAAyB,CACrC,QACA,OAAQ,EACR,cAAe,EACf,YAAa,EACd,CAAC,CACF,KAAK,kBAAkB,EAAY,CACjC,QACA,GAAI,EAAM,YAAc,CAAE,YAAa,EAAM,YAAa,CAAG,EAAE,CAC/D,QACA,QAAS,SACT,QAAS,EAAY,QACrB,KAAM,EAAY,KAClB,IAAK,EAAY,IACjB,IAAK,EAAY,IACjB,QAAS,EAAY,QACrB,UAAW,EAAY,UACvB,aAAc,EAAY,aAC1B,kBAAmB,EAA0B,EAAM,CACnD,SAAU,EAAM,WAAa,GAC7B,gBACA,WAAY,EAAK,KAAK,GAAmB,CAAE,GAAG,EAAM,OAAO,CAC5D,CAAC,MAEF,KAAK,kBAAkB,EAAY,CACjC,QACA,GAAI,EAAM,YAAc,CAAE,YAAa,EAAM,YAAa,CAAG,EAAE,CAC/D,QACA,GAAG,GAAqB,EAAM,QAAmB,EAAM,UAAY,EAAE,CAAE,CACrE,OAAQ,EACR,MAAO,EAAM,OAAO,MACpB,MACD,CAAC,CACF,MACA,IAAK,EACL,gBACA,WAAY,EAAK,KAAK,GAAmB,CAAE,GAAG,EAAM,OAAO,CAC5D,CAAC,CAIN,GAAM,CAAE,MAAK,UAAS,UAAW,KAAK,WACpC,QAAQ,SACR,CAAC,EAAQ,IAAuB,CAAG,IAAuB,CAAC,CAC3D,CAAE,MAAU,MAA0B,CACvC,CACD,GAAI,IAAQ,IAAA,GACV,OAAO,EAAK,oBAAoB,EAAM,UAAU,EAAM,2BAA2B,CAEnF,IAAI,EACJ,EAAS,GAAU,CACjB,EAAa,GACb,CAIF,EAAY,GAA4B,CAAE,CACxC,QACA,MACA,QACA,QAAS,EAAM,SAAA,KACf,UAAW,EACZ,CAAC,CACF,KAAK,oBAAoB,WAAW,EAAI,CACxC,GAAQ,EAAM,IAAW,CACvB,KAAK,oBAAoB,aAAa,EAAI,CAC1C,GAAW,GAA4B,CAAE,EAAM,CAC3C,MAAS,GAAK,IAAW,QAC7B,EAAa,WAAY,GAAW,CAC9B,EAAO,OAAS,EAA0B,IAAI,EAAO,MAAM,GAC/D,EAAO,MAAQ,SACf,EAAO,MAAQ,sBAAsB,EAAI,wBAAwB,GAAU,QAAQ,GAAQ,YAAY,IACvG,EAAO,WAAa,KAAK,KAAK,CAC9B,EAAO,QAAU,KAAK,KAAK,GAC3B,CACF,KAAK,WAAW,MAAM,EAAM,GAC5B,CAGF,IAAM,EAAU,MADE,KAAK,sBACQ,CAAC,iBAAiB,CAC/C,KAAM,EACN,UAAW,EAAM,oBAAsB,KAAK,0BAC7C,CAAC,CAAC,QAEH,GAAI,EAAQ,SAAW,YAMrB,OALA,KAAK,oBAAoB,aAAa,EAAI,CAKnC,EAHL,EAAQ,SAAW,SACf,EAAQ,MACP,GAAY,SAAW,0BAA0B,EAAM,UAAU,EAAM,cAC1D,CAMtB,KAAK,mBAAmB,EAAW,CACnC,IAAM,EAAgB,KAAK,aAAa,EAAM,CAS9C,OARI,CAAC,GAAiB,CAAC,EAA0B,IAAI,EAAc,GACjE,EAAa,WAAY,GAAW,CAClC,EAAO,MAAQ,UACf,EAAO,WAAa,KAAK,KAAK,EAC9B,CAEJ,KAAK,WAAW,MAAM,EAAM,CAErB,CAAE,QAAO,MAAK,QA/XxB,GAAY,KAGR,EAAO,EAAgB,gBAAgB,CAAA,KACvC,EAAO,EAAgB,2BAA2B,CAAA"}
@@ -1,2 +1,2 @@
1
- const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../adapters/process/env.cjs`),n=require(`../../../schemas/team/capabilityCeiling.cjs`),r=require(`../../../shared/paths.cjs`),i=require(`../../../shared/modelInfo.cjs`),a=require(`../../intercom/nativeTeamChannel.cjs`),o=require(`./mcpDirectToolAllowlist.cjs`),s=require(`./nestedPath.cjs`),c=require(`./piSpawn.cjs`),l=require(`./toolBudget.cjs`);let u=require(`node:fs`);u=e.__toESM(u,1);let d=require(`node:path`);d=e.__toESM(d,1);let f=require(`@agimon-ai/doompi-extension-contracts/child-process`),p=require(`node:url`);const m=`Task: `,h=`read`,g=`structured_output`,_=/[^\w.-]/g,v=/^\d+$/,y=`dist`,b=`extensions/subagentPromptRuntimeEntry`,x=`.cts`;function S(e,t=`.ts`){let n=(0,p.fileURLToPath)(require(`url`).pathToFileURL(__filename).href),r=n.split(d.sep).includes(y),i=r?y:`src`,a=r?`.mjs`:t,o=d.dirname(n);for(;;){let t=d.join(o,i,`${e}${a}`);if(u.existsSync(t))return t;let n=d.dirname(o);if(n===o)throw Error(`Could not resolve runtime extension ${e}`);o=n}}function C(e=process.env){let n=e[t.DOOMPI_CHILD_EXTENSIONS_ENV];if(!n)return[];let r;try{r=JSON.parse(n)}catch(e){let n=e instanceof Error?` ${e.message}`:``;throw Error(`${t.DOOMPI_CHILD_EXTENSIONS_ENV} must be valid JSON.${n}`)}if(!Array.isArray(r)||!r.every(e=>typeof e==`string`))throw Error(`${t.DOOMPI_CHILD_EXTENSIONS_ENV} must be a JSON array of extension paths.`);return r}function w(e=process.env){let n=e[t.DOOMPI_SKILL_DIRS_ENV];return n?n.split(d.delimiter).filter(Boolean):[]}function T(e,t,n=!1){if(!e||!t)return e;let r=e.lastIndexOf(`:`);return r!==-1&&i.THINKING_LEVELS.some(t=>t===e.substring(r+1))?n?`${e.slice(0,r)}:${t}`:e:`${e}:${t}`}function E(e){return e.includes(`/`)||e.endsWith(`.ts`)||e.endsWith(`.js`)}function D(e){let t=n.intersectSubagentCapabilityCeilings(e.capabilityCeiling,e.inheritedCapabilityCeiling),r=t?.allowedTools===void 0?void 0:new Set(t.allowedTools),i=t?.allowedTools?.filter(e=>!(t.allowMcpTools===!0&&e===`mcp`)),s=[...new Set((e.excludeTools??[]).map(e=>e.trim()).filter(e=>e.length>0))],c=new Set(s),l=t?.requiredTools??[],u=t?.allowMcpTools===!0,d=u&&[...e.tools??[],...l].includes(`mcp`),f=[...new Set([...e.tools??[],...l])].filter(e=>!E(e)&&!(d&&e===`mcp`)),p=[...e.mcpDirectTools??[],...d?[`*`]:[]];if(e.requireReadTool&&c.has(h))throw Error(`Child tool exclusions exclude required tool '${h}' for lazy skill loading.`);if(e.structuredOutput&&c.has(g))throw Error(`Child tool exclusions exclude required tool '${g}'.`);if(e.requireReadTool&&r&&!r.has(h))throw Error(`Capability ceiling from ${t?.sources.join(`, `)||`unknown source`} excludes required tool '${h}' for lazy skill loading.`);let m=e.tools===void 0?i??[]:(e.requireReadTool&&f.length>0&&!f.includes(h)&&!r?[h,...f]:f).filter(e=>!r||r.has(e)).filter(e=>!c.has(e)),_=m.includes(`subagent`),v=t?.denyExtensions?[]:(e.tools??[]).filter(e=>!f.includes(e)&&E(e)),y=t?.denyExtensions?[]:o.resolveMcpDirectToolSelections(p,e.cwd),C=y.filter(e=>(u||!r||r.has(e.name))&&!c.has(e.name)),w=C.map(e=>e.name),T=e.tools!==void 0||p.length>0||r!==void 0,D=[...e.structuredOutput?[g]:[],...e.teamToolEnabled?[a.NATIVE_TEAM_TOOL_NAME]:[]].filter(e=>!c.has(e)),O=[...new Set([...m,...w,...D])];if(e.requireReadTool&&T&&!O.includes(h))throw Error(`Effective child tool allowlist excludes required tool '${h}' for lazy skill loading.`);let k=T?[...new Set([...e.tools===void 0?[]:m,...p.length?w:[],...D])]:[],A=_?[S(b,x),S(`extension/fanout-child`)]:[S(b,x)],j=t?.denyExtensions===!0||e.extensions!==void 0,M=t?.denyExtensions?[]:[...v,...e.extensions??[],...e.subagentOnlyExtensions??[]],N=j?[...new Set([...A,...M])]:[...new Set([...A,...v,...e.subagentOnlyExtensions??[]])],P=e.tools===void 0?void 0:[...new Set([...f,...y.map(e=>e.name)])],F=t?{ceiling:t,...P?{requestedTools:P}:{},effectiveTools:O,removedTools:P?.filter(e=>!O.includes(e))??[],internalTools:D,extensionsDenied:t.denyExtensions,removedExtensionCount:t.denyExtensions?(e.extensions?.length??0)+(e.subagentOnlyExtensions?.length??0)+(e.tools??[]).filter(E).length:0,requestedMcpToolCount:p.length,effectiveMcpTools:w}:void 0;return{...t?{capabilityCeiling:t}:{},excludedTools:s,requestedBuiltinTools:f,declaredBuiltinTools:m,toolExtensionPaths:v,resolvedMcpSelections:y,effectiveMcpSelections:C,effectiveMcpTools:w,explicitToolAllowlist:T,internalTools:D,effectiveToolAllowlist:O,requiredChildTools:k,fanoutAuthorized:_,runtimeExtensions:A,configuredExtensions:M,extensionArgs:N,disableAmbientExtensions:j,...F?{capabilityAudit:F}:{}}}function O(e){let r=e.environment??process.env,i=n.intersectSubagentCapabilityCeilings(e.inheritedCapabilityCeiling,n.decodeSubagentCapabilityCeiling(r[t.SUBAGENT_CAPABILITY_CEILING_ENV])),a=D({tools:e.tools,extensions:e.extensions,subagentOnlyExtensions:e.subagentOnlyExtensions,mcpDirectTools:e.mcpDirectTools,cwd:e.cwd,requireReadTool:e.requireReadTool,excludeTools:e.excludeTools,structuredOutput:e.structuredOutput,teamToolEnabled:e.teamToolEnabled,capabilityCeiling:e.capabilityCeiling,inheritedCapabilityCeiling:i}),o=a.explicitToolAllowlist?a.effectiveToolAllowlist:void 0;return{toolPlan:a,...o&&o.length>0?{tools:o}:{},...o?.length===0?{noTools:`all`}:{},...a.excludedTools.length>0?{excludeTools:a.excludedTools}:{},extensions:[...new Set([...C(r),...a.extensionArgs])],noAmbientExtensions:a.disableAmbientExtensions,skillPaths:e.inheritSkills?w(r):[],noSkills:!e.inheritSkills}}function k(){return u.mkdirSync(r.currentChildLaunchTempDir(),{recursive:!0,mode:448}),u.mkdtempSync(d.join(r.currentChildLaunchTempDir(),`doom-team-launch-`))}function A(e){let r=[...e.baseArgs];e.sessionFile?(u.mkdirSync(d.dirname(e.sessionFile),{recursive:!0}),r.push(`--session`,e.sessionFile)):(e.sessionEnabled||r.push(`--no-session`),e.sessionDir&&(u.mkdirSync(e.sessionDir,{recursive:!0}),r.push(`--session-dir`,e.sessionDir)));let i=T(e.model,e.thinking);i&&r.push(`--model`,i);let{toolPlan:o,...p}=O({tools:e.tools,extensions:e.extensions,subagentOnlyExtensions:e.subagentOnlyExtensions,mcpDirectTools:e.mcpDirectTools,cwd:e.cwd,requireReadTool:e.requireReadTool,excludeTools:e.excludeTools,structuredOutput:!!e.structuredOutput,teamToolEnabled:!!e.teamMember||e.teamToolEnabled===!0,capabilityCeiling:e.capabilityCeiling,inheritSkills:e.inheritSkills});o.explicitToolAllowlist&&(r.push(o.effectiveToolAllowlist.length>0?`--tools`:`--no-tools`),o.effectiveToolAllowlist.length>0&&r.push(o.effectiveToolAllowlist.join(`,`))),o.excludedTools.length>0&&r.push(`--exclude-tools`,o.excludedTools.join(`,`)),o.disableAmbientExtensions&&r.push(`--no-extensions`);for(let e of o.extensionArgs)r.push(`--extension`,e);e.inheritProjectContext||r.push(`--no-context-files`),e.inheritSkills||r.push(`--no-skills`);let h,g=()=>(h??=k(),h);if(e.systemPrompt!==void 0&&e.systemPrompt!==null){let t=(e.promptFileStem??`prompt`).replace(_,`_`),n=d.join(g(),`${t}.md`);u.writeFileSync(n,e.systemPrompt,{mode:384}),r.push(e.systemPromptMode===`replace`?`--system-prompt`:`--append-system-prompt`,n)}if(e.task.length>8e3){let t=d.join(g(),`task.md`);u.writeFileSync(t,`${m}${e.task}`,{mode:384}),r.push(`@${t}`)}else r.push(`${m}${e.task}`);let y={...e.teamMember?a.nativeTeamMemberEnvironment(e.teamMember):a.clearNativeTeamMemberEnvironment()},b=process.env.PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT??c.resolvePiPackageRoot();b&&(y[t.PI_CODING_AGENT_PACKAGE_ROOT_ENV]=b);let x;o.requiredChildTools.length>0&&(x=d.join(g(),`tool-diagnostic.json`),y[t.REQUIRED_CHILD_TOOLS_ENV]=JSON.stringify(o.requiredChildTools),y[t.CHILD_TOOL_DIAGNOSTIC_PATH_ENV]=x),y[t.MCP_DIRECT_CHILD_TOOLS_ENV]=o.effectiveMcpTools.length>0?JSON.stringify(o.effectiveMcpTools):void 0,y[f.SUBAGENT_CHILD_ENV]=`1`,y[t.SUBAGENT_FANOUT_CHILD_ENV]=o.fanoutAuthorized?`1`:`0`;let S=!!(process.env.PI_SUBAGENT_PARENT_EVENT_SINK&&process.env.PI_SUBAGENT_PARENT_ROOT_RUN_ID&&process.env.PI_SUBAGENT_PARENT_CAPABILITY_TOKEN),C=e.parentRunId??e.runId??(S?process.env.PI_SUBAGENT_RUN_ID:void 0)??process.env.PI_SUBAGENT_PARENT_RUN_ID??``,w=e.parentChildIndex===void 0?e.childIndex===void 0?process.env.PI_SUBAGENT_PARENT_CHILD_INDEX??``:String(e.childIndex):String(e.parentChildIndex),E=Number(process.env[t.SUBAGENT_PARENT_DEPTH_ENV]),D=e.parentDepth??(S&&Number.isFinite(E)?E+1:1),A=e.parentPath??[...s.parseNestedPathEnv(process.env.PI_SUBAGENT_PARENT_PATH),...C?[{runId:C,...w&&v.test(w)?{stepIndex:Number(w)}:{},...e.childAgentName?{agent:e.childAgentName}:{}}]:[]];y[t.SUBAGENT_PARENT_EVENT_SINK_ENV]=o.fanoutAuthorized?e.parentEventSink??process.env.PI_SUBAGENT_PARENT_EVENT_SINK??``:``,y[t.SUBAGENT_PARENT_CONTROL_INBOX_ENV]=o.fanoutAuthorized?e.parentControlInbox??process.env.PI_SUBAGENT_PARENT_CONTROL_INBOX??``:``,y[t.SUBAGENT_PARENT_ROOT_RUN_ID_ENV]=o.fanoutAuthorized?e.parentRootRunId??process.env.PI_SUBAGENT_PARENT_ROOT_RUN_ID??e.runId??``:``,y[t.SUBAGENT_PARENT_RUN_ID_ENV]=o.fanoutAuthorized?C:``,y[t.SUBAGENT_PARENT_CHILD_INDEX_ENV]=o.fanoutAuthorized?w:``,y[t.SUBAGENT_PARENT_DEPTH_ENV]=o.fanoutAuthorized?String(D):``,y[t.SUBAGENT_PARENT_PATH_ENV]=o.fanoutAuthorized?s.encodeNestedPathEnv(A):``,y[t.SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]=o.fanoutAuthorized?e.parentCapabilityToken??process.env.PI_SUBAGENT_PARENT_CAPABILITY_TOKEN??``:``,y[t.INHERIT_PROJECT_CONTEXT_ENV]=e.inheritProjectContext?`1`:`0`,y[t.INHERIT_SKILLS_ENV]=e.inheritSkills?`1`:`0`,y[t.PI_INTERCOM_STABLE_ID_ENV]=e.intercomSessionName||void 0,y[t.PI_INTERCOM_SESSION_ID_ENV]=void 0,e.intercomSessionName&&(y[t.SUBAGENT_INTERCOM_SESSION_NAME_ENV]=e.intercomSessionName),e.runId&&(y[t.SUBAGENT_RUN_ID_ENV]=e.runId),e.childAgentName&&(y[t.SUBAGENT_CHILD_AGENT_ENV]=e.childAgentName),e.childIndex!==void 0&&(y[t.SUBAGENT_CHILD_INDEX_ENV]=String(e.childIndex)),!o.capabilityCeiling&&e.mcpDirectTools?.length?y[t.MCP_DIRECT_TOOLS_ENV]=e.mcpDirectTools.join(`,`):o.capabilityCeiling&&o.effectiveMcpSelections.length&&!o.capabilityCeiling.denyExtensions?y[t.MCP_DIRECT_TOOLS_ENV]=o.effectiveMcpSelections.map(e=>e.selector).join(`,`):y[t.MCP_DIRECT_TOOLS_ENV]=`__none__`;let j=n.encodeSubagentCapabilityCeiling(o.capabilityCeiling);j&&(y[t.SUBAGENT_CAPABILITY_CEILING_ENV]=j),e.structuredOutput&&(y[t.STRUCTURED_OUTPUT_CAPTURE_ENV]=e.structuredOutput.outputPath,y[t.STRUCTURED_OUTPUT_SCHEMA_ENV]=e.structuredOutput.schemaPath),e.steerInboxDir&&(y[t.SUBAGENT_STEER_INBOX_ENV]=e.steerInboxDir),e.steerCapabilityPath&&(y[t.SUBAGENT_STEER_CAPABILITY_ENV]=e.steerCapabilityPath),e.steerAckDir&&(y[t.SUBAGENT_STEER_ACK_DIR_ENV]=e.steerAckDir);let M=l.encodeToolBudgetEnv(e.toolBudget);return M&&(y[t.TOOL_BUDGET_ENV]=M),y[t.TOOL_BUDGET_ZERO_AUTH_ENV]=e.allowZeroToolBudget?`1`:void 0,y[f.SUBAGENT_PARENT_SESSION_ENV]=e.parentSessionId??process.env[f.SUBAGENT_PARENT_SESSION_ENV]??``,y[f.DOOM_CHILD_PROCESS_CONTEXT_ENV]=e.parentSessionId?(0,f.encodeChildProcessContext)({parentSessionId:e.parentSessionId,workingDirectory:e.cwd??process.cwd(),...e.childAgentName?{mode:e.childAgentName}:{}}):void 0,{args:r,env:y,sdk:{...e.model||e.thinking?{model:T(e.model,e.thinking)}:{},...p,noContextFiles:!e.inheritProjectContext,sessionEnabled:e.sessionEnabled,...e.sessionDir?{sessionDir:e.sessionDir}:{},...e.sessionFile?{sessionFile:e.sessionFile}:{},...e.systemPrompt!==void 0&&e.systemPrompt!==null?{systemPrompt:e.systemPrompt}:{},...e.systemPromptMode?{systemPromptMode:e.systemPromptMode}:{}},tempDir:h,toolDiagnosticPath:x,capabilityAudit:o.capabilityAudit}}exports.buildPiArgs=A,exports.resolvePiSdkResourcePlan=O;
1
+ const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../adapters/process/env.cjs`),n=require(`../../../schemas/team/capabilityCeiling.cjs`),r=require(`../../../shared/paths.cjs`),i=require(`../../../shared/modelInfo.cjs`),a=require(`../../intercom/nativeTeamChannel.cjs`),o=require(`./mcpDirectToolAllowlist.cjs`),s=require(`./nestedPath.cjs`),c=require(`./piSpawn.cjs`),l=require(`./toolBudget.cjs`);let u=require(`node:fs`);u=e.__toESM(u,1);let d=require(`node:path`);d=e.__toESM(d,1);let f=require(`@agimon-ai/doompi-extension-contracts/child-process`),p=require(`node:url`);const m=`Task: `,h=`read`,g=`structured_output`,_=/[^\w.-]/g,v=/^\d+$/,y=`dist`,b=`extensions/subagentPromptRuntimeEntry`,x=`.cts`;function S(e,t=`.ts`){let n=(0,p.fileURLToPath)(require(`url`).pathToFileURL(__filename).href),r=n.split(d.sep).includes(y),i=r?y:`src`,a=r?`.mjs`:t,o=d.dirname(n);for(;;){let t=d.join(o,i,`${e}${a}`);if(u.existsSync(t))return t;let n=d.dirname(o);if(n===o)throw Error(`Could not resolve runtime extension ${e}`);o=n}}function C(e=process.env){let n=e[t.DOOMPI_CHILD_EXTENSIONS_ENV];if(!n)return[];let r;try{r=JSON.parse(n)}catch(e){let n=e instanceof Error?` ${e.message}`:``;throw Error(`${t.DOOMPI_CHILD_EXTENSIONS_ENV} must be valid JSON.${n}`)}if(!Array.isArray(r)||!r.every(e=>typeof e==`string`))throw Error(`${t.DOOMPI_CHILD_EXTENSIONS_ENV} must be a JSON array of extension paths.`);return r}function w(e=process.env){let n=e[t.DOOMPI_SKILL_DIRS_ENV];return n?n.split(d.delimiter).filter(Boolean):[]}function T(e,t,n=!1){if(!e||!t)return e;let r=e.lastIndexOf(`:`);return r!==-1&&i.THINKING_LEVELS.some(t=>t===e.substring(r+1))?n?`${e.slice(0,r)}:${t}`:e:`${e}:${t}`}function E(e){return e.includes(`/`)||e.endsWith(`.ts`)||e.endsWith(`.js`)}function D(e){let t=n.intersectSubagentCapabilityCeilings(e.capabilityCeiling,e.inheritedCapabilityCeiling),r=t?.allowedTools===void 0?void 0:new Set(t.allowedTools),i=t?.allowedTools?.filter(e=>!(t.allowMcpTools===!0&&e===`mcp`)),s=[...new Set((e.excludeTools??[]).map(e=>e.trim()).filter(e=>e.length>0))],c=new Set(s),l=t?.requiredTools??[],u=t?.allowMcpTools===!0,d=u&&[...e.tools??[],...l].includes(`mcp`),f=[...new Set([...e.tools??[],...l])].filter(e=>!E(e)&&!(d&&e===`mcp`)),p=[...e.mcpDirectTools??[],...d?[`*`]:[]];if(e.requireReadTool&&c.has(h))throw Error(`Child tool exclusions exclude required tool '${h}' for lazy skill loading.`);if(e.structuredOutput&&c.has(g))throw Error(`Child tool exclusions exclude required tool '${g}'.`);if(e.requireReadTool&&r&&!r.has(h))throw Error(`Capability ceiling from ${t?.sources.join(`, `)||`unknown source`} excludes required tool '${h}' for lazy skill loading.`);let m=e.tools===void 0?i??[]:(e.requireReadTool&&f.length>0&&!f.includes(h)&&!r?[h,...f]:f).filter(e=>!r||r.has(e)).filter(e=>!c.has(e)),_=m.includes(`subagent`),v=t?.denyExtensions?[]:(e.tools??[]).filter(e=>!f.includes(e)&&E(e)),y=t?.denyExtensions?[]:o.resolveMcpDirectToolSelections(p,e.cwd),C=y.filter(e=>(u||!r||r.has(e.name))&&!c.has(e.name)),w=C.map(e=>e.name),T=e.tools!==void 0||p.length>0||r!==void 0,D=[...e.structuredOutput?[g]:[],...e.teamToolEnabled?[a.NATIVE_TEAM_TOOL_NAME]:[]].filter(e=>!c.has(e)),O=[...new Set([...m,...w,...D])];if(e.requireReadTool&&T&&!O.includes(h))throw Error(`Effective child tool allowlist excludes required tool '${h}' for lazy skill loading.`);let k=T?[...new Set([...e.tools===void 0?[]:m,...p.length?w:[],...D])]:[],A=_?[S(b,x),S(`extension/fanout-child`)]:[S(b,x)],j=t?.denyExtensions===!0||e.extensions!==void 0,M=t?.denyExtensions?[]:[...v,...e.extensions??[],...e.subagentOnlyExtensions??[]],N=j?[...new Set([...A,...M])]:[...new Set([...A,...v,...e.subagentOnlyExtensions??[]])],P=e.tools===void 0?void 0:[...new Set([...f,...y.map(e=>e.name)])],F=t?{ceiling:t,...P?{requestedTools:P}:{},effectiveTools:O,removedTools:P?.filter(e=>!O.includes(e))??[],internalTools:D,extensionsDenied:t.denyExtensions,removedExtensionCount:t.denyExtensions?(e.extensions?.length??0)+(e.subagentOnlyExtensions?.length??0)+(e.tools??[]).filter(E).length:0,requestedMcpToolCount:p.length,effectiveMcpTools:w}:void 0;return{...t?{capabilityCeiling:t}:{},excludedTools:s,requestedBuiltinTools:f,declaredBuiltinTools:m,toolExtensionPaths:v,resolvedMcpSelections:y,effectiveMcpSelections:C,effectiveMcpTools:w,explicitToolAllowlist:T,internalTools:D,effectiveToolAllowlist:O,requiredChildTools:k,fanoutAuthorized:_,runtimeExtensions:A,configuredExtensions:M,extensionArgs:N,disableAmbientExtensions:j,...F?{capabilityAudit:F}:{}}}function O(e){let r=e.environment??process.env,i=n.intersectSubagentCapabilityCeilings(e.inheritedCapabilityCeiling,n.decodeSubagentCapabilityCeiling(r[t.SUBAGENT_CAPABILITY_CEILING_ENV])),a=D({tools:e.tools,extensions:e.extensions,subagentOnlyExtensions:e.subagentOnlyExtensions,mcpDirectTools:e.mcpDirectTools,cwd:e.cwd,requireReadTool:e.requireReadTool,excludeTools:e.excludeTools,structuredOutput:e.structuredOutput,teamToolEnabled:e.teamToolEnabled,capabilityCeiling:e.capabilityCeiling,inheritedCapabilityCeiling:i}),o=a.explicitToolAllowlist?a.effectiveToolAllowlist:void 0,s=C(r);return{toolPlan:a,...o&&o.length>0?{tools:o}:{},...o?.length===0?{noTools:`all`}:{},...a.excludedTools.length>0?{excludeTools:a.excludedTools}:{},extensions:[...new Set([...s,...a.extensionArgs])],extensionsProvidedExternally:!!r[t.DOOMPI_CHILD_EXTENSIONS_ENV]?.trim(),noAmbientExtensions:a.disableAmbientExtensions,skillPaths:e.inheritSkills?w(r):[],noSkills:!e.inheritSkills}}function k(){return u.mkdirSync(r.currentChildLaunchTempDir(),{recursive:!0,mode:448}),u.mkdtempSync(d.join(r.currentChildLaunchTempDir(),`doom-team-launch-`))}function A(e){let r=[...e.baseArgs];e.sessionFile?(u.mkdirSync(d.dirname(e.sessionFile),{recursive:!0}),r.push(`--session`,e.sessionFile)):(e.sessionEnabled||r.push(`--no-session`),e.sessionDir&&(u.mkdirSync(e.sessionDir,{recursive:!0}),r.push(`--session-dir`,e.sessionDir)));let i=T(e.model,e.thinking);i&&r.push(`--model`,i);let{toolPlan:o,...p}=O({tools:e.tools,extensions:e.extensions,subagentOnlyExtensions:e.subagentOnlyExtensions,mcpDirectTools:e.mcpDirectTools,cwd:e.cwd,requireReadTool:e.requireReadTool,excludeTools:e.excludeTools,structuredOutput:!!e.structuredOutput,teamToolEnabled:!!e.teamMember||e.teamToolEnabled===!0,capabilityCeiling:e.capabilityCeiling,inheritSkills:e.inheritSkills});o.explicitToolAllowlist&&(r.push(o.effectiveToolAllowlist.length>0?`--tools`:`--no-tools`),o.effectiveToolAllowlist.length>0&&r.push(o.effectiveToolAllowlist.join(`,`))),o.excludedTools.length>0&&r.push(`--exclude-tools`,o.excludedTools.join(`,`)),o.disableAmbientExtensions&&r.push(`--no-extensions`);for(let e of o.extensionArgs)r.push(`--extension`,e);e.inheritProjectContext||r.push(`--no-context-files`),e.inheritSkills||r.push(`--no-skills`);let h,g=()=>(h??=k(),h);if(e.systemPrompt!==void 0&&e.systemPrompt!==null){let t=(e.promptFileStem??`prompt`).replace(_,`_`),n=d.join(g(),`${t}.md`);u.writeFileSync(n,e.systemPrompt,{mode:384}),r.push(e.systemPromptMode===`replace`?`--system-prompt`:`--append-system-prompt`,n)}if(e.task.length>8e3){let t=d.join(g(),`task.md`);u.writeFileSync(t,`${m}${e.task}`,{mode:384}),r.push(`@${t}`)}else r.push(`${m}${e.task}`);let y={...e.teamMember?a.nativeTeamMemberEnvironment(e.teamMember):a.clearNativeTeamMemberEnvironment()},b=process.env.PI_SUBAGENTS_PI_CODING_AGENT_PACKAGE_ROOT??c.resolvePiPackageRoot();b&&(y[t.PI_CODING_AGENT_PACKAGE_ROOT_ENV]=b);let x;o.requiredChildTools.length>0&&(x=d.join(g(),`tool-diagnostic.json`),y[t.REQUIRED_CHILD_TOOLS_ENV]=JSON.stringify(o.requiredChildTools),y[t.CHILD_TOOL_DIAGNOSTIC_PATH_ENV]=x),y[t.MCP_DIRECT_CHILD_TOOLS_ENV]=o.effectiveMcpTools.length>0?JSON.stringify(o.effectiveMcpTools):void 0,y[f.SUBAGENT_CHILD_ENV]=`1`,y[t.SUBAGENT_FANOUT_CHILD_ENV]=o.fanoutAuthorized?`1`:`0`;let S=!!(process.env.PI_SUBAGENT_PARENT_EVENT_SINK&&process.env.PI_SUBAGENT_PARENT_ROOT_RUN_ID&&process.env.PI_SUBAGENT_PARENT_CAPABILITY_TOKEN),C=e.parentRunId??e.runId??(S?process.env.PI_SUBAGENT_RUN_ID:void 0)??process.env.PI_SUBAGENT_PARENT_RUN_ID??``,w=e.parentChildIndex===void 0?e.childIndex===void 0?process.env.PI_SUBAGENT_PARENT_CHILD_INDEX??``:String(e.childIndex):String(e.parentChildIndex),E=Number(process.env[t.SUBAGENT_PARENT_DEPTH_ENV]),D=e.parentDepth??(S&&Number.isFinite(E)?E+1:1),A=e.parentPath??[...s.parseNestedPathEnv(process.env.PI_SUBAGENT_PARENT_PATH),...C?[{runId:C,...w&&v.test(w)?{stepIndex:Number(w)}:{},...e.childAgentName?{agent:e.childAgentName}:{}}]:[]];y[t.SUBAGENT_PARENT_EVENT_SINK_ENV]=o.fanoutAuthorized?e.parentEventSink??process.env.PI_SUBAGENT_PARENT_EVENT_SINK??``:``,y[t.SUBAGENT_PARENT_CONTROL_INBOX_ENV]=o.fanoutAuthorized?e.parentControlInbox??process.env.PI_SUBAGENT_PARENT_CONTROL_INBOX??``:``,y[t.SUBAGENT_PARENT_ROOT_RUN_ID_ENV]=o.fanoutAuthorized?e.parentRootRunId??process.env.PI_SUBAGENT_PARENT_ROOT_RUN_ID??e.runId??``:``,y[t.SUBAGENT_PARENT_RUN_ID_ENV]=o.fanoutAuthorized?C:``,y[t.SUBAGENT_PARENT_CHILD_INDEX_ENV]=o.fanoutAuthorized?w:``,y[t.SUBAGENT_PARENT_DEPTH_ENV]=o.fanoutAuthorized?String(D):``,y[t.SUBAGENT_PARENT_PATH_ENV]=o.fanoutAuthorized?s.encodeNestedPathEnv(A):``,y[t.SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]=o.fanoutAuthorized?e.parentCapabilityToken??process.env.PI_SUBAGENT_PARENT_CAPABILITY_TOKEN??``:``,y[t.INHERIT_PROJECT_CONTEXT_ENV]=e.inheritProjectContext?`1`:`0`,y[t.INHERIT_SKILLS_ENV]=e.inheritSkills?`1`:`0`,y[t.PI_INTERCOM_STABLE_ID_ENV]=e.intercomSessionName||void 0,y[t.PI_INTERCOM_SESSION_ID_ENV]=void 0,e.intercomSessionName&&(y[t.SUBAGENT_INTERCOM_SESSION_NAME_ENV]=e.intercomSessionName),e.runId&&(y[t.SUBAGENT_RUN_ID_ENV]=e.runId),e.childAgentName&&(y[t.SUBAGENT_CHILD_AGENT_ENV]=e.childAgentName),e.childIndex!==void 0&&(y[t.SUBAGENT_CHILD_INDEX_ENV]=String(e.childIndex)),!o.capabilityCeiling&&e.mcpDirectTools?.length?y[t.MCP_DIRECT_TOOLS_ENV]=e.mcpDirectTools.join(`,`):o.capabilityCeiling&&o.effectiveMcpSelections.length&&!o.capabilityCeiling.denyExtensions?y[t.MCP_DIRECT_TOOLS_ENV]=o.effectiveMcpSelections.map(e=>e.selector).join(`,`):y[t.MCP_DIRECT_TOOLS_ENV]=`__none__`;let j=n.encodeSubagentCapabilityCeiling(o.capabilityCeiling);j&&(y[t.SUBAGENT_CAPABILITY_CEILING_ENV]=j),e.structuredOutput&&(y[t.STRUCTURED_OUTPUT_CAPTURE_ENV]=e.structuredOutput.outputPath,y[t.STRUCTURED_OUTPUT_SCHEMA_ENV]=e.structuredOutput.schemaPath),e.steerInboxDir&&(y[t.SUBAGENT_STEER_INBOX_ENV]=e.steerInboxDir),e.steerCapabilityPath&&(y[t.SUBAGENT_STEER_CAPABILITY_ENV]=e.steerCapabilityPath),e.steerAckDir&&(y[t.SUBAGENT_STEER_ACK_DIR_ENV]=e.steerAckDir);let M=l.encodeToolBudgetEnv(e.toolBudget);return M&&(y[t.TOOL_BUDGET_ENV]=M),y[t.TOOL_BUDGET_ZERO_AUTH_ENV]=e.allowZeroToolBudget?`1`:void 0,y[f.SUBAGENT_PARENT_SESSION_ENV]=e.parentSessionId??process.env[f.SUBAGENT_PARENT_SESSION_ENV]??``,y[f.DOOM_CHILD_PROCESS_CONTEXT_ENV]=e.parentSessionId?(0,f.encodeChildProcessContext)({parentSessionId:e.parentSessionId,workingDirectory:e.cwd??process.cwd(),...e.childAgentName?{mode:e.childAgentName}:{}}):void 0,{args:r,env:y,sdk:{...e.model||e.thinking?{model:T(e.model,e.thinking)}:{},...p,noContextFiles:!e.inheritProjectContext,sessionEnabled:e.sessionEnabled,...e.sessionDir?{sessionDir:e.sessionDir}:{},...e.sessionFile?{sessionFile:e.sessionFile}:{},...e.systemPrompt!==void 0&&e.systemPrompt!==null?{systemPrompt:e.systemPrompt}:{},...e.systemPromptMode?{systemPromptMode:e.systemPromptMode}:{}},tempDir:h,toolDiagnosticPath:x,capabilityAudit:o.capabilityAudit}}exports.buildPiArgs=A,exports.resolvePiSdkResourcePlan=O;
2
2
  //# sourceMappingURL=piArgs.cjs.map