@ashx-j/lunr 0.2.7 → 0.2.8

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 (59) hide show
  1. package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.d.ts.map +1 -1
  2. package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.js +2 -2
  3. package/dist/builtin-extensions/pi-subagents/src/extension/control-notices.js.map +1 -1
  4. package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
  5. package/dist/builtin-extensions/pi-subagents/src/extension/index.js +1 -7
  6. package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
  7. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  8. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +21 -0
  9. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  10. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +1 -0
  11. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  12. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  13. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +3 -0
  14. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  15. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +9 -11
  16. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  17. package/dist/catalog/models.json +1 -1
  18. package/dist/catalog/providers/github-copilot.json +1 -1
  19. package/dist/catalog/providers/google-vertex.json +1 -1
  20. package/dist/catalog/providers/google.json +1 -1
  21. package/dist/catalog/providers/nvidia.json +1 -1
  22. package/dist/catalog/providers/opencode-go.json +1 -1
  23. package/dist/catalog/providers/opencode.json +1 -1
  24. package/dist/catalog/providers/openrouter.json +1 -1
  25. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  26. package/dist/catalog/publication.json +3 -3
  27. package/dist/core/rollback.d.ts +1 -1
  28. package/dist/core/rollback.d.ts.map +1 -1
  29. package/dist/core/rollback.js +1 -1
  30. package/dist/core/rollback.js.map +1 -1
  31. package/dist/core/slash-commands.d.ts.map +1 -1
  32. package/dist/core/slash-commands.js +2 -1
  33. package/dist/core/slash-commands.js.map +1 -1
  34. package/dist/modes/interactive/components/assistant-message.d.ts +6 -1
  35. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  36. package/dist/modes/interactive/components/assistant-message.js +92 -64
  37. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  38. package/dist/modes/interactive/components/thinking-summary.d.ts +4 -4
  39. package/dist/modes/interactive/components/thinking-summary.d.ts.map +1 -1
  40. package/dist/modes/interactive/components/thinking-summary.js +7 -5
  41. package/dist/modes/interactive/components/thinking-summary.js.map +1 -1
  42. package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
  43. package/dist/modes/interactive/components/thinking-tail.js +5 -3
  44. package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
  45. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  46. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  47. package/dist/modes/interactive/interactive-mode.js +42 -40
  48. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  49. package/docs/providers.md +2 -0
  50. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  51. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  52. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  53. package/examples/extensions/gondolin/package-lock.json +2 -2
  54. package/examples/extensions/gondolin/package.json +1 -1
  55. package/examples/extensions/sandbox/package-lock.json +2 -2
  56. package/examples/extensions/sandbox/package.json +1 -1
  57. package/examples/extensions/with-deps/package-lock.json +2 -2
  58. package/examples/extensions/with-deps/package.json +1 -1
  59. package/package.json +4 -4
@@ -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,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAQ1D,OAAO,EAKN,KAAK,cAAc,EACnB,KAAK,YAAY,EASjB,MAAM,uBAAuB,CAAC;AA6lC/B;;GAEG;AACH,wBAAsB,OAAO,CAC5B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW,EAAE,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,YAAY,CAAC,CAySvB","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 { AgentConfig } from \"../../agents/agents.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 { buildAgentMemoryInjection } from \"../../agents/agent-memory.ts\";\nimport { evaluateCompletionMutationGuard } from \"../shared/completion-guard.ts\";\nimport { getPiSpawnCommand } from \"../shared/pi-spawn.ts\";\nimport { createJsonlWriter } from \"../../shared/jsonl-writer.ts\";\nimport { attachPostExitStdioGuard, trySignalChild } from \"../../shared/post-exit-stdio-guard.ts\";\nimport { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from \"../shared/pi-args.ts\";\nimport { filterToolsForInheritedChild, 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 {\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 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\tagent: AgentConfig,\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 ?? agent.thinking;\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: agent.name,\n\t\t\trunId: options.runId,\n\t\t\tchildIndex: options.index ?? 0,\n\t\t})\n\t\t: undefined;\n\tconst parentPermissionMode = snapshotParentPermissionMode(options.parentSessionId);\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: agent.systemPromptMode,\n\t\tinheritProjectContext: agent.inheritProjectContext,\n\t\tinheritSkills: agent.inheritSkills,\n\t\trequireReadTool: Boolean(shared.resolvedSkillNames?.length),\n\t\ttools: filterToolsForInheritedChild(agent.tools, parentPermissionMode),\n\t\textensions: agent.extensions,\n\t\tsubagentOnlyExtensions: agent.subagentOnlyExtensions,\n\t\tsystemPrompt: appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget),\n\t\tmcpDirectTools: agent.mcpDirectTools,\n\t\tcwd: options.cwd ?? runtimeCwd,\n\t\tpromptFileStem: agent.name,\n\t\tintercomSessionName: options.intercomSessionName,\n\t\torchestratorIntercomTarget: options.orchestratorIntercomTarget,\n\t\trunId: options.runId,\n\t\tchildAgentName: agent.name,\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\tparentPermissionMode,\n\t});\n\n\tconst thinkingFromModel = modelArg ? splitKnownThinkingSuffix(modelArg).thinkingSuffix.slice(1) || undefined : undefined;\n\tconst result: SingleResult = {\n\t\tagent: agent.name,\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\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\tagent: agent.name,\n\t\tstatus: \"running\",\n\t\ttask,\n\t\tskills: shared.resolvedSkillNames,\n\t\tmodel: modelArg,\n\t\tthinking: thinkingFromModel,\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 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\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 = trySignalChild(proc, \"SIGTERM\");\n\t\t\t\tif (!termSent) return;\n\t\t\t\tforcedTerminationSignal = true;\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 = trySignalChild(proc, \"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 !== agent.name) 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: agent.name, 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\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: agent.name,\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: agent.name,\n\t\t\t\tindex: options.index,\n\t\t\t\tts: now,\n\t\t\t\tmessage: `${agent.name} 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\ttrySignalChild(proc, \"SIGINT\");\n\t\t\tturnBudgetTerminationTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || result.timedOut) return;\n\t\t\t\ttrySignalChild(proc, \"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\ttrySignalChild(proc, \"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 processLine = (line: string) => {\n\t\t\tif (!line.trim()) return;\n\t\t\tjsonlWriter.writeLine(line);\n\t\t\tlet evt: { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };\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\tagent: agent.name,\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 === \"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: `${agent.name} 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\ttrySignalChild(proc, \"SIGINT\");\n\t\t\t\ttimeoutTerminationTimer = setTimeout(() => {\n\t\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\t\ttrySignalChild(proc, \"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\ttrySignalChild(proc, \"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\ttrySignalChild(proc, \"SIGTERM\");\n\t\t\t\tprotocolHardKillTimer = setTimeout(() => {\n\t\t\t\t\tif (!childExited) trySignalChild(proc, \"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\tproc.on(\"exit\", () => {\n\t\t\tchildExited = true;\n\t\t\tclearFinalDrainTimers();\n\t\t});\n\t\tproc.on(\"close\", (code, signal) => {\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\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 (processClosed || detached) return;\n\t\t\t\tproc.kill(\"SIGTERM\");\n\t\t\t\tsetTimeout(() => !proc.killed && proc.kill(\"SIGKILL\"), 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\ttrySignalChild(proc, \"SIGINT\");\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tif (settled || processClosed || detached) return;\n\t\t\t\t\ttrySignalChild(proc, \"SIGTERM\");\n\t\t\t\t}, 1000).unref?.();\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 && agent.completionGuard !== false\n\t\t? evaluateCompletionMutationGuard({\n\t\t\tagent: agent.name,\n\t\t\ttask: shared.originalTask ?? task,\n\t\t\tmessages: result.messages,\n\t\t\ttools: agent.tools,\n\t\t\tmcpDirectTools: agent.mcpDirectTools,\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 ?? agent.name,\n\t\t\tagent: agent.name,\n\t\t\tindex: options.index,\n\t\t\tts: Date.now(),\n\t\t\tmessage: `${agent.name} 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\tagents: AgentConfig[],\n\tagentName: string,\n\ttask: string,\n\toptions: RunSyncOptions,\n): Promise<SingleResult> {\n\tconst agent = agents.find((a) => a.name === agentName);\n\tif (!agent) {\n\t\treturn {\n\t\t\tagent: agentName,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\terror: `Unknown agent: ${agentName}`,\n\t\t};\n\t}\n\tconst outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);\n\tif (outputModeValidationError) {\n\t\treturn {\n\t\t\tagent: agentName,\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,\n\t\tagentName,\n\t\tacceptanceRole: agent.acceptanceRole,\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 ?? agent.skills ?? [];\n\tconst skillCwd = options.cwd ?? runtimeCwd;\n\tconst { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(\n\t\tskillNames,\n\t\tskillCwd,\n\t\truntimeCwd,\n\t\tagent.skillPath,\n\t\tagent.filePath ? path.dirname(agent.filePath) : skillCwd,\n\t);\n\tif (skillNames.some((skill) => skill.trim() === \"pi-subagents\") && missingSkills.includes(\"pi-subagents\")) {\n\t\treturn {\n\t\t\tagent: agentName,\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 = agent.systemPrompt?.trim() || \"\";\n\tif (resolvedSkills.length > 0) {\n\t\tconst skillInjection = buildSkillInjection(resolvedSkills);\n\t\tsystemPrompt = systemPrompt ? `${systemPrompt}\\n\\n${skillInjection}` : skillInjection;\n\t}\n\tconst memoryInjection = buildAgentMemoryInjection(agent, skillCwd);\n\tif (memoryInjection) {\n\t\tsystemPrompt = systemPrompt ? `${systemPrompt}\\n\\n${memoryInjection}` : memoryInjection;\n\t}\n\tsystemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, agent);\n\n\tconst candidates = buildModelCandidates(\n\t\toptions.modelOverride ?? agent.model,\n\t\tagent.fallbackModels,\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, agentName, options.index);\n\t\tensureArtifactsDir(options.artifactsDir);\n\t\tif (options.artifactConfig?.includeInput !== false) {\n\t\t\t\twriteArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\\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: agentName,\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\tagent: agentName,\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, agent, 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 ?? agent.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\tagent: agentName,\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":"AACA;;GAEG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAQ1D,OAAO,EAKN,KAAK,cAAc,EACnB,KAAK,YAAY,EASjB,MAAM,uBAAuB,CAAC;AAgnC/B;;GAEG;AACH,wBAAsB,OAAO,CAC5B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,WAAW,EAAE,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,YAAY,CAAC,CAySvB","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 { AgentConfig } from \"../../agents/agents.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 { buildAgentMemoryInjection } from \"../../agents/agent-memory.ts\";\nimport { evaluateCompletionMutationGuard } from \"../shared/completion-guard.ts\";\nimport { getPiSpawnCommand } from \"../shared/pi-spawn.ts\";\nimport { createJsonlWriter } from \"../../shared/jsonl-writer.ts\";\nimport { attachPostExitStdioGuard, trySignalChild } from \"../../shared/post-exit-stdio-guard.ts\";\nimport { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from \"../shared/pi-args.ts\";\nimport { filterToolsForInheritedChild, 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 {\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\tagent: AgentConfig,\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 ?? agent.thinking;\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: agent.name,\n\t\t\trunId: options.runId,\n\t\t\tchildIndex: options.index ?? 0,\n\t\t})\n\t\t: undefined;\n\tconst parentPermissionMode = snapshotParentPermissionMode(options.parentSessionId);\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: agent.systemPromptMode,\n\t\tinheritProjectContext: agent.inheritProjectContext,\n\t\tinheritSkills: agent.inheritSkills,\n\t\trequireReadTool: Boolean(shared.resolvedSkillNames?.length),\n\t\ttools: filterToolsForInheritedChild(agent.tools, parentPermissionMode),\n\t\textensions: agent.extensions,\n\t\tsubagentOnlyExtensions: agent.subagentOnlyExtensions,\n\t\tsystemPrompt: appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget),\n\t\tmcpDirectTools: agent.mcpDirectTools,\n\t\tcwd: options.cwd ?? runtimeCwd,\n\t\tpromptFileStem: agent.name,\n\t\tintercomSessionName: options.intercomSessionName,\n\t\torchestratorIntercomTarget: options.orchestratorIntercomTarget,\n\t\trunId: options.runId,\n\t\tchildAgentName: agent.name,\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\tparentPermissionMode,\n\t});\n\n\tconst thinkingFromModel = modelArg ? splitKnownThinkingSuffix(modelArg).thinkingSuffix.slice(1) || undefined : undefined;\n\tconst result: SingleResult = {\n\t\tagent: agent.name,\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\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\tagent: agent.name,\n\t\tstatus: \"running\",\n\t\ttask,\n\t\tskills: shared.resolvedSkillNames,\n\t\tmodel: modelArg,\n\t\tthinking: thinkingFromModel,\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 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\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 = trySignalChild(proc, \"SIGTERM\");\n\t\t\t\tif (!termSent) return;\n\t\t\t\tforcedTerminationSignal = true;\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 = trySignalChild(proc, \"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 !== agent.name) 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: agent.name, 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\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: agent.name,\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: agent.name,\n\t\t\t\tindex: options.index,\n\t\t\t\tts: now,\n\t\t\t\tmessage: `${agent.name} 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\ttrySignalChild(proc, \"SIGINT\");\n\t\t\tturnBudgetTerminationTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || result.timedOut) return;\n\t\t\t\ttrySignalChild(proc, \"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\ttrySignalChild(proc, \"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 processLine = (line: string) => {\n\t\t\tif (!line.trim()) return;\n\t\t\tjsonlWriter.writeLine(line);\n\t\t\tlet evt: { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };\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\tagent: agent.name,\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\tfireUpdate();\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: `${agent.name} 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\ttrySignalChild(proc, \"SIGINT\");\n\t\t\t\ttimeoutTerminationTimer = setTimeout(() => {\n\t\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\t\ttrySignalChild(proc, \"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\ttrySignalChild(proc, \"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\ttrySignalChild(proc, \"SIGTERM\");\n\t\t\t\tprotocolHardKillTimer = setTimeout(() => {\n\t\t\t\t\tif (!childExited) trySignalChild(proc, \"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\tproc.on(\"exit\", () => {\n\t\t\tchildExited = true;\n\t\t\tclearFinalDrainTimers();\n\t\t});\n\t\tproc.on(\"close\", (code, signal) => {\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\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 (processClosed || detached) return;\n\t\t\t\tproc.kill(\"SIGTERM\");\n\t\t\t\tsetTimeout(() => !proc.killed && proc.kill(\"SIGKILL\"), 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\ttrySignalChild(proc, \"SIGINT\");\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tif (settled || processClosed || detached) return;\n\t\t\t\t\ttrySignalChild(proc, \"SIGTERM\");\n\t\t\t\t}, 1000).unref?.();\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 && agent.completionGuard !== false\n\t\t? evaluateCompletionMutationGuard({\n\t\t\tagent: agent.name,\n\t\t\ttask: shared.originalTask ?? task,\n\t\t\tmessages: result.messages,\n\t\t\ttools: agent.tools,\n\t\t\tmcpDirectTools: agent.mcpDirectTools,\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 ?? agent.name,\n\t\t\tagent: agent.name,\n\t\t\tindex: options.index,\n\t\t\tts: Date.now(),\n\t\t\tmessage: `${agent.name} 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\tagents: AgentConfig[],\n\tagentName: string,\n\ttask: string,\n\toptions: RunSyncOptions,\n): Promise<SingleResult> {\n\tconst agent = agents.find((a) => a.name === agentName);\n\tif (!agent) {\n\t\treturn {\n\t\t\tagent: agentName,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\terror: `Unknown agent: ${agentName}`,\n\t\t};\n\t}\n\tconst outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);\n\tif (outputModeValidationError) {\n\t\treturn {\n\t\t\tagent: agentName,\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,\n\t\tagentName,\n\t\tacceptanceRole: agent.acceptanceRole,\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 ?? agent.skills ?? [];\n\tconst skillCwd = options.cwd ?? runtimeCwd;\n\tconst { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(\n\t\tskillNames,\n\t\tskillCwd,\n\t\truntimeCwd,\n\t\tagent.skillPath,\n\t\tagent.filePath ? path.dirname(agent.filePath) : skillCwd,\n\t);\n\tif (skillNames.some((skill) => skill.trim() === \"pi-subagents\") && missingSkills.includes(\"pi-subagents\")) {\n\t\treturn {\n\t\t\tagent: agentName,\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 = agent.systemPrompt?.trim() || \"\";\n\tif (resolvedSkills.length > 0) {\n\t\tconst skillInjection = buildSkillInjection(resolvedSkills);\n\t\tsystemPrompt = systemPrompt ? `${systemPrompt}\\n\\n${skillInjection}` : skillInjection;\n\t}\n\tconst memoryInjection = buildAgentMemoryInjection(agent, skillCwd);\n\tif (memoryInjection) {\n\t\tsystemPrompt = systemPrompt ? `${systemPrompt}\\n\\n${memoryInjection}` : memoryInjection;\n\t}\n\tsystemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, agent);\n\n\tconst candidates = buildModelCandidates(\n\t\toptions.modelOverride ?? agent.model,\n\t\tagent.fallbackModels,\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, agentName, options.index);\n\t\tensureArtifactsDir(options.artifactsDir);\n\t\tif (options.artifactConfig?.includeInput !== false) {\n\t\t\t\twriteArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\\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: agentName,\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\tagent: agentName,\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, agent, 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 ?? agent.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\tagent: agentName,\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"]}
@@ -86,6 +86,21 @@ function stripAcceptanceReportsFromMessages(messages) {
86
86
  }
87
87
  }
88
88
  }
89
+ function extractThinkingText(content) {
90
+ if (!Array.isArray(content))
91
+ return undefined;
92
+ let latest = "";
93
+ for (const part of content) {
94
+ if (!part || typeof part !== "object")
95
+ continue;
96
+ if ("type" in part && part.type === "thinking" && "thinking" in part && typeof part.thinking === "string") {
97
+ const text = part.thinking.trim();
98
+ if (text)
99
+ latest = part.thinking;
100
+ }
101
+ }
102
+ return latest || undefined;
103
+ }
89
104
  function snapshotProgress(progress) {
90
105
  return {
91
106
  ...progress,
@@ -630,6 +645,12 @@ async function runSingleAttempt(runtimeCwd, agent, task, model, options, shared)
630
645
  progress.durationMs = now - startTime;
631
646
  progress.lastActivityAt = now;
632
647
  updateActivityState(now);
648
+ if ((evt.type === "message_update" || evt.type === "message_start") && evt.message) {
649
+ const thinkingText = extractThinkingText(evt.message.content);
650
+ if (thinkingText)
651
+ progress.thinkingText = thinkingText;
652
+ fireUpdate();
653
+ }
633
654
  if (evt.type === "tool_execution_start") {
634
655
  const toolArgs = evt.args && typeof evt.args === "object" && !Array.isArray(evt.args)
635
656
  ? evt.args