@ashx-j/lunr 0.2.11 → 0.2.13
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.
- package/CHANGELOG.md +13 -0
- package/README.md +211 -236
- package/dist/builtin-extensions/ashxj-tui.d.ts +3 -0
- package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.js +28 -11
- package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
- package/dist/builtin-extensions/index.d.ts.map +1 -1
- package/dist/builtin-extensions/index.js +0 -2
- package/dist/builtin-extensions/index.js.map +1 -1
- package/dist/builtin-extensions/lunr-cron.d.ts +1 -1
- package/dist/builtin-extensions/lunr-cron.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-cron.js +1 -1
- package/dist/builtin-extensions/lunr-cron.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.js +3 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/chain-validation.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/doctor.d.ts +0 -2
- package/dist/builtin-extensions/pi-subagents/src/extension/doctor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/doctor.js +1 -23
- package/dist/builtin-extensions/pi-subagents/src/extension/doctor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js +6 -7
- package/dist/builtin-extensions/pi-subagents/src/extension/fanout-child.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +7 -14
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts +12 -9
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +38 -34
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +3 -3
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +32 -47
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/intercom/intercom-bridge.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts +7 -10
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js +90 -109
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts +3 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js +37 -50
- package/dist/builtin-extensions/pi-subagents/src/runs/background/async-resume.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/run-status.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.d.ts +4 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.js +5 -5
- package/dist/builtin-extensions/pi-subagents/src/runs/background/scheduled-runs.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +31 -13
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-clarify.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts +4 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +48 -20
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +90 -64
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts +6 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +262 -161
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js +24 -33
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/acceptance.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts +25 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js +51 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/child-tools.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js +20 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/dynamic-fanout.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +4 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts +5 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js +9 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/pi-args.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js +20 -17
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/task-intent.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts +70 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js +153 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/child-spec.js.map +1 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/settings.d.ts +13 -8
- package/dist/builtin-extensions/pi-subagents/src/shared/settings.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/settings.js +20 -51
- package/dist/builtin-extensions/pi-subagents/src/shared/settings.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +80 -20
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js +10 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +20 -32
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +23 -449
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +13 -11
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.d.ts +2 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.js +18 -17
- package/dist/builtin-extensions/pi-subagents/src/watchdog/runtime.js.map +1 -1
- package/dist/builtin-extensions/simple-pi-memory.d.ts +2 -2
- package/dist/builtin-extensions/simple-pi-memory.d.ts.map +1 -1
- package/dist/builtin-extensions/simple-pi-memory.js +41 -17
- package/dist/builtin-extensions/simple-pi-memory.js.map +1 -1
- package/dist/catalog/models.json +1 -1
- package/dist/catalog/providers/amazon-bedrock.json +1 -1
- package/dist/catalog/providers/azure-openai-responses.json +1 -1
- package/dist/catalog/providers/cloudflare-ai-gateway.json +1 -1
- package/dist/catalog/providers/cloudflare-workers-ai.json +1 -1
- package/dist/catalog/providers/fireworks.json +1 -1
- package/dist/catalog/providers/github-copilot.json +1 -1
- package/dist/catalog/providers/huggingface.json +1 -1
- package/dist/catalog/providers/minimax-cn.json +1 -1
- package/dist/catalog/providers/minimax.json +1 -1
- package/dist/catalog/providers/mistral.json +1 -1
- package/dist/catalog/providers/nvidia.json +1 -1
- package/dist/catalog/providers/openai.json +1 -1
- package/dist/catalog/providers/opencode-go.json +1 -1
- package/dist/catalog/providers/opencode.json +1 -1
- package/dist/catalog/providers/openrouter.json +1 -1
- package/dist/catalog/providers/qwen-cloud-cn.json +1 -1
- package/dist/catalog/providers/qwen-cloud.json +1 -1
- package/dist/catalog/providers/qwen-token-plan-cn.json +1 -1
- package/dist/catalog/providers/qwen-token-plan.json +1 -1
- package/dist/catalog/providers/together.json +1 -1
- package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
- package/dist/catalog/providers/zai-coding-cn.json +1 -1
- package/dist/catalog/providers/zai.json +1 -1
- package/dist/catalog/publication.json +3 -3
- package/dist/core/agent-session.d.ts +7 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +34 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/context-breakdown.d.ts +3 -1
- package/dist/core/context-breakdown.d.ts.map +1 -1
- package/dist/core/context-breakdown.js +5 -2
- package/dist/core/context-breakdown.js.map +1 -1
- package/dist/core/customize.d.ts +2 -0
- package/dist/core/customize.d.ts.map +1 -1
- package/dist/core/customize.js +6 -0
- package/dist/core/customize.js.map +1 -1
- package/dist/core/memory-cap.d.ts +4 -8
- package/dist/core/memory-cap.d.ts.map +1 -1
- package/dist/core/memory-cap.js +8 -8
- package/dist/core/memory-cap.js.map +1 -1
- package/dist/core/permissions.d.ts +2 -0
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +30 -14
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +1 -9
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/rollback.d.ts.map +1 -1
- package/dist/core/rollback.js +1 -1
- package/dist/core/rollback.js.map +1 -1
- package/dist/core/settings-manager.d.ts +16 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +37 -10
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -5
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/subagent-permission-inherit.d.ts +34 -22
- package/dist/core/subagent-permission-inherit.d.ts.map +1 -1
- package/dist/core/subagent-permission-inherit.js +37 -80
- package/dist/core/subagent-permission-inherit.js.map +1 -1
- package/dist/core/swarm.d.ts.map +1 -1
- package/dist/core/swarm.js +13 -7
- package/dist/core/swarm.js.map +1 -1
- package/dist/core/system-prompt.d.ts +6 -4
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +38 -48
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/usage-adapters/openai-codex.d.ts +1 -1
- package/dist/core/usage-adapters/openai-codex.d.ts.map +1 -1
- package/dist/core/usage-adapters/openai-codex.js +22 -17
- package/dist/core/usage-adapters/openai-codex.js.map +1 -1
- package/dist/core/usage-service.d.ts +10 -1
- package/dist/core/usage-service.d.ts.map +1 -1
- package/dist/core/usage-service.js +17 -6
- package/dist/core/usage-service.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +1 -4
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +1 -4
- package/dist/gateway/cron.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +0 -5
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +2 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +21 -0
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +7 -4
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +122 -162
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/usage-view.d.ts +1 -1
- package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/usage-view.js +7 -4
- package/dist/modes/interactive/components/usage-view.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +55 -96
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/skill-tag-autocomplete.d.ts +13 -0
- package/dist/modes/interactive/skill-tag-autocomplete.d.ts.map +1 -0
- package/dist/modes/interactive/skill-tag-autocomplete.js +72 -0
- package/dist/modes/interactive/skill-tag-autocomplete.js.map +1 -0
- package/docs/compaction.md +23 -23
- package/docs/containerization.md +24 -24
- package/docs/custom-provider.md +18 -18
- package/docs/development.md +16 -15
- package/docs/docs.json +6 -2
- package/docs/extensions.md +82 -80
- package/docs/features.md +102 -0
- package/docs/index.md +25 -19
- package/docs/json.md +7 -7
- package/docs/keybindings.md +4 -4
- package/docs/models.md +2 -2
- package/docs/packages.md +38 -42
- package/docs/prompt-templates.md +4 -4
- package/docs/providers.md +22 -10
- package/docs/quickstart.md +81 -46
- package/docs/rpc.md +7 -13
- package/docs/sdk.md +43 -41
- package/docs/security.md +18 -18
- package/docs/session-format.md +9 -9
- package/docs/sessions.md +13 -12
- package/docs/settings.md +63 -24
- package/docs/shell-aliases.md +2 -2
- package/docs/skills.md +21 -11
- package/docs/terminal-setup.md +5 -5
- package/docs/termux.md +7 -7
- package/docs/themes.md +12 -14
- package/docs/tmux.md +4 -4
- package/docs/tui.md +23 -23
- package/docs/usage.md +112 -87
- package/docs/windows.md +10 -2
- package/examples/README.md +3 -1
- package/examples/extensions/README.md +6 -6
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/doom-overlay/README.md +2 -2
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/plan-mode/README.md +3 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +13 -11
- package/examples/extensions/todo.ts +2 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/README.md +9 -5
- package/package.json +6 -7
- package/dist/builtin-extensions/lunr-behavior.d.ts +0 -44
- package/dist/builtin-extensions/lunr-behavior.d.ts.map +0 -1
- package/dist/builtin-extensions/lunr-behavior.js +0 -213
- package/dist/builtin-extensions/lunr-behavior.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/agents/context-builder.md +0 -46
- package/dist/builtin-extensions/pi-subagents/agents/deep-researcher.md +0 -36
- package/dist/builtin-extensions/pi-subagents/agents/delegate.md +0 -12
- package/dist/builtin-extensions/pi-subagents/agents/oracle.md +0 -73
- package/dist/builtin-extensions/pi-subagents/agents/planner.md +0 -55
- package/dist/builtin-extensions/pi-subagents/agents/research-writer.md +0 -37
- package/dist/builtin-extensions/pi-subagents/agents/researcher.md +0 -52
- package/dist/builtin-extensions/pi-subagents/agents/reviewer.md +0 -79
- package/dist/builtin-extensions/pi-subagents/agents/scout.md +0 -50
- package/dist/builtin-extensions/pi-subagents/agents/worker.md +0 -55
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts +0 -20
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js +0 -1273
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.d.ts +0 -49
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.js +0 -252
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-memory.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.d.ts +0 -3
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.js +0 -6
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-scope.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.d.ts +0 -3
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.js +0 -23
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-selection.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.d.ts +0 -8
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.js +0 -143
- package/dist/builtin-extensions/pi-subagents/src/agents/agent-serializer.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agents.d.ts +0 -176
- package/dist/builtin-extensions/pi-subagents/src/agents/agents.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/agents.js +0 -1326
- package/dist/builtin-extensions/pi-subagents/src/agents/agents.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.d.ts +0 -7
- package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.js +0 -307
- package/dist/builtin-extensions/pi-subagents/src/agents/chain-serializer.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/identity.d.ts +0 -8
- package/dist/builtin-extensions/pi-subagents/src/agents/identity.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/identity.js +0 -30
- package/dist/builtin-extensions/pi-subagents/src/agents/identity.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.d.ts +0 -35
- package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.js +0 -151
- package/dist/builtin-extensions/pi-subagents/src/agents/proactive-skills.js.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.d.ts +0 -131
- package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.d.ts.map +0 -1
- package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.js +0 -491
- package/dist/builtin-extensions/pi-subagents/src/profiles/profiles.js.map +0 -1
- package/dist/core/behavior-preset.d.ts +0 -60
- package/dist/core/behavior-preset.d.ts.map +0 -1
- package/dist/core/behavior-preset.js +0 -257
- package/dist/core/behavior-preset.js.map +0 -1
|
@@ -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;AAmnC/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\t// lunr: compact rows no longer show thinking. Skip per-token\n\t\t\t\t// invalidates on message_update so the running glyph is not rebuilt\n\t\t\t\t// on every stream delta.\n\t\t\t\tif (evt.type === \"message_start\") fireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_execution_start\") {\n\t\t\t\tconst toolArgs = evt.args && typeof evt.args === \"object\" && !Array.isArray(evt.args)\n\t\t\t\t\t? evt.args as Record<string, unknown>\n\t\t\t\t\t: {};\n\t\t\t\tif (options.allowIntercomDetach && (evt.toolName === \"intercom\" || evt.toolName === \"contact_supervisor\")) {\n\t\t\t\t\tintercomStarted = true;\n\t\t\t\t}\n\t\t\t\tprogress.toolCount++;\n\t\t\t\tif (options.toolBudget) {\n\t\t\t\t\tresult.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);\n\t\t\t\t}\n\t\t\t\tprogress.currentTool = evt.toolName;\n\t\t\t\tprogress.currentToolArgs = extractToolArgsPreview(toolArgs);\n\t\t\t\tprogress.currentToolStartedAt = now;\n\t\t\t\tprogress.currentPath = resolveCurrentPath(evt.toolName, toolArgs);\n\t\t\t\tconst mutates = isMutatingTool(evt.toolName, toolArgs);\n\t\t\t\tobservedMutationAttempt = observedMutationAttempt || mutates;\n\t\t\t\tpendingToolResult = { tool: evt.toolName ?? \"tool\", path: progress.currentPath, mutates, startedAt: now };\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_execution_end\") {\n\t\t\t\tif (progress.currentTool) {\n\t\t\t\t\tprogress.recentTools.push({\n\t\t\t\t\t\ttool: progress.currentTool,\n\t\t\t\t\t\targs: progress.currentToolArgs || \"\",\n\t\t\t\t\t\tendMs: now,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tprogress.currentTool = undefined;\n\t\t\t\tprogress.currentToolArgs = undefined;\n\t\t\t\tprogress.currentToolStartedAt = undefined;\n\t\t\t\tprogress.currentPath = undefined;\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"message_end\" && evt.message) {\n\t\t\t\tresult.messages.push(evt.message);\n\t\t\t\tif (evt.message.role === \"assistant\") {\n\t\t\t\t\tresult.usage.turns++;\n\t\t\t\t\tprogress.turnCount = result.usage.turns;\n\t\t\t\t\tconst stopReason = (evt.message as { stopReason?: string }).stopReason;\n\t\t\t\t\tconst hasToolCall = Array.isArray(evt.message.content)\n\t\t\t\t\t\t&& evt.message.content.some((part) => (part as { type?: string }).type === \"toolCall\");\n\t\t\t\t\tconst terminalAssistantStop = stopReason === \"stop\" && !hasToolCall;\n\t\t\t\t\tupdateTurnBudget(result.usage.turns, terminalAssistantStop);\n\t\t\t\t\tconst u = evt.message.usage;\n\t\t\t\t\tif (u) {\n\t\t\t\t\t\tresult.usage.input += u.input || 0;\n\t\t\t\t\t\tresult.usage.output += u.output || 0;\n\t\t\t\t\t\tresult.usage.cacheRead += u.cacheRead || 0;\n\t\t\t\t\t\tresult.usage.cacheWrite += u.cacheWrite || 0;\n\t\t\t\t\t\tresult.usage.cost += u.cost?.total || 0;\n\t\t\t\t\t\tprogress.tokens = result.usage.input + result.usage.output;\n\t\t\t\t\t}\n\t\t\t\t\tif (!result.model && evt.message.model) {\n\t\t\t\t\t\tresult.model = evt.message.model;\n\t\t\t\t\t\tconst parsed = splitKnownThinkingSuffix(evt.message.model);\n\t\t\t\t\t\tif (parsed.thinkingSuffix) result.thinking = parsed.thinkingSuffix.slice(1);\n\t\t\t\t\t\tprogress.model = result.model;\n\t\t\t\t\t\tprogress.thinking = result.thinking;\n\t\t\t\t\t}\n\t\t\t\t\tif (evt.message.errorMessage) assistantError = evt.message.errorMessage;\n\t\t\t\t\tconst assistantText = extractTextFromContent(evt.message.content);\n\t\t\t\t\tappendRecentOutput(progress, assistantText.split(\"\\n\").slice(-10));\n\t\t\t\t\t// Final assistant message: start the exit drain window.\n\t\t\t\t\tif (terminalAssistantStop) {\n\t\t\t\t\t\tif (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;\n\t\t\t\t\t\tcleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;\n\t\t\t\t\t\tapplyChildLifecycle(projectChildLifecycle(evt, true));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tupdateActivityState(now);\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_result_end\" && evt.message) {\n\t\t\t\tresult.messages.push(evt.message);\n\t\t\t\tconst resultText = extractTextFromContent(evt.message.content);\n\t\t\t\tif (options.toolBudget && pendingToolResult && resultText.includes(\"Tool budget hard limit reached\")) {\n\t\t\t\t\tresult.toolBudgetBlocked = true;\n\t\t\t\t\tresult.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount, pendingToolResult.tool);\n\t\t\t\t}\n\t\t\t\tappendRecentOutput(progress, resultText.split(\"\\n\").slice(-10));\n\t\t\t\tconst toolSnapshot = pendingToolResult;\n\t\t\t\tpendingToolResult = undefined;\n\t\t\t\tif (toolSnapshot?.mutates && didMutatingToolFail(resultText)) {\n\t\t\t\t\trecordMutatingFailure(mutatingFailures, {\n\t\t\t\t\t\ttool: toolSnapshot.tool,\n\t\t\t\t\t\tpath: toolSnapshot.path,\n\t\t\t\t\t\terror: resultText.split(\"\\n\").find((line) => line.trim())?.trim().slice(0, 180) ?? \"mutating tool failed\",\n\t\t\t\t\t\tts: now,\n\t\t\t\t\t}, mutatingFailureWindowMs);\n\t\t\t\t\tif (shouldEscalateMutatingFailures(mutatingFailures, controlConfig.failedToolAttemptsBeforeAttention)) {\n\t\t\t\t\t\temitNeedsAttention(now, {\n\t\t\t\t\t\t\tmessage: `${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,SAAS,EAAE,MAAM,uBAAuB,CAAC;AASvD,OAAO,EAKN,KAAK,cAAc,EACnB,KAAK,YAAY,EASjB,MAAM,uBAAuB,CAAC;AA+nC/B;;GAEG;AACH,wBAAsB,OAAO,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,YAAY,CAAC,CAuTvB","sourcesContent":["// @ts-nocheck\n/**\n * Core execution logic for running subagents\n */\n\nimport { spawn } from \"node:child_process\";\nimport { splitKnownThinkingSuffix } from \"../../shared/model-info.ts\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { Message } from \"@ashx-j/lunr-ai\";\nimport type { ChildSpec } from \"../../shared/types.ts\";\nimport { childRowLabel } from \"../../shared/types.ts\";\nimport {\n\tensureArtifactsDir,\n\tgetArtifactPaths,\n\twriteArtifact,\n\twriteMetadata,\n} from \"../../shared/artifacts.ts\";\nimport { createChildTranscriptWriter, type ChildTranscriptWriter } from \"../../shared/child-transcript.ts\";\nimport {\n\ttype AgentProgress,\n\ttype ArtifactPaths,\n\ttype ControlEvent,\n\ttype ModelAttempt,\n\ttype RunSyncOptions,\n\ttype SingleResult,\n\ttype Usage,\n\tDEFAULT_MAX_OUTPUT,\n\tINTERCOM_DETACH_REQUEST_EVENT,\n\tINTERCOM_DETACH_RESPONSE_EVENT,\n\ttype AcceptanceLedger,\n\ttype ResolvedAcceptanceConfig,\n\ttruncateOutput,\n\tgetSubagentDepthEnv,\n} from \"../../shared/types.ts\";\nimport {\n\tDEFAULT_CONTROL_CONFIG,\n\tbuildControlEvent,\n\tclaimControlNotification,\n\tderiveActivityState,\n\tshouldNotifyControlEvent,\n} from \"../shared/subagent-control.ts\";\nimport {\n\tgetFinalOutput,\n\tfindLatestSessionFile,\n\tdetectSubagentError,\n\textractToolArgsPreview,\n\textractTextFromContent,\n} from \"../../shared/utils.ts\";\nimport { buildSkillInjection, resolveSkillsWithFallback } from \"../../agents/skills.ts\";\nimport { resolveChildExcludeTools } from \"../shared/child-tools.ts\";\nimport { normalizeSkillInput } from \"../../agents/skills.ts\";\nimport { evaluateCompletionMutationGuard } from \"../shared/completion-guard.ts\";\nimport { getPiSpawnCommand } from \"../shared/pi-spawn.ts\";\nimport { createJsonlWriter } from \"../../shared/jsonl-writer.ts\";\nimport { attachPostExitStdioGuard, trySignalChild } from \"../../shared/post-exit-stdio-guard.ts\";\nimport { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from \"../shared/pi-args.ts\";\nimport { PLAN_MODE_WRITE_SPAWN_ERROR, resolveChildPermissions, snapshotParentPermissionMode } from \"../../../../../core/subagent-permission-inherit.ts\";\nimport { readStructuredOutput } from \"../shared/structured-output.ts\";\nimport { readChildToolDiagnosticError } from \"../shared/tool-availability.ts\";\nimport { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from \"../shared/single-output.ts\";\nimport {\n\tbuildModelCandidates,\n\tformatModelAttemptNote,\n\tisRetryableModelFailure,\n} from \"../shared/model-fallback.ts\";\nimport {\n\tcreateMutatingFailureState,\n\tdidMutatingToolFail,\n\tisMutatingTool,\n\tnextLongRunningTrigger,\n\trecordMutatingFailure,\n\tresetMutatingFailureState,\n\tresolveCurrentPath,\n\tshouldEscalateMutatingFailures,\n\tsummarizeRecentMutatingFailures,\n} from \"../shared/long-running-guard.ts\";\nimport { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from \"../shared/acceptance.ts\";\nimport { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, shouldAbortForTurnBudget, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from \"../shared/turn-budget.ts\";\nimport { initialToolBudgetState, toolBudgetState } from \"../shared/tool-budget.ts\";\nimport { resolveWatchdogConfig } from \"../../watchdog/settings.ts\";\nimport { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from \"../shared/child-protocol.ts\";\nimport {\n\tacceptChildWatchdogEvent,\n\tchildWatchdogIsActive,\n\tisChildWatchdogStatusEvent,\n\tresolveChildWatchdogConfig,\n\ttype ChildWatchdogStateSnapshot,\n} from \"../../watchdog/child-status.ts\";\n\nconst artifactOutputByResult = new WeakMap<SingleResult, string>();\nconst acceptanceOutputByResult = new WeakMap<SingleResult, string>();\n\nfunction emptyUsage(): Usage {\n\treturn { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };\n}\n\nfunction sumUsage(target: Usage, source: Usage): void {\n\ttarget.input += source.input;\n\ttarget.output += source.output;\n\ttarget.cacheRead += source.cacheRead;\n\ttarget.cacheWrite += source.cacheWrite;\n\ttarget.cost += source.cost;\n\ttarget.turns += source.turns;\n}\n\nfunction formatTimeoutMessage(timeoutMs: number): string {\n\treturn `Subagent timed out after ${timeoutMs}ms.`;\n}\n\nfunction resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; remainingMs: number; message: string } | undefined {\n\tif (options.timeoutMs === undefined) return undefined;\n\tconst deadlineAt = options.deadlineAt ?? Date.now() + options.timeoutMs;\n\treturn {\n\t\ttimeoutMs: options.timeoutMs,\n\t\tremainingMs: Math.max(0, deadlineAt - Date.now()),\n\t\tmessage: formatTimeoutMessage(options.timeoutMs),\n\t};\n}\n\nfunction buildPendingAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {\n\treturn {\n\t\tstatus: \"pending\",\n\t\texplicit: acceptance.explicit,\n\t\teffectiveAcceptance: acceptance,\n\t\tinferredReason: acceptance.inferredReason,\n\t\tcriteria: acceptance.criteria,\n\t\truntimeChecks: [],\n\t\tverifyRuns: [],\n\t};\n}\n\nfunction appendRecentOutput(progress: AgentProgress, lines: string[]): void {\n\tif (lines.length === 0) return;\n\tprogress.recentOutput.push(...lines.filter((line) => line.trim()));\n\tif (progress.recentOutput.length > 50) {\n\t\tprogress.recentOutput.splice(0, progress.recentOutput.length - 50);\n\t}\n}\n\nfunction stripAcceptanceReportsFromMessages(messages: Message[] | undefined): void {\n\tfor (const message of messages ?? []) {\n\t\tif (message.role !== \"assistant\" || !Array.isArray(message.content)) continue;\n\t\tfor (const part of message.content) {\n\t\t\tif (part.type === \"text\" && \"text\" in part && typeof part.text === \"string\") {\n\t\t\t\tpart.text = stripAcceptanceReport(part.text);\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction extractThinkingText(content: unknown): string | undefined {\n\tif (!Array.isArray(content)) return undefined;\n\tlet latest = \"\";\n\tfor (const part of content) {\n\t\tif (!part || typeof part !== \"object\") continue;\n\t\tif (\"type\" in part && part.type === \"thinking\" && \"thinking\" in part && typeof part.thinking === \"string\") {\n\t\t\tconst text = part.thinking.trim();\n\t\t\tif (text) latest = part.thinking;\n\t\t}\n\t}\n\treturn latest || undefined;\n}\n\nfunction snapshotProgress(progress: AgentProgress): AgentProgress {\n\treturn {\n\t\t...progress,\n\t\tskills: progress.skills ? [...progress.skills] : undefined,\n\t\trecentTools: progress.recentTools.map((tool) => ({ ...tool })),\n\t\trecentOutput: [...progress.recentOutput],\n\t};\n}\n\nfunction snapshotResult(result: SingleResult, progress: AgentProgress): SingleResult {\n\treturn {\n\t\t...result,\n\t\tmessages: result.outputMode === \"file-only\" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined,\n\t\tusage: { ...result.usage },\n\t\tskills: result.skills ? [...result.skills] : undefined,\n\t\tattemptedModels: result.attemptedModels ? [...result.attemptedModels] : undefined,\n\t\tmodelAttempts: result.modelAttempts\n\t\t\t? result.modelAttempts.map((attempt) => ({\n\t\t\t\t...attempt,\n\t\t\t\tusage: attempt.usage ? { ...attempt.usage } : undefined,\n\t\t\t}))\n\t\t\t: undefined,\n\t\tcontrolEvents: result.controlEvents ? result.controlEvents.map((event) => ({ ...event })) : undefined,\n\t\tprogress,\n\t\tprogressSummary: result.progressSummary ? { ...result.progressSummary } : undefined,\n\t\tartifactPaths: result.artifactPaths ? { ...result.artifactPaths } : undefined,\n\t\ttruncation: result.truncation ? { ...result.truncation } : undefined,\n\t\toutputReference: result.outputReference ? { ...result.outputReference } : undefined,\n\t};\n}\n\nasync function runSingleAttempt(\n\truntimeCwd: string,\n\tspec: ChildSpec,\n\ttask: string,\n\tmodel: string | undefined,\n\toptions: RunSyncOptions,\n\tshared: {\n\t\tsessionEnabled: boolean;\n\t\tsystemPrompt: string;\n\t\tresolvedSkillNames?: string[];\n\t\tskillsWarning?: string;\n\t\tjsonlPath?: string;\n\t\tartifactPaths?: ArtifactPaths;\n\t\ttranscriptWriter?: ChildTranscriptWriter;\n\t\tattemptNotes: string[];\n\t\toutputSnapshot?: SingleOutputSnapshot;\n\t\toriginalTask?: string;\n\t},\n): Promise<SingleResult> {\n\tconst effectiveThinking = options.thinkingOverride;\n\tconst modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);\n\tconst watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);\n\tconst childWatchdog = watchdogConfig.ok\n\t\t? resolveChildWatchdogConfig({\n\t\t\tconfig: watchdogConfig.config,\n\t\t\tagent: spec.childId,\n\t\t\trunId: options.runId,\n\t\t\tchildIndex: options.index ?? 0,\n\t\t})\n\t\t: undefined;\n\tconst excludeTools = resolveChildExcludeTools({\n\t\tpermissions: spec.effectivePermissions,\n\t\tfanoutAuthorized: spec.fanoutAuthorized,\n\t});\n\tconst appendPrompt = appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget);\n\tconst { args, env: sharedEnv, tempDir, toolDiagnosticPath } = buildPiArgs({\n\t\tbaseArgs: [\"--mode\", \"json\", \"-p\"],\n\t\ttask,\n\t\tsessionEnabled: shared.sessionEnabled,\n\t\tsessionDir: options.sessionDir,\n\t\tsessionFile: options.sessionFile,\n\t\tmodel: modelArg,\n\t\tthinking: effectiveThinking,\n\t\tsystemPromptMode: \"append\",\n\t\tinheritProjectContext: true,\n\t\tinheritSkills: false,\n\t\trequireReadTool: Boolean(shared.resolvedSkillNames?.length),\n\t\texcludeTools,\n\t\tchildPermission: spec.effectivePermissions,\n\t\tchildId: spec.childId,\n\t\tchildDescription: spec.description,\n\t\tsystemPrompt: appendPrompt || undefined,\n\t\tcwd: options.cwd ?? runtimeCwd,\n\t\tpromptFileStem: spec.childId,\n\t\tintercomSessionName: options.intercomSessionName,\n\t\torchestratorIntercomTarget: options.orchestratorIntercomTarget,\n\t\trunId: options.runId,\n\t\tchildAgentName: spec.childId,\n\t\tchildIndex: options.index ?? 0,\n\t\tparentEventSink: options.nestedRoute?.eventSink,\n\t\tparentControlInbox: options.nestedRoute?.controlInbox,\n\t\tparentRootRunId: options.nestedRoute?.rootRunId,\n\t\tparentCapabilityToken: options.nestedRoute?.capabilityToken,\n\t\tparentSessionId: options.parentSessionId,\n\t\tstructuredOutput: options.structuredOutput,\n\t\ttoolBudget: options.toolBudget,\n\t\tchildWatchdog,\n\t\twaitToolEnabled: options.waitToolEnabled,\n\t});\n\n\tconst thinkingFromModel = modelArg ? splitKnownThinkingSuffix(modelArg).thinkingSuffix.slice(1) || undefined : undefined;\n\tconst result: SingleResult = {\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\ttask: shared.originalTask ?? task,\n\t\texitCode: 0,\n\t\tmessages: [],\n\t\tusage: emptyUsage(),\n\t\tmodel: modelArg,\n\t\tthinking: thinkingFromModel,\n\t\tartifactPaths: shared.artifactPaths,\n\t\ttranscriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,\n\t\tskills: shared.resolvedSkillNames,\n\t\tskillsWarning: shared.skillsWarning,\n\t\t...(options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {}),\n\t\t...(options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}),\n\t};\n\tconst startTime = Date.now();\n\tif (options.structuredOutput) {\n\t\ttry {\n\t\t\tif (existsSync(options.structuredOutput.outputPath)) unlinkSync(options.structuredOutput.outputPath);\n\t\t} catch {\n\t\t\t// Missing/stale structured-output files are handled after the child exits.\n\t\t}\n\t}\n\tconst controlConfig = options.controlConfig ?? DEFAULT_CONTROL_CONFIG;\n\tlet interruptedByControl = false;\n\tconst allControlEvents: ControlEvent[] = [];\n\tlet pendingControlEvents: ControlEvent[] = [];\n\tconst emittedControlEventKeys = new Set<string>();\n\tconst emitControlEvent = (event: ControlEvent) => {\n\t\tif (!shouldNotifyControlEvent(controlConfig, event)) return;\n\t\tif (!claimControlNotification(controlConfig, event, emittedControlEventKeys)) return;\n\t\tallControlEvents.push(event);\n\t\tpendingControlEvents.push(event);\n\t\toptions.onControlEvent?.(event);\n\t};\n\n\tconst progress: AgentProgress = {\n\t\tindex: options.index ?? 0,\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\tstatus: \"running\",\n\t\ttask,\n\t\tskills: shared.resolvedSkillNames,\n\t\tmodel: modelArg,\n\t\tthinking: thinkingFromModel,\n\t\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 !== spec.childId && event.agent !== spec.description) return;\n\t\t\t\tif (typeof event.childIndex === \"number\" && event.childIndex !== (options.index ?? 0)) return;\n\t\t\t} else if (!intercomStarted) return;\n\t\t\toptions.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true, runId: options.runId, agent: spec.childId, childIndex: options.index ?? 0 });\n\t\t\tdetachForIntercom();\n\t\t});\n\n\t\tconst finish = (code: number) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tclearFinalDrainTimers();\n\t\t\tclearWatchdogTailTimer();\n\t\t\tclearStdioGuard();\n\t\t\tclearTimeoutTimers();\n\t\t\tclearTurnBudgetTimers();\n\t\t\tif (protocolHardKillTimer) {\n\t\t\t\tclearTimeout(protocolHardKillTimer);\n\t\t\t\tprotocolHardKillTimer = undefined;\n\t\t\t}\n\t\t\tif (activityTimer) {\n\t\t\t\tclearInterval(activityTimer);\n\t\t\t\tactivityTimer = undefined;\n\t\t\t}\n\t\t\tunsubscribeIntercomDetach?.();\n\t\t\tremoveAbortListener?.();\n\t\t\tremoveInterruptListener?.();\n\t\t\tresolve(code);\n\t\t};\n\n\t\tconst drainPendingControlEvents = (): ControlEvent[] | undefined => {\n\t\t\tif (pendingControlEvents.length === 0) return undefined;\n\t\t\tconst events = pendingControlEvents;\n\t\t\tpendingControlEvents = [];\n\t\t\treturn events;\n\t\t};\n\n\t\tlet activeLongRunningNotified = false;\n\t\tlet pendingToolResult: { tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined;\n\t\tconst mutatingFailures = createMutatingFailureState();\n\t\tconst mutatingFailureWindowMs = 5 * 60_000;\n\t\tconst currentToolDurationMs = (now: number) => progress.currentToolStartedAt ? Math.max(0, now - progress.currentToolStartedAt) : undefined;\n\t\tconst emitNeedsAttention = (now: number, input: { message?: string; reason?: ControlEvent[\"reason\"]; recentFailureSummary?: string; currentTool?: string; currentPath?: string; currentToolDurationMs?: number } = {}): boolean => {\n\t\t\tif (!controlConfig.enabled) return false;\n\t\t\tconst previous = progress.activityState;\n\t\t\tprogress.activityState = \"needs_attention\";\n\t\t\tconst event = buildControlEvent({\n\t\t\t\ttype: \"needs_attention\",\n\t\t\t\tfrom: previous,\n\t\t\t\tto: \"needs_attention\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.description,\n\t\t\t\tindex: options.index,\n\t\t\t\tts: now,\n\t\t\t\tlastActivityAt: progress.lastActivityAt,\n\t\t\t\tmessage: input.message,\n\t\t\t\treason: input.reason ?? \"idle\",\n\t\t\t\tturns: result.usage.turns,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t\ttoolCount: progress.toolCount,\n\t\t\t\tcurrentTool: input.currentTool ?? progress.currentTool,\n\t\t\t\tcurrentToolDurationMs: input.currentToolDurationMs ?? currentToolDurationMs(now),\n\t\t\t\tcurrentPath: input.currentPath ?? progress.currentPath,\n\t\t\t\trecentFailureSummary: input.recentFailureSummary,\n\t\t\t});\n\t\t\temitControlEvent(event);\n\t\t\treturn previous !== \"needs_attention\";\n\t\t};\n\t\tconst emitActiveLongRunning = (now: number, reason: ControlEvent[\"reason\"]): boolean => {\n\t\t\tif (!controlConfig.enabled || activeLongRunningNotified || progress.activityState === \"needs_attention\") return false;\n\t\t\tactiveLongRunningNotified = true;\n\t\t\tconst previous = progress.activityState;\n\t\t\tprogress.activityState = \"active_long_running\";\n\t\t\temitControlEvent(buildControlEvent({\n\t\t\t\ttype: \"active_long_running\",\n\t\t\t\tfrom: previous,\n\t\t\t\tto: \"active_long_running\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.description,\n\t\t\t\tindex: options.index,\n\t\t\t\tts: now,\n\t\t\t\tmessage: `${spec.description} is still active but long-running`,\n\t\t\t\treason,\n\t\t\t\tturns: result.usage.turns,\n\t\t\t\ttokens: progress.tokens,\n\t\t\t\ttoolCount: progress.toolCount,\n\t\t\t\tcurrentTool: progress.currentTool,\n\t\t\t\tcurrentToolDurationMs: currentToolDurationMs(now),\n\t\t\t\tcurrentPath: progress.currentPath,\n\t\t\t\telapsedMs: now - startTime,\n\t\t\t}));\n\t\t\treturn true;\n\t\t};\n\t\tconst requestTurnBudgetAbort = (turnCount: number) => {\n\t\t\tconst budget = options.turnBudget;\n\t\t\tif (!budget || result.timedOut || result.turnBudgetExceeded || interruptedByControl || processClosed || settled || detached) return;\n\t\t\tconst message = turnBudgetExceededMessage(budget, turnCount);\n\t\t\tresult.turnBudgetExceeded = true;\n\t\t\tresult.wrapUpRequested = true;\n\t\t\tresult.turnBudget = turnBudgetState(budget, turnCount, true);\n\t\t\tresult.error = message;\n\t\t\tresult.finalOutput = message;\n\t\t\tprogress.status = \"failed\";\n\t\t\tprogress.error = message;\n\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\tfireUpdate();\n\t\t\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\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\t\t\t\tchildIndex: options.index ?? 0,\n\t\t\t\t});\n\t\t\t\tif (!next) return;\n\t\t\t\tupdateChildWatchdogState(next);\n\t\t\t\tif (childWatchdogIsActive(next)) {\n\t\t\t\t\tclearFinalDrainTimers();\n\t\t\t\t\tarmWatchdogTail();\n\t\t\t\t} else {\n\t\t\t\t\tclearWatchdogTailTimer();\n\t\t\t\t\tif (cleanTerminalAssistantStopReceived || agentSettledReceived) startFinalDrain();\n\t\t\t\t}\n\t\t\t\tfireUpdate();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst now = Date.now();\n\t\t\tprogress.durationMs = now - startTime;\n\t\t\tprogress.lastActivityAt = now;\n\t\t\tupdateActivityState(now);\n\n\t\t\tif ((evt.type === \"message_update\" || evt.type === \"message_start\") && evt.message) {\n\t\t\t\tconst thinkingText = extractThinkingText(evt.message.content);\n\t\t\t\tif (thinkingText) progress.thinkingText = thinkingText;\n\t\t\t\t// lunr: compact rows no longer show thinking. Skip per-token\n\t\t\t\t// invalidates on message_update so the running glyph is not rebuilt\n\t\t\t\t// on every stream delta.\n\t\t\t\tif (evt.type === \"message_start\") fireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_execution_start\") {\n\t\t\t\tconst toolArgs = evt.args && typeof evt.args === \"object\" && !Array.isArray(evt.args)\n\t\t\t\t\t? evt.args as Record<string, unknown>\n\t\t\t\t\t: {};\n\t\t\t\tif (options.allowIntercomDetach && (evt.toolName === \"intercom\" || evt.toolName === \"contact_supervisor\")) {\n\t\t\t\t\tintercomStarted = true;\n\t\t\t\t}\n\t\t\t\tprogress.toolCount++;\n\t\t\t\tif (options.toolBudget) {\n\t\t\t\t\tresult.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);\n\t\t\t\t}\n\t\t\t\tprogress.currentTool = evt.toolName;\n\t\t\t\tprogress.currentToolArgs = extractToolArgsPreview(toolArgs);\n\t\t\t\tprogress.currentToolStartedAt = now;\n\t\t\t\tprogress.currentPath = resolveCurrentPath(evt.toolName, toolArgs);\n\t\t\t\tconst mutates = isMutatingTool(evt.toolName, toolArgs);\n\t\t\t\tobservedMutationAttempt = observedMutationAttempt || mutates;\n\t\t\t\tpendingToolResult = { tool: evt.toolName ?? \"tool\", path: progress.currentPath, mutates, startedAt: now };\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_execution_end\") {\n\t\t\t\tif (progress.currentTool) {\n\t\t\t\t\tprogress.recentTools.push({\n\t\t\t\t\t\ttool: progress.currentTool,\n\t\t\t\t\t\targs: progress.currentToolArgs || \"\",\n\t\t\t\t\t\tendMs: now,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tprogress.currentTool = undefined;\n\t\t\t\tprogress.currentToolArgs = undefined;\n\t\t\t\tprogress.currentToolStartedAt = undefined;\n\t\t\t\tprogress.currentPath = undefined;\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"message_end\" && evt.message) {\n\t\t\t\tresult.messages.push(evt.message);\n\t\t\t\tif (evt.message.role === \"assistant\") {\n\t\t\t\t\tresult.usage.turns++;\n\t\t\t\t\tprogress.turnCount = result.usage.turns;\n\t\t\t\t\tconst stopReason = (evt.message as { stopReason?: string }).stopReason;\n\t\t\t\t\tconst hasToolCall = Array.isArray(evt.message.content)\n\t\t\t\t\t\t&& evt.message.content.some((part) => (part as { type?: string }).type === \"toolCall\");\n\t\t\t\t\tconst terminalAssistantStop = stopReason === \"stop\" && !hasToolCall;\n\t\t\t\t\tupdateTurnBudget(result.usage.turns, terminalAssistantStop);\n\t\t\t\t\tconst u = evt.message.usage;\n\t\t\t\t\tif (u) {\n\t\t\t\t\t\tresult.usage.input += u.input || 0;\n\t\t\t\t\t\tresult.usage.output += u.output || 0;\n\t\t\t\t\t\tresult.usage.cacheRead += u.cacheRead || 0;\n\t\t\t\t\t\tresult.usage.cacheWrite += u.cacheWrite || 0;\n\t\t\t\t\t\tresult.usage.cost += u.cost?.total || 0;\n\t\t\t\t\t\tprogress.tokens = result.usage.input + result.usage.output;\n\t\t\t\t\t}\n\t\t\t\t\tif (!result.model && evt.message.model) {\n\t\t\t\t\t\tresult.model = evt.message.model;\n\t\t\t\t\t\tconst parsed = splitKnownThinkingSuffix(evt.message.model);\n\t\t\t\t\t\tif (parsed.thinkingSuffix) result.thinking = parsed.thinkingSuffix.slice(1);\n\t\t\t\t\t\tprogress.model = result.model;\n\t\t\t\t\t\tprogress.thinking = result.thinking;\n\t\t\t\t\t}\n\t\t\t\t\tif (evt.message.errorMessage) assistantError = evt.message.errorMessage;\n\t\t\t\t\tconst assistantText = extractTextFromContent(evt.message.content);\n\t\t\t\t\tappendRecentOutput(progress, assistantText.split(\"\\n\").slice(-10));\n\t\t\t\t\t// Final assistant message: start the exit drain window.\n\t\t\t\t\tif (terminalAssistantStop) {\n\t\t\t\t\t\tif (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;\n\t\t\t\t\t\tcleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;\n\t\t\t\t\t\tapplyChildLifecycle(projectChildLifecycle(evt, true));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tupdateActivityState(now);\n\t\t\t\tfireUpdate();\n\t\t\t}\n\n\t\t\tif (evt.type === \"tool_result_end\" && evt.message) {\n\t\t\t\tresult.messages.push(evt.message);\n\t\t\t\tconst resultText = extractTextFromContent(evt.message.content);\n\t\t\t\tif (options.toolBudget && pendingToolResult && resultText.includes(\"Tool budget hard limit reached\")) {\n\t\t\t\t\tresult.toolBudgetBlocked = true;\n\t\t\t\t\tresult.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount, pendingToolResult.tool);\n\t\t\t\t}\n\t\t\t\tappendRecentOutput(progress, resultText.split(\"\\n\").slice(-10));\n\t\t\t\tconst toolSnapshot = pendingToolResult;\n\t\t\t\tpendingToolResult = undefined;\n\t\t\t\tif (toolSnapshot?.mutates && didMutatingToolFail(resultText)) {\n\t\t\t\t\trecordMutatingFailure(mutatingFailures, {\n\t\t\t\t\t\ttool: toolSnapshot.tool,\n\t\t\t\t\t\tpath: toolSnapshot.path,\n\t\t\t\t\t\terror: resultText.split(\"\\n\").find((line) => line.trim())?.trim().slice(0, 180) ?? \"mutating tool failed\",\n\t\t\t\t\t\tts: now,\n\t\t\t\t\t}, mutatingFailureWindowMs);\n\t\t\t\t\tif (shouldEscalateMutatingFailures(mutatingFailures, controlConfig.failedToolAttemptsBeforeAttention)) {\n\t\t\t\t\t\temitNeedsAttention(now, {\n\t\t\t\t\t\t\tmessage: `${spec.description} needs attention after repeated mutating tool failures`,\n\t\t\t\t\t\t\treason: \"tool_failures\",\n\t\t\t\t\t\t\tcurrentTool: toolSnapshot.tool,\n\t\t\t\t\t\t\tcurrentPath: toolSnapshot.path,\n\t\t\t\t\t\t\tcurrentToolDurationMs: toolSnapshot.startedAt ? Math.max(0, now - toolSnapshot.startedAt) : undefined,\n\t\t\t\t\t\t\trecentFailureSummary: summarizeRecentMutatingFailures(mutatingFailures),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if (toolSnapshot?.mutates) {\n\t\t\t\t\tresetMutatingFailureState(mutatingFailures);\n\t\t\t\t}\n\t\t\t\tfireUpdate();\n\t\t\t}\n\t\t};\n\n\t\tif (controlConfig.enabled) {\n\t\t\tactivityTimer = setInterval(() => {\n\t\t\t\tif (processClosed || settled || detached) return;\n\t\t\t\tconst now = Date.now();\n\t\t\t\tif (updateActivityState(now)) {\n\t\t\t\t\tprogress.durationMs = now - startTime;\n\t\t\t\t\tfireUpdate();\n\t\t\t\t}\n\t\t\t}, 1000);\n\t\t\tactivityTimer.unref?.();\n\t\t}\n\n\t\tif (attemptTimeout) {\n\t\t\ttimeoutTimer = setTimeout(() => {\n\t\t\t\tif (processClosed || settled || detached || interruptedByControl) return;\n\t\t\t\tresult.timedOut = true;\n\t\t\t\tresult.error = attemptTimeout.message;\n\t\t\t\tresult.finalOutput = attemptTimeout.message;\n\t\t\t\tprogress.status = \"failed\";\n\t\t\t\tprogress.error = attemptTimeout.message;\n\t\t\t\tprogress.durationMs = Date.now() - startTime;\n\t\t\t\tfireUpdate();\n\t\t\t\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\n\t\t? evaluateCompletionMutationGuard({\n\t\t\tagent: spec.effectivePermissions,\n\t\t\ttask: shared.originalTask ?? task,\n\t\t\tmessages: result.messages,\n\t\t\ttools: spec.effectivePermissions === \"read-only\" ? [\"read\", \"grep\", \"find\", \"ls\", \"bash\"] : undefined,\n\t\t})\n\t\t: undefined;\n\tif (completionGuard?.triggered && !observedMutationAttempt) {\n\t\tresult.exitCode = 1;\n\t\tresult.error = \"Subagent completed without making edits for an implementation task.\\nIt appears to have returned planning or scratchpad output instead of applying changes.\";\n\t\tprogress.status = \"failed\";\n\t\tprogress.error = result.error;\n\t\temitControlEvent(buildControlEvent({\n\t\t\tfrom: progress.activityState,\n\t\t\tto: \"needs_attention\",\n\t\t\trunId: options.runId ?? spec.childId,\n\t\t\tagent: spec.description,\n\t\t\tindex: options.index,\n\t\t\tts: Date.now(),\n\t\t\tmessage: `${spec.description} completed without making edits for an implementation task`,\n\t\t\treason: \"completion_guard\",\n\t\t}));\n\t}\n\t\tif (options.outputPath && result.exitCode === 0) {\n\t\t\tconst resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);\n\t\t\tfullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);\n\t\t\tresult.savedOutputPath = resolvedOutput.savedPath;\n\t\t\tresult.outputSaveError = resolvedOutput.saveError;\n\t\t\tif (resolvedOutput.savedPath) {\n\t\t\t\tresult.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);\n\t\t\t}\n\t}\n\t\tartifactOutputByResult.set(result, fullOutput);\n\t\tacceptanceOutputByResult.set(result, acceptanceOutput);\n\tresult.outputMode = options.outputMode ?? \"inline\";\n\tresult.finalOutput = options.outputMode === \"file-only\" && result.savedOutputPath && result.outputReference\n\t\t? result.outputReference.message\n\t\t: fullOutput;\n\tresult.controlEvents = allControlEvents.length ? allControlEvents : undefined;\n\tif (options.onUpdate) {\n\t\tconst finalText = result.finalOutput || result.error || \"(no output)\";\n\t\tconst progressSnapshot = snapshotProgress(progress);\n\t\tconst resultSnapshot = snapshotResult(result, progressSnapshot);\n\t\toptions.onUpdate({\n\t\t\tcontent: [{ type: \"text\", text: finalText }],\n\t\t\tdetails: {\n\t\t\t\tmode: \"single\",\n\t\t\t\tresults: [resultSnapshot],\n\t\t\t\tprogress: [progressSnapshot],\n\t\t\t\tcontrolEvents: allControlEvents.length ? allControlEvents : undefined,\n\t\t\t},\n\t\t});\n\t}\n\treturn result;\n}\n\n/**\n * Run a subagent synchronously (blocking until complete)\n */\nexport async function runSync(\n\truntimeCwd: string,\n\tspec: ChildSpec,\n\toptions: RunSyncOptions,\n): Promise<SingleResult> {\n\tconst task = spec.task;\n\tconst label = childRowLabel(spec);\n\tconst parentMode = snapshotParentPermissionMode(options.parentSessionId);\n\tconst resolvedPermissions = resolveChildPermissions(parentMode, spec.requestedPermissions);\n\tif (!resolvedPermissions.ok) {\n\t\tconst error = resolvedPermissions.error || PLAN_MODE_WRITE_SPAWN_ERROR;\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\terror,\n\t\t};\n\t}\n\tconst outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${label})`);\n\tif (outputModeValidationError) {\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\toutputMode: options.outputMode,\n\t\t\terror: outputModeValidationError,\n\t\t};\n\t}\n\n\tconst shareEnabled = options.share === true;\n\tconst effectiveAcceptance = resolveEffectiveAcceptance({\n\t\texplicit: options.acceptance ?? spec.acceptance,\n\t\tpermissions: spec.effectivePermissions,\n\t\ttask,\n\t\tmode: options.acceptanceContext?.mode ?? \"single\",\n\t\tasync: options.acceptanceContext?.async,\n\t\tdynamic: options.acceptanceContext?.dynamic,\n\t\tdynamicGroup: options.acceptanceContext?.dynamicGroup,\n\t});\n\tconst acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);\n\tconst taskWithAcceptance = acceptancePrompt ? `${task}\\n${acceptancePrompt}` : task;\n\tconst sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;\n\tconst skillNames = options.skills ?? (spec.skill === false ? [] : (normalizeSkillInput(spec.skill) || []));\n\tconst skillCwd = options.cwd ?? spec.cwd ?? runtimeCwd;\n\tconst { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(\n\t\tskillNames,\n\t\tskillCwd,\n\t\truntimeCwd,\n\t\tundefined,\n\t\tskillCwd,\n\t);\n\tif (skillNames.some((skill) => skill.trim() === \"pi-subagents\") && missingSkills.includes(\"pi-subagents\")) {\n\t\treturn {\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\tagent: spec.description,\n\t\t\ttask,\n\t\t\texitCode: 1,\n\t\t\tmessages: [],\n\t\t\tusage: emptyUsage(),\n\t\t\terror: \"Skills not found: pi-subagents\",\n\t\t};\n\t}\n\tlet systemPrompt = \"\";\n\tif (resolvedSkills.length > 0) {\n\t\tsystemPrompt = buildSkillInjection(resolvedSkills);\n\t}\n\tsystemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, {\n\t\ttools: spec.effectivePermissions === \"read-only\" ? [\"read\", \"grep\", \"find\", \"ls\", \"bash\"] : undefined,\n\t});\n\n\tconst candidates = buildModelCandidates(\n\t\toptions.modelOverride ?? spec.model,\n\t\tundefined,\n\t\toptions.availableModels,\n\t\toptions.preferredModelProvider,\n\t\t{ scope: options.modelScope },\n\t);\n\tconst attemptedModels: string[] = [];\n\tconst modelAttempts: ModelAttempt[] = [];\n\tconst aggregateUsage = emptyUsage();\n\tconst attemptNotes: string[] = [];\n\tlet totalToolCount = 0;\n\tlet totalDurationMs = 0;\n\n\tlet artifactPathsResult: ArtifactPaths | undefined;\n\tlet jsonlPath: string | undefined;\n\tlet transcriptWriter: ChildTranscriptWriter | undefined;\n\tif (options.artifactsDir && options.artifactConfig?.enabled !== false) {\n\t\tartifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, spec.childId, options.index);\n\t\tensureArtifactsDir(options.artifactsDir);\n\t\tif (options.artifactConfig?.includeInput !== false) {\n\t\t\t\twriteArtifact(artifactPathsResult.inputPath, `# Task for ${label}\\n\\n${taskWithAcceptance}`);\n\t\t}\n\t\tif (options.artifactConfig?.includeJsonl !== false) {\n\t\t\tjsonlPath = artifactPathsResult.jsonlPath;\n\t\t}\n\t\tif (options.artifactConfig?.includeTranscript !== false) {\n\t\t\ttranscriptWriter = createChildTranscriptWriter({\n\t\t\t\ttranscriptPath: artifactPathsResult.transcriptPath,\n\t\t\t\tsource: \"foreground\",\n\t\t\t\trunId: options.runId,\n\t\t\t\tagent: spec.childId,\n\t\t\t\tchildIndex: options.index,\n\t\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t\t});\n\t\t\ttranscriptWriter.writeInitialUserMessage(taskWithAcceptance);\n\t\t}\n\t}\n\n\tconst persistResultMetadata = (target: SingleResult): void => {\n\t\tif (!artifactPathsResult || options.artifactConfig?.enabled === false || options.artifactConfig?.includeMetadata === false) return;\n\t\twriteMetadata(artifactPathsResult.metadataPath, {\n\t\t\trunId: options.runId,\n\t\t\tchildId: spec.childId,\n\t\t\tdescription: spec.description,\n\t\t\tpermissions: spec.effectivePermissions,\n\t\t\ttask,\n\t\t\texitCode: target.exitCode,\n\t\t\tusage: target.usage,\n\t\t\tmodel: target.model,\n\t\t\tattemptedModels: target.attemptedModels,\n\t\t\tmodelAttempts: target.modelAttempts,\n\t\t\tdurationMs: target.progressSummary?.durationMs,\n\t\t\ttoolCount: target.progressSummary?.toolCount,\n\t\t\terror: target.error,\n\t\t\tacceptance: target.acceptance,\n\t\t\t...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),\n\t\t\ttranscriptError: target.transcriptError,\n\t\t\tskills: target.skills,\n\t\t\tskillsWarning: target.skillsWarning,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\t};\n\n\tconst detachedAwareOptions: RunSyncOptions = options.onDetachedExit\n\t\t? {\n\t\t\t...options,\n\t\t\tonDetachedExit: (recoveredResult) => {\n\t\t\t\tvoid (async () => {\n\t\t\t\t\tconst childWrittenOutput = options.outputPath\n\t\t\t\t\t\t? extractChildWrittenOutput(recoveredResult.messages, options.outputPath, options.cwd ?? runtimeCwd)\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\trecoveredResult.acceptance = await evaluateAcceptance({\n\t\t\t\t\t\tacceptance: effectiveAcceptance,\n\t\t\t\t\t\toutput: acceptanceOutputByResult.get(recoveredResult) ?? recoveredResult.finalOutput ?? \"\",\n\t\t\t\t\t\tfileOutput: childWrittenOutput !== undefined && options.outputPath\n\t\t\t\t\t\t\t? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === \"file-only\" }\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t\t\t\t});\n\t\t\t\t\tconst acceptanceFailure = acceptanceFailureMessage(recoveredResult.acceptance);\n\t\t\t\t\tstripAcceptanceReportsFromMessages(recoveredResult.messages);\n\t\t\t\t\tif (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0) {\n\t\t\t\t\t\trecoveredResult.exitCode = 1;\n\t\t\t\t\t\trecoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\\n${acceptanceFailure}` : acceptanceFailure;\n\t\t\t\t\t\tif (recoveredResult.progress) {\n\t\t\t\t\t\t\trecoveredResult.progress.status = \"failed\";\n\t\t\t\t\t\t\trecoveredResult.progress.error = recoveredResult.error;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpersistResultMetadata(recoveredResult);\n\t\t\t\t\toptions.onDetachedExit?.(recoveredResult);\n\t\t\t\t})().catch((error) => {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\trecoveredResult.exitCode = 1;\n\t\t\t\t\trecoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\\nAcceptance evaluation failed: ${message}` : `Acceptance evaluation failed: ${message}`;\n\t\t\t\t\toptions.onDetachedExit?.(recoveredResult);\n\t\t\t\t});\n\t\t\t},\n\t\t}\n\t\t: options;\n\n\tlet lastResult: SingleResult | undefined;\n\tconst modelsToTry = candidates.length > 0 ? candidates : [undefined];\n\tfor (let i = 0; i < modelsToTry.length; i++) {\n\t\tconst candidate = modelsToTry[i];\n\t\tconst outputSnapshot = captureSingleOutputSnapshot(options.outputPath);\n\t\tconst result = await runSingleAttempt(runtimeCwd, spec, taskWithAcceptance, candidate, detachedAwareOptions, {\n\t\t\tsessionEnabled,\n\t\t\tsystemPrompt,\n\t\t\tresolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,\n\t\t\tskillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(\", \")}` : undefined,\n\t\t\tjsonlPath,\n\t\t\tartifactPaths: artifactPathsResult,\n\t\t\ttranscriptWriter,\n\t\t\tattemptNotes,\n\t\t\toutputSnapshot,\n\t\t\toriginalTask: task,\n\t\t});\n\t\tlastResult = result;\n\t\tif (result.model) attemptedModels.push(result.model);\n\t\telse if (candidate) attemptedModels.push(candidate);\n\t\tsumUsage(aggregateUsage, result.usage);\n\t\ttotalToolCount += result.progressSummary?.toolCount ?? 0;\n\t\ttotalDurationMs += result.progressSummary?.durationMs ?? 0;\n\t\tconst attemptSucceeded = result.exitCode === 0 && !result.error;\n\t\tconst attempt: ModelAttempt = {\n\t\t\tmodel: result.model ?? candidate ?? spec.model ?? \"default\",\n\t\t\tsuccess: attemptSucceeded,\n\t\t\texitCode: result.exitCode,\n\t\t\terror: result.error,\n\t\t\tusage: { ...result.usage },\n\t\t};\n\t\tmodelAttempts.push(attempt);\n\t\tif (result.detached || result.timedOut || result.turnBudgetExceeded) {\n\t\t\tbreak;\n\t\t}\n\t\tif (attemptSucceeded) {\n\t\t\tbreak;\n\t\t}\n\t\tif (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {\n\t\t\tbreak;\n\t\t}\n\t\tattemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));\n\t}\n\n\tconst result = lastResult ?? {\n\t\tchildId: spec.childId,\n\t\tdescription: spec.description,\n\t\tpermissions: spec.effectivePermissions,\n\t\tagent: spec.description,\n\t\ttask,\n\t\texitCode: 1,\n\t\tmessages: [],\n\t\tusage: emptyUsage(),\n\t\terror: \"Subagent did not produce a result.\",\n\t} satisfies SingleResult;\n\n\tresult.usage = aggregateUsage;\n\tresult.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;\n\tresult.modelAttempts = modelAttempts.length > 0 ? modelAttempts : undefined;\n\tresult.progressSummary = {\n\t\ttoolCount: totalToolCount,\n\t\ttokens: aggregateUsage.input + aggregateUsage.output,\n\t\tdurationMs: totalDurationMs,\n\t};\n\tif (attemptNotes.length > 0 && result.progress) {\n\t\tresult.progress.recentOutput = [...attemptNotes, ...result.progress.recentOutput];\n\t\tif (result.progress.recentOutput.length > 50) {\n\t\t\tresult.progress.recentOutput.splice(50);\n\t\t}\n\t}\n\n\tif (transcriptWriter) result.transcriptPath = artifactPathsResult?.transcriptPath;\n\tif (transcriptWriter?.getError()) result.transcriptError = transcriptWriter.getError();\n\n\tif (artifactPathsResult && options.artifactConfig?.enabled !== false) {\n\t\tresult.artifactPaths = artifactPathsResult;\n\t\tif (options.artifactConfig?.includeOutput !== false) {\n\t\t\twriteArtifact(artifactPathsResult.outputPath, artifactOutputByResult.get(result) ?? result.finalOutput ?? \"\");\n\t\t}\n\t\tif (options.maxOutput) {\n\t\t\tconst config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };\n\t\t\tconst truncationResult = truncateOutput(result.finalOutput ?? \"\", config, artifactPathsResult.outputPath);\n\t\t\tif (truncationResult.truncated) result.truncation = truncationResult;\n\t\t}\n\t} else if (options.maxOutput) {\n\t\tconst config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };\n\t\tconst truncationResult = truncateOutput(result.finalOutput ?? \"\", config);\n\t\tif (truncationResult.truncated) result.truncation = truncationResult;\n\t}\n\n\tif (options.sessionFile && (existsSync(options.sessionFile) || result.messages?.length)) {\n\t\tresult.sessionFile = options.sessionFile;\n\t} else if (shareEnabled && options.sessionDir) {\n\t\tconst sessionFile = findLatestSessionFile(options.sessionDir);\n\t\tif (sessionFile) result.sessionFile = sessionFile;\n\t}\n\n\tconst childWrittenOutput = options.outputPath\n\t\t? extractChildWrittenOutput(result.messages, options.outputPath, options.cwd ?? runtimeCwd)\n\t\t: undefined;\n\tif (result.detached) {\n\t\tresult.acceptance = buildPendingAcceptanceLedger(effectiveAcceptance);\n\t} else if (result.stopped) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"stopped\", message: \"Acceptance was not evaluated because the subagent was stopped.\" });\n\t} else if (result.timedOut) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"timeout\", message: \"Acceptance was not evaluated because the subagent timed out.\" });\n\t} else if (result.turnBudgetExceeded) {\n\t\tresult.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: \"turn-budget\", message: \"Acceptance was not evaluated because the subagent exceeded its turn budget.\" });\n\t} else {\n\t\tresult.acceptance = await evaluateAcceptance({\n\t\t\tacceptance: effectiveAcceptance,\n\t\t\toutput: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? \"\",\n\t\t\tfileOutput: childWrittenOutput !== undefined && options.outputPath\n\t\t\t\t? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === \"file-only\" }\n\t\t\t\t: undefined,\n\t\t\tcwd: options.cwd ?? runtimeCwd,\n\t\t});\n\t}\n\tconst acceptanceFailure = acceptanceFailureMessage(result.acceptance);\n\tstripAcceptanceReportsFromMessages(result.messages);\n\tif (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {\n\t\tresult.exitCode = 1;\n\t\tresult.error = result.error ? `${result.error}\\n${acceptanceFailure}` : acceptanceFailure;\n\t\tif (result.progress) {\n\t\t\tresult.progress.status = \"failed\";\n\t\t\tresult.progress.error = result.error;\n\t\t}\n\t}\n\tpersistResultMetadata(result);\n\n\treturn result;\n}\n"]}
|