@ashx-j/lunr 0.2.27 → 0.2.28

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.
Files changed (138) hide show
  1. package/CHANGELOG.md +10 -1
  2. package/dist/builtin-extensions/narumiruna-pi-goal/src/accounting.d.ts.map +1 -1
  3. package/dist/builtin-extensions/narumiruna-pi-goal/src/accounting.js +5 -2
  4. package/dist/builtin-extensions/narumiruna-pi-goal/src/accounting.js.map +1 -1
  5. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.d.ts +4 -1
  6. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.d.ts.map +1 -1
  7. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js +2 -0
  8. package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js.map +1 -1
  9. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +5 -0
  10. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
  11. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +2 -0
  12. package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
  13. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +5 -1
  14. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +68 -40
  16. package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts +0 -3
  18. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  19. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +28 -4
  20. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  22. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +1 -0
  23. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  24. package/dist/builtin-extensions/pi-subagents/src/shared/session-tokens.d.ts +7 -1
  25. package/dist/builtin-extensions/pi-subagents/src/shared/session-tokens.d.ts.map +1 -1
  26. package/dist/builtin-extensions/pi-subagents/src/shared/session-tokens.js +70 -38
  27. package/dist/builtin-extensions/pi-subagents/src/shared/session-tokens.js.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +9 -0
  29. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  31. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  32. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +13 -62
  33. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  35. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  37. package/dist/builtin-extensions/pi-tps.d.ts.map +1 -1
  38. package/dist/builtin-extensions/pi-tps.js +10 -2
  39. package/dist/builtin-extensions/pi-tps.js.map +1 -1
  40. package/dist/catalog/models.json +1 -1
  41. package/dist/catalog/providers/google-vertex.json +1 -1
  42. package/dist/catalog/providers/google.json +1 -1
  43. package/dist/catalog/publication.json +3 -3
  44. package/dist/core/agent-session.d.ts +6 -15
  45. package/dist/core/agent-session.d.ts.map +1 -1
  46. package/dist/core/agent-session.js +35 -50
  47. package/dist/core/agent-session.js.map +1 -1
  48. package/dist/core/cache-stats.d.ts +1 -1
  49. package/dist/core/cache-stats.d.ts.map +1 -1
  50. package/dist/core/cache-stats.js +1 -1
  51. package/dist/core/cache-stats.js.map +1 -1
  52. package/dist/core/compaction/branch-summarization.d.ts +2 -1
  53. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  54. package/dist/core/compaction/branch-summarization.js +1 -0
  55. package/dist/core/compaction/branch-summarization.js.map +1 -1
  56. package/dist/core/compaction/compaction.d.ts +5 -11
  57. package/dist/core/compaction/compaction.d.ts.map +1 -1
  58. package/dist/core/compaction/compaction.js +38 -92
  59. package/dist/core/compaction/compaction.js.map +1 -1
  60. package/dist/core/context-breakdown.d.ts +2 -2
  61. package/dist/core/context-breakdown.d.ts.map +1 -1
  62. package/dist/core/context-breakdown.js +22 -20
  63. package/dist/core/context-breakdown.js.map +1 -1
  64. package/dist/core/extensions/types.d.ts +3 -1
  65. package/dist/core/extensions/types.d.ts.map +1 -1
  66. package/dist/core/extensions/types.js.map +1 -1
  67. package/dist/core/session-manager.d.ts +5 -1
  68. package/dist/core/session-manager.d.ts.map +1 -1
  69. package/dist/core/session-manager.js +16 -1
  70. package/dist/core/session-manager.js.map +1 -1
  71. package/dist/core/usage-accounting.d.ts +47 -0
  72. package/dist/core/usage-accounting.d.ts.map +1 -0
  73. package/dist/core/usage-accounting.js +205 -0
  74. package/dist/core/usage-accounting.js.map +1 -0
  75. package/dist/core/usage-history.d.ts +1 -18
  76. package/dist/core/usage-history.d.ts.map +1 -1
  77. package/dist/core/usage-history.js +46 -114
  78. package/dist/core/usage-history.js.map +1 -1
  79. package/dist/gateway/mobile-commands.d.ts.map +1 -1
  80. package/dist/gateway/mobile-commands.js +13 -1
  81. package/dist/gateway/mobile-commands.js.map +1 -1
  82. package/dist/modes/interactive/components/context-view.d.ts.map +1 -1
  83. package/dist/modes/interactive/components/context-view.js +2 -2
  84. package/dist/modes/interactive/components/context-view.js.map +1 -1
  85. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  86. package/dist/modes/interactive/components/footer.js +8 -12
  87. package/dist/modes/interactive/components/footer.js.map +1 -1
  88. package/dist/modes/interactive/components/usage-view.d.ts +5 -0
  89. package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/usage-view.js +21 -6
  91. package/dist/modes/interactive/components/usage-view.js.map +1 -1
  92. package/dist/modes/interactive/interactive-mode.d.ts +1 -5
  93. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  94. package/dist/modes/interactive/interactive-mode.js +55 -22
  95. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  96. package/dist/node-runtime/{agent-session-services-KDMSPQDY.js → agent-session-services-RTUGP3XR.js} +5 -4
  97. package/dist/node-runtime/{builtin-extensions-OX6KELNX.js → builtin-extensions-N4EUD6SB.js} +1 -1
  98. package/dist/node-runtime/{chunk-A2WO6WIN.js → chunk-4QRUZ64T.js} +11 -5
  99. package/dist/node-runtime/{chunk-ZDJUR45D.js → chunk-BMRXLQWQ.js} +41 -38
  100. package/dist/node-runtime/{chunk-MQPKUHRN.js → chunk-DFMPRAQQ.js} +8 -8
  101. package/dist/node-runtime/{chunk-C3P6Q3B2.js → chunk-E5DQGNJI.js} +10 -10
  102. package/dist/node-runtime/{chunk-FIRA54AC.js → chunk-ICKGAKWX.js} +22 -55
  103. package/dist/node-runtime/{chunk-JWEVT363.js → chunk-JEK35V2M.js} +14 -8
  104. package/dist/node-runtime/{chunk-EPR2F3GU.js → chunk-PWHTSQC4.js} +15 -1
  105. package/dist/node-runtime/{chunk-AYHH7YSD.js → chunk-QT3F6UEV.js} +61 -30
  106. package/dist/node-runtime/{chunk-JQYNHAKX.js → chunk-RY3FJ6WI.js} +29 -27
  107. package/dist/node-runtime/{chunk-I2H5QMWW.js → chunk-STMX7W5I.js} +1 -1
  108. package/dist/node-runtime/chunk-SWWHZ2RI.js +158 -0
  109. package/dist/node-runtime/{chunk-QNVMKNNN.js → chunk-U3YVZ6UC.js} +4 -4
  110. package/dist/node-runtime/{command-3HMSY6W2.js → command-YRT7I2BW.js} +1 -1
  111. package/dist/node-runtime/{commands-OCSSEEWJ.js → commands-2ETOWWRA.js} +5 -5
  112. package/dist/node-runtime/{export-html-BRWF5AHI.js → export-html-DBQRRMAY.js} +1 -1
  113. package/dist/node-runtime/{gateway-6623C2U3.js → gateway-V6K2CDWN.js} +9 -9
  114. package/dist/node-runtime/{goal-AIHEDUYA.js → goal-J7TYMOBK.js} +9 -3
  115. package/dist/node-runtime/index.js +9 -8
  116. package/dist/node-runtime/{interactive-mode-FM5DNUSW.js → interactive-mode-ASZ7APYP.js} +7 -6
  117. package/dist/node-runtime/{lunr-cron-LKVA5CZZ.js → lunr-cron-WD3YH4CR.js} +4 -4
  118. package/dist/node-runtime/main.js +7 -6
  119. package/dist/node-runtime/{mobile-commands-XT3NQXLD.js → mobile-commands-ICUXFW5V.js} +2 -2
  120. package/dist/node-runtime/{pi-subagents-LT3CTO4E.js → pi-subagents-QPU3OXE4.js} +17 -62
  121. package/dist/node-runtime/rpc-entry.js +7 -6
  122. package/dist/node-runtime/{session-handoff-LNSPDTKY.js → session-handoff-LYH53ESV.js} +2 -2
  123. package/dist/node-runtime/{session-manager-YIFIAVGC.js → session-manager-ZJ3N5Q74.js} +1 -1
  124. package/dist/node-runtime/{subagent-executor-Q5F7DBVM.js → subagent-executor-UI6SHG3X.js} +294 -231
  125. package/docs/features.md +10 -0
  126. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  127. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  128. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  129. package/examples/extensions/gondolin/package-lock.json +2 -2
  130. package/examples/extensions/gondolin/package.json +1 -1
  131. package/examples/extensions/sandbox/package-lock.json +2 -2
  132. package/examples/extensions/sandbox/package.json +1 -1
  133. package/examples/extensions/with-deps/package-lock.json +2 -2
  134. package/examples/extensions/with-deps/package.json +1 -1
  135. package/install-lock/package-lock.json +24 -24
  136. package/install-lock/package.json +2 -2
  137. package/npm-shrinkwrap.json +21 -21
  138. package/package.json +7 -7
@@ -1,6 +1,3 @@
1
- /**
2
- * Core execution logic for running subagents
3
- */
4
1
  import type { ChildSpec } from "../../shared/types.ts";
5
2
  import { type RunSyncOptions, type SingleResult } from "../../shared/types.ts";
6
3
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"execution.d.ts","sourceRoot":"","sources":["../../../../../../src/builtin-extensions/pi-subagents/src/runs/foreground/execution.ts"],"names":[],"mappings":"AACA;;GAEG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AASvD,OAAO,EAKN,KAAK,cAAc,EACnB,KAAK,YAAY,EASjB,MAAM,uBAAuB,CAAC;AAwtC/B;;GAEG;AACH,wBAAsB,OAAO,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,YAAY,CAAC,CAuTvB","sourcesContent":["// @ts-nocheck\n/**\n * Core execution logic for running subagents\n */\n\nimport { spawn } from \"node:child_process\";\nimport { splitKnownThinkingSuffix } from \"../../shared/model-info.ts\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Message } from \"@ashx-j/lunr-ai\";\nimport type { ChildSpec } from \"../../shared/types.ts\";\nimport { childRowLabel } from \"../../shared/types.ts\";\nimport {\n\tensureArtifactsDir,\n\tgetArtifactPaths,\n\twriteArtifact,\n\twriteMetadata,\n} from \"../../shared/artifacts.ts\";\nimport { createChildTranscriptWriter, type ChildTranscriptWriter } from \"../../shared/child-transcript.ts\";\nimport {\n\ttype AgentProgress,\n\ttype ArtifactPaths,\n\ttype ControlEvent,\n\ttype ModelAttempt,\n\ttype RunSyncOptions,\n\ttype SingleResult,\n\ttype Usage,\n\tDEFAULT_MAX_OUTPUT,\n\tINTERCOM_DETACH_REQUEST_EVENT,\n\tINTERCOM_DETACH_RESPONSE_EVENT,\n\ttype AcceptanceLedger,\n\ttype ResolvedAcceptanceConfig,\n\ttruncateOutput,\n\tgetSubagentDepthEnv,\n} from \"../../shared/types.ts\";\nimport {\n\tDEFAULT_CONTROL_CONFIG,\n\tbuildControlEvent,\n\tclaimControlNotification,\n\tderiveActivityState,\n\tshouldNotifyControlEvent,\n} from \"../shared/subagent-control.ts\";\nimport {\n\tgetFinalOutput,\n\tfindLatestSessionFile,\n\tdetectSubagentError,\n\textractToolArgsPreview,\n\textractTextFromContent,\n} from \"../../shared/utils.ts\";\nimport { buildSkillInjection, resolveSkillsWithFallback } from \"../../agents/skills.ts\";\nimport { resolveChildExcludeTools } from \"../shared/child-tools.ts\";\nimport { normalizeSkillInput } from \"../../agents/skills.ts\";\nimport { evaluateCompletionMutationGuard } from \"../shared/completion-guard.ts\";\nimport { getPiSpawnCommand } from \"../shared/pi-spawn.ts\";\nimport { createJsonlWriter } from \"../../shared/jsonl-writer.ts\";\nimport { attachPostExitStdioGuard } from \"../../shared/post-exit-stdio-guard.ts\";\nimport { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from \"../shared/pi-args.ts\";\nimport { READ_ONLY_WRITE_SPAWN_ERROR, resolveChildPermissions, snapshotParentPermissionMode } from \"../../../../../core/subagent-permission-inherit.ts\";\nimport { readStructuredOutput } from \"../shared/structured-output.ts\";\nimport { readChildToolDiagnosticError } from \"../shared/tool-availability.ts\";\nimport { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from \"../shared/single-output.ts\";\nimport {\n\tbuildModelCandidates,\n\tformatModelAttemptNote,\n\tisRetryableModelFailure,\n} from \"../shared/model-fallback.ts\";\nimport {\n\tcreateMutatingFailureState,\n\tdidMutatingToolFail,\n\tisMutatingTool,\n\tnextLongRunningTrigger,\n\trecordMutatingFailure,\n\tresetMutatingFailureState,\n\tresolveCurrentPath,\n\tshouldEscalateMutatingFailures,\n\tsummarizeRecentMutatingFailures,\n} from \"../shared/long-running-guard.ts\";\nimport { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from \"../shared/acceptance.ts\";\nimport { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, shouldAbortForTurnBudget, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from \"../shared/turn-budget.ts\";\nimport { initialToolBudgetState, toolBudgetState } from \"../shared/tool-budget.ts\";\nimport { resolveWatchdogConfig } from \"../../watchdog/settings.ts\";\nimport { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from \"../shared/child-protocol.ts\";\nimport { createChildEventDecoder } from \"../../../../../core/subagent-event-transport.ts\";\nimport {\n\tacceptChildWatchdogEvent,\n\tchildWatchdogIsActive,\n\tisChildWatchdogStatusEvent,\n\tresolveChildWatchdogConfig,\n\ttype ChildWatchdogStateSnapshot,\n} from \"../../watchdog/child-status.ts\";\n\nconst artifactOutputByResult = new WeakMap<SingleResult, string>();\nconst acceptanceOutputByResult = new WeakMap<SingleResult, string>();\n\nfunction emptyUsage(): Usage {\n\treturn { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };\n}\n\nfunction sumUsage(target: Usage, source: Usage): void {\n\ttarget.input += source.input;\n\ttarget.output += source.output;\n\ttarget.cacheRead += source.cacheRead;\n\ttarget.cacheWrite += source.cacheWrite;\n\ttarget.cost += source.cost;\n\ttarget.turns += source.turns;\n}\n\nfunction formatTimeoutMessage(timeoutMs: number): string {\n\treturn `Subagent timed out after ${timeoutMs}ms.`;\n}\n\nfunction resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; remainingMs: number; message: string } | undefined {\n\tif (options.timeoutMs === undefined) return undefined;\n\tconst deadlineAt = options.deadlineAt ?? Date.now() + options.timeoutMs;\n\treturn {\n\t\ttimeoutMs: options.timeoutMs,\n\t\tremainingMs: Math.max(0, deadlineAt - Date.now()),\n\t\tmessage: formatTimeoutMessage(options.timeoutMs),\n\t};\n}\n\nfunction buildPendingAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {\n\treturn {\n\t\tstatus: \"pending\",\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: [],\n\t\tverifyRuns: [],\n\t};\n}\n\nfunction appendRecentOutput(progress: AgentProgress, lines: string[]): void {\n\tif (lines.length === 0) return;\n\tprogress.recentOutput.push(...lines.filter((line) => line.trim()));\n\tif (progress.recentOutput.length > 50) {\n\t\tprogress.recentOutput.splice(0, progress.recentOutput.length - 50);\n\t}\n}\n\nfunction stripAcceptanceReportsFromMessages(messages: Message[] | undefined): void {\n\tfor (const message of messages ?? []) {\n\t\tif (message.role !== \"assistant\" || !Array.isArray(message.content)) continue;\n\t\tfor (const part of message.content) {\n\t\t\tif (part.type === \"text\" && \"text\" in part && typeof part.text === \"string\") {\n\t\t\t\tpart.text = stripAcceptanceReport(part.text);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction extractThinkingText(content: unknown): string | undefined {\n\tif (!Array.isArray(content)) return undefined;\n\tlet latest = \"\";\n\tfor (const part of content) {\n\t\tif (!part || typeof part !== \"object\") continue;\n\t\tif (\"type\" in part && part.type === \"thinking\" && \"thinking\" in part && typeof part.thinking === \"string\") {\n\t\t\tconst text = part.thinking.trim();\n\t\t\tif (text) latest = part.thinking;\n\t\t}\n\t}\n\treturn latest || undefined;\n}\n\nfunction snapshotProgress(progress: AgentProgress): AgentProgress {\n\treturn {\n\t\t...progress,\n\t\tskills: progress.skills ? [...progress.skills] : undefined,\n\t\trecentTools: progress.recentTools.map((tool) => ({ ...tool })),\n\t\trecentOutput: [...progress.recentOutput],\n\t};\n}\n\nfunction snapshotResult(result: SingleResult, progress: AgentProgress): SingleResult {\n\treturn {\n\t\t...result,\n\t\tmessages: result.outputMode === \"file-only\" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined,\n\t\tusage: { ...result.usage },\n\t\tskills: result.skills ? [...result.skills] : undefined,\n\t\tattemptedModels: result.attemptedModels ? [...result.attemptedModels] : undefined,\n\t\tmodelAttempts: result.modelAttempts\n\t\t\t? result.modelAttempts.map((attempt) => ({\n\t\t\t\t...attempt,\n\t\t\t\tusage: attempt.usage ? { ...attempt.usage } : undefined,\n\t\t\t}))\n\t\t\t: undefined,\n\t\tcontrolEvents: result.controlEvents ? result.controlEvents.map((event) => ({ ...event })) : undefined,\n\t\tprogress,\n\t\tprogressSummary: result.progressSummary ? { ...result.progressSummary } : undefined,\n\t\tartifactPaths: result.artifactPaths ? { ...result.artifactPaths } : undefined,\n\t\ttruncation: result.truncation ? { ...result.truncation } : undefined,\n\t\toutputReference: result.outputReference ? { ...result.outputReference } : undefined,\n\t};\n}\n\nasync function runSingleAttempt(\n\truntimeCwd: string,\n\tspec: ChildSpec,\n\ttask: string,\n\tmodel: string | undefined,\n\toptions: RunSyncOptions,\n\tshared: {\n\t\tsessionEnabled: boolean;\n\t\tsystemPrompt: string;\n\t\tresolvedSkillNames?: string[];\n\t\tskillsWarning?: string;\n\t\tjsonlPath?: string;\n\t\tartifactPaths?: ArtifactPaths;\n\t\ttranscriptWriter?: ChildTranscriptWriter;\n\t\tattemptNotes: string[];\n\t\toutputSnapshot?: SingleOutputSnapshot;\n\t\toriginalTask?: string;\n\t},\n): Promise<SingleResult> {\n\tconst effectiveThinking = options.thinkingOverride;\n\tconst modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);\n\tconst watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);\n\tconst childWatchdog = watchdogConfig.ok\n\t\t? resolveChildWatchdogConfig({\n\t\t\tconfig: watchdogConfig.config,\n\t\t\tagent: spec.childId,\n\t\t\trunId: options.runId,\n\t\t\tchildIndex: options.index ?? 0,\n\t\t})\n\t\t: undefined;\n\tconst excludeTools = resolveChildExcludeTools({\n\t\tpermissions: spec.effectivePermissions,\n\t\tfanoutAuthorized: spec.fanoutAuthorized,\n\t});\n\tconst appendPrompt = appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget);\n\tconst { args, env: sharedEnv, tempDir, toolDiagnosticPath } = buildPiArgs({\n\t\tbaseArgs: [\"--mode\", \"json\", \"-p\"],\n\t\ttask,\n\t\tsessionEnabled: shared.sessionEnabled,\n\t\tsessionDir: options.sessionDir,\n\t\tsessionFile: options.sessionFile,\n\t\tmodel: modelArg,\n\t\tthinking: effectiveThinking,\n\t\tsystemPromptMode: \"append\",\n\t\tinheritProjectContext: true,\n\t\tinheritSkills: false,\n\t\trequireReadTool: Boolean(shared.resolvedSkillNames?.length),\n\t\texcludeTools,\n\t\tcommunicationEnabled: options.communicationEnabled,\n\t\tchildPermission: spec.effectivePermissions,\n\t\tchildId: spec.childId,\n\t\tchildDescription: spec.description,\n\t\tsystemPrompt: appendPrompt || undefined,\n\t\tcwd: options.cwd ?? runtimeCwd,\n\t\tpromptFileStem: spec.childId,\n\t\tintercomSessionName: options.intercomSessionName,\n\t\torchestratorIntercomTarget: options.orchestratorIntercomTarget,\n\t\trunId: options.runId,\n\t\tchildAgentName: spec.childId,\n\t\tchildIndex: options.index ?? 0,\n\t\tparentEventSink: options.nestedRoute?.eventSink,\n\t\tparentControlInbox: options.nestedRoute?.controlInbox,\n\t\tparentRootRunId: options.nestedRoute?.rootRunId,\n\t\tparentCapabilityToken: options.nestedRoute?.capabilityToken,\n\t\tparentSessionId: options.parentSessionId,\n\t\tstructuredOutput: options.structuredOutput,\n\t\ttoolBudget: options.toolBudget,\n\t\tchildWatchdog,\n\t\twaitToolEnabled: options.waitToolEnabled,\n\t});\n\n\tconst thinkingFromModel = modelArg ? splitKnownThinkingSuffix(modelArg).thinkingSuffix.slice(1) || undefined : undefined;\n\tconst result: SingleResult = {\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\ttask: shared.originalTask ?? task,\n\t\texitCode: 0,\n\t\tmessages: [],\n\t\tusage: emptyUsage(),\n\t\tmodel: modelArg,\n\t\tthinking: thinkingFromModel,\n\t\tmodelSelection: spec.modelSelection,\n\t\tartifactPaths: shared.artifactPaths,\n\t\ttranscriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,\n\t\tskills: shared.resolvedSkillNames,\n\t\tskillsWarning: shared.skillsWarning,\n\t\t...(options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {}),\n\t\t...(options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}),\n\t};\n\tconst startTime = Date.now();\n\tif (options.structuredOutput) {\n\t\ttry {\n\t\t\tif (existsSync(options.structuredOutput.outputPath)) unlinkSync(options.structuredOutput.outputPath);\n\t\t} catch {\n\t\t\t// Missing/stale structured-output files are handled after the child exits.\n\t\t}\n\t}\n\tconst controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;\n\tlet interruptedByControl = false;\n\tconst allControlEvents: ControlEvent[] = [];\n\tlet pendingControlEvents: ControlEvent[] = [];\n\tconst emittedControlEventKeys = new Set<string>();\n\tconst emitControlEvent = (event: ControlEvent) => {\n\t\tif (!shouldNotifyControlEvent(controlConfig, event)) return;\n\t\tif (!claimControlNotification(controlConfig, event, emittedControlEventKeys)) return;\n\t\tallControlEvents.push(event);\n\t\tpendingControlEvents.push(event);\n\t\toptions.onControlEvent?.(event);\n\t};\n\n\tconst progress: AgentProgress = {\n\t\tindex: options.index ?? 0,\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\tstatus: \"running\",\n\t\ttask,\n\t\tskills: shared.resolvedSkillNames,\n\t\tmodel: modelArg,\n\t\tthinking: thinkingFromModel,\n\t\tmodelSelection: spec.modelSelection,\n\t\trecentTools: [],\n\t\trecentOutput: [...shared.attemptNotes],\n\t\ttoolCount: 0,\n\t\ttokens: 0,\n\t\tdurationMs: 0,\n\t\tlastActivityAt: startTime,\n\t};\n\tresult.progress = progress;\n\tconst attemptTimeout = resolveAttemptTimeout(options);\n\tif (attemptTimeout?.remainingMs === 0) {\n\t\tcleanupTempDir(tempDir);\n\t\tresult.exitCode = 1;\n\t\tresult.timedOut = true;\n\t\tresult.error = attemptTimeout.message;\n\t\tresult.finalOutput = attemptTimeout.message;\n\t\tprogress.status = \"failed\";\n\t\tprogress.error = attemptTimeout.message;\n\t\tresult.progressSummary = {\n\t\t\ttoolCount: progress.toolCount,\n\t\t\ttokens: progress.tokens,\n\t\t\tdurationMs: progress.durationMs,\n\t\t};\n\t\treturn result;\n\t}\n\tconst spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };\n\tlet observedMutationAttempt = false;\n\n\tconst exitCode = await new Promise<number>((resolve) => {\n\t\tconst spawnSpec = getPiSpawnCommand(args);\n\t\tconst proc = spawn(spawnSpec.command, spawnSpec.args, {\n\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t\tenv: spawnEnv,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t});\n\t\tconst jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);\n\t\tlet spawned = typeof proc.pid === \"number\" && proc.pid > 0;\n\t\tproc.once(\"spawn\", () => { spawned = true; });\n\t\tlet processClosed = false;\n\t\tlet settled = false;\n\t\tlet detached = false;\n\t\tlet intercomStarted = false;\n\t\tlet assistantError: string | undefined;\n\t\tlet removeAbortListener: (() => void) | undefined;\n\t\tlet removeInterruptListener: (() => void) | undefined;\n\t\tlet activityTimer: NodeJS.Timeout | undefined;\n\t\tlet timeoutTimer: NodeJS.Timeout | undefined;\n\t\tlet timeoutTerminationTimer: NodeJS.Timeout | undefined;\n\t\tlet timeoutHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet turnBudgetSoftReached = false;\n\t\tlet turnBudgetTerminationTimer: NodeJS.Timeout | undefined;\n\t\tlet turnBudgetHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet protocolHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet cancellationHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet interruptTerminationTimer: NodeJS.Timeout | undefined;\n\t\tlet terminationCheckTimer: NodeJS.Timeout | undefined;\n\t\tlet terminationUnconfirmedReported = false;\n\t\tconst clearCancellationTimers = () => {\n\t\t\tif (terminationCheckTimer) clearTimeout(terminationCheckTimer);\n\t\t\tterminationCheckTimer = undefined;\n\t\t\tif (cancellationHardKillTimer) clearTimeout(cancellationHardKillTimer);\n\t\t\tif (interruptTerminationTimer) clearTimeout(interruptTerminationTimer);\n\t\t\tcancellationHardKillTimer = undefined;\n\t\t\tinterruptTerminationTimer = undefined;\n\t\t};\n\t\tconst recordProcessError = (error: unknown) => {\n\t\t\tif (!result.error) result.error = error instanceof Error ? error.message : String(error);\n\t\t\tprogress.error = result.error;\n\t\t\tif (!childExited && !processClosed && !settled && !detached) {\n\t\t\t\tprogress.status = \"running\";\n\t\t\t\tfireUpdate();\n\t\t\t}\n\t\t};\n\t\t// A delivered signal is not an exit acknowledgement. Keep this attempt owned\n\t\t// until close, including when the OS refuses every termination signal.\n\t\tconst signalChild = (signal: NodeJS.Signals): boolean => {\n\t\t\tif (childExited || processClosed || settled || detached) return false;\n\t\t\tlet sent = false;\n\t\t\ttry {\n\t\t\t\tsent = proc.kill(signal);\n\t\t\t} catch (error) {\n\t\t\t\trecordProcessError(error);\n\t\t\t}\n\t\t\tif (!sent && !childExited && !processClosed && !settled && !detached) {\n\t\t\t\trecordProcessError(`Subagent ${signal} could not be delivered; child exit is not confirmed.`);\n\t\t\t}\n\t\t\tif (signal === \"SIGKILL\" && !childExited && !processClosed && !settled && !detached && !terminationCheckTimer && !terminationUnconfirmedReported) {\n\t\t\t\tterminationCheckTimer = setTimeout(() => {\n\t\t\t\t\tterminationCheckTimer = undefined;\n\t\t\t\t\tif (childExited || processClosed || settled || detached) return;\n\t\t\t\t\tterminationUnconfirmedReported = true;\n\t\t\t\t\tconst message = `Subagent termination unconfirmed after SIGKILL${proc.pid ? ` (diagnostic PID ${proc.pid})` : \"\"}. This foreground call still owns the child and waits for its exit. Verify the child process before starting replacement work.`;\n\t\t\t\t\tprogress.status = \"running\";\n\t\t\t\t\tprogress.error = message;\n\t\t\t\t\tprogress.activityState = \"needs_attention\";\n\t\t\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\t\t\tappendRecentOutput(progress, [message]);\n\t\t\t\t\temitUpdateSnapshot(message);\n\t\t\t\t}, 2000);\n\t\t\t\tterminationCheckTimer.unref?.();\n\t\t\t}\n\t\t\treturn sent;\n\t\t};\n\t\tconst scheduleCancellationHardKill = (delayMs: number) => {\n\t\t\tif (cancellationHardKillTimer || childExited || processClosed || settled || detached) return;\n\t\t\tcancellationHardKillTimer = setTimeout(() => {\n\t\t\t\tcancellationHardKillTimer = undefined;\n\t\t\t\tif (!childExited && !processClosed && !settled && !detached) signalChild(\"SIGKILL\");\n\t\t\t}, delayMs);\n\t\t\tcancellationHardKillTimer.unref?.();\n\t\t};\n\t\tconst clearTurnBudgetTimers = () => {\n\t\t\tif (turnBudgetTerminationTimer) {\n\t\t\t\tclearTimeout(turnBudgetTerminationTimer);\n\t\t\t\tturnBudgetTerminationTimer = undefined;\n\t\t\t}\n\t\t\tif (turnBudgetHardKillTimer) {\n\t\t\t\tclearTimeout(turnBudgetHardKillTimer);\n\t\t\t\tturnBudgetHardKillTimer = undefined;\n\t\t\t}\n\t\t};\n\t\tconst clearTimeoutTimers = () => {\n\t\t\tif (timeoutTimer) {\n\t\t\t\tclearTimeout(timeoutTimer);\n\t\t\t\ttimeoutTimer = undefined;\n\t\t\t}\n\t\t\tif (timeoutTerminationTimer) {\n\t\t\t\tclearTimeout(timeoutTerminationTimer);\n\t\t\t\ttimeoutTerminationTimer = undefined;\n\t\t\t}\n\t\t\tif (timeoutHardKillTimer) {\n\t\t\t\tclearTimeout(timeoutHardKillTimer);\n\t\t\t\ttimeoutHardKillTimer = undefined;\n\t\t\t}\n\t\t};\n\n\t\tconst detachForIntercom = () => {\n\t\t\tdetached = true;\n\t\t\tprocessClosed = true;\n\t\t\tresult.detached = true;\n\t\t\tresult.detachedReason = \"intercom coordination\";\n\t\t\tprogress.status = \"detached\";\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tresult.progressSummary = {\n\t\t\t\ttoolCount: progress.toolCount,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t\tdurationMs: progress.durationMs,\n\t\t\t};\n\t\t\tfinish(-2);\n\t\t};\n\n\t\t// If the child emits a terminal assistant stop but never exits,\n\t\t// give it a short grace period to flush naturally, then clean it up.\n\t\tconst FINAL_STOP_GRACE_MS = 1000;\n\t\tconst HARD_KILL_MS = 3000;\n\t\tlet childExited = false;\n\t\tlet forcedTerminationSignal = false;\n\t\tlet cleanTerminalAssistantStopReceived = false;\n\t\tlet agentSettledReceived = false;\n\t\tlet finalDrainTimer: NodeJS.Timeout | undefined;\n\t\tlet finalHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet watchdogTailTimer: NodeJS.Timeout | undefined;\n\t\tlet childWatchdogState: ChildWatchdogStateSnapshot | undefined;\n\t\tconst updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {\n\t\t\tchildWatchdogState = snapshot;\n\t\t\tresult.watchdog = snapshot;\n\t\t\tprogress.watchdog = snapshot;\n\t\t};\n\t\tconst clearWatchdogTailTimer = () => {\n\t\t\tif (watchdogTailTimer) {\n\t\t\t\tclearTimeout(watchdogTailTimer);\n\t\t\t\twatchdogTailTimer = undefined;\n\t\t\t}\n\t\t};\n\t\tconst clearFinalDrainTimers = () => {\n\t\t\tif (finalDrainTimer) {\n\t\t\t\tclearTimeout(finalDrainTimer);\n\t\t\t\tfinalDrainTimer = undefined;\n\t\t\t}\n\t\t\tif (finalHardKillTimer) {\n\t\t\t\tclearTimeout(finalHardKillTimer);\n\t\t\t\tfinalHardKillTimer = undefined;\n\t\t\t}\n\t\t};\n\t\tconst startFinalDrain = () => {\n\t\t\tif (childWatchdogIsActive(childWatchdogState)) {\n\t\t\t\tarmWatchdogTail();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (childExited || finalDrainTimer || settled || processClosed || detached) return;\n\t\t\tfinalDrainTimer = setTimeout(() => {\n\t\t\t\tif (settled || processClosed || detached) return;\n\t\t\t\tconst termSent = signalChild(\"SIGTERM\");\n\t\t\t\tforcedTerminationSignal = termSent || forcedTerminationSignal;\n\t\t\t\tif (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {\n\t\t\t\t\tresult.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;\n\t\t\t\t}\n\t\t\t\tfinalHardKillTimer = setTimeout(() => {\n\t\t\t\t\tif (settled || processClosed || detached) return;\n\t\t\t\t\tforcedTerminationSignal = signalChild(\"SIGKILL\") || forcedTerminationSignal;\n\t\t\t\t}, HARD_KILL_MS);\n\t\t\t\tfinalHardKillTimer.unref?.();\n\t\t\t}, FINAL_STOP_GRACE_MS);\n\t\t\tfinalDrainTimer.unref?.();\n\t\t};\n\t\tfunction armWatchdogTail(): void {\n\t\t\tif ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || settled || processClosed || detached) return;\n\t\t\twatchdogTailTimer = setTimeout(() => {\n\t\t\t\twatchdogTailTimer = undefined;\n\t\t\t\tupdateChildWatchdogState({\n\t\t\t\t\tphase: \"stale\",\n\t\t\t\t\tseq: (childWatchdogState?.seq ?? 0) + 1,\n\t\t\t\t\tlastUpdate: Date.now(),\n\t\t\t\t\tfollowUpPending: false,\n\t\t\t\t\treason: \"child watchdog tail timeout\",\n\t\t\t\t\ttimedOut: true,\n\t\t\t\t});\n\t\t\t\tstartFinalDrain();\n\t\t\t\tfireUpdate();\n\t\t\t}, childWatchdog?.watchdogTailTimeoutMs ?? 120_000);\n\t\t\twatchdogTailTimer.unref?.();\n\t\t}\n\t\tconst applyChildLifecycle = (action: ChildLifecycleAction): void => {\n\t\t\tif (action === \"cancel-drain\") {\n\t\t\t\tclearFinalDrainTimers();\n\t\t\t\tclearWatchdogTailTimer();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (action === \"start-drain\") startFinalDrain();\n\t\t};\n\n\t\tconst unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {\n\t\t\tif (!options.allowIntercomDetach || detached || processClosed) return;\n\t\t\tif (!payload || typeof payload !== \"object\") return;\n\t\t\tconst event = payload as { requestId?: unknown; runId?: unknown; agent?: unknown; childIndex?: unknown };\n\t\t\tconst requestId = event.requestId;\n\t\t\tif (typeof requestId !== \"string\" || requestId.length === 0) return;\n\t\t\tconst hasRoute = event.runId !== undefined || event.agent !== undefined || event.childIndex !== undefined;\n\t\t\tif (hasRoute) {\n\t\t\t\tif (typeof event.runId === \"string\" && event.runId !== options.runId) return;\n\t\t\t\tif (typeof event.agent === \"string\" && event.agent !== spec.childId && event.agent !== spec.description) return;\n\t\t\t\tif (typeof event.childIndex === \"number\" && event.childIndex !== (options.index ?? 0)) return;\n\t\t\t} else if (!intercomStarted) return;\n\t\t\toptions.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true, runId: options.runId, agent: spec.childId, childIndex: options.index ?? 0 });\n\t\t\tdetachForIntercom();\n\t\t});\n\n\t\tconst finish = (code: number) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearCancellationTimers();\n\t\t\tclearFinalDrainTimers();\n\t\t\tclearWatchdogTailTimer();\n\t\t\tclearStdioGuard();\n\t\t\tclearTimeoutTimers();\n\t\t\tclearTurnBudgetTimers();\n\t\t\tif (protocolHardKillTimer) {\n\t\t\t\tclearTimeout(protocolHardKillTimer);\n\t\t\t\tprotocolHardKillTimer = undefined;\n\t\t\t}\n\t\t\tif (activityTimer) {\n\t\t\t\tclearInterval(activityTimer);\n\t\t\t\tactivityTimer = undefined;\n\t\t\t}\n\t\t\tunsubscribeIntercomDetach?.();\n\t\t\tremoveAbortListener?.();\n\t\t\tremoveInterruptListener?.();\n\t\t\tresolve(code);\n\t\t};\n\n\t\tconst drainPendingControlEvents = (): ControlEvent[] | undefined => {\n\t\t\tif (pendingControlEvents.length === 0) return undefined;\n\t\t\tconst events = pendingControlEvents;\n\t\t\tpendingControlEvents = [];\n\t\t\treturn events;\n\t\t};\n\n\t\tlet activeLongRunningNotified = false;\n\t\tlet pendingToolResult: { tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined;\n\t\tconst mutatingFailures = createMutatingFailureState();\n\t\tconst mutatingFailureWindowMs = 5 * 60_000;\n\t\tconst currentToolDurationMs = (now: number) => progress.currentToolStartedAt ? Math.max(0, now - progress.currentToolStartedAt) : undefined;\n\t\tconst emitNeedsAttention = (now: number, input: { message?: string; reason?: ControlEvent[\"reason\"]; recentFailureSummary?: string; currentTool?: string; currentPath?: string; currentToolDurationMs?: number } = {}): boolean => {\n\t\t\tif (!controlConfig.enabled) return false;\n\t\t\tconst previous = progress.activityState;\n\t\t\tprogress.activityState = \"needs_attention\";\n\t\t\tconst event = buildControlEvent({\n\t\t\t\ttype: \"needs_attention\",\n\t\t\t\tfrom: previous,\n\t\t\t\tto: \"needs_attention\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.description,\n\t\t\t\tindex: options.index,\n\t\t\t\tts: now,\n\t\t\t\tlastActivityAt: progress.lastActivityAt,\n\t\t\t\tmessage: input.message,\n\t\t\t\treason: input.reason ?? \"idle\",\n\t\t\t\tturns: result.usage.turns,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t\ttoolCount: progress.toolCount,\n\t\t\t\tcurrentTool: input.currentTool ?? progress.currentTool,\n\t\t\t\tcurrentToolDurationMs: input.currentToolDurationMs ?? currentToolDurationMs(now),\n\t\t\t\tcurrentPath: input.currentPath ?? progress.currentPath,\n\t\t\t\trecentFailureSummary: input.recentFailureSummary,\n\t\t\t});\n\t\t\temitControlEvent(event);\n\t\t\treturn previous !== \"needs_attention\";\n\t\t};\n\t\tconst emitActiveLongRunning = (now: number, reason: ControlEvent[\"reason\"]): boolean => {\n\t\t\tif (!controlConfig.enabled || activeLongRunningNotified || progress.activityState === \"needs_attention\") return false;\n\t\t\tactiveLongRunningNotified = true;\n\t\t\tconst previous = progress.activityState;\n\t\t\tprogress.activityState = \"active_long_running\";\n\t\t\temitControlEvent(buildControlEvent({\n\t\t\t\ttype: \"active_long_running\",\n\t\t\t\tfrom: previous,\n\t\t\t\tto: \"active_long_running\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.description,\n\t\t\t\tindex: options.index,\n\t\t\t\tts: now,\n\t\t\t\tmessage: `${spec.description} is still active but long-running`,\n\t\t\t\treason,\n\t\t\t\tturns: result.usage.turns,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t\ttoolCount: progress.toolCount,\n\t\t\t\tcurrentTool: progress.currentTool,\n\t\t\t\tcurrentToolDurationMs: currentToolDurationMs(now),\n\t\t\t\tcurrentPath: progress.currentPath,\n\t\t\t\telapsedMs: now - startTime,\n\t\t\t}));\n\t\t\treturn true;\n\t\t};\n\t\tconst requestTurnBudgetAbort = (turnCount: number) => {\n\t\t\tconst budget = options.turnBudget;\n\t\t\tif (!budget || result.timedOut || result.turnBudgetExceeded || interruptedByControl || processClosed || settled || detached) return;\n\t\t\tconst message = turnBudgetExceededMessage(budget, turnCount);\n\t\t\tresult.turnBudgetExceeded = true;\n\t\t\tresult.wrapUpRequested = true;\n\t\t\tresult.turnBudget = turnBudgetState(budget, turnCount, true);\n\t\t\tresult.error = message;\n\t\t\tresult.finalOutput = message;\n\t\t\tprogress.status = \"failed\";\n\t\t\tprogress.error = message;\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tfireUpdate();\n\t\t\tsignalChild(\"SIGINT\");\n\t\t\tturnBudgetTerminationTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || result.timedOut) return;\n\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t}, 1000);\n\t\t\tturnBudgetTerminationTimer.unref?.();\n\t\t\tturnBudgetHardKillTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || result.timedOut) return;\n\t\t\t\tsignalChild(\"SIGKILL\");\n\t\t\t}, 4000);\n\t\t\tturnBudgetHardKillTimer.unref?.();\n\t\t};\n\n\t\tconst updateTurnBudget = (turnCount: number, terminalAssistantStop: boolean) => {\n\t\t\tconst budget = options.turnBudget;\n\t\t\tif (!budget || result.timedOut || result.turnBudgetExceeded) return;\n\t\t\tif (turnCount < budget.maxTurns) {\n\t\t\t\tresult.turnBudget = { ...budget, outcome: \"within-budget\", turnCount };\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!turnBudgetSoftReached) {\n\t\t\t\tturnBudgetSoftReached = true;\n\t\t\t\tresult.wrapUpRequested = true;\n\t\t\t\tappendRecentOutput(progress, [turnBudgetSoftNote(budget, turnCount)]);\n\t\t\t}\n\t\t\tresult.turnBudget = turnBudgetState(budget, turnCount, false);\n\t\t\tif (shouldAbortForTurnBudget(budget, turnCount, terminalAssistantStop)) {\n\t\t\t\trequestTurnBudgetAbort(turnCount);\n\t\t\t}\n\t\t};\n\n\t\tconst updateActivityState = (now: number): boolean => {\n\t\t\tif (!controlConfig.enabled) return false;\n\t\t\tconst idleState = deriveActivityState({\n\t\t\t\tconfig: controlConfig,\n\t\t\t\tstartedAt: startTime,\n\t\t\t\tlastActivityAt: progress.lastActivityAt,\n\t\t\t\tnow,\n\t\t\t});\n\t\t\tif (idleState === \"needs_attention\") {\n\t\t\t\treturn progress.activityState === \"needs_attention\" ? false : emitNeedsAttention(now);\n\t\t\t}\n\t\t\tconst activeReason = nextLongRunningTrigger(controlConfig, {\n\t\t\t\tstartedAt: startTime,\n\t\t\t\tnow,\n\t\t\t\tturns: result.usage.turns,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t});\n\t\t\treturn activeReason ? emitActiveLongRunning(now, activeReason) : false;\n\t\t};\n\n\n\t\tconst emitUpdateSnapshot = (text: string) => {\n\t\t\tif (!options.onUpdate || processClosed) return;\n\t\t\tconst progressSnapshot = snapshotProgress(progress);\n\t\t\tconst resultSnapshot = snapshotResult(result, progressSnapshot);\n\t\t\tconst controlEvents = drainPendingControlEvents();\n\t\t\toptions.onUpdate({\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: {\n\t\t\t\t\tmode: \"single\",\n\t\t\t\t\tresults: [resultSnapshot],\n\t\t\t\t\tprogress: [progressSnapshot],\n\t\t\t\t\tcontrolEvents,\n\t\t\t\t},\n\t\t\t});\n\t\t};\n\n\t\tconst fireUpdate = () => {\n\t\t\tif (!options.onUpdate || processClosed) return;\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tconst output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);\n\t\t\temitUpdateSnapshot(output || \"(running...)\");\n\t\t};\n\n\t\tconst decodeChildEvent = createChildEventDecoder();\n\t\tconst processLine = (line: string) => {\n\t\t\tif (!line.trim()) return;\n\t\t\tlet evt: { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };\n\t\t\tlet assembled: string | undefined;\n\t\t\ttry {\n\t\t\t\tassembled = decodeChildEvent(line);\n\t\t\t} catch {\n\t\t\t\tjsonlWriter.writeLine(line);\n\t\t\t\tshared.transcriptWriter?.writeStdoutLine(line);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (assembled === undefined) return;\n\t\t\tline = assembled;\n\t\t\tjsonlWriter.writeLine(line);\n\t\t\ttry {\n\t\t\t\tevt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };\n\t\t\t} catch {\n\t\t\t\tshared.transcriptWriter?.writeStdoutLine(line);\n\t\t\t\t// Non-JSON stdout lines are expected; only structured events are parsed.\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tshared.transcriptWriter?.writeChildEvent(evt);\n\t\t\tif (evt.type === \"agent_settled\") agentSettledReceived = true;\n\t\t\tapplyChildLifecycle(projectChildLifecycle(evt));\n\n\t\t\tif (isChildWatchdogStatusEvent(evt)) {\n\t\t\t\tif (!childWatchdog) return;\n\t\t\t\tconst next = acceptChildWatchdogEvent({\n\t\t\t\t\tcurrent: childWatchdogState,\n\t\t\t\t\tevent: evt,\n\t\t\t\t\trunId: options.runId,\n\t\t\t\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\t\t\t\tchildIndex: options.index ?? 0,\n\t\t\t\t});\n\t\t\t\tif (!next) return;\n\t\t\t\tupdateChildWatchdogState(next);\n\t\t\t\tif (childWatchdogIsActive(next)) {\n\t\t\t\t\tclearFinalDrainTimers();\n\t\t\t\t\tarmWatchdogTail();\n\t\t\t\t} else {\n\t\t\t\t\tclearWatchdogTailTimer();\n\t\t\t\t\tif (cleanTerminalAssistantStopReceived || agentSettledReceived) startFinalDrain();\n\t\t\t\t}\n\t\t\t\tfireUpdate();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst now = Date.now();\n\t\t\tprogress.durationMs = now - startTime;\n\t\t\tprogress.lastActivityAt = now;\n\t\t\tupdateActivityState(now);\n\n\t\t\tif ((evt.type === \"message_update\" || evt.type === \"message_start\") && evt.message) {\n\t\t\t\tconst thinkingText = extractThinkingText(evt.message.content);\n\t\t\t\tif (thinkingText) progress.thinkingText = thinkingText;\n\t\t\t\t// lunr: compact rows no longer show thinking. Skip per-token\n\t\t\t\t// invalidates on message_update so the running glyph is not rebuilt\n\t\t\t\t// on every stream delta.\n\t\t\t\tif (evt.type === \"message_start\") fireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_execution_start\") {\n\t\t\t\tconst toolArgs = evt.args && typeof evt.args === \"object\" && !Array.isArray(evt.args)\n\t\t\t\t\t? evt.args as Record<string, unknown>\n\t\t\t\t\t: {};\n\t\t\t\tif (options.allowIntercomDetach && (evt.toolName === \"intercom\" || evt.toolName === \"contact_supervisor\")) {\n\t\t\t\t\tintercomStarted = true;\n\t\t\t\t}\n\t\t\t\tprogress.toolCount++;\n\t\t\t\tif (options.toolBudget) {\n\t\t\t\t\tresult.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);\n\t\t\t\t}\n\t\t\t\tprogress.currentTool = evt.toolName;\n\t\t\t\tprogress.currentToolArgs = extractToolArgsPreview(toolArgs);\n\t\t\t\tprogress.currentToolStartedAt = now;\n\t\t\t\tprogress.currentPath = resolveCurrentPath(evt.toolName, toolArgs);\n\t\t\t\tconst mutates = isMutatingTool(evt.toolName, toolArgs);\n\t\t\t\tobservedMutationAttempt = observedMutationAttempt || mutates;\n\t\t\t\tpendingToolResult = { tool: evt.toolName ?? \"tool\", path: progress.currentPath, mutates, startedAt: now };\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_execution_end\") {\n\t\t\t\tif (progress.currentTool) {\n\t\t\t\t\tprogress.recentTools.push({\n\t\t\t\t\t\ttool: progress.currentTool,\n\t\t\t\t\t\targs: progress.currentToolArgs || \"\",\n\t\t\t\t\t\tendMs: now,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tprogress.currentTool = undefined;\n\t\t\t\tprogress.currentToolArgs = undefined;\n\t\t\t\tprogress.currentToolStartedAt = undefined;\n\t\t\t\tprogress.currentPath = undefined;\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"message_end\" && evt.message) {\n\t\t\t\tresult.messages.push(evt.message);\n\t\t\t\tif (evt.message.role === \"assistant\") {\n\t\t\t\t\tresult.usage.turns++;\n\t\t\t\t\tprogress.turnCount = result.usage.turns;\n\t\t\t\t\tconst stopReason = (evt.message as { stopReason?: string }).stopReason;\n\t\t\t\t\tconst hasToolCall = Array.isArray(evt.message.content)\n\t\t\t\t\t\t&& evt.message.content.some((part) => (part as { type?: string }).type === \"toolCall\");\n\t\t\t\t\tconst terminalAssistantStop = stopReason === \"stop\" && !hasToolCall;\n\t\t\t\t\tupdateTurnBudget(result.usage.turns, terminalAssistantStop);\n\t\t\t\t\tconst u = evt.message.usage;\n\t\t\t\t\tif (u) {\n\t\t\t\t\t\tresult.usage.input += u.input || 0;\n\t\t\t\t\t\tresult.usage.output += u.output || 0;\n\t\t\t\t\t\tresult.usage.cacheRead += u.cacheRead || 0;\n\t\t\t\t\t\tresult.usage.cacheWrite += u.cacheWrite || 0;\n\t\t\t\t\t\tresult.usage.cost += u.cost?.total || 0;\n\t\t\t\t\t\tprogress.tokens = result.usage.input + result.usage.output;\n\t\t\t\t\t}\n\t\t\t\t\tif (!result.model && evt.message.model) {\n\t\t\t\t\t\tresult.model = evt.message.model;\n\t\t\t\t\t\tconst parsed = splitKnownThinkingSuffix(evt.message.model);\n\t\t\t\t\t\tif (parsed.thinkingSuffix) result.thinking = parsed.thinkingSuffix.slice(1);\n\t\t\t\t\t\tprogress.model = result.model;\n\t\t\t\t\t\tprogress.thinking = result.thinking;\n\t\t\t\t\t}\n\t\t\t\t\tif (evt.message.errorMessage) assistantError = evt.message.errorMessage;\n\t\t\t\t\tconst assistantText = extractTextFromContent(evt.message.content);\n\t\t\t\t\tappendRecentOutput(progress, assistantText.split(\"\\n\").slice(-10));\n\t\t\t\t\t// Final assistant message: start the exit drain window.\n\t\t\t\t\tif (terminalAssistantStop) {\n\t\t\t\t\t\tif (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;\n\t\t\t\t\t\tcleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;\n\t\t\t\t\t\tapplyChildLifecycle(projectChildLifecycle(evt, true));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tupdateActivityState(now);\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_result_end\" && evt.message) {\n\t\t\t\tresult.messages.push(evt.message);\n\t\t\t\tconst resultText = extractTextFromContent(evt.message.content);\n\t\t\t\tif (options.toolBudget && pendingToolResult && resultText.includes(\"Tool budget hard limit reached\")) {\n\t\t\t\t\tresult.toolBudgetBlocked = true;\n\t\t\t\t\tresult.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount, pendingToolResult.tool);\n\t\t\t\t}\n\t\t\t\tappendRecentOutput(progress, resultText.split(\"\\n\").slice(-10));\n\t\t\t\tconst toolSnapshot = pendingToolResult;\n\t\t\t\tpendingToolResult = undefined;\n\t\t\t\tif (toolSnapshot?.mutates && didMutatingToolFail(resultText)) {\n\t\t\t\t\trecordMutatingFailure(mutatingFailures, {\n\t\t\t\t\t\ttool: toolSnapshot.tool,\n\t\t\t\t\t\tpath: toolSnapshot.path,\n\t\t\t\t\t\terror: resultText.split(\"\\n\").find((line) => line.trim())?.trim().slice(0, 180) ?? \"mutating tool failed\",\n\t\t\t\t\t\tts: now,\n\t\t\t\t\t}, mutatingFailureWindowMs);\n\t\t\t\t\tif (shouldEscalateMutatingFailures(mutatingFailures, controlConfig.failedToolAttemptsBeforeAttention)) {\n\t\t\t\t\t\temitNeedsAttention(now, {\n\t\t\t\t\t\t\tmessage: `${spec.description} needs attention after repeated mutating tool failures`,\n\t\t\t\t\t\t\treason: \"tool_failures\",\n\t\t\t\t\t\t\tcurrentTool: toolSnapshot.tool,\n\t\t\t\t\t\t\tcurrentPath: toolSnapshot.path,\n\t\t\t\t\t\t\tcurrentToolDurationMs: toolSnapshot.startedAt ? Math.max(0, now - toolSnapshot.startedAt) : undefined,\n\t\t\t\t\t\t\trecentFailureSummary: summarizeRecentMutatingFailures(mutatingFailures),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if (toolSnapshot?.mutates) {\n\t\t\t\t\tresetMutatingFailureState(mutatingFailures);\n\t\t\t\t}\n\t\t\t\tfireUpdate();\n\t\t\t}\n\t\t};\n\n\t\tif (controlConfig.enabled) {\n\t\t\tactivityTimer = setInterval(() => {\n\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\tconst now = Date.now();\n\t\t\t\tif (updateActivityState(now)) {\n\t\t\t\t\tprogress.durationMs = now - startTime;\n\t\t\t\t\tfireUpdate();\n\t\t\t\t}\n\t\t\t}, 1000);\n\t\t\tactivityTimer.unref?.();\n\t\t}\n\n\t\tif (attemptTimeout) {\n\t\t\ttimeoutTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || interruptedByControl) return;\n\t\t\t\tresult.timedOut = true;\n\t\t\t\tresult.error = attemptTimeout.message;\n\t\t\t\tresult.finalOutput = attemptTimeout.message;\n\t\t\t\tprogress.status = \"failed\";\n\t\t\t\tprogress.error = attemptTimeout.message;\n\t\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\t\tfireUpdate();\n\t\t\t\tsignalChild(\"SIGINT\");\n\t\t\t\ttimeoutTerminationTimer = setTimeout(() => {\n\t\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t\t}, 1000);\n\t\t\t\ttimeoutTerminationTimer.unref?.();\n\t\t\t\ttimeoutHardKillTimer = setTimeout(() => {\n\t\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\t\tsignalChild(\"SIGKILL\");\n\t\t\t\t}, 4000);\n\t\t\t\ttimeoutHardKillTimer.unref?.();\n\t\t\t}, attemptTimeout.remainingMs);\n\t\t\ttimeoutTimer.unref?.();\n\t\t}\n\n\t\tconst stderrTail = createBoundedByteTail();\n\t\tconst failProtocol = (limit: ProtocolOutputLimit): void => {\n\t\t\tif (result.protocolError) return;\n\t\t\tresult.protocolError = limit;\n\t\t\tresult.error = formatProtocolOutputLimit(limit);\n\t\t\tprogress.status = \"failed\";\n\t\t\tprogress.error = result.error;\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tfireUpdate();\n\t\t\tif (!childExited) {\n\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t\tprotocolHardKillTimer = setTimeout(() => {\n\t\t\t\t\tif (!childExited) signalChild(\"SIGKILL\");\n\t\t\t\t}, 3000);\n\t\t\t\tprotocolHardKillTimer.unref?.();\n\t\t\t}\n\t\t};\n\t\tconst stdoutReader = createBoundedLineReader({ onLine: processLine, onLimit: failProtocol });\n\t\tconst stderrReader = createBoundedLineReader({\n\t\t\tstream: \"stderr\",\n\t\t\tmaxPendingLineBytes: MAX_CHILD_STDERR_BYTES,\n\t\t\tonLine: (line) => shared.transcriptWriter?.writeStderrLine(line),\n\t\t\tonLimit: (limit) => shared.transcriptWriter?.writeStderrLine(formatProtocolOutputLimit(limit)),\n\t\t});\n\n\t\tconst clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });\n\t\tproc.stdout.on(\"data\", (chunk: Buffer) => stdoutReader.push(chunk));\n\t\tproc.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tstderrTail.push(chunk);\n\t\t\tstderrReader.push(chunk);\n\t\t});\n\t\tconst confirmChildExit = () => {\n\t\t\tchildExited = true;\n\t\t\tif (terminationUnconfirmedReported) {\n\t\t\t\tprogress.error = result.error;\n\t\t\t\tprogress.activityState = undefined;\n\t\t\t}\n\t\t\tclearCancellationTimers();\n\t\t\tclearFinalDrainTimers();\n\t\t};\n\t\tproc.on(\"exit\", confirmChildExit);\n\t\tproc.on(\"close\", (code, signal) => {\n\t\t\tconfirmChildExit();\n\t\t\tclearFinalDrainTimers();\n\t\t\tclearStdioGuard();\n\t\t\tvoid jsonlWriter.close().catch(() => {\n\t\t\t\t// JSONL artifact flush is best effort.\n\t\t\t});\n\t\t\tconst toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);\n\t\t\tcleanupTempDir(tempDir);\n\t\t\tstdoutReader.end();\n\t\t\tstderrReader.end();\n\t\t\tconst stderr = stderrTail.text();\n\t\t\tlet closeError = result.error ?? toolDiagnosticError ?? assistantError;\n\t\t\tconst forcedDrainAfterFinalSuccess = forcedTerminationSignal && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;\n\t\t\tif (code !== 0 && stderr.trim() && !closeError && !forcedDrainAfterFinalSuccess) {\n\t\t\t\tcloseError = stderr.trim();\n\t\t\t}\n\t\t\tconst finalCode = forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (code ?? 1) : (code ?? 0);\n\t\t\tif (detached) {\n\t\t\t\tconst recoveredProgress = snapshotProgress(progress);\n\t\t\t\tconst recoveredResult = snapshotResult(result, recoveredProgress);\n\t\t\t\tif (!recoveredResult.error && closeError) recoveredResult.error = closeError;\n\t\t\t\trecoveredResult.exitCode = recoveredResult.error && finalCode === 0 ? 1 : finalCode;\n\t\t\t\trecoveredProgress.status = recoveredResult.exitCode === 0 ? \"completed\" : \"failed\";\n\t\t\t\trecoveredProgress.durationMs = Date.now() - startTime;\n\t\t\t\tif (recoveredResult.error) recoveredProgress.error = recoveredResult.error;\n\t\t\t\trecoveredResult.progressSummary = {\n\t\t\t\t\ttoolCount: recoveredProgress.toolCount,\n\t\t\t\t\ttokens: recoveredProgress.tokens,\n\t\t\t\t\tdurationMs: recoveredProgress.durationMs,\n\t\t\t\t};\n\t\t\t\tconst acceptanceOutput = getFinalOutput(recoveredResult.messages ?? []).trim()\n\t\t\t\t\t|| recoveredResult.error\n\t\t\t\t\t|| recoveredResult.finalOutput\n\t\t\t\t\t|| \"Detached child exited without final output.\";\n\t\t\t\tacceptanceOutputByResult.set(recoveredResult, acceptanceOutput);\n\t\t\t\tlet fullOutput = stripAcceptanceReport(acceptanceOutput);\n\t\t\t\tfullOutput = fullOutput.trim() || recoveredResult.error || recoveredResult.finalOutput || \"Detached child exited without final output.\";\n\t\t\t\trecoveredResult.outputMode = options.outputMode ?? \"inline\";\n\t\t\t\tif (options.outputPath && recoveredResult.exitCode === 0) {\n\t\t\t\t\tconst resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);\n\t\t\t\t\tfullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);\n\t\t\t\t\trecoveredResult.savedOutputPath = resolvedOutput.savedPath;\n\t\t\t\t\trecoveredResult.outputSaveError = resolvedOutput.saveError;\n\t\t\t\t\tif (resolvedOutput.savedPath) {\n\t\t\t\t\t\trecoveredResult.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);\n\t\t\t\t\t} else {\n\t\t\t\t\t\trecoveredResult.exitCode = 1;\n\t\t\t\t\t\trecoveredResult.error = `Output file was not finalized after detached child exit: ${resolvedOutput.saveError ?? options.outputPath}`;\n\t\t\t\t\t\trecoveredProgress.status = \"failed\";\n\t\t\t\t\t\trecoveredProgress.error = recoveredResult.error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trecoveredResult.finalOutput = options.outputMode === \"file-only\" && recoveredResult.savedOutputPath && recoveredResult.outputReference\n\t\t\t\t\t? recoveredResult.outputReference.message\n\t\t\t\t\t: fullOutput;\n\t\t\t\tif (recoveredResult.artifactPaths && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\twriteArtifact(recoveredResult.artifactPaths.outputPath, fullOutput);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Detached children may outlive test/temp cleanup; recovered status is best-effort.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\toptions.onDetachedExit?.(recoveredResult);\n\t\t\t\tfinish(-2);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!result.error && closeError) result.error = closeError;\n\t\t\tprocessClosed = true;\n\t\t\tfinish(finalCode);\n\t\t});\n\t\tproc.on(\"error\", (error) => {\n\t\t\t// kill/send errors after spawn do not prove exit and must not discard escalation.\n\t\t\tif (spawned) {\n\t\t\t\trecordProcessError(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tclearFinalDrainTimers();\n\t\t\tclearStdioGuard();\n\t\t\tvoid jsonlWriter.close().catch(() => {\n\t\t\t\t// JSONL artifact flush is best effort.\n\t\t\t});\n\t\t\tcleanupTempDir(tempDir);\n\t\t\tstdoutReader.end();\n\t\t\tstderrReader.end();\n\t\t\tif (!result.error) {\n\t\t\t\tresult.error = error instanceof Error ? error.message : String(error);\n\t\t\t}\n\t\t\tfinish(1);\n\t\t});\n\n\t\tif (options.signal) {\n\t\t\tconst kill = () => {\n\t\t\t\tif (childExited || processClosed || settled || detached) return;\n\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t\tscheduleCancellationHardKill(3000);\n\t\t\t};\n\t\t\tif (options.signal.aborted) kill();\n\t\t\telse {\n\t\t\t\toptions.signal.addEventListener(\"abort\", kill, { once: true });\n\t\t\t\tremoveAbortListener = () => options.signal?.removeEventListener(\"abort\", kill);\n\t\t\t}\n\t\t}\n\n\t\tif (options.interruptSignal) {\n\t\t\tconst interrupt = () => {\n\t\t\t\tif (processClosed || detached || settled) return;\n\t\t\t\tif (result.timedOut) return;\n\t\t\t\tinterruptedByControl = true;\n\t\t\t\tclearTimeoutTimers();\n\t\t\t\tprogress.status = \"running\";\n\t\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\t\tresult.interrupted = true;\n\t\t\t\tresult.finalOutput = \"Interrupted. Waiting for explicit next action.\";\n\t\t\t\tprogress.activityState = undefined;\n\t\t\t\tfireUpdate();\n\t\t\t\tsignalChild(\"SIGINT\");\n\t\t\t\tinterruptTerminationTimer = setTimeout(() => {\n\t\t\t\t\tif (childExited || settled || processClosed || detached) return;\n\t\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t\t}, 1000);\n\t\t\t\tinterruptTerminationTimer.unref?.();\n\t\t\t\tscheduleCancellationHardKill(4000);\n\t\t\t};\n\t\t\tif (options.interruptSignal.aborted) interrupt();\n\t\t\telse {\n\t\t\t\toptions.interruptSignal.addEventListener(\"abort\", interrupt, { once: true });\n\t\t\t\tremoveInterruptListener = () => options.interruptSignal?.removeEventListener(\"abort\", interrupt);\n\t\t\t}\n\t\t}\n\t});\n\tresult.exitCode = exitCode;\n\tif (interruptedByControl) {\n\t\tresult.exitCode = 0;\n\t\tresult.interrupted = true;\n\t\tresult.error = undefined;\n\t\tresult.finalOutput = result.finalOutput || \"Interrupted. Waiting for explicit next action.\";\n\t\tresult.controlEvents = allControlEvents.length ? allControlEvents : undefined;\n\t\tprogress.activityState = undefined;\n\t\tprogress.durationMs = Date.now() - startTime;\n\t\tresult.progressSummary = {\n\t\t\ttoolCount: progress.toolCount,\n\t\t\ttokens: progress.tokens,\n\t\t\tdurationMs: progress.durationMs,\n\t\t};\n\t\treturn result;\n\t}\n\tif (result.detached) {\n\t\tresult.exitCode = -2;\n\t\tresult.finalOutput = \"Detached for intercom coordination before task completion.\";\n\t\tresult.outputMode = options.outputMode ?? \"inline\";\n\t\tif (options.outputPath) {\n\t\t\tresult.outputSaveError = \"Output file was not finalized because the subagent detached for intercom coordination.\";\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (result.error && result.exitCode === 0) {\n\t\tresult.exitCode = 1;\n\t}\n\tif (result.exitCode === 0 && !result.error) {\n\t\tconst errInfo = detectSubagentError(result.messages);\n\t\tif (errInfo.hasError) {\n\t\t\tresult.exitCode = errInfo.exitCode ?? 1;\n\t\t\tresult.error = errInfo.details\n\t\t\t\t? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`\n\t\t\t\t: `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;\n\t\t}\n\t}\n\tif (result.exitCode === 0 && !result.error) {\n\t\tconst finalText = getFinalOutput(result.messages);\n\t\tconst missingStructuredOutput = options.structuredOutput\n\t\t\t? !existsSync(options.structuredOutput.outputPath)\n\t\t\t: false;\n\t\tif (!finalText?.trim() && (!options.structuredOutput || missingStructuredOutput)) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.error = \"Subagent produced no output (possible model cold-start or empty response).\";\n\t\t}\n\t}\n\tif (options.structuredOutput && result.exitCode === 0 && !result.error) {\n\t\tconst structured = readStructuredOutput({\n\t\t\tschema: options.structuredOutput.schema,\n\t\t\tschemaPath: options.structuredOutput.schemaPath,\n\t\t\toutputPath: options.structuredOutput.outputPath,\n\t\t});\n\t\tresult.structuredOutputSchemaPath = options.structuredOutput.schemaPath;\n\t\tresult.structuredOutputPath = options.structuredOutput.outputPath;\n\t\tif (structured.error) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.error = structured.error;\n\t\t} else {\n\t\t\tresult.structuredOutput = structured.value;\n\t\t}\n\t}\n\n\tprogress.status = result.exitCode === 0 ? \"completed\" : \"failed\";\n\tprogress.durationMs = Date.now() - startTime;\n\tif (result.error) {\n\t\tprogress.error = result.error;\n\t\tif (progress.currentTool) {\n\t\t\tprogress.failedTool = progress.currentTool;\n\t\t}\n\t}\n\n\tresult.progressSummary = {\n\t\ttoolCount: progress.toolCount,\n\t\ttokens: progress.tokens,\n\t\tdurationMs: progress.durationMs,\n\t};\n\n\tconst acceptanceOutput = getFinalOutput(result.messages);\n\tlet fullOutput = stripAcceptanceReport(acceptanceOutput);\n\tif (result.timedOut) {\n\t\tconst timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);\n\t\tfullOutput = fullOutput.trim()\n\t\t\t? `${timeoutMessage}\\n\\nPartial output before timeout:\\n${fullOutput}`\n\t\t\t: timeoutMessage;\n\t} else if (result.turnBudgetExceeded && result.turnBudget) {\n\t\tfullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);\n\t} else if (result.wrapUpRequested && result.turnBudget?.outcome === \"wrap-up-requested\") {\n\t\tconst note = turnBudgetSoftNote(result.turnBudget, result.turnBudget.wrapUpRequestedAtTurn ?? result.turnBudget.turnCount);\n\t\tfullOutput = fullOutput.trim() ? `${note}\\n\\n${fullOutput}` : note;\n\t}\n\tconst completionGuard = result.exitCode === 0 && !result.error\n\t\t? evaluateCompletionMutationGuard({\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\ttask: shared.originalTask ?? task,\n\t\t\tmessages: result.messages,\n\t\t\ttools: spec.effectivePermissions === \"read-only\" ? [\"read\", \"grep\", \"find\", \"ls\", \"bash\"] : undefined,\n\t\t})\n\t\t: undefined;\n\tif (completionGuard?.triggered && !observedMutationAttempt) {\n\t\tresult.exitCode = 1;\n\t\tresult.error = \"Subagent completed without making edits for an implementation task.\\nIt appears to have returned planning or scratchpad output instead of applying changes.\";\n\t\tprogress.status = \"failed\";\n\t\tprogress.error = result.error;\n\t\temitControlEvent(buildControlEvent({\n\t\t\tfrom: progress.activityState,\n\t\t\tto: \"needs_attention\",\n\t\t\trunId: options.runId ?? spec.childId,\n\t\t\tagent: spec.description,\n\t\t\tindex: options.index,\n\t\t\tts: Date.now(),\n\t\t\tmessage: `${spec.description} completed without making edits for an implementation task`,\n\t\t\treason: \"completion_guard\",\n\t\t}));\n\t}\n\t\tif (options.outputPath && result.exitCode === 0) {\n\t\t\tconst resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);\n\t\t\tfullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);\n\t\t\tresult.savedOutputPath = resolvedOutput.savedPath;\n\t\t\tresult.outputSaveError = resolvedOutput.saveError;\n\t\t\tif (resolvedOutput.savedPath) {\n\t\t\t\tresult.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);\n\t\t\t}\n\t}\n\t\tartifactOutputByResult.set(result, fullOutput);\n\t\tacceptanceOutputByResult.set(result, acceptanceOutput);\n\tresult.outputMode = options.outputMode ?? \"inline\";\n\tresult.finalOutput = options.outputMode === \"file-only\" && result.savedOutputPath && result.outputReference\n\t\t? result.outputReference.message\n\t\t: fullOutput;\n\tresult.controlEvents = allControlEvents.length ? allControlEvents : undefined;\n\tif (options.onUpdate) {\n\t\tconst finalText = result.finalOutput || result.error || \"(no output)\";\n\t\tconst progressSnapshot = snapshotProgress(progress);\n\t\tconst resultSnapshot = snapshotResult(result, progressSnapshot);\n\t\toptions.onUpdate({\n\t\t\tcontent: [{ type: \"text\", text: finalText }],\n\t\t\tdetails: {\n\t\t\t\tmode: \"single\",\n\t\t\t\tresults: [resultSnapshot],\n\t\t\t\tprogress: [progressSnapshot],\n\t\t\t\tcontrolEvents: allControlEvents.length ? allControlEvents : undefined,\n\t\t\t},\n\t\t});\n\t}\n\treturn result;\n}\n\n/**\n * Run a subagent synchronously (blocking until complete)\n */\nexport async function runSync(\n\truntimeCwd: string,\n\tspec: ChildSpec,\n\toptions: RunSyncOptions,\n): Promise<SingleResult> {\n\tconst task = spec.task;\n\tconst label = childRowLabel(spec);\n\tconst parentMode = snapshotParentPermissionMode(options.parentSessionId);\n\tconst resolvedPermissions = resolveChildPermissions(parentMode, spec.requestedPermissions);\n\tif (!resolvedPermissions.ok) {\n\t\tconst error = resolvedPermissions.error || READ_ONLY_WRITE_SPAWN_ERROR;\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\terror,\n\t\t};\n\t}\n\tconst outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${label})`);\n\tif (outputModeValidationError) {\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\toutputMode: options.outputMode,\n\t\t\terror: outputModeValidationError,\n\t\t};\n\t}\n\n\tconst shareEnabled = options.share === true;\n\tconst effectiveAcceptance = resolveEffectiveAcceptance({\n\t\texplicit: options.acceptance ?? spec.acceptance,\n\t\tpermissions: spec.effectivePermissions,\n\t\ttask,\n\t\tmode: options.acceptanceContext?.mode ?? \"single\",\n\t\tasync: options.acceptanceContext?.async,\n\t\tdynamic: options.acceptanceContext?.dynamic,\n\t\tdynamicGroup: options.acceptanceContext?.dynamicGroup,\n\t});\n\tconst acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);\n\tconst taskWithAcceptance = acceptancePrompt ? `${task}\\n${acceptancePrompt}` : task;\n\tconst sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;\n\tconst skillNames = options.skills ?? (spec.skill === false ? [] : (normalizeSkillInput(spec.skill) || []));\n\tconst skillCwd = options.cwd ?? spec.cwd ?? runtimeCwd;\n\tconst { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(\n\t\tskillNames,\n\t\tskillCwd,\n\t\truntimeCwd,\n\t\tundefined,\n\t\tskillCwd,\n\t);\n\tif (skillNames.some((skill) => skill.trim() === \"pi-subagents\") && missingSkills.includes(\"pi-subagents\")) {\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\terror: \"Skills not found: pi-subagents\",\n\t\t};\n\t}\n\tlet systemPrompt = \"\";\n\tif (resolvedSkills.length > 0) {\n\t\tsystemPrompt = buildSkillInjection(resolvedSkills);\n\t}\n\tsystemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, {\n\t\ttools: spec.effectivePermissions === \"read-only\" ? [\"read\", \"grep\", \"find\", \"ls\", \"bash\"] : undefined,\n\t});\n\n\tconst candidates = buildModelCandidates(\n\t\toptions.modelOverride ?? spec.model,\n\t\tundefined,\n\t\toptions.availableModels,\n\t\toptions.preferredModelProvider,\n\t\t{ scope: options.modelScope },\n\t);\n\tconst attemptedModels: string[] = [];\n\tconst modelAttempts: ModelAttempt[] = [];\n\tconst aggregateUsage = emptyUsage();\n\tconst attemptNotes: string[] = [];\n\tlet totalToolCount = 0;\n\tlet totalDurationMs = 0;\n\n\tlet artifactPathsResult: ArtifactPaths | undefined;\n\tlet jsonlPath: string | undefined;\n\tlet transcriptWriter: ChildTranscriptWriter | undefined;\n\tif (options.artifactsDir && options.artifactConfig?.enabled !== false) {\n\t\tartifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, spec.childId, options.index);\n\t\tensureArtifactsDir(options.artifactsDir);\n\t\tif (options.artifactConfig?.includeInput !== false) {\n\t\t\t\twriteArtifact(artifactPathsResult.inputPath, `# Task for ${label}\\n\\n${taskWithAcceptance}`);\n\t\t}\n\t\tif (options.artifactConfig?.includeJsonl !== false) {\n\t\t\tjsonlPath = artifactPathsResult.jsonlPath;\n\t\t}\n\t\tif (options.artifactConfig?.includeTranscript !== false) {\n\t\t\ttranscriptWriter = createChildTranscriptWriter({\n\t\t\t\ttranscriptPath: artifactPathsResult.transcriptPath,\n\t\t\t\tsource: \"foreground\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.childId,\n\t\t\t\tchildIndex: options.index,\n\t\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t\t});\n\t\t\ttranscriptWriter.writeInitialUserMessage(taskWithAcceptance);\n\t\t}\n\t}\n\n\tconst persistResultMetadata = (target: SingleResult): void => {\n\t\tif (!artifactPathsResult || options.artifactConfig?.enabled === false || options.artifactConfig?.includeMetadata === false) return;\n\t\twriteMetadata(artifactPathsResult.metadataPath, {\n\t\t\trunId: options.runId,\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\ttask,\n\t\t\texitCode: target.exitCode,\n\t\t\tusage: target.usage,\n\t\t\tmodel: target.model,\n\t\t\tattemptedModels: target.attemptedModels,\n\t\t\tmodelAttempts: target.modelAttempts,\n\t\t\tdurationMs: target.progressSummary?.durationMs,\n\t\t\ttoolCount: target.progressSummary?.toolCount,\n\t\t\terror: target.error,\n\t\t\tacceptance: target.acceptance,\n\t\t\t...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),\n\t\t\ttranscriptError: target.transcriptError,\n\t\t\tskills: target.skills,\n\t\t\tskillsWarning: target.skillsWarning,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t};\n\n\tconst detachedAwareOptions: RunSyncOptions = options.onDetachedExit\n\t\t? {\n\t\t\t...options,\n\t\t\tonDetachedExit: (recoveredResult) => {\n\t\t\t\tvoid (async () => {\n\t\t\t\t\tconst childWrittenOutput = options.outputPath\n\t\t\t\t\t\t? extractChildWrittenOutput(recoveredResult.messages, options.outputPath, options.cwd ?? runtimeCwd)\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\trecoveredResult.acceptance = await evaluateAcceptance({\n\t\t\t\t\t\tacceptance: effectiveAcceptance,\n\t\t\t\t\t\toutput: acceptanceOutputByResult.get(recoveredResult) ?? recoveredResult.finalOutput ?? \"\",\n\t\t\t\t\t\tfileOutput: childWrittenOutput !== undefined && options.outputPath\n\t\t\t\t\t\t\t? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === \"file-only\" }\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t\t\t\t});\n\t\t\t\t\tconst acceptanceFailure = acceptanceFailureMessage(recoveredResult.acceptance);\n\t\t\t\t\tstripAcceptanceReportsFromMessages(recoveredResult.messages);\n\t\t\t\t\tif (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0) {\n\t\t\t\t\t\trecoveredResult.exitCode = 1;\n\t\t\t\t\t\trecoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\\n${acceptanceFailure}` : acceptanceFailure;\n\t\t\t\t\t\tif (recoveredResult.progress) {\n\t\t\t\t\t\t\trecoveredResult.progress.status = \"failed\";\n\t\t\t\t\t\t\trecoveredResult.progress.error = recoveredResult.error;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpersistResultMetadata(recoveredResult);\n\t\t\t\t\toptions.onDetachedExit?.(recoveredResult);\n\t\t\t\t})().catch((error) => {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\trecoveredResult.exitCode = 1;\n\t\t\t\t\trecoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\\nAcceptance evaluation failed: ${message}` : `Acceptance evaluation failed: ${message}`;\n\t\t\t\t\toptions.onDetachedExit?.(recoveredResult);\n\t\t\t\t});\n\t\t\t},\n\t\t}\n\t\t: options;\n\n\tlet lastResult: SingleResult | undefined;\n\tconst modelsToTry = candidates.length > 0 ? candidates : [undefined];\n\tfor (let i = 0; i < modelsToTry.length; i++) {\n\t\tconst candidate = modelsToTry[i];\n\t\tconst outputSnapshot = captureSingleOutputSnapshot(options.outputPath);\n\t\tconst result = await runSingleAttempt(runtimeCwd, spec, taskWithAcceptance, candidate, detachedAwareOptions, {\n\t\t\tsessionEnabled,\n\t\t\tsystemPrompt,\n\t\t\tresolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,\n\t\t\tskillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(\", \")}` : undefined,\n\t\t\tjsonlPath,\n\t\t\tartifactPaths: artifactPathsResult,\n\t\t\ttranscriptWriter,\n\t\t\tattemptNotes,\n\t\t\toutputSnapshot,\n\t\t\toriginalTask: task,\n\t\t});\n\t\tlastResult = result;\n\t\tif (result.model) attemptedModels.push(result.model);\n\t\telse if (candidate) attemptedModels.push(candidate);\n\t\tsumUsage(aggregateUsage, result.usage);\n\t\ttotalToolCount += result.progressSummary?.toolCount ?? 0;\n\t\ttotalDurationMs += result.progressSummary?.durationMs ?? 0;\n\t\tconst attemptSucceeded = result.exitCode === 0 && !result.error;\n\t\tconst attempt: ModelAttempt = {\n\t\t\tmodel: result.model ?? candidate ?? spec.model ?? \"default\",\n\t\t\tsuccess: attemptSucceeded,\n\t\t\texitCode: result.exitCode,\n\t\t\terror: result.error,\n\t\t\tusage: { ...result.usage },\n\t\t};\n\t\tmodelAttempts.push(attempt);\n\t\tif (result.detached || result.timedOut || result.turnBudgetExceeded) {\n\t\t\tbreak;\n\t\t}\n\t\tif (attemptSucceeded) {\n\t\t\tbreak;\n\t\t}\n\t\tif (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {\n\t\t\tbreak;\n\t\t}\n\t\tattemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));\n\t}\n\n\tconst result = lastResult ?? {\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\ttask,\n\t\texitCode: 1,\n\t\tmessages: [],\n\t\tusage: emptyUsage(),\n\t\terror: \"Subagent did not produce a result.\",\n\t} satisfies SingleResult;\n\n\tresult.usage = aggregateUsage;\n\tresult.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;\n\tresult.modelAttempts = modelAttempts.length > 0 ? modelAttempts : undefined;\n\tresult.progressSummary = {\n\t\ttoolCount: totalToolCount,\n\t\ttokens: aggregateUsage.input + aggregateUsage.output,\n\t\tdurationMs: totalDurationMs,\n\t};\n\tif (attemptNotes.length > 0 && result.progress) {\n\t\tresult.progress.recentOutput = [...attemptNotes, ...result.progress.recentOutput];\n\t\tif (result.progress.recentOutput.length > 50) {\n\t\t\tresult.progress.recentOutput.splice(50);\n\t\t}\n\t}\n\n\tif (transcriptWriter) result.transcriptPath = artifactPathsResult?.transcriptPath;\n\tif (transcriptWriter?.getError()) result.transcriptError = transcriptWriter.getError();\n\n\tif (artifactPathsResult && options.artifactConfig?.enabled !== false) {\n\t\tresult.artifactPaths = artifactPathsResult;\n\t\tif (options.artifactConfig?.includeOutput !== false) {\n\t\t\twriteArtifact(artifactPathsResult.outputPath, artifactOutputByResult.get(result) ?? result.finalOutput ?? \"\");\n\t\t}\n\t\tif (options.maxOutput) {\n\t\t\tconst config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };\n\t\t\tconst truncationResult = truncateOutput(result.finalOutput ?? \"\", config, artifactPathsResult.outputPath);\n\t\t\tif (truncationResult.truncated) result.truncation = truncationResult;\n\t\t}\n\t} else if (options.maxOutput) {\n\t\tconst config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };\n\t\tconst truncationResult = truncateOutput(result.finalOutput ?? \"\", config);\n\t\tif (truncationResult.truncated) result.truncation = truncationResult;\n\t}\n\n\tif (options.sessionFile && (existsSync(options.sessionFile) || result.messages?.length)) {\n\t\tresult.sessionFile = options.sessionFile;\n\t} else if (shareEnabled && options.sessionDir) {\n\t\tconst sessionFile = findLatestSessionFile(options.sessionDir);\n\t\tif (sessionFile) result.sessionFile = sessionFile;\n\t}\n\n\tconst childWrittenOutput = options.outputPath\n\t\t? extractChildWrittenOutput(result.messages, options.outputPath, options.cwd ?? runtimeCwd)\n\t\t: undefined;\n\tif (result.detached) {\n\t\tresult.acceptance = buildPendingAcceptanceLedger(effectiveAcceptance);\n\t} else if (result.stopped) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"stopped\", message: \"Acceptance was not evaluated because the subagent was stopped.\" });\n\t} else if (result.timedOut) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"timeout\", message: \"Acceptance was not evaluated because the subagent timed out.\" });\n\t} else if (result.turnBudgetExceeded) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"turn-budget\", message: \"Acceptance was not evaluated because the subagent exceeded its turn budget.\" });\n\t} else {\n\t\tresult.acceptance = await evaluateAcceptance({\n\t\t\tacceptance: effectiveAcceptance,\n\t\t\toutput: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? \"\",\n\t\t\tfileOutput: childWrittenOutput !== undefined && options.outputPath\n\t\t\t\t? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === \"file-only\" }\n\t\t\t\t: undefined,\n\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t});\n\t}\n\tconst acceptanceFailure = acceptanceFailureMessage(result.acceptance);\n\tstripAcceptanceReportsFromMessages(result.messages);\n\tif (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {\n\t\tresult.exitCode = 1;\n\t\tresult.error = result.error ? `${result.error}\\n${acceptanceFailure}` : acceptanceFailure;\n\t\tif (result.progress) {\n\t\t\tresult.progress.status = \"failed\";\n\t\t\tresult.progress.error = result.error;\n\t\t}\n\t}\n\tpersistResultMetadata(result);\n\n\treturn result;\n}\n"]}
1
+ {"version":3,"file":"execution.d.ts","sourceRoot":"","sources":["../../../../../../src/builtin-extensions/pi-subagents/src/runs/foreground/execution.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AASvD,OAAO,EAKN,KAAK,cAAc,EACnB,KAAK,YAAY,EASjB,MAAM,uBAAuB,CAAC;AAquC/B;;GAEG;AACH,wBAAsB,OAAO,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,YAAY,CAAC,CA+TvB","sourcesContent":["// @ts-nocheck\nimport { parseSessionRequests, parseSessionUsage, sessionUsageDelta } from \"../../shared/session-tokens.ts\";\n/**\n * Core execution logic for running subagents\n */\n\nimport { spawn } from \"node:child_process\";\nimport { splitKnownThinkingSuffix } from \"../../shared/model-info.ts\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Message } from \"@ashx-j/lunr-ai\";\nimport type { ChildSpec } from \"../../shared/types.ts\";\nimport { childRowLabel } from \"../../shared/types.ts\";\nimport {\n\tensureArtifactsDir,\n\tgetArtifactPaths,\n\twriteArtifact,\n\twriteMetadata,\n} from \"../../shared/artifacts.ts\";\nimport { createChildTranscriptWriter, type ChildTranscriptWriter } from \"../../shared/child-transcript.ts\";\nimport {\n\ttype AgentProgress,\n\ttype ArtifactPaths,\n\ttype ControlEvent,\n\ttype ModelAttempt,\n\ttype RunSyncOptions,\n\ttype SingleResult,\n\ttype Usage,\n\tDEFAULT_MAX_OUTPUT,\n\tINTERCOM_DETACH_REQUEST_EVENT,\n\tINTERCOM_DETACH_RESPONSE_EVENT,\n\ttype AcceptanceLedger,\n\ttype ResolvedAcceptanceConfig,\n\ttruncateOutput,\n\tgetSubagentDepthEnv,\n} from \"../../shared/types.ts\";\nimport {\n\tDEFAULT_CONTROL_CONFIG,\n\tbuildControlEvent,\n\tclaimControlNotification,\n\tderiveActivityState,\n\tshouldNotifyControlEvent,\n} from \"../shared/subagent-control.ts\";\nimport {\n\tgetFinalOutput,\n\tfindLatestSessionFile,\n\tdetectSubagentError,\n\textractToolArgsPreview,\n\textractTextFromContent,\n} from \"../../shared/utils.ts\";\nimport { buildSkillInjection, resolveSkillsWithFallback } from \"../../agents/skills.ts\";\nimport { resolveChildExcludeTools } from \"../shared/child-tools.ts\";\nimport { normalizeSkillInput } from \"../../agents/skills.ts\";\nimport { evaluateCompletionMutationGuard } from \"../shared/completion-guard.ts\";\nimport { getPiSpawnCommand } from \"../shared/pi-spawn.ts\";\nimport { createJsonlWriter } from \"../../shared/jsonl-writer.ts\";\nimport { attachPostExitStdioGuard } from \"../../shared/post-exit-stdio-guard.ts\";\nimport { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from \"../shared/pi-args.ts\";\nimport { READ_ONLY_WRITE_SPAWN_ERROR, resolveChildPermissions, snapshotParentPermissionMode } from \"../../../../../core/subagent-permission-inherit.ts\";\nimport { readStructuredOutput } from \"../shared/structured-output.ts\";\nimport { readChildToolDiagnosticError } from \"../shared/tool-availability.ts\";\nimport { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from \"../shared/single-output.ts\";\nimport {\n\tbuildModelCandidates,\n\tformatModelAttemptNote,\n\tisRetryableModelFailure,\n} from \"../shared/model-fallback.ts\";\nimport {\n\tcreateMutatingFailureState,\n\tdidMutatingToolFail,\n\tisMutatingTool,\n\tnextLongRunningTrigger,\n\trecordMutatingFailure,\n\tresetMutatingFailureState,\n\tresolveCurrentPath,\n\tshouldEscalateMutatingFailures,\n\tsummarizeRecentMutatingFailures,\n} from \"../shared/long-running-guard.ts\";\nimport { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from \"../shared/acceptance.ts\";\nimport { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, shouldAbortForTurnBudget, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from \"../shared/turn-budget.ts\";\nimport { initialToolBudgetState, toolBudgetState } from \"../shared/tool-budget.ts\";\nimport { resolveWatchdogConfig } from \"../../watchdog/settings.ts\";\nimport { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from \"../shared/child-protocol.ts\";\nimport { createChildEventDecoder } from \"../../../../../core/subagent-event-transport.ts\";\nimport {\n\tacceptChildWatchdogEvent,\n\tchildWatchdogIsActive,\n\tisChildWatchdogStatusEvent,\n\tresolveChildWatchdogConfig,\n\ttype ChildWatchdogStateSnapshot,\n} from \"../../watchdog/child-status.ts\";\n\nconst artifactOutputByResult = new WeakMap<SingleResult, string>();\nconst acceptanceOutputByResult = new WeakMap<SingleResult, string>();\n\nfunction emptyUsage(): Usage {\n\treturn { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0, unknownRequests: 0, partialRequests: 0, unknownCosts: 0 };\n}\n\nfunction sumUsage(target: Usage, source: Usage): void {\n\ttarget.input += source.input;\n\ttarget.output += source.output;\n\ttarget.cacheRead += source.cacheRead;\n\ttarget.cacheWrite += source.cacheWrite;\n\ttarget.cost += source.cost;\n\ttarget.turns += source.turns;\n\ttarget.unknownRequests += source.unknownRequests ?? 0;\n\ttarget.partialRequests += source.partialRequests ?? 0;\n\ttarget.unknownCosts += source.unknownCosts ?? 0;\n}\n\nfunction formatTimeoutMessage(timeoutMs: number): string {\n\treturn `Subagent timed out after ${timeoutMs}ms.`;\n}\n\nfunction resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; remainingMs: number; message: string } | undefined {\n\tif (options.timeoutMs === undefined) return undefined;\n\tconst deadlineAt = options.deadlineAt ?? Date.now() + options.timeoutMs;\n\treturn {\n\t\ttimeoutMs: options.timeoutMs,\n\t\tremainingMs: Math.max(0, deadlineAt - Date.now()),\n\t\tmessage: formatTimeoutMessage(options.timeoutMs),\n\t};\n}\n\nfunction buildPendingAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {\n\treturn {\n\t\tstatus: \"pending\",\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: [],\n\t\tverifyRuns: [],\n\t};\n}\n\nfunction appendRecentOutput(progress: AgentProgress, lines: string[]): void {\n\tif (lines.length === 0) return;\n\tprogress.recentOutput.push(...lines.filter((line) => line.trim()));\n\tif (progress.recentOutput.length > 50) {\n\t\tprogress.recentOutput.splice(0, progress.recentOutput.length - 50);\n\t}\n}\n\nfunction stripAcceptanceReportsFromMessages(messages: Message[] | undefined): void {\n\tfor (const message of messages ?? []) {\n\t\tif (message.role !== \"assistant\" || !Array.isArray(message.content)) continue;\n\t\tfor (const part of message.content) {\n\t\t\tif (part.type === \"text\" && \"text\" in part && typeof part.text === \"string\") {\n\t\t\t\tpart.text = stripAcceptanceReport(part.text);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction extractThinkingText(content: unknown): string | undefined {\n\tif (!Array.isArray(content)) return undefined;\n\tlet latest = \"\";\n\tfor (const part of content) {\n\t\tif (!part || typeof part !== \"object\") continue;\n\t\tif (\"type\" in part && part.type === \"thinking\" && \"thinking\" in part && typeof part.thinking === \"string\") {\n\t\t\tconst text = part.thinking.trim();\n\t\t\tif (text) latest = part.thinking;\n\t\t}\n\t}\n\treturn latest || undefined;\n}\n\nfunction snapshotProgress(progress: AgentProgress): AgentProgress {\n\treturn {\n\t\t...progress,\n\t\tskills: progress.skills ? [...progress.skills] : undefined,\n\t\trecentTools: progress.recentTools.map((tool) => ({ ...tool })),\n\t\trecentOutput: [...progress.recentOutput],\n\t};\n}\n\nfunction snapshotResult(result: SingleResult, progress: AgentProgress): SingleResult {\n\treturn {\n\t\t...result,\n\t\tmessages: result.outputMode === \"file-only\" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined,\n\t\tusage: { ...result.usage },\n\t\tskills: result.skills ? [...result.skills] : undefined,\n\t\tattemptedModels: result.attemptedModels ? [...result.attemptedModels] : undefined,\n\t\tmodelAttempts: result.modelAttempts\n\t\t\t? result.modelAttempts.map((attempt) => ({\n\t\t\t\t...attempt,\n\t\t\t\tusage: attempt.usage ? { ...attempt.usage } : undefined,\n\t\t\t}))\n\t\t\t: undefined,\n\t\tcontrolEvents: result.controlEvents ? result.controlEvents.map((event) => ({ ...event })) : undefined,\n\t\tprogress,\n\t\tprogressSummary: result.progressSummary ? { ...result.progressSummary } : undefined,\n\t\tartifactPaths: result.artifactPaths ? { ...result.artifactPaths } : undefined,\n\t\ttruncation: result.truncation ? { ...result.truncation } : undefined,\n\t\toutputReference: result.outputReference ? { ...result.outputReference } : undefined,\n\t};\n}\n\nasync function runSingleAttempt(\n\truntimeCwd: string,\n\tspec: ChildSpec,\n\ttask: string,\n\tmodel: string | undefined,\n\toptions: RunSyncOptions,\n\tshared: {\n\t\tsessionEnabled: boolean;\n\t\tsystemPrompt: string;\n\t\tresolvedSkillNames?: string[];\n\t\tskillsWarning?: string;\n\t\tjsonlPath?: string;\n\t\tartifactPaths?: ArtifactPaths;\n\t\ttranscriptWriter?: ChildTranscriptWriter;\n\t\tattemptNotes: string[];\n\t\toutputSnapshot?: SingleOutputSnapshot;\n\t\toriginalTask?: string;\n\t},\n): Promise<SingleResult> {\n\tconst usageBeforeAttempt = parseSessionUsage(options.sessionFile ?? options.sessionDir);\n\tconst effectiveThinking = options.thinkingOverride;\n\tconst modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);\n\tconst watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);\n\tconst childWatchdog = watchdogConfig.ok\n\t\t? resolveChildWatchdogConfig({\n\t\t\tconfig: watchdogConfig.config,\n\t\t\tagent: spec.childId,\n\t\t\trunId: options.runId,\n\t\t\tchildIndex: options.index ?? 0,\n\t\t})\n\t\t: undefined;\n\tconst excludeTools = resolveChildExcludeTools({\n\t\tpermissions: spec.effectivePermissions,\n\t\tfanoutAuthorized: spec.fanoutAuthorized,\n\t});\n\tconst appendPrompt = appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget);\n\tconst { args, env: sharedEnv, tempDir, toolDiagnosticPath } = buildPiArgs({\n\t\tbaseArgs: [\"--mode\", \"json\", \"-p\"],\n\t\ttask,\n\t\tsessionEnabled: shared.sessionEnabled,\n\t\tsessionDir: options.sessionDir,\n\t\tsessionFile: options.sessionFile,\n\t\tmodel: modelArg,\n\t\tthinking: effectiveThinking,\n\t\tsystemPromptMode: \"append\",\n\t\tinheritProjectContext: true,\n\t\tinheritSkills: false,\n\t\trequireReadTool: Boolean(shared.resolvedSkillNames?.length),\n\t\texcludeTools,\n\t\tcommunicationEnabled: options.communicationEnabled,\n\t\tchildPermission: spec.effectivePermissions,\n\t\tchildId: spec.childId,\n\t\tchildDescription: spec.description,\n\t\tsystemPrompt: appendPrompt || undefined,\n\t\tcwd: options.cwd ?? runtimeCwd,\n\t\tpromptFileStem: spec.childId,\n\t\tintercomSessionName: options.intercomSessionName,\n\t\torchestratorIntercomTarget: options.orchestratorIntercomTarget,\n\t\trunId: options.runId,\n\t\tchildAgentName: spec.childId,\n\t\tchildIndex: options.index ?? 0,\n\t\tparentEventSink: options.nestedRoute?.eventSink,\n\t\tparentControlInbox: options.nestedRoute?.controlInbox,\n\t\tparentRootRunId: options.nestedRoute?.rootRunId,\n\t\tparentCapabilityToken: options.nestedRoute?.capabilityToken,\n\t\tparentSessionId: options.parentSessionId,\n\t\tstructuredOutput: options.structuredOutput,\n\t\ttoolBudget: options.toolBudget,\n\t\tchildWatchdog,\n\t\twaitToolEnabled: options.waitToolEnabled,\n\t});\n\n\tconst thinkingFromModel = modelArg ? splitKnownThinkingSuffix(modelArg).thinkingSuffix.slice(1) || undefined : undefined;\n\tconst result: SingleResult = {\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\ttask: shared.originalTask ?? task,\n\t\texitCode: 0,\n\t\tmessages: [],\n\t\tusage: emptyUsage(),\n\t\tmodel: modelArg,\n\t\tthinking: thinkingFromModel,\n\t\tmodelSelection: spec.modelSelection,\n\t\tartifactPaths: shared.artifactPaths,\n\t\ttranscriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,\n\t\tskills: shared.resolvedSkillNames,\n\t\tskillsWarning: shared.skillsWarning,\n\t\t...(options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {}),\n\t\t...(options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}),\n\t};\n\tconst startTime = Date.now();\n\tif (options.structuredOutput) {\n\t\ttry {\n\t\t\tif (existsSync(options.structuredOutput.outputPath)) unlinkSync(options.structuredOutput.outputPath);\n\t\t} catch {\n\t\t\t// Missing/stale structured-output files are handled after the child exits.\n\t\t}\n\t}\n\tconst controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;\n\tlet interruptedByControl = false;\n\tconst allControlEvents: ControlEvent[] = [];\n\tlet pendingControlEvents: ControlEvent[] = [];\n\tconst emittedControlEventKeys = new Set<string>();\n\tconst emitControlEvent = (event: ControlEvent) => {\n\t\tif (!shouldNotifyControlEvent(controlConfig, event)) return;\n\t\tif (!claimControlNotification(controlConfig, event, emittedControlEventKeys)) return;\n\t\tallControlEvents.push(event);\n\t\tpendingControlEvents.push(event);\n\t\toptions.onControlEvent?.(event);\n\t};\n\n\tconst progress: AgentProgress = {\n\t\tindex: options.index ?? 0,\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\tstatus: \"running\",\n\t\ttask,\n\t\tskills: shared.resolvedSkillNames,\n\t\tmodel: modelArg,\n\t\tthinking: thinkingFromModel,\n\t\tmodelSelection: spec.modelSelection,\n\t\trecentTools: [],\n\t\trecentOutput: [...shared.attemptNotes],\n\t\ttoolCount: 0,\n\t\ttokens: 0,\n\t\tdurationMs: 0,\n\t\tlastActivityAt: startTime,\n\t};\n\tresult.progress = progress;\n\tconst attemptTimeout = resolveAttemptTimeout(options);\n\tif (attemptTimeout?.remainingMs === 0) {\n\t\tcleanupTempDir(tempDir);\n\t\tresult.exitCode = 1;\n\t\tresult.timedOut = true;\n\t\tresult.error = attemptTimeout.message;\n\t\tresult.finalOutput = attemptTimeout.message;\n\t\tprogress.status = \"failed\";\n\t\tprogress.error = attemptTimeout.message;\n\t\tresult.progressSummary = {\n\t\t\ttoolCount: progress.toolCount,\n\t\t\ttokens: progress.tokens,\n\t\t\tdurationMs: progress.durationMs,\n\t\t};\n\t\treturn result;\n\t}\n\tconst spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };\n\tlet observedMutationAttempt = false;\n\n\tconst exitCode = await new Promise<number>((resolve) => {\n\t\tconst spawnSpec = getPiSpawnCommand(args);\n\t\tconst proc = spawn(spawnSpec.command, spawnSpec.args, {\n\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t\tenv: spawnEnv,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t});\n\t\tconst jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);\n\t\tlet spawned = typeof proc.pid === \"number\" && proc.pid > 0;\n\t\tproc.once(\"spawn\", () => { spawned = true; });\n\t\tlet processClosed = false;\n\t\tlet settled = false;\n\t\tlet detached = false;\n\t\tlet intercomStarted = false;\n\t\tlet assistantError: string | undefined;\n\t\tlet removeAbortListener: (() => void) | undefined;\n\t\tlet removeInterruptListener: (() => void) | undefined;\n\t\tlet activityTimer: NodeJS.Timeout | undefined;\n\t\tlet timeoutTimer: NodeJS.Timeout | undefined;\n\t\tlet timeoutTerminationTimer: NodeJS.Timeout | undefined;\n\t\tlet timeoutHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet turnBudgetSoftReached = false;\n\t\tlet turnBudgetTerminationTimer: NodeJS.Timeout | undefined;\n\t\tlet turnBudgetHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet protocolHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet cancellationHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet interruptTerminationTimer: NodeJS.Timeout | undefined;\n\t\tlet terminationCheckTimer: NodeJS.Timeout | undefined;\n\t\tlet terminationUnconfirmedReported = false;\n\t\tconst clearCancellationTimers = () => {\n\t\t\tif (terminationCheckTimer) clearTimeout(terminationCheckTimer);\n\t\t\tterminationCheckTimer = undefined;\n\t\t\tif (cancellationHardKillTimer) clearTimeout(cancellationHardKillTimer);\n\t\t\tif (interruptTerminationTimer) clearTimeout(interruptTerminationTimer);\n\t\t\tcancellationHardKillTimer = undefined;\n\t\t\tinterruptTerminationTimer = undefined;\n\t\t};\n\t\tconst recordProcessError = (error: unknown) => {\n\t\t\tif (!result.error) result.error = error instanceof Error ? error.message : String(error);\n\t\t\tprogress.error = result.error;\n\t\t\tif (!childExited && !processClosed && !settled && !detached) {\n\t\t\t\tprogress.status = \"running\";\n\t\t\t\tfireUpdate();\n\t\t\t}\n\t\t};\n\t\t// A delivered signal is not an exit acknowledgement. Keep this attempt owned\n\t\t// until close, including when the OS refuses every termination signal.\n\t\tconst signalChild = (signal: NodeJS.Signals): boolean => {\n\t\t\tif (childExited || processClosed || settled || detached) return false;\n\t\t\tlet sent = false;\n\t\t\ttry {\n\t\t\t\tsent = proc.kill(signal);\n\t\t\t} catch (error) {\n\t\t\t\trecordProcessError(error);\n\t\t\t}\n\t\t\tif (!sent && !childExited && !processClosed && !settled && !detached) {\n\t\t\t\trecordProcessError(`Subagent ${signal} could not be delivered; child exit is not confirmed.`);\n\t\t\t}\n\t\t\tif (signal === \"SIGKILL\" && !childExited && !processClosed && !settled && !detached && !terminationCheckTimer && !terminationUnconfirmedReported) {\n\t\t\t\tterminationCheckTimer = setTimeout(() => {\n\t\t\t\t\tterminationCheckTimer = undefined;\n\t\t\t\t\tif (childExited || processClosed || settled || detached) return;\n\t\t\t\t\tterminationUnconfirmedReported = true;\n\t\t\t\t\tconst message = `Subagent termination unconfirmed after SIGKILL${proc.pid ? ` (diagnostic PID ${proc.pid})` : \"\"}. This foreground call still owns the child and waits for its exit. Verify the child process before starting replacement work.`;\n\t\t\t\t\tprogress.status = \"running\";\n\t\t\t\t\tprogress.error = message;\n\t\t\t\t\tprogress.activityState = \"needs_attention\";\n\t\t\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\t\t\tappendRecentOutput(progress, [message]);\n\t\t\t\t\temitUpdateSnapshot(message);\n\t\t\t\t}, 2000);\n\t\t\t\tterminationCheckTimer.unref?.();\n\t\t\t}\n\t\t\treturn sent;\n\t\t};\n\t\tconst scheduleCancellationHardKill = (delayMs: number) => {\n\t\t\tif (cancellationHardKillTimer || childExited || processClosed || settled || detached) return;\n\t\t\tcancellationHardKillTimer = setTimeout(() => {\n\t\t\t\tcancellationHardKillTimer = undefined;\n\t\t\t\tif (!childExited && !processClosed && !settled && !detached) signalChild(\"SIGKILL\");\n\t\t\t}, delayMs);\n\t\t\tcancellationHardKillTimer.unref?.();\n\t\t};\n\t\tconst clearTurnBudgetTimers = () => {\n\t\t\tif (turnBudgetTerminationTimer) {\n\t\t\t\tclearTimeout(turnBudgetTerminationTimer);\n\t\t\t\tturnBudgetTerminationTimer = undefined;\n\t\t\t}\n\t\t\tif (turnBudgetHardKillTimer) {\n\t\t\t\tclearTimeout(turnBudgetHardKillTimer);\n\t\t\t\tturnBudgetHardKillTimer = undefined;\n\t\t\t}\n\t\t};\n\t\tconst clearTimeoutTimers = () => {\n\t\t\tif (timeoutTimer) {\n\t\t\t\tclearTimeout(timeoutTimer);\n\t\t\t\ttimeoutTimer = undefined;\n\t\t\t}\n\t\t\tif (timeoutTerminationTimer) {\n\t\t\t\tclearTimeout(timeoutTerminationTimer);\n\t\t\t\ttimeoutTerminationTimer = undefined;\n\t\t\t}\n\t\t\tif (timeoutHardKillTimer) {\n\t\t\t\tclearTimeout(timeoutHardKillTimer);\n\t\t\t\ttimeoutHardKillTimer = undefined;\n\t\t\t}\n\t\t};\n\n\t\tconst detachForIntercom = () => {\n\t\t\tdetached = true;\n\t\t\tprocessClosed = true;\n\t\t\tresult.detached = true;\n\t\t\tresult.detachedReason = \"intercom coordination\";\n\t\t\tprogress.status = \"detached\";\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tresult.progressSummary = {\n\t\t\t\ttoolCount: progress.toolCount,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t\tdurationMs: progress.durationMs,\n\t\t\t};\n\t\t\tfinish(-2);\n\t\t};\n\n\t\t// If the child emits a terminal assistant stop but never exits,\n\t\t// give it a short grace period to flush naturally, then clean it up.\n\t\tconst FINAL_STOP_GRACE_MS = 1000;\n\t\tconst HARD_KILL_MS = 3000;\n\t\tlet childExited = false;\n\t\tlet forcedTerminationSignal = false;\n\t\tlet cleanTerminalAssistantStopReceived = false;\n\t\tlet agentSettledReceived = false;\n\t\tlet finalDrainTimer: NodeJS.Timeout | undefined;\n\t\tlet finalHardKillTimer: NodeJS.Timeout | undefined;\n\t\tlet watchdogTailTimer: NodeJS.Timeout | undefined;\n\t\tlet childWatchdogState: ChildWatchdogStateSnapshot | undefined;\n\t\tconst updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {\n\t\t\tchildWatchdogState = snapshot;\n\t\t\tresult.watchdog = snapshot;\n\t\t\tprogress.watchdog = snapshot;\n\t\t};\n\t\tconst clearWatchdogTailTimer = () => {\n\t\t\tif (watchdogTailTimer) {\n\t\t\t\tclearTimeout(watchdogTailTimer);\n\t\t\t\twatchdogTailTimer = undefined;\n\t\t\t}\n\t\t};\n\t\tconst clearFinalDrainTimers = () => {\n\t\t\tif (finalDrainTimer) {\n\t\t\t\tclearTimeout(finalDrainTimer);\n\t\t\t\tfinalDrainTimer = undefined;\n\t\t\t}\n\t\t\tif (finalHardKillTimer) {\n\t\t\t\tclearTimeout(finalHardKillTimer);\n\t\t\t\tfinalHardKillTimer = undefined;\n\t\t\t}\n\t\t};\n\t\tconst startFinalDrain = () => {\n\t\t\tif (childWatchdogIsActive(childWatchdogState)) {\n\t\t\t\tarmWatchdogTail();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (childExited || finalDrainTimer || settled || processClosed || detached) return;\n\t\t\tfinalDrainTimer = setTimeout(() => {\n\t\t\t\tif (settled || processClosed || detached) return;\n\t\t\t\tconst termSent = signalChild(\"SIGTERM\");\n\t\t\t\tforcedTerminationSignal = termSent || forcedTerminationSignal;\n\t\t\t\tif (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {\n\t\t\t\t\tresult.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;\n\t\t\t\t}\n\t\t\t\tfinalHardKillTimer = setTimeout(() => {\n\t\t\t\t\tif (settled || processClosed || detached) return;\n\t\t\t\t\tforcedTerminationSignal = signalChild(\"SIGKILL\") || forcedTerminationSignal;\n\t\t\t\t}, HARD_KILL_MS);\n\t\t\t\tfinalHardKillTimer.unref?.();\n\t\t\t}, FINAL_STOP_GRACE_MS);\n\t\t\tfinalDrainTimer.unref?.();\n\t\t};\n\t\tfunction armWatchdogTail(): void {\n\t\t\tif ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || settled || processClosed || detached) return;\n\t\t\twatchdogTailTimer = setTimeout(() => {\n\t\t\t\twatchdogTailTimer = undefined;\n\t\t\t\tupdateChildWatchdogState({\n\t\t\t\t\tphase: \"stale\",\n\t\t\t\t\tseq: (childWatchdogState?.seq ?? 0) + 1,\n\t\t\t\t\tlastUpdate: Date.now(),\n\t\t\t\t\tfollowUpPending: false,\n\t\t\t\t\treason: \"child watchdog tail timeout\",\n\t\t\t\t\ttimedOut: true,\n\t\t\t\t});\n\t\t\t\tstartFinalDrain();\n\t\t\t\tfireUpdate();\n\t\t\t}, childWatchdog?.watchdogTailTimeoutMs ?? 120_000);\n\t\t\twatchdogTailTimer.unref?.();\n\t\t}\n\t\tconst applyChildLifecycle = (action: ChildLifecycleAction): void => {\n\t\t\tif (action === \"cancel-drain\") {\n\t\t\t\tclearFinalDrainTimers();\n\t\t\t\tclearWatchdogTailTimer();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (action === \"start-drain\") startFinalDrain();\n\t\t};\n\n\t\tconst unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {\n\t\t\tif (!options.allowIntercomDetach || detached || processClosed) return;\n\t\t\tif (!payload || typeof payload !== \"object\") return;\n\t\t\tconst event = payload as { requestId?: unknown; runId?: unknown; agent?: unknown; childIndex?: unknown };\n\t\t\tconst requestId = event.requestId;\n\t\t\tif (typeof requestId !== \"string\" || requestId.length === 0) return;\n\t\t\tconst hasRoute = event.runId !== undefined || event.agent !== undefined || event.childIndex !== undefined;\n\t\t\tif (hasRoute) {\n\t\t\t\tif (typeof event.runId === \"string\" && event.runId !== options.runId) return;\n\t\t\t\tif (typeof event.agent === \"string\" && event.agent !== spec.childId && event.agent !== spec.description) return;\n\t\t\t\tif (typeof event.childIndex === \"number\" && event.childIndex !== (options.index ?? 0)) return;\n\t\t\t} else if (!intercomStarted) return;\n\t\t\toptions.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true, runId: options.runId, agent: spec.childId, childIndex: options.index ?? 0 });\n\t\t\tdetachForIntercom();\n\t\t});\n\n\t\tconst finish = (code: number) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearCancellationTimers();\n\t\t\tclearFinalDrainTimers();\n\t\t\tclearWatchdogTailTimer();\n\t\t\tclearStdioGuard();\n\t\t\tclearTimeoutTimers();\n\t\t\tclearTurnBudgetTimers();\n\t\t\tif (protocolHardKillTimer) {\n\t\t\t\tclearTimeout(protocolHardKillTimer);\n\t\t\t\tprotocolHardKillTimer = undefined;\n\t\t\t}\n\t\t\tif (activityTimer) {\n\t\t\t\tclearInterval(activityTimer);\n\t\t\t\tactivityTimer = undefined;\n\t\t\t}\n\t\t\tunsubscribeIntercomDetach?.();\n\t\t\tremoveAbortListener?.();\n\t\t\tremoveInterruptListener?.();\n\t\t\tresolve(code);\n\t\t};\n\n\t\tconst drainPendingControlEvents = (): ControlEvent[] | undefined => {\n\t\t\tif (pendingControlEvents.length === 0) return undefined;\n\t\t\tconst events = pendingControlEvents;\n\t\t\tpendingControlEvents = [];\n\t\t\treturn events;\n\t\t};\n\n\t\tlet activeLongRunningNotified = false;\n\t\tlet pendingToolResult: { tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined;\n\t\tconst mutatingFailures = createMutatingFailureState();\n\t\tconst mutatingFailureWindowMs = 5 * 60_000;\n\t\tconst currentToolDurationMs = (now: number) => progress.currentToolStartedAt ? Math.max(0, now - progress.currentToolStartedAt) : undefined;\n\t\tconst emitNeedsAttention = (now: number, input: { message?: string; reason?: ControlEvent[\"reason\"]; recentFailureSummary?: string; currentTool?: string; currentPath?: string; currentToolDurationMs?: number } = {}): boolean => {\n\t\t\tif (!controlConfig.enabled) return false;\n\t\t\tconst previous = progress.activityState;\n\t\t\tprogress.activityState = \"needs_attention\";\n\t\t\tconst event = buildControlEvent({\n\t\t\t\ttype: \"needs_attention\",\n\t\t\t\tfrom: previous,\n\t\t\t\tto: \"needs_attention\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.description,\n\t\t\t\tindex: options.index,\n\t\t\t\tts: now,\n\t\t\t\tlastActivityAt: progress.lastActivityAt,\n\t\t\t\tmessage: input.message,\n\t\t\t\treason: input.reason ?? \"idle\",\n\t\t\t\tturns: result.usage.turns,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t\ttoolCount: progress.toolCount,\n\t\t\t\tcurrentTool: input.currentTool ?? progress.currentTool,\n\t\t\t\tcurrentToolDurationMs: input.currentToolDurationMs ?? currentToolDurationMs(now),\n\t\t\t\tcurrentPath: input.currentPath ?? progress.currentPath,\n\t\t\t\trecentFailureSummary: input.recentFailureSummary,\n\t\t\t});\n\t\t\temitControlEvent(event);\n\t\t\treturn previous !== \"needs_attention\";\n\t\t};\n\t\tconst emitActiveLongRunning = (now: number, reason: ControlEvent[\"reason\"]): boolean => {\n\t\t\tif (!controlConfig.enabled || activeLongRunningNotified || progress.activityState === \"needs_attention\") return false;\n\t\t\tactiveLongRunningNotified = true;\n\t\t\tconst previous = progress.activityState;\n\t\t\tprogress.activityState = \"active_long_running\";\n\t\t\temitControlEvent(buildControlEvent({\n\t\t\t\ttype: \"active_long_running\",\n\t\t\t\tfrom: previous,\n\t\t\t\tto: \"active_long_running\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.description,\n\t\t\t\tindex: options.index,\n\t\t\t\tts: now,\n\t\t\t\tmessage: `${spec.description} is still active but long-running`,\n\t\t\t\treason,\n\t\t\t\tturns: result.usage.turns,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t\ttoolCount: progress.toolCount,\n\t\t\t\tcurrentTool: progress.currentTool,\n\t\t\t\tcurrentToolDurationMs: currentToolDurationMs(now),\n\t\t\t\tcurrentPath: progress.currentPath,\n\t\t\t\telapsedMs: now - startTime,\n\t\t\t}));\n\t\t\treturn true;\n\t\t};\n\t\tconst requestTurnBudgetAbort = (turnCount: number) => {\n\t\t\tconst budget = options.turnBudget;\n\t\t\tif (!budget || result.timedOut || result.turnBudgetExceeded || interruptedByControl || processClosed || settled || detached) return;\n\t\t\tconst message = turnBudgetExceededMessage(budget, turnCount);\n\t\t\tresult.turnBudgetExceeded = true;\n\t\t\tresult.wrapUpRequested = true;\n\t\t\tresult.turnBudget = turnBudgetState(budget, turnCount, true);\n\t\t\tresult.error = message;\n\t\t\tresult.finalOutput = message;\n\t\t\tprogress.status = \"failed\";\n\t\t\tprogress.error = message;\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tfireUpdate();\n\t\t\tsignalChild(\"SIGINT\");\n\t\t\tturnBudgetTerminationTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || result.timedOut) return;\n\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t}, 1000);\n\t\t\tturnBudgetTerminationTimer.unref?.();\n\t\t\tturnBudgetHardKillTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || result.timedOut) return;\n\t\t\t\tsignalChild(\"SIGKILL\");\n\t\t\t}, 4000);\n\t\t\tturnBudgetHardKillTimer.unref?.();\n\t\t};\n\n\t\tconst updateTurnBudget = (turnCount: number, terminalAssistantStop: boolean) => {\n\t\t\tconst budget = options.turnBudget;\n\t\t\tif (!budget || result.timedOut || result.turnBudgetExceeded) return;\n\t\t\tif (turnCount < budget.maxTurns) {\n\t\t\t\tresult.turnBudget = { ...budget, outcome: \"within-budget\", turnCount };\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!turnBudgetSoftReached) {\n\t\t\t\tturnBudgetSoftReached = true;\n\t\t\t\tresult.wrapUpRequested = true;\n\t\t\t\tappendRecentOutput(progress, [turnBudgetSoftNote(budget, turnCount)]);\n\t\t\t}\n\t\t\tresult.turnBudget = turnBudgetState(budget, turnCount, false);\n\t\t\tif (shouldAbortForTurnBudget(budget, turnCount, terminalAssistantStop)) {\n\t\t\t\trequestTurnBudgetAbort(turnCount);\n\t\t\t}\n\t\t};\n\n\t\tconst updateActivityState = (now: number): boolean => {\n\t\t\tif (!controlConfig.enabled) return false;\n\t\t\tconst idleState = deriveActivityState({\n\t\t\t\tconfig: controlConfig,\n\t\t\t\tstartedAt: startTime,\n\t\t\t\tlastActivityAt: progress.lastActivityAt,\n\t\t\t\tnow,\n\t\t\t});\n\t\t\tif (idleState === \"needs_attention\") {\n\t\t\t\treturn progress.activityState === \"needs_attention\" ? false : emitNeedsAttention(now);\n\t\t\t}\n\t\t\tconst activeReason = nextLongRunningTrigger(controlConfig, {\n\t\t\t\tstartedAt: startTime,\n\t\t\t\tnow,\n\t\t\t\tturns: result.usage.turns,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t});\n\t\t\treturn activeReason ? emitActiveLongRunning(now, activeReason) : false;\n\t\t};\n\n\n\t\tconst emitUpdateSnapshot = (text: string) => {\n\t\t\tif (!options.onUpdate || processClosed) return;\n\t\t\tconst progressSnapshot = snapshotProgress(progress);\n\t\t\tconst resultSnapshot = snapshotResult(result, progressSnapshot);\n\t\t\tconst controlEvents = drainPendingControlEvents();\n\t\t\toptions.onUpdate({\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: {\n\t\t\t\t\tmode: \"single\",\n\t\t\t\t\tresults: [resultSnapshot],\n\t\t\t\t\tprogress: [progressSnapshot],\n\t\t\t\t\tcontrolEvents,\n\t\t\t\t},\n\t\t\t});\n\t\t};\n\n\t\tconst fireUpdate = () => {\n\t\t\tif (!options.onUpdate || processClosed) return;\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tconst output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);\n\t\t\temitUpdateSnapshot(output || \"(running...)\");\n\t\t};\n\n\t\tconst decodeChildEvent = createChildEventDecoder();\n\t\tconst processLine = (line: string) => {\n\t\t\tif (!line.trim()) return;\n\t\t\tlet evt: { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };\n\t\t\tlet assembled: string | undefined;\n\t\t\ttry {\n\t\t\t\tassembled = decodeChildEvent(line);\n\t\t\t} catch {\n\t\t\t\tjsonlWriter.writeLine(line);\n\t\t\t\tshared.transcriptWriter?.writeStdoutLine(line);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (assembled === undefined) return;\n\t\t\tline = assembled;\n\t\t\tjsonlWriter.writeLine(line);\n\t\t\ttry {\n\t\t\t\tevt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };\n\t\t\t} catch {\n\t\t\t\tshared.transcriptWriter?.writeStdoutLine(line);\n\t\t\t\t// Non-JSON stdout lines are expected; only structured events are parsed.\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tshared.transcriptWriter?.writeChildEvent(evt);\n\t\t\tif (evt.type === \"agent_settled\") agentSettledReceived = true;\n\t\t\tapplyChildLifecycle(projectChildLifecycle(evt));\n\n\t\t\tif (isChildWatchdogStatusEvent(evt)) {\n\t\t\t\tif (!childWatchdog) return;\n\t\t\t\tconst next = acceptChildWatchdogEvent({\n\t\t\t\t\tcurrent: childWatchdogState,\n\t\t\t\t\tevent: evt,\n\t\t\t\t\trunId: options.runId,\n\t\t\t\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\t\t\t\tchildIndex: options.index ?? 0,\n\t\t\t\t});\n\t\t\t\tif (!next) return;\n\t\t\t\tupdateChildWatchdogState(next);\n\t\t\t\tif (childWatchdogIsActive(next)) {\n\t\t\t\t\tclearFinalDrainTimers();\n\t\t\t\t\tarmWatchdogTail();\n\t\t\t\t} else {\n\t\t\t\t\tclearWatchdogTailTimer();\n\t\t\t\t\tif (cleanTerminalAssistantStopReceived || agentSettledReceived) startFinalDrain();\n\t\t\t\t}\n\t\t\t\tfireUpdate();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst now = Date.now();\n\t\t\tprogress.durationMs = now - startTime;\n\t\t\tprogress.lastActivityAt = now;\n\t\t\tupdateActivityState(now);\n\n\t\t\tif ((evt.type === \"message_update\" || evt.type === \"message_start\") && evt.message) {\n\t\t\t\tconst thinkingText = extractThinkingText(evt.message.content);\n\t\t\t\tif (thinkingText) progress.thinkingText = thinkingText;\n\t\t\t\t// lunr: compact rows no longer show thinking. Skip per-token\n\t\t\t\t// invalidates on message_update so the running glyph is not rebuilt\n\t\t\t\t// on every stream delta.\n\t\t\t\tif (evt.type === \"message_start\") fireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_execution_start\") {\n\t\t\t\tconst toolArgs = evt.args && typeof evt.args === \"object\" && !Array.isArray(evt.args)\n\t\t\t\t\t? evt.args as Record<string, unknown>\n\t\t\t\t\t: {};\n\t\t\t\tif (options.allowIntercomDetach && (evt.toolName === \"intercom\" || evt.toolName === \"contact_supervisor\")) {\n\t\t\t\t\tintercomStarted = true;\n\t\t\t\t}\n\t\t\t\tprogress.toolCount++;\n\t\t\t\tif (options.toolBudget) {\n\t\t\t\t\tresult.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);\n\t\t\t\t}\n\t\t\t\tprogress.currentTool = evt.toolName;\n\t\t\t\tprogress.currentToolArgs = extractToolArgsPreview(toolArgs);\n\t\t\t\tprogress.currentToolStartedAt = now;\n\t\t\t\tprogress.currentPath = resolveCurrentPath(evt.toolName, toolArgs);\n\t\t\t\tconst mutates = isMutatingTool(evt.toolName, toolArgs);\n\t\t\t\tobservedMutationAttempt = observedMutationAttempt || mutates;\n\t\t\t\tpendingToolResult = { tool: evt.toolName ?? \"tool\", path: progress.currentPath, mutates, startedAt: now };\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_execution_end\") {\n\t\t\t\tif (progress.currentTool) {\n\t\t\t\t\tprogress.recentTools.push({\n\t\t\t\t\t\ttool: progress.currentTool,\n\t\t\t\t\t\targs: progress.currentToolArgs || \"\",\n\t\t\t\t\t\tendMs: now,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tprogress.currentTool = undefined;\n\t\t\t\tprogress.currentToolArgs = undefined;\n\t\t\t\tprogress.currentToolStartedAt = undefined;\n\t\t\t\tprogress.currentPath = undefined;\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"message_end\" && evt.message) {\n\t\t\t\tresult.messages.push(evt.message);\n\t\t\t\tif (evt.message.role === \"assistant\") {\n\t\t\t\t\tresult.usage.turns++;\n\t\t\t\t\tprogress.turnCount = result.usage.turns;\n\t\t\t\t\tconst stopReason = (evt.message as { stopReason?: string }).stopReason;\n\t\t\t\t\tconst hasToolCall = Array.isArray(evt.message.content)\n\t\t\t\t\t\t&& evt.message.content.some((part) => (part as { type?: string }).type === \"toolCall\");\n\t\t\t\t\tconst terminalAssistantStop = stopReason === \"stop\" && !hasToolCall;\n\t\t\t\t\tupdateTurnBudget(result.usage.turns, terminalAssistantStop);\n\t\t\t\t\tconst u = evt.message.usage;\n\t\t\t\t\tif (u) {\n\t\t\t\t\t\tresult.usage.input += u.input || 0;\n\t\t\t\t\t\tresult.usage.output += u.output || 0;\n\t\t\t\t\t\tresult.usage.cacheRead += u.cacheRead || 0;\n\t\t\t\t\t\tresult.usage.cacheWrite += u.cacheWrite || 0;\n\t\t\t\t\t\tresult.usage.cost += u.cost?.total || 0;\n\t\t\t\t\t\tresult.usage.unknownRequests += Number(u.measurement === \"unknown\");\n\t\t\t\t\t\tresult.usage.partialRequests += Number(u.measurement === \"partial\");\n\t\t\t\t\t\tresult.usage.unknownCosts += Number(u.costSource === \"unknown\");\n\t\t\t\t\t\tprogress.tokens = result.usage.input + result.usage.output + result.usage.cacheRead + result.usage.cacheWrite;\n\t\t\t\t\t}\n\t\t\t\t\tif (!result.model && evt.message.model) {\n\t\t\t\t\t\tresult.model = evt.message.model;\n\t\t\t\t\t\tconst parsed = splitKnownThinkingSuffix(evt.message.model);\n\t\t\t\t\t\tif (parsed.thinkingSuffix) result.thinking = parsed.thinkingSuffix.slice(1);\n\t\t\t\t\t\tprogress.model = result.model;\n\t\t\t\t\t\tprogress.thinking = result.thinking;\n\t\t\t\t\t}\n\t\t\t\t\tif (evt.message.errorMessage) assistantError = evt.message.errorMessage;\n\t\t\t\t\tconst assistantText = extractTextFromContent(evt.message.content);\n\t\t\t\t\tappendRecentOutput(progress, assistantText.split(\"\\n\").slice(-10));\n\t\t\t\t\t// Final assistant message: start the exit drain window.\n\t\t\t\t\tif (terminalAssistantStop) {\n\t\t\t\t\t\tif (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;\n\t\t\t\t\t\tcleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;\n\t\t\t\t\t\tapplyChildLifecycle(projectChildLifecycle(evt, true));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tupdateActivityState(now);\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_result_end\" && evt.message) {\n\t\t\t\tresult.messages.push(evt.message);\n\t\t\t\tconst resultText = extractTextFromContent(evt.message.content);\n\t\t\t\tif (options.toolBudget && pendingToolResult && resultText.includes(\"Tool budget hard limit reached\")) {\n\t\t\t\t\tresult.toolBudgetBlocked = true;\n\t\t\t\t\tresult.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount, pendingToolResult.tool);\n\t\t\t\t}\n\t\t\t\tappendRecentOutput(progress, resultText.split(\"\\n\").slice(-10));\n\t\t\t\tconst toolSnapshot = pendingToolResult;\n\t\t\t\tpendingToolResult = undefined;\n\t\t\t\tif (toolSnapshot?.mutates && didMutatingToolFail(resultText)) {\n\t\t\t\t\trecordMutatingFailure(mutatingFailures, {\n\t\t\t\t\t\ttool: toolSnapshot.tool,\n\t\t\t\t\t\tpath: toolSnapshot.path,\n\t\t\t\t\t\terror: resultText.split(\"\\n\").find((line) => line.trim())?.trim().slice(0, 180) ?? \"mutating tool failed\",\n\t\t\t\t\t\tts: now,\n\t\t\t\t\t}, mutatingFailureWindowMs);\n\t\t\t\t\tif (shouldEscalateMutatingFailures(mutatingFailures, controlConfig.failedToolAttemptsBeforeAttention)) {\n\t\t\t\t\t\temitNeedsAttention(now, {\n\t\t\t\t\t\t\tmessage: `${spec.description} needs attention after repeated mutating tool failures`,\n\t\t\t\t\t\t\treason: \"tool_failures\",\n\t\t\t\t\t\t\tcurrentTool: toolSnapshot.tool,\n\t\t\t\t\t\t\tcurrentPath: toolSnapshot.path,\n\t\t\t\t\t\t\tcurrentToolDurationMs: toolSnapshot.startedAt ? Math.max(0, now - toolSnapshot.startedAt) : undefined,\n\t\t\t\t\t\t\trecentFailureSummary: summarizeRecentMutatingFailures(mutatingFailures),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if (toolSnapshot?.mutates) {\n\t\t\t\t\tresetMutatingFailureState(mutatingFailures);\n\t\t\t\t}\n\t\t\t\tfireUpdate();\n\t\t\t}\n\t\t};\n\n\t\tif (controlConfig.enabled) {\n\t\t\tactivityTimer = setInterval(() => {\n\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\tconst now = Date.now();\n\t\t\t\tif (updateActivityState(now)) {\n\t\t\t\t\tprogress.durationMs = now - startTime;\n\t\t\t\t\tfireUpdate();\n\t\t\t\t}\n\t\t\t}, 1000);\n\t\t\tactivityTimer.unref?.();\n\t\t}\n\n\t\tif (attemptTimeout) {\n\t\t\ttimeoutTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || interruptedByControl) return;\n\t\t\t\tresult.timedOut = true;\n\t\t\t\tresult.error = attemptTimeout.message;\n\t\t\t\tresult.finalOutput = attemptTimeout.message;\n\t\t\t\tprogress.status = \"failed\";\n\t\t\t\tprogress.error = attemptTimeout.message;\n\t\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\t\tfireUpdate();\n\t\t\t\tsignalChild(\"SIGINT\");\n\t\t\t\ttimeoutTerminationTimer = setTimeout(() => {\n\t\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t\t}, 1000);\n\t\t\t\ttimeoutTerminationTimer.unref?.();\n\t\t\t\ttimeoutHardKillTimer = setTimeout(() => {\n\t\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\t\tsignalChild(\"SIGKILL\");\n\t\t\t\t}, 4000);\n\t\t\t\ttimeoutHardKillTimer.unref?.();\n\t\t\t}, attemptTimeout.remainingMs);\n\t\t\ttimeoutTimer.unref?.();\n\t\t}\n\n\t\tconst stderrTail = createBoundedByteTail();\n\t\tconst failProtocol = (limit: ProtocolOutputLimit): void => {\n\t\t\tif (result.protocolError) return;\n\t\t\tresult.protocolError = limit;\n\t\t\tresult.error = formatProtocolOutputLimit(limit);\n\t\t\tprogress.status = \"failed\";\n\t\t\tprogress.error = result.error;\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tfireUpdate();\n\t\t\tif (!childExited) {\n\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t\tprotocolHardKillTimer = setTimeout(() => {\n\t\t\t\t\tif (!childExited) signalChild(\"SIGKILL\");\n\t\t\t\t}, 3000);\n\t\t\t\tprotocolHardKillTimer.unref?.();\n\t\t\t}\n\t\t};\n\t\tconst stdoutReader = createBoundedLineReader({ onLine: processLine, onLimit: failProtocol });\n\t\tconst stderrReader = createBoundedLineReader({\n\t\t\tstream: \"stderr\",\n\t\t\tmaxPendingLineBytes: MAX_CHILD_STDERR_BYTES,\n\t\t\tonLine: (line) => shared.transcriptWriter?.writeStderrLine(line),\n\t\t\tonLimit: (limit) => shared.transcriptWriter?.writeStderrLine(formatProtocolOutputLimit(limit)),\n\t\t});\n\n\t\tconst clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });\n\t\tproc.stdout.on(\"data\", (chunk: Buffer) => stdoutReader.push(chunk));\n\t\tproc.stderr.on(\"data\", (chunk: Buffer) => {\n\t\t\tstderrTail.push(chunk);\n\t\t\tstderrReader.push(chunk);\n\t\t});\n\t\tconst confirmChildExit = () => {\n\t\t\tchildExited = true;\n\t\t\tif (terminationUnconfirmedReported) {\n\t\t\t\tprogress.error = result.error;\n\t\t\t\tprogress.activityState = undefined;\n\t\t\t}\n\t\t\tclearCancellationTimers();\n\t\t\tclearFinalDrainTimers();\n\t\t};\n\t\tproc.on(\"exit\", confirmChildExit);\n\t\tproc.on(\"close\", (code, signal) => {\n\t\t\tconfirmChildExit();\n\t\t\tclearFinalDrainTimers();\n\t\t\tclearStdioGuard();\n\t\t\tvoid jsonlWriter.close().catch(() => {\n\t\t\t\t// JSONL artifact flush is best effort.\n\t\t\t});\n\t\t\tconst toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);\n\t\t\tcleanupTempDir(tempDir);\n\t\t\tstdoutReader.end();\n\t\t\tstderrReader.end();\n\t\t\tconst persistedUsage = parseSessionUsage(options.sessionFile ?? options.sessionDir ?? result.sessionFile);\n\t\t\tif (persistedUsage) {\n\t\t\t\tresult.usageRequests = parseSessionRequests(options.sessionFile ?? options.sessionDir ?? result.sessionFile);\n\t\t\t\tresult.usage = options.sessionFile ? persistedUsage : sessionUsageDelta(persistedUsage, usageBeforeAttempt);\n\t\t\t\tprogress.tokens = result.usage.input + result.usage.output + result.usage.cacheRead + result.usage.cacheWrite;\n\t\t\t}\n\t\t\tconst stderr = stderrTail.text();\n\t\t\tlet closeError = result.error ?? toolDiagnosticError ?? assistantError;\n\t\t\tconst forcedDrainAfterFinalSuccess = forcedTerminationSignal && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;\n\t\t\tif (code !== 0 && stderr.trim() && !closeError && !forcedDrainAfterFinalSuccess) {\n\t\t\t\tcloseError = stderr.trim();\n\t\t\t}\n\t\t\tconst finalCode = forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (code ?? 1) : (code ?? 0);\n\t\t\tif (detached) {\n\t\t\t\tconst recoveredProgress = snapshotProgress(progress);\n\t\t\t\tconst recoveredResult = snapshotResult(result, recoveredProgress);\n\t\t\t\tif (!recoveredResult.error && closeError) recoveredResult.error = closeError;\n\t\t\t\trecoveredResult.exitCode = recoveredResult.error && finalCode === 0 ? 1 : finalCode;\n\t\t\t\trecoveredProgress.status = recoveredResult.exitCode === 0 ? \"completed\" : \"failed\";\n\t\t\t\trecoveredProgress.durationMs = Date.now() - startTime;\n\t\t\t\tif (recoveredResult.error) recoveredProgress.error = recoveredResult.error;\n\t\t\t\trecoveredResult.progressSummary = {\n\t\t\t\t\ttoolCount: recoveredProgress.toolCount,\n\t\t\t\t\ttokens: recoveredProgress.tokens,\n\t\t\t\t\tdurationMs: recoveredProgress.durationMs,\n\t\t\t\t};\n\t\t\t\tconst acceptanceOutput = getFinalOutput(recoveredResult.messages ?? []).trim()\n\t\t\t\t\t|| recoveredResult.error\n\t\t\t\t\t|| recoveredResult.finalOutput\n\t\t\t\t\t|| \"Detached child exited without final output.\";\n\t\t\t\tacceptanceOutputByResult.set(recoveredResult, acceptanceOutput);\n\t\t\t\tlet fullOutput = stripAcceptanceReport(acceptanceOutput);\n\t\t\t\tfullOutput = fullOutput.trim() || recoveredResult.error || recoveredResult.finalOutput || \"Detached child exited without final output.\";\n\t\t\t\trecoveredResult.outputMode = options.outputMode ?? \"inline\";\n\t\t\t\tif (options.outputPath && recoveredResult.exitCode === 0) {\n\t\t\t\t\tconst resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);\n\t\t\t\t\tfullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);\n\t\t\t\t\trecoveredResult.savedOutputPath = resolvedOutput.savedPath;\n\t\t\t\t\trecoveredResult.outputSaveError = resolvedOutput.saveError;\n\t\t\t\t\tif (resolvedOutput.savedPath) {\n\t\t\t\t\t\trecoveredResult.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);\n\t\t\t\t\t} else {\n\t\t\t\t\t\trecoveredResult.exitCode = 1;\n\t\t\t\t\t\trecoveredResult.error = `Output file was not finalized after detached child exit: ${resolvedOutput.saveError ?? options.outputPath}`;\n\t\t\t\t\t\trecoveredProgress.status = \"failed\";\n\t\t\t\t\t\trecoveredProgress.error = recoveredResult.error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trecoveredResult.finalOutput = options.outputMode === \"file-only\" && recoveredResult.savedOutputPath && recoveredResult.outputReference\n\t\t\t\t\t? recoveredResult.outputReference.message\n\t\t\t\t\t: fullOutput;\n\t\t\t\tif (recoveredResult.artifactPaths && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\twriteArtifact(recoveredResult.artifactPaths.outputPath, fullOutput);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Detached children may outlive test/temp cleanup; recovered status is best-effort.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\toptions.onDetachedExit?.(recoveredResult);\n\t\t\t\tfinish(-2);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!result.error && closeError) result.error = closeError;\n\t\t\tprocessClosed = true;\n\t\t\tfinish(finalCode);\n\t\t});\n\t\tproc.on(\"error\", (error) => {\n\t\t\t// kill/send errors after spawn do not prove exit and must not discard escalation.\n\t\t\tif (spawned) {\n\t\t\t\trecordProcessError(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tclearFinalDrainTimers();\n\t\t\tclearStdioGuard();\n\t\t\tvoid jsonlWriter.close().catch(() => {\n\t\t\t\t// JSONL artifact flush is best effort.\n\t\t\t});\n\t\t\tcleanupTempDir(tempDir);\n\t\t\tstdoutReader.end();\n\t\t\tstderrReader.end();\n\t\t\tif (!result.error) {\n\t\t\t\tresult.error = error instanceof Error ? error.message : String(error);\n\t\t\t}\n\t\t\tfinish(1);\n\t\t});\n\n\t\tif (options.signal) {\n\t\t\tconst kill = () => {\n\t\t\t\tif (childExited || processClosed || settled || detached) return;\n\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t\tscheduleCancellationHardKill(3000);\n\t\t\t};\n\t\t\tif (options.signal.aborted) kill();\n\t\t\telse {\n\t\t\t\toptions.signal.addEventListener(\"abort\", kill, { once: true });\n\t\t\t\tremoveAbortListener = () => options.signal?.removeEventListener(\"abort\", kill);\n\t\t\t}\n\t\t}\n\n\t\tif (options.interruptSignal) {\n\t\t\tconst interrupt = () => {\n\t\t\t\tif (processClosed || detached || settled) return;\n\t\t\t\tif (result.timedOut) return;\n\t\t\t\tinterruptedByControl = true;\n\t\t\t\tclearTimeoutTimers();\n\t\t\t\tprogress.status = \"running\";\n\t\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\t\tresult.interrupted = true;\n\t\t\t\tresult.finalOutput = \"Interrupted. Waiting for explicit next action.\";\n\t\t\t\tprogress.activityState = undefined;\n\t\t\t\tfireUpdate();\n\t\t\t\tsignalChild(\"SIGINT\");\n\t\t\t\tinterruptTerminationTimer = setTimeout(() => {\n\t\t\t\t\tif (childExited || settled || processClosed || detached) return;\n\t\t\t\t\tsignalChild(\"SIGTERM\");\n\t\t\t\t}, 1000);\n\t\t\t\tinterruptTerminationTimer.unref?.();\n\t\t\t\tscheduleCancellationHardKill(4000);\n\t\t\t};\n\t\t\tif (options.interruptSignal.aborted) interrupt();\n\t\t\telse {\n\t\t\t\toptions.interruptSignal.addEventListener(\"abort\", interrupt, { once: true });\n\t\t\t\tremoveInterruptListener = () => options.interruptSignal?.removeEventListener(\"abort\", interrupt);\n\t\t\t}\n\t\t}\n\t});\n\tresult.exitCode = exitCode;\n\tif (interruptedByControl) {\n\t\tresult.exitCode = 0;\n\t\tresult.interrupted = true;\n\t\tresult.error = undefined;\n\t\tresult.finalOutput = result.finalOutput || \"Interrupted. Waiting for explicit next action.\";\n\t\tresult.controlEvents = allControlEvents.length ? allControlEvents : undefined;\n\t\tprogress.activityState = undefined;\n\t\tprogress.durationMs = Date.now() - startTime;\n\t\tresult.progressSummary = {\n\t\t\ttoolCount: progress.toolCount,\n\t\t\ttokens: progress.tokens,\n\t\t\tdurationMs: progress.durationMs,\n\t\t};\n\t\treturn result;\n\t}\n\tif (result.detached) {\n\t\tresult.exitCode = -2;\n\t\tresult.finalOutput = \"Detached for intercom coordination before task completion.\";\n\t\tresult.outputMode = options.outputMode ?? \"inline\";\n\t\tif (options.outputPath) {\n\t\t\tresult.outputSaveError = \"Output file was not finalized because the subagent detached for intercom coordination.\";\n\t\t}\n\t\treturn result;\n\t}\n\n\tif (result.error && result.exitCode === 0) {\n\t\tresult.exitCode = 1;\n\t}\n\tif (result.exitCode === 0 && !result.error) {\n\t\tconst errInfo = detectSubagentError(result.messages);\n\t\tif (errInfo.hasError) {\n\t\t\tresult.exitCode = errInfo.exitCode ?? 1;\n\t\t\tresult.error = errInfo.details\n\t\t\t\t? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`\n\t\t\t\t: `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;\n\t\t}\n\t}\n\tif (result.exitCode === 0 && !result.error) {\n\t\tconst finalText = getFinalOutput(result.messages);\n\t\tconst missingStructuredOutput = options.structuredOutput\n\t\t\t? !existsSync(options.structuredOutput.outputPath)\n\t\t\t: false;\n\t\tif (!finalText?.trim() && (!options.structuredOutput || missingStructuredOutput)) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.error = \"Subagent produced no output (possible model cold-start or empty response).\";\n\t\t}\n\t}\n\tif (options.structuredOutput && result.exitCode === 0 && !result.error) {\n\t\tconst structured = readStructuredOutput({\n\t\t\tschema: options.structuredOutput.schema,\n\t\t\tschemaPath: options.structuredOutput.schemaPath,\n\t\t\toutputPath: options.structuredOutput.outputPath,\n\t\t});\n\t\tresult.structuredOutputSchemaPath = options.structuredOutput.schemaPath;\n\t\tresult.structuredOutputPath = options.structuredOutput.outputPath;\n\t\tif (structured.error) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.error = structured.error;\n\t\t} else {\n\t\t\tresult.structuredOutput = structured.value;\n\t\t}\n\t}\n\n\tprogress.status = result.exitCode === 0 ? \"completed\" : \"failed\";\n\tprogress.durationMs = Date.now() - startTime;\n\tif (result.error) {\n\t\tprogress.error = result.error;\n\t\tif (progress.currentTool) {\n\t\t\tprogress.failedTool = progress.currentTool;\n\t\t}\n\t}\n\n\tresult.progressSummary = {\n\t\ttoolCount: progress.toolCount,\n\t\ttokens: progress.tokens,\n\t\tdurationMs: progress.durationMs,\n\t};\n\n\tconst acceptanceOutput = getFinalOutput(result.messages);\n\tlet fullOutput = stripAcceptanceReport(acceptanceOutput);\n\tif (result.timedOut) {\n\t\tconst timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);\n\t\tfullOutput = fullOutput.trim()\n\t\t\t? `${timeoutMessage}\\n\\nPartial output before timeout:\\n${fullOutput}`\n\t\t\t: timeoutMessage;\n\t} else if (result.turnBudgetExceeded && result.turnBudget) {\n\t\tfullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);\n\t} else if (result.wrapUpRequested && result.turnBudget?.outcome === \"wrap-up-requested\") {\n\t\tconst note = turnBudgetSoftNote(result.turnBudget, result.turnBudget.wrapUpRequestedAtTurn ?? result.turnBudget.turnCount);\n\t\tfullOutput = fullOutput.trim() ? `${note}\\n\\n${fullOutput}` : note;\n\t}\n\tconst completionGuard = result.exitCode === 0 && !result.error\n\t\t? evaluateCompletionMutationGuard({\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\ttask: shared.originalTask ?? task,\n\t\t\tmessages: result.messages,\n\t\t\ttools: spec.effectivePermissions === \"read-only\" ? [\"read\", \"grep\", \"find\", \"ls\", \"bash\"] : undefined,\n\t\t})\n\t\t: undefined;\n\tif (completionGuard?.triggered && !observedMutationAttempt) {\n\t\tresult.exitCode = 1;\n\t\tresult.error = \"Subagent completed without making edits for an implementation task.\\nIt appears to have returned planning or scratchpad output instead of applying changes.\";\n\t\tprogress.status = \"failed\";\n\t\tprogress.error = result.error;\n\t\temitControlEvent(buildControlEvent({\n\t\t\tfrom: progress.activityState,\n\t\t\tto: \"needs_attention\",\n\t\t\trunId: options.runId ?? spec.childId,\n\t\t\tagent: spec.description,\n\t\t\tindex: options.index,\n\t\t\tts: Date.now(),\n\t\t\tmessage: `${spec.description} completed without making edits for an implementation task`,\n\t\t\treason: \"completion_guard\",\n\t\t}));\n\t}\n\t\tif (options.outputPath && result.exitCode === 0) {\n\t\t\tconst resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);\n\t\t\tfullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);\n\t\t\tresult.savedOutputPath = resolvedOutput.savedPath;\n\t\t\tresult.outputSaveError = resolvedOutput.saveError;\n\t\t\tif (resolvedOutput.savedPath) {\n\t\t\t\tresult.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);\n\t\t\t}\n\t}\n\t\tartifactOutputByResult.set(result, fullOutput);\n\t\tacceptanceOutputByResult.set(result, acceptanceOutput);\n\tresult.outputMode = options.outputMode ?? \"inline\";\n\tresult.finalOutput = options.outputMode === \"file-only\" && result.savedOutputPath && result.outputReference\n\t\t? result.outputReference.message\n\t\t: fullOutput;\n\tresult.controlEvents = allControlEvents.length ? allControlEvents : undefined;\n\tif (options.onUpdate) {\n\t\tconst finalText = result.finalOutput || result.error || \"(no output)\";\n\t\tconst progressSnapshot = snapshotProgress(progress);\n\t\tconst resultSnapshot = snapshotResult(result, progressSnapshot);\n\t\toptions.onUpdate({\n\t\t\tcontent: [{ type: \"text\", text: finalText }],\n\t\t\tdetails: {\n\t\t\t\tmode: \"single\",\n\t\t\t\tresults: [resultSnapshot],\n\t\t\t\tprogress: [progressSnapshot],\n\t\t\t\tcontrolEvents: allControlEvents.length ? allControlEvents : undefined,\n\t\t\t},\n\t\t});\n\t}\n\treturn result;\n}\n\n/**\n * Run a subagent synchronously (blocking until complete)\n */\nexport async function runSync(\n\truntimeCwd: string,\n\tspec: ChildSpec,\n\toptions: RunSyncOptions,\n): Promise<SingleResult> {\n\tconst task = spec.task;\n\tconst label = childRowLabel(spec);\n\tconst parentMode = snapshotParentPermissionMode(options.parentSessionId);\n\tconst resolvedPermissions = resolveChildPermissions(parentMode, spec.requestedPermissions);\n\tif (!resolvedPermissions.ok) {\n\t\tconst error = resolvedPermissions.error || READ_ONLY_WRITE_SPAWN_ERROR;\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\terror,\n\t\t};\n\t}\n\tconst outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${label})`);\n\tif (outputModeValidationError) {\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\toutputMode: options.outputMode,\n\t\t\terror: outputModeValidationError,\n\t\t};\n\t}\n\n\tconst shareEnabled = options.share === true;\n\tconst effectiveAcceptance = resolveEffectiveAcceptance({\n\t\texplicit: options.acceptance ?? spec.acceptance,\n\t\tpermissions: spec.effectivePermissions,\n\t\ttask,\n\t\tmode: options.acceptanceContext?.mode ?? \"single\",\n\t\tasync: options.acceptanceContext?.async,\n\t\tdynamic: options.acceptanceContext?.dynamic,\n\t\tdynamicGroup: options.acceptanceContext?.dynamicGroup,\n\t});\n\tconst acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);\n\tconst taskWithAcceptance = acceptancePrompt ? `${task}\\n${acceptancePrompt}` : task;\n\tconst sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;\n\tconst skillNames = options.skills ?? (spec.skill === false ? [] : (normalizeSkillInput(spec.skill) || []));\n\tconst skillCwd = options.cwd ?? spec.cwd ?? runtimeCwd;\n\tconst { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(\n\t\tskillNames,\n\t\tskillCwd,\n\t\truntimeCwd,\n\t\tundefined,\n\t\tskillCwd,\n\t);\n\tif (skillNames.some((skill) => skill.trim() === \"pi-subagents\") && missingSkills.includes(\"pi-subagents\")) {\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\terror: \"Skills not found: pi-subagents\",\n\t\t};\n\t}\n\tlet systemPrompt = \"\";\n\tif (resolvedSkills.length > 0) {\n\t\tsystemPrompt = buildSkillInjection(resolvedSkills);\n\t}\n\tsystemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, {\n\t\ttools: spec.effectivePermissions === \"read-only\" ? [\"read\", \"grep\", \"find\", \"ls\", \"bash\"] : undefined,\n\t});\n\n\tconst candidates = buildModelCandidates(\n\t\toptions.modelOverride ?? spec.model,\n\t\tundefined,\n\t\toptions.availableModels,\n\t\toptions.preferredModelProvider,\n\t\t{ scope: options.modelScope },\n\t);\n\tconst attemptedModels: string[] = [];\n\tconst modelAttempts: ModelAttempt[] = [];\n\tconst aggregateUsage = emptyUsage();\n\tconst attemptNotes: string[] = [];\n\tlet totalToolCount = 0;\n\tlet totalDurationMs = 0;\n\n\tlet artifactPathsResult: ArtifactPaths | undefined;\n\tlet jsonlPath: string | undefined;\n\tlet transcriptWriter: ChildTranscriptWriter | undefined;\n\tif (options.artifactsDir && options.artifactConfig?.enabled !== false) {\n\t\tartifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, spec.childId, options.index);\n\t\tensureArtifactsDir(options.artifactsDir);\n\t\tif (options.artifactConfig?.includeInput !== false) {\n\t\t\t\twriteArtifact(artifactPathsResult.inputPath, `# Task for ${label}\\n\\n${taskWithAcceptance}`);\n\t\t}\n\t\tif (options.artifactConfig?.includeJsonl !== false) {\n\t\t\tjsonlPath = artifactPathsResult.jsonlPath;\n\t\t}\n\t\tif (options.artifactConfig?.includeTranscript !== false) {\n\t\t\ttranscriptWriter = createChildTranscriptWriter({\n\t\t\t\ttranscriptPath: artifactPathsResult.transcriptPath,\n\t\t\t\tsource: \"foreground\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.childId,\n\t\t\t\tchildIndex: options.index,\n\t\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t\t});\n\t\t\ttranscriptWriter.writeInitialUserMessage(taskWithAcceptance);\n\t\t}\n\t}\n\n\tconst persistResultMetadata = (target: SingleResult): void => {\n\t\tif (!artifactPathsResult || options.artifactConfig?.enabled === false || options.artifactConfig?.includeMetadata === false) return;\n\t\twriteMetadata(artifactPathsResult.metadataPath, {\n\t\t\trunId: options.runId,\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\ttask,\n\t\t\texitCode: target.exitCode,\n\t\t\tusage: target.usage,\n\t\t\tmodel: target.model,\n\t\t\tattemptedModels: target.attemptedModels,\n\t\t\tmodelAttempts: target.modelAttempts,\n\t\t\tdurationMs: target.progressSummary?.durationMs,\n\t\t\ttoolCount: target.progressSummary?.toolCount,\n\t\t\terror: target.error,\n\t\t\tacceptance: target.acceptance,\n\t\t\t...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),\n\t\t\ttranscriptError: target.transcriptError,\n\t\t\tskills: target.skills,\n\t\t\tskillsWarning: target.skillsWarning,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t};\n\n\tconst detachedAwareOptions: RunSyncOptions = options.onDetachedExit\n\t\t? {\n\t\t\t...options,\n\t\t\tonDetachedExit: (recoveredResult) => {\n\t\t\t\tvoid (async () => {\n\t\t\t\t\tconst childWrittenOutput = options.outputPath\n\t\t\t\t\t\t? extractChildWrittenOutput(recoveredResult.messages, options.outputPath, options.cwd ?? runtimeCwd)\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\trecoveredResult.acceptance = await evaluateAcceptance({\n\t\t\t\t\t\tacceptance: effectiveAcceptance,\n\t\t\t\t\t\toutput: acceptanceOutputByResult.get(recoveredResult) ?? recoveredResult.finalOutput ?? \"\",\n\t\t\t\t\t\tfileOutput: childWrittenOutput !== undefined && options.outputPath\n\t\t\t\t\t\t\t? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === \"file-only\" }\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t\t\t\t});\n\t\t\t\t\tconst acceptanceFailure = acceptanceFailureMessage(recoveredResult.acceptance);\n\t\t\t\t\tstripAcceptanceReportsFromMessages(recoveredResult.messages);\n\t\t\t\t\tif (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0) {\n\t\t\t\t\t\trecoveredResult.exitCode = 1;\n\t\t\t\t\t\trecoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\\n${acceptanceFailure}` : acceptanceFailure;\n\t\t\t\t\t\tif (recoveredResult.progress) {\n\t\t\t\t\t\t\trecoveredResult.progress.status = \"failed\";\n\t\t\t\t\t\t\trecoveredResult.progress.error = recoveredResult.error;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpersistResultMetadata(recoveredResult);\n\t\t\t\t\toptions.onDetachedExit?.(recoveredResult);\n\t\t\t\t})().catch((error) => {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\trecoveredResult.exitCode = 1;\n\t\t\t\t\trecoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\\nAcceptance evaluation failed: ${message}` : `Acceptance evaluation failed: ${message}`;\n\t\t\t\t\toptions.onDetachedExit?.(recoveredResult);\n\t\t\t\t});\n\t\t\t},\n\t\t}\n\t\t: options;\n\n\tconst priorRequestIds = new Set(parseSessionRequests(options.sessionFile ?? options.sessionDir).map((request) => request.id));\n\tlet lastResult: SingleResult | undefined;\n\tconst modelsToTry = candidates.length > 0 ? candidates : [undefined];\n\tfor (let i = 0; i < modelsToTry.length; i++) {\n\t\tconst candidate = modelsToTry[i];\n\t\tconst outputSnapshot = captureSingleOutputSnapshot(options.outputPath);\n\t\tconst usageBeforeAttempt = parseSessionUsage(options.sessionFile ?? options.sessionDir);\n\t\tconst result = await runSingleAttempt(runtimeCwd, spec, taskWithAcceptance, candidate, detachedAwareOptions, {\n\t\t\tsessionEnabled,\n\t\t\tsystemPrompt,\n\t\t\tresolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,\n\t\t\tskillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(\", \")}` : undefined,\n\t\t\tjsonlPath,\n\t\t\tartifactPaths: artifactPathsResult,\n\t\t\ttranscriptWriter,\n\t\t\tattemptNotes,\n\t\t\toutputSnapshot,\n\t\t\toriginalTask: task,\n\t\t});\n\t\tconst persistedUsage = parseSessionUsage(options.sessionFile ?? options.sessionDir ?? result.sessionFile);\n\t\tif (persistedUsage) result.usage = sessionUsageDelta(persistedUsage, usageBeforeAttempt);\n\t\tlastResult = result;\n\t\tif (result.model) attemptedModels.push(result.model);\n\t\telse if (candidate) attemptedModels.push(candidate);\n\t\tsumUsage(aggregateUsage, result.usage);\n\t\ttotalToolCount += result.progressSummary?.toolCount ?? 0;\n\t\ttotalDurationMs += result.progressSummary?.durationMs ?? 0;\n\t\tconst attemptSucceeded = result.exitCode === 0 && !result.error;\n\t\tconst attempt: ModelAttempt = {\n\t\t\tmodel: result.model ?? candidate ?? spec.model ?? \"default\",\n\t\t\tsuccess: attemptSucceeded,\n\t\t\texitCode: result.exitCode,\n\t\t\terror: result.error,\n\t\t\tusage: { ...result.usage },\n\t\t};\n\t\tmodelAttempts.push(attempt);\n\t\tif (result.detached || result.timedOut || result.turnBudgetExceeded) {\n\t\t\tbreak;\n\t\t}\n\t\tif (attemptSucceeded) {\n\t\t\tbreak;\n\t\t}\n\t\tif (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {\n\t\t\tbreak;\n\t\t}\n\t\tattemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));\n\t}\n\n\tconst result = lastResult ?? {\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\ttask,\n\t\texitCode: 1,\n\t\tmessages: [],\n\t\tusage: emptyUsage(),\n\t\terror: \"Subagent did not produce a result.\",\n\t} satisfies SingleResult;\n\n\tconst cumulativeResumeUsage = options.sessionFile ? parseSessionUsage(options.sessionFile) : null;\n\tresult.usage = cumulativeResumeUsage ?? aggregateUsage;\n\tresult.usageRequests = parseSessionRequests(options.sessionFile ?? options.sessionDir ?? result.sessionFile)\n\t\t.filter((request) => Boolean(options.sessionFile) || !priorRequestIds.has(request.id));\n\tif (result.usageRequests.length === 0) result.usageRequests = undefined;\n\tresult.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;\n\tresult.modelAttempts = modelAttempts.length > 0 ? modelAttempts : undefined;\n\tresult.progressSummary = {\n\t\ttoolCount: totalToolCount,\n\t\ttokens: result.usage.input + result.usage.output + result.usage.cacheRead + result.usage.cacheWrite,\n\t\tdurationMs: totalDurationMs,\n\t};\n\tif (attemptNotes.length > 0 && result.progress) {\n\t\tresult.progress.recentOutput = [...attemptNotes, ...result.progress.recentOutput];\n\t\tif (result.progress.recentOutput.length > 50) {\n\t\t\tresult.progress.recentOutput.splice(50);\n\t\t}\n\t}\n\n\tif (transcriptWriter) result.transcriptPath = artifactPathsResult?.transcriptPath;\n\tif (transcriptWriter?.getError()) result.transcriptError = transcriptWriter.getError();\n\n\tif (artifactPathsResult && options.artifactConfig?.enabled !== false) {\n\t\tresult.artifactPaths = artifactPathsResult;\n\t\tif (options.artifactConfig?.includeOutput !== false) {\n\t\t\twriteArtifact(artifactPathsResult.outputPath, artifactOutputByResult.get(result) ?? result.finalOutput ?? \"\");\n\t\t}\n\t\tif (options.maxOutput) {\n\t\t\tconst config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };\n\t\t\tconst truncationResult = truncateOutput(result.finalOutput ?? \"\", config, artifactPathsResult.outputPath);\n\t\t\tif (truncationResult.truncated) result.truncation = truncationResult;\n\t\t}\n\t} else if (options.maxOutput) {\n\t\tconst config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };\n\t\tconst truncationResult = truncateOutput(result.finalOutput ?? \"\", config);\n\t\tif (truncationResult.truncated) result.truncation = truncationResult;\n\t}\n\n\tif (options.sessionFile && (existsSync(options.sessionFile) || result.messages?.length)) {\n\t\tresult.sessionFile = options.sessionFile;\n\t} else if (shareEnabled && options.sessionDir) {\n\t\tconst sessionFile = findLatestSessionFile(options.sessionDir);\n\t\tif (sessionFile) result.sessionFile = sessionFile;\n\t}\n\n\tconst childWrittenOutput = options.outputPath\n\t\t? extractChildWrittenOutput(result.messages, options.outputPath, options.cwd ?? runtimeCwd)\n\t\t: undefined;\n\tif (result.detached) {\n\t\tresult.acceptance = buildPendingAcceptanceLedger(effectiveAcceptance);\n\t} else if (result.stopped) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"stopped\", message: \"Acceptance was not evaluated because the subagent was stopped.\" });\n\t} else if (result.timedOut) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"timeout\", message: \"Acceptance was not evaluated because the subagent timed out.\" });\n\t} else if (result.turnBudgetExceeded) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"turn-budget\", message: \"Acceptance was not evaluated because the subagent exceeded its turn budget.\" });\n\t} else {\n\t\tresult.acceptance = await evaluateAcceptance({\n\t\t\tacceptance: effectiveAcceptance,\n\t\t\toutput: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? \"\",\n\t\t\tfileOutput: childWrittenOutput !== undefined && options.outputPath\n\t\t\t\t? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === \"file-only\" }\n\t\t\t\t: undefined,\n\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t});\n\t}\n\tconst acceptanceFailure = acceptanceFailureMessage(result.acceptance);\n\tstripAcceptanceReportsFromMessages(result.messages);\n\tif (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {\n\t\tresult.exitCode = 1;\n\t\tresult.error = result.error ? `${result.error}\\n${acceptanceFailure}` : acceptanceFailure;\n\t\tif (result.progress) {\n\t\t\tresult.progress.status = \"failed\";\n\t\t\tresult.progress.error = result.error;\n\t\t}\n\t}\n\tpersistResultMetadata(result);\n\n\treturn result;\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  // @ts-nocheck
2
+ import { parseSessionRequests, parseSessionUsage, sessionUsageDelta } from "../../shared/session-tokens.js";
2
3
  /**
3
4
  * Core execution logic for running subagents
4
5
  */
@@ -35,7 +36,7 @@ import { acceptChildWatchdogEvent, childWatchdogIsActive, isChildWatchdogStatusE
35
36
  const artifactOutputByResult = new WeakMap();
36
37
  const acceptanceOutputByResult = new WeakMap();
37
38
  function emptyUsage() {
38
- return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
39
+ return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0, unknownRequests: 0, partialRequests: 0, unknownCosts: 0 };
39
40
  }
40
41
  function sumUsage(target, source) {
41
42
  target.input += source.input;
@@ -44,6 +45,9 @@ function sumUsage(target, source) {
44
45
  target.cacheWrite += source.cacheWrite;
45
46
  target.cost += source.cost;
46
47
  target.turns += source.turns;
48
+ target.unknownRequests += source.unknownRequests ?? 0;
49
+ target.partialRequests += source.partialRequests ?? 0;
50
+ target.unknownCosts += source.unknownCosts ?? 0;
47
51
  }
48
52
  function formatTimeoutMessage(timeoutMs) {
49
53
  return `Subagent timed out after ${timeoutMs}ms.`;
@@ -133,6 +137,7 @@ function snapshotResult(result, progress) {
133
137
  };
134
138
  }
135
139
  async function runSingleAttempt(runtimeCwd, spec, task, model, options, shared) {
140
+ const usageBeforeAttempt = parseSessionUsage(options.sessionFile ?? options.sessionDir);
136
141
  const effectiveThinking = options.thinkingOverride;
137
142
  const modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);
138
143
  const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
@@ -804,7 +809,10 @@ async function runSingleAttempt(runtimeCwd, spec, task, model, options, shared)
804
809
  result.usage.cacheRead += u.cacheRead || 0;
805
810
  result.usage.cacheWrite += u.cacheWrite || 0;
806
811
  result.usage.cost += u.cost?.total || 0;
807
- progress.tokens = result.usage.input + result.usage.output;
812
+ result.usage.unknownRequests += Number(u.measurement === "unknown");
813
+ result.usage.partialRequests += Number(u.measurement === "partial");
814
+ result.usage.unknownCosts += Number(u.costSource === "unknown");
815
+ progress.tokens = result.usage.input + result.usage.output + result.usage.cacheRead + result.usage.cacheWrite;
808
816
  }
809
817
  if (!result.model && evt.message.model) {
810
818
  result.model = evt.message.model;
@@ -955,6 +963,12 @@ async function runSingleAttempt(runtimeCwd, spec, task, model, options, shared)
955
963
  cleanupTempDir(tempDir);
956
964
  stdoutReader.end();
957
965
  stderrReader.end();
966
+ const persistedUsage = parseSessionUsage(options.sessionFile ?? options.sessionDir ?? result.sessionFile);
967
+ if (persistedUsage) {
968
+ result.usageRequests = parseSessionRequests(options.sessionFile ?? options.sessionDir ?? result.sessionFile);
969
+ result.usage = options.sessionFile ? persistedUsage : sessionUsageDelta(persistedUsage, usageBeforeAttempt);
970
+ progress.tokens = result.usage.input + result.usage.output + result.usage.cacheRead + result.usage.cacheWrite;
971
+ }
958
972
  const stderr = stderrTail.text();
959
973
  let closeError = result.error ?? toolDiagnosticError ?? assistantError;
960
974
  const forcedDrainAfterFinalSuccess = forcedTerminationSignal && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
@@ -1397,11 +1411,13 @@ export async function runSync(runtimeCwd, spec, options) {
1397
1411
  },
1398
1412
  }
1399
1413
  : options;
1414
+ const priorRequestIds = new Set(parseSessionRequests(options.sessionFile ?? options.sessionDir).map((request) => request.id));
1400
1415
  let lastResult;
1401
1416
  const modelsToTry = candidates.length > 0 ? candidates : [undefined];
1402
1417
  for (let i = 0; i < modelsToTry.length; i++) {
1403
1418
  const candidate = modelsToTry[i];
1404
1419
  const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
1420
+ const usageBeforeAttempt = parseSessionUsage(options.sessionFile ?? options.sessionDir);
1405
1421
  const result = await runSingleAttempt(runtimeCwd, spec, taskWithAcceptance, candidate, detachedAwareOptions, {
1406
1422
  sessionEnabled,
1407
1423
  systemPrompt,
@@ -1414,6 +1430,9 @@ export async function runSync(runtimeCwd, spec, options) {
1414
1430
  outputSnapshot,
1415
1431
  originalTask: task,
1416
1432
  });
1433
+ const persistedUsage = parseSessionUsage(options.sessionFile ?? options.sessionDir ?? result.sessionFile);
1434
+ if (persistedUsage)
1435
+ result.usage = sessionUsageDelta(persistedUsage, usageBeforeAttempt);
1417
1436
  lastResult = result;
1418
1437
  if (result.model)
1419
1438
  attemptedModels.push(result.model);
@@ -1453,12 +1472,17 @@ export async function runSync(runtimeCwd, spec, options) {
1453
1472
  usage: emptyUsage(),
1454
1473
  error: "Subagent did not produce a result.",
1455
1474
  };
1456
- result.usage = aggregateUsage;
1475
+ const cumulativeResumeUsage = options.sessionFile ? parseSessionUsage(options.sessionFile) : null;
1476
+ result.usage = cumulativeResumeUsage ?? aggregateUsage;
1477
+ result.usageRequests = parseSessionRequests(options.sessionFile ?? options.sessionDir ?? result.sessionFile)
1478
+ .filter((request) => Boolean(options.sessionFile) || !priorRequestIds.has(request.id));
1479
+ if (result.usageRequests.length === 0)
1480
+ result.usageRequests = undefined;
1457
1481
  result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;
1458
1482
  result.modelAttempts = modelAttempts.length > 0 ? modelAttempts : undefined;
1459
1483
  result.progressSummary = {
1460
1484
  toolCount: totalToolCount,
1461
- tokens: aggregateUsage.input + aggregateUsage.output,
1485
+ tokens: result.usage.input + result.usage.output + result.usage.cacheRead + result.usage.cacheWrite,
1462
1486
  durationMs: totalDurationMs,
1463
1487
  };
1464
1488
  if (attemptNotes.length > 0 && result.progress) {