@kr78/pi-coding-agent 17.2.10
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 +14861 -0
- package/README.md +35 -0
- package/dist/CHANGELOG-chtdxh1z.md +14861 -0
- package/dist/cli.js +20483 -0
- package/dist/template-c2hyaytt.js +1653 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +104 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/extensions/README.md +142 -0
- package/examples/extensions/api-demo.ts +78 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +31 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +549 -0
- package/examples/extensions/reload-runtime.ts +38 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +17 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +149 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +118 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +46 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +82 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +41 -0
- package/examples/sdk/08-slash-commands.ts +46 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/12-redis-sessions.ts +54 -0
- package/examples/sdk/13-sql-sessions.ts +61 -0
- package/examples/sdk/README.md +169 -0
- package/package.json +587 -0
- package/scripts/bench-guard.ts +71 -0
- package/scripts/bench-title-models.ts +332 -0
- package/scripts/build-binary.ts +117 -0
- package/scripts/bundle-dist.ts +123 -0
- package/scripts/compile-binary.ts +69 -0
- package/scripts/embed-mupdf-wasm.ts +67 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-aria-snapshot.ts +134 -0
- package/scripts/generate-docs-index.ts +58 -0
- package/scripts/generate-share-viewer.ts +34 -0
- package/scripts/legacy-pi-virtual-module.ts +205 -0
- package/scripts/measure-prompt-tokens.ts +63 -0
- package/scripts/omp +51 -0
- package/scripts/omp.ts +19 -0
- package/scripts/security-compare.ts +40 -0
- package/src/advisor/advise-tool.ts +234 -0
- package/src/advisor/config.ts +341 -0
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +6 -0
- package/src/advisor/runtime.ts +1412 -0
- package/src/advisor/transcript-recorder.ts +215 -0
- package/src/advisor/watchdog.ts +135 -0
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +847 -0
- package/src/auto-thinking/classifier.ts +220 -0
- package/src/autolearn/controller.ts +152 -0
- package/src/autolearn/managed-skills.ts +255 -0
- package/src/autoresearch/command-resume.md +14 -0
- package/src/autoresearch/dashboard.ts +436 -0
- package/src/autoresearch/git.ts +331 -0
- package/src/autoresearch/helpers.ts +218 -0
- package/src/autoresearch/index.ts +541 -0
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +103 -0
- package/src/autoresearch/resume-message.md +10 -0
- package/src/autoresearch/state.ts +273 -0
- package/src/autoresearch/storage.ts +700 -0
- package/src/autoresearch/tools/init-experiment.ts +268 -0
- package/src/autoresearch/tools/log-experiment.ts +520 -0
- package/src/autoresearch/tools/run-experiment.ts +407 -0
- package/src/autoresearch/tools/update-notes.ts +109 -0
- package/src/autoresearch/types.ts +168 -0
- package/src/capability/context-file.ts +44 -0
- package/src/capability/extension-module.ts +34 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +117 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +467 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +115 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule-buckets.ts +66 -0
- package/src/capability/rule.ts +298 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +69 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +187 -0
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/agents-cli.ts +138 -0
- package/src/cli/args.ts +368 -0
- package/src/cli/auth-broker-cli.ts +940 -0
- package/src/cli/auth-gateway-cli.ts +674 -0
- package/src/cli/bench-cli.ts +990 -0
- package/src/cli/browser-relay-cli.ts +119 -0
- package/src/cli/classify-install-target.ts +76 -0
- package/src/cli/claude-trace-cli.ts +795 -0
- package/src/cli/command-help.ts +107 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/completion-gen.ts +550 -0
- package/src/cli/config-cli.ts +459 -0
- package/src/cli/dry-balance-cli.ts +864 -0
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/file-processor.ts +132 -0
- package/src/cli/flag-tables.ts +367 -0
- package/src/cli/gallery-cli.ts +272 -0
- package/src/cli/gallery-fixtures/agentic.ts +420 -0
- package/src/cli/gallery-fixtures/codeintel.ts +187 -0
- package/src/cli/gallery-fixtures/edit.ts +254 -0
- package/src/cli/gallery-fixtures/fs.ts +245 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +46 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +177 -0
- package/src/cli/gallery-fixtures/search.ts +135 -0
- package/src/cli/gallery-fixtures/shell.ts +241 -0
- package/src/cli/gallery-fixtures/types.ts +57 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli/gc-cli.ts +1566 -0
- package/src/cli/grep-cli.ts +161 -0
- package/src/cli/grievances-cli.ts +256 -0
- package/src/cli/help-extra.ts +89 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/models-cli.ts +385 -0
- package/src/cli/plugin-cli.ts +996 -0
- package/src/cli/profile-alias.ts +369 -0
- package/src/cli/profile-bootstrap.ts +233 -0
- package/src/cli/read-cli.ts +99 -0
- package/src/cli/session-picker.ts +110 -0
- package/src/cli/setup-cli.ts +312 -0
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli/shell-cli.ts +176 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/startup-cwd.ts +58 -0
- package/src/cli/stats-cli.ts +229 -0
- package/src/cli/thinking-levels.ts +7 -0
- package/src/cli/tiny-models-cli.ts +153 -0
- package/src/cli/ttsr-cli.ts +1013 -0
- package/src/cli/update-cli.ts +1184 -0
- package/src/cli/usage-cli.ts +1084 -0
- package/src/cli/usage-error.ts +7 -0
- package/src/cli/web-search-cli.ts +144 -0
- package/src/cli/worktree-cli.ts +311 -0
- package/src/cli-commands.ts +308 -0
- package/src/cli.ts +428 -0
- package/src/collab/crypto.ts +63 -0
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +764 -0
- package/src/collab/host.ts +692 -0
- package/src/collab/protocol.ts +296 -0
- package/src/collab/relay-client.ts +282 -0
- package/src/collab/replication-shrink.ts +111 -0
- package/src/commands/acp.ts +35 -0
- package/src/commands/agents.ts +58 -0
- package/src/commands/auth-broker.ts +100 -0
- package/src/commands/auth-gateway.ts +70 -0
- package/src/commands/bench.ts +64 -0
- package/src/commands/browser-relay.ts +53 -0
- package/src/commands/cleanse.ts +45 -0
- package/src/commands/commit.ts +47 -0
- package/src/commands/complete.ts +67 -0
- package/src/commands/completions.ts +61 -0
- package/src/commands/config.ts +52 -0
- package/src/commands/dry-balance.ts +43 -0
- package/src/commands/gallery.ts +61 -0
- package/src/commands/gc.ts +47 -0
- package/src/commands/grep.ts +49 -0
- package/src/commands/grievances.ts +52 -0
- package/src/commands/install.ts +107 -0
- package/src/commands/join.ts +40 -0
- package/src/commands/launch-help.ts +116 -0
- package/src/commands/launch.ts +34 -0
- package/src/commands/models.ts +61 -0
- package/src/commands/plugin.ts +79 -0
- package/src/commands/read.ts +39 -0
- package/src/commands/say.ts +146 -0
- package/src/commands/setup.ts +68 -0
- package/src/commands/share.ts +71 -0
- package/src/commands/shell.ts +30 -0
- package/src/commands/ssh.ts +61 -0
- package/src/commands/stats.ts +30 -0
- package/src/commands/tiny-models.ts +36 -0
- package/src/commands/token.ts +164 -0
- package/src/commands/ttsr.ts +125 -0
- package/src/commands/update.ts +34 -0
- package/src/commands/usage.ts +55 -0
- package/src/commands/web-search.ts +43 -0
- package/src/commands/worktree.ts +63 -0
- package/src/commit/agentic/agent.ts +322 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +383 -0
- package/src/commit/agentic/lock-files.ts +107 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +60 -0
- package/src/commit/agentic/tools/analyze-file.ts +148 -0
- package/src/commit/agentic/tools/git-file-diff.ts +191 -0
- package/src/commit/agentic/tools/git-hunk.ts +52 -0
- package/src/commit/agentic/tools/git-overview.ts +62 -0
- package/src/commit/agentic/tools/index.ts +54 -0
- package/src/commit/agentic/tools/propose-changelog.ts +147 -0
- package/src/commit/agentic/tools/propose-commit.ts +109 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/schemas.ts +11 -0
- package/src/commit/agentic/tools/split-commit.ts +241 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +183 -0
- package/src/commit/analysis/conventional.ts +64 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +107 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +40 -0
- package/src/commit/changelog/generate.ts +101 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +85 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +69 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +49 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +95 -0
- package/src/commit/pipeline.ts +244 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/shared-llm.ts +70 -0
- package/src/commit/types.ts +118 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/commit/utils.ts +58 -0
- package/src/config/api-key-resolver.ts +81 -0
- package/src/config/append-only-context-mode.ts +76 -0
- package/src/config/config-file.ts +347 -0
- package/src/config/inline-tool-descriptors-mode.ts +26 -0
- package/src/config/keybindings.ts +703 -0
- package/src/config/mcp-schema.json +252 -0
- package/src/config/model-discovery.ts +1077 -0
- package/src/config/model-registry.ts +2822 -0
- package/src/config/model-resolver.ts +2115 -0
- package/src/config/model-roles.ts +113 -0
- package/src/config/models-config-schema-bundle.ts +315 -0
- package/src/config/models-config-schema.ts +14 -0
- package/src/config/models-config.ts +130 -0
- package/src/config/prompt-templates.ts +205 -0
- package/src/config/provider-globals.ts +25 -0
- package/src/config/resolve-config-value.ts +95 -0
- package/src/config/service-tier.ts +146 -0
- package/src/config/settings-schema.ts +5887 -0
- package/src/config/settings.ts +2394 -0
- package/src/config.ts +242 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +960 -0
- package/src/dap/client.ts +1043 -0
- package/src/dap/config.ts +480 -0
- package/src/dap/defaults.json +212 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1841 -0
- package/src/dap/types.ts +611 -0
- package/src/debug/index.ts +584 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +966 -0
- package/src/debug/profiler.ts +168 -0
- package/src/debug/protocol-probe.ts +267 -0
- package/src/debug/raw-sse-buffer.ts +421 -0
- package/src/debug/raw-sse.ts +312 -0
- package/src/debug/remote-debugger.ts +151 -0
- package/src/debug/report-bundle.ts +411 -0
- package/src/debug/system-info.ts +111 -0
- package/src/debug/terminal-info.ts +124 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +300 -0
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-defaults.ts +39 -0
- package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
- package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
- package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
- package/src/discovery/builtin-rules/go-ioutil.md +37 -0
- package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
- package/src/discovery/builtin-rules/go-new-expr.md +44 -0
- package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
- package/src/discovery/builtin-rules/go-range-int.md +45 -0
- package/src/discovery/builtin-rules/index.ts +74 -0
- package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
- package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
- package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
- package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
- package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
- package/src/discovery/builtin-rules/rs-result-type.md +20 -0
- package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
- package/src/discovery/builtin-rules/ts-import-type.md +43 -0
- package/src/discovery/builtin-rules/ts-no-any.md +66 -0
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
- package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
- package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
- package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/builtin-rules/ts-set-map.md +28 -0
- package/src/discovery/builtin.ts +945 -0
- package/src/discovery/claude-plugins.ts +640 -0
- package/src/discovery/claude.ts +591 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +553 -0
- package/src/discovery/contained-path.ts +78 -0
- package/src/discovery/cursor.ts +223 -0
- package/src/discovery/gemini.ts +386 -0
- package/src/discovery/github.ts +337 -0
- package/src/discovery/helpers.ts +1183 -0
- package/src/discovery/index.ts +82 -0
- package/src/discovery/mcp-json.ts +182 -0
- package/src/discovery/omp-extension-roots.ts +272 -0
- package/src/discovery/omp-plugins.ts +409 -0
- package/src/discovery/opencode.ts +441 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +77 -0
- package/src/discovery/vscode.ts +106 -0
- package/src/discovery/windsurf.ts +149 -0
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +1008 -0
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/file-snapshot-store.ts +148 -0
- package/src/edit/hashline/block-resolver.ts +33 -0
- package/src/edit/hashline/diff.ts +402 -0
- package/src/edit/hashline/execute.ts +313 -0
- package/src/edit/hashline/filesystem.ts +247 -0
- package/src/edit/hashline/index.ts +5 -0
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/hashline/params.ts +12 -0
- package/src/edit/index.ts +699 -0
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +53 -0
- package/src/edit/modes/patch.ts +1958 -0
- package/src/edit/modes/replace.ts +1191 -0
- package/src/edit/normalize.ts +345 -0
- package/src/edit/notebook.ts +242 -0
- package/src/edit/read-file.ts +25 -0
- package/src/edit/renderer.ts +998 -0
- package/src/edit/snapshot-details.ts +77 -0
- package/src/edit/streaming.ts +710 -0
- package/src/eval/agent-bridge.ts +224 -0
- package/src/eval/backend-helpers.ts +48 -0
- package/src/eval/backend.ts +71 -0
- package/src/eval/bridge-timeout.ts +65 -0
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/completion-bridge.ts +212 -0
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/executor-base.ts +562 -0
- package/src/eval/idle-timeout.ts +91 -0
- package/src/eval/index.ts +6 -0
- package/src/eval/jl/executor.ts +551 -0
- package/src/eval/jl/index.ts +54 -0
- package/src/eval/jl/kernel.ts +236 -0
- package/src/eval/jl/prelude.jl +736 -0
- package/src/eval/jl/prelude.ts +3 -0
- package/src/eval/jl/runner.jl +666 -0
- package/src/eval/jl/runtime.ts +118 -0
- package/src/eval/js/context-manager.ts +872 -0
- package/src/eval/js/executor.ts +178 -0
- package/src/eval/js/index.ts +41 -0
- package/src/eval/js/process-entry.ts +31 -0
- package/src/eval/js/shared/helpers.ts +170 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/local-module-loader.ts +364 -0
- package/src/eval/js/shared/prelude.ts +2 -0
- package/src/eval/js/shared/prelude.txt +294 -0
- package/src/eval/js/shared/rewrite-imports.ts +550 -0
- package/src/eval/js/shared/runtime.ts +590 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +163 -0
- package/src/eval/js/worker-core.ts +380 -0
- package/src/eval/js/worker-entry.ts +37 -0
- package/src/eval/js/worker-protocol.ts +47 -0
- package/src/eval/kernel-base.ts +569 -0
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +643 -0
- package/src/eval/py/index.ts +57 -0
- package/src/eval/py/kernel.ts +235 -0
- package/src/eval/py/prelude.py +673 -0
- package/src/eval/py/prelude.ts +3 -0
- package/src/eval/py/runner.py +1425 -0
- package/src/eval/py/runtime.ts +276 -0
- package/src/eval/py/spawn-options.ts +134 -0
- package/src/eval/py/tool-bridge.ts +201 -0
- package/src/eval/rb/executor.ts +511 -0
- package/src/eval/rb/index.ts +54 -0
- package/src/eval/rb/kernel.ts +231 -0
- package/src/eval/rb/prelude.rb +551 -0
- package/src/eval/rb/prelude.ts +3 -0
- package/src/eval/rb/runner.rb +581 -0
- package/src/eval/rb/runtime.ts +132 -0
- package/src/eval/runtime-env.ts +104 -0
- package/src/eval/session-id.ts +8 -0
- package/src/eval/types.ts +48 -0
- package/src/exa/index.ts +2 -0
- package/src/exa/mcp-client.ts +370 -0
- package/src/exa/types.ts +69 -0
- package/src/exec/bash-executor.ts +627 -0
- package/src/exec/direnv.ts +145 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/non-interactive-env.ts +118 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +320 -0
- package/src/export/html/share-loader.js +102 -0
- package/src/export/html/template.css +1355 -0
- package/src/export/html/template.html +55 -0
- package/src/export/html/template.js +1653 -0
- package/src/export/html/tool-views.generated.js +35 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/html/web-palette.ts +126 -0
- package/src/export/share.ts +686 -0
- package/src/export/ttsr.ts +590 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
- package/src/extensibility/custom-commands/index.ts +2 -0
- package/src/extensibility/custom-commands/loader.ts +242 -0
- package/src/extensibility/custom-commands/types.ts +119 -0
- package/src/extensibility/custom-tools/index.ts +7 -0
- package/src/extensibility/custom-tools/loader.ts +301 -0
- package/src/extensibility/custom-tools/types.ts +287 -0
- package/src/extensibility/custom-tools/wrapper.ts +50 -0
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/get-commands-handler.ts +78 -0
- package/src/extensibility/extensions/index.ts +16 -0
- package/src/extensibility/extensions/load-errors.ts +13 -0
- package/src/extensibility/extensions/loader.ts +663 -0
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/model-api.ts +39 -0
- package/src/extensibility/extensions/runner.ts +1396 -0
- package/src/extensibility/extensions/types.ts +1603 -0
- package/src/extensibility/extensions/wrapper.ts +395 -0
- package/src/extensibility/hooks/index.ts +6 -0
- package/src/extensibility/hooks/loader.ts +243 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +124 -0
- package/src/extensibility/hooks/types.ts +612 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
- package/src/extensibility/legacy-typebox.ts +101 -0
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/doctor.ts +65 -0
- package/src/extensibility/plugins/git-url.ts +367 -0
- package/src/extensibility/plugins/index.ts +9 -0
- package/src/extensibility/plugins/installer.ts +201 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
- package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
- package/src/extensibility/plugins/loader.ts +535 -0
- package/src/extensibility/plugins/manager.ts +1142 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +927 -0
- package/src/extensibility/plugins/marketplace/registry.ts +192 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +192 -0
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/extensibility/plugins/parser.ts +107 -0
- package/src/extensibility/plugins/runtime-config.ts +9 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/session-handler-types.ts +21 -0
- package/src/extensibility/shared-events.ts +401 -0
- package/src/extensibility/skills.ts +531 -0
- package/src/extensibility/slash-commands.ts +131 -0
- package/src/extensibility/tool-event-input.ts +80 -0
- package/src/extensibility/tool-proxy.ts +28 -0
- package/src/extensibility/utils.ts +192 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +521 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +251 -0
- package/src/hindsight/backend.ts +354 -0
- package/src/hindsight/bank.ts +156 -0
- package/src/hindsight/client.ts +680 -0
- package/src/hindsight/config.ts +193 -0
- package/src/hindsight/content.ts +266 -0
- package/src/hindsight/index.ts +8 -0
- package/src/hindsight/mental-models.ts +429 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +530 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/index.ts +66 -0
- package/src/internal-urls/agent-protocol.ts +180 -0
- package/src/internal-urls/artifact-protocol.ts +151 -0
- package/src/internal-urls/docs-index.ts +102 -0
- package/src/internal-urls/filesystem-resource.ts +34 -0
- package/src/internal-urls/history-protocol.ts +198 -0
- package/src/internal-urls/index.ts +28 -0
- package/src/internal-urls/issue-pr-protocol.ts +594 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/local-protocol.ts +471 -0
- package/src/internal-urls/mcp-protocol.ts +168 -0
- package/src/internal-urls/memory-protocol.ts +380 -0
- package/src/internal-urls/omp-protocol.ts +94 -0
- package/src/internal-urls/parse.ts +103 -0
- package/src/internal-urls/registry-helpers.ts +132 -0
- package/src/internal-urls/router.ts +153 -0
- package/src/internal-urls/rule-protocol.ts +45 -0
- package/src/internal-urls/security-protocol.ts +261 -0
- package/src/internal-urls/skill-protocol.ts +131 -0
- package/src/internal-urls/ssh-protocol.ts +368 -0
- package/src/internal-urls/types.ts +196 -0
- package/src/internal-urls/vault-protocol.ts +940 -0
- package/src/internal-urls/xd-protocol.ts +46 -0
- package/src/irc/bus.ts +380 -0
- package/src/jsonrpc/message-framing.ts +142 -0
- package/src/launch/broker.ts +1368 -0
- package/src/launch/client.ts +521 -0
- package/src/launch/ensure.ts +73 -0
- package/src/launch/paths.ts +14 -0
- package/src/launch/presence.ts +82 -0
- package/src/launch/protocol.ts +448 -0
- package/src/launch/spawn-options.ts +17 -0
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/launch/terminal-output.ts +46 -0
- package/src/lib/xai-http.ts +150 -0
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +517 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +422 -0
- package/src/live/visualizer.ts +221 -0
- package/src/live/voices.ts +18 -0
- package/src/lsp/client.ts +1465 -0
- package/src/lsp/clients/biome-client.ts +263 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +85 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +549 -0
- package/src/lsp/defaults.json +499 -0
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/edits.ts +288 -0
- package/src/lsp/format-options.ts +119 -0
- package/src/lsp/index.ts +2821 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/mux/daemon.ts +348 -0
- package/src/lsp/mux/protocol.ts +96 -0
- package/src/lsp/mux/server.ts +797 -0
- package/src/lsp/render.ts +668 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +479 -0
- package/src/lsp/utils.ts +747 -0
- package/src/main.ts +1799 -0
- package/src/markit/NOTICE +32 -0
- package/src/markit/converters/docx.ts +56 -0
- package/src/markit/converters/epub.ts +136 -0
- package/src/markit/converters/pdf/columns.ts +103 -0
- package/src/markit/converters/pdf/extract.ts +598 -0
- package/src/markit/converters/pdf/grid.ts +780 -0
- package/src/markit/converters/pdf/headers.ts +106 -0
- package/src/markit/converters/pdf/index.ts +146 -0
- package/src/markit/converters/pdf/render.ts +501 -0
- package/src/markit/converters/pdf/types.ts +84 -0
- package/src/markit/converters/pptx.ts +325 -0
- package/src/markit/converters/xlsx.ts +173 -0
- package/src/markit/index.ts +2 -0
- package/src/markit/registry.ts +59 -0
- package/src/markit/types.ts +35 -0
- package/src/mcp/client.ts +511 -0
- package/src/mcp/config-writer.ts +377 -0
- package/src/mcp/config.ts +385 -0
- package/src/mcp/index.ts +29 -0
- package/src/mcp/json-rpc.ts +122 -0
- package/src/mcp/loader.ts +125 -0
- package/src/mcp/manager.ts +1524 -0
- package/src/mcp/oauth-credentials.ts +104 -0
- package/src/mcp/oauth-discovery.ts +587 -0
- package/src/mcp/oauth-flow.ts +830 -0
- package/src/mcp/render.ts +214 -0
- package/src/mcp/request-id.ts +24 -0
- package/src/mcp/smithery-auth.ts +108 -0
- package/src/mcp/smithery-connect.ts +154 -0
- package/src/mcp/smithery-registry.ts +500 -0
- package/src/mcp/startup-events.ts +116 -0
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/tool-bridge.ts +691 -0
- package/src/mcp/tool-cache.ts +117 -0
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +508 -0
- package/src/mcp/transports/index.ts +7 -0
- package/src/mcp/transports/sse.ts +372 -0
- package/src/mcp/transports/stdio.ts +905 -0
- package/src/mcp/types.ts +467 -0
- package/src/memories/index.ts +1434 -0
- package/src/memories/storage.ts +578 -0
- package/src/memory-backend/index.ts +19 -0
- package/src/memory-backend/local-backend.ts +47 -0
- package/src/memory-backend/messages.ts +19 -0
- package/src/memory-backend/off-backend.ts +25 -0
- package/src/memory-backend/resolve.ts +25 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/memory-backend/types.ts +166 -0
- package/src/mnemopi/backend.ts +629 -0
- package/src/mnemopi/config.ts +267 -0
- package/src/mnemopi/embed-client.ts +293 -0
- package/src/mnemopi/embed-protocol.ts +35 -0
- package/src/mnemopi/embed-worker.ts +114 -0
- package/src/mnemopi/index.ts +3 -0
- package/src/mnemopi/state.ts +940 -0
- package/src/modes/acp/acp-agent.ts +2595 -0
- package/src/modes/acp/acp-client-bridge.ts +154 -0
- package/src/modes/acp/acp-event-mapper.ts +1084 -0
- package/src/modes/acp/acp-mode.ts +48 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/advisor-config.ts +635 -0
- package/src/modes/components/advisor-message.ts +109 -0
- package/src/modes/components/agent-dashboard.ts +1252 -0
- package/src/modes/components/agent-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +1126 -0
- package/src/modes/components/agent-transcript-viewer.ts +647 -0
- package/src/modes/components/ask-dialog.ts +1017 -0
- package/src/modes/components/assistant-message.ts +978 -0
- package/src/modes/components/background-tan-message.ts +36 -0
- package/src/modes/components/bash-execution.ts +233 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/btw-panel.ts +161 -0
- package/src/modes/components/cache-invalidation-marker.ts +110 -0
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/chat-transcript-builder.ts +518 -0
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/collab-prompt-message.ts +32 -0
- package/src/modes/components/compaction-summary-message.ts +221 -0
- package/src/modes/components/copy-selector.ts +218 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.ts +1036 -0
- package/src/modes/components/custom-message.ts +70 -0
- package/src/modes/components/diff.ts +254 -0
- package/src/modes/components/dynamic-border.ts +37 -0
- package/src/modes/components/error-banner.ts +33 -0
- package/src/modes/components/eval-execution.ts +169 -0
- package/src/modes/components/execution-shared.ts +101 -0
- package/src/modes/components/extensions/extension-dashboard.ts +492 -0
- package/src/modes/components/extensions/extension-list.ts +507 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +326 -0
- package/src/modes/components/extensions/state-manager.ts +648 -0
- package/src/modes/components/extensions/types.ts +186 -0
- package/src/modes/components/footer.ts +274 -0
- package/src/modes/components/history-search.ts +268 -0
- package/src/modes/components/hook-editor.ts +213 -0
- package/src/modes/components/hook-input.ts +87 -0
- package/src/modes/components/hook-message.ts +67 -0
- package/src/modes/components/hook-selector.ts +691 -0
- package/src/modes/components/index.ts +42 -0
- package/src/modes/components/keybinding-hints.ts +56 -0
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/login-dialog.ts +197 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1413 -0
- package/src/modes/components/message-frame.ts +98 -0
- package/src/modes/components/model-browser.ts +888 -0
- package/src/modes/components/model-hub.ts +2014 -0
- package/src/modes/components/model-picker.ts +237 -0
- package/src/modes/components/move-overlay.ts +293 -0
- package/src/modes/components/oauth-selector.ts +474 -0
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/overlay-box.ts +109 -0
- package/src/modes/components/pause-screen.ts +208 -0
- package/src/modes/components/plan-review-overlay.ts +1226 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/plugin-selector.ts +100 -0
- package/src/modes/components/plugin-settings.ts +745 -0
- package/src/modes/components/queue-mode-selector.ts +61 -0
- package/src/modes/components/read-tool-group.ts +856 -0
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/segment-track.ts +89 -0
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/selector-helpers.ts +129 -0
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/session-selector.ts +1035 -0
- package/src/modes/components/settings-defs.ts +267 -0
- package/src/modes/components/settings-selector.ts +1445 -0
- package/src/modes/components/show-images-selector.ts +50 -0
- package/src/modes/components/skill-message.ts +110 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/status-line/component.ts +1843 -0
- package/src/modes/components/status-line/context-thresholds.ts +86 -0
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/index.ts +5 -0
- package/src/modes/components/status-line/presets.ts +106 -0
- package/src/modes/components/status-line/segments.ts +710 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/types.ts +159 -0
- package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
- package/src/modes/components/theme-selector.ts +68 -0
- package/src/modes/components/thinking-selector.ts +57 -0
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +26 -0
- package/src/modes/components/todo-reminder.ts +43 -0
- package/src/modes/components/tool-execution.ts +1467 -0
- package/src/modes/components/transcript-container.ts +527 -0
- package/src/modes/components/tree-selector.ts +1006 -0
- package/src/modes/components/ttsr-notification.ts +123 -0
- package/src/modes/components/usage-row.ts +52 -0
- package/src/modes/components/user-message-selector.ts +227 -0
- package/src/modes/components/user-message.ts +167 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +578 -0
- package/src/modes/controllers/btw-controller.ts +246 -0
- package/src/modes/controllers/command-controller-shared.ts +109 -0
- package/src/modes/controllers/command-controller.ts +2024 -0
- package/src/modes/controllers/event-controller.ts +2173 -0
- package/src/modes/controllers/extension-ui-controller.ts +1243 -0
- package/src/modes/controllers/input-controller.ts +2037 -0
- package/src/modes/controllers/live-command-controller.ts +261 -0
- package/src/modes/controllers/mcp-command-controller.ts +2552 -0
- package/src/modes/controllers/omfg-controller.ts +287 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +2058 -0
- package/src/modes/controllers/session-focus-controller.ts +117 -0
- package/src/modes/controllers/ssh-command-controller.ts +385 -0
- package/src/modes/controllers/streaming-reveal.ts +399 -0
- package/src/modes/controllers/tan-command-controller.ts +243 -0
- package/src/modes/controllers/todo-command-controller.ts +487 -0
- package/src/modes/controllers/tool-args-reveal.ts +591 -0
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/github-ref-autocomplete.ts +75 -0
- package/src/modes/gradient-highlight.ts +99 -0
- package/src/modes/image-references.ts +137 -0
- package/src/modes/index.ts +10 -0
- package/src/modes/interactive-mode.ts +5055 -0
- package/src/modes/internal-url-autocomplete.ts +158 -0
- package/src/modes/loop-limit.ts +192 -0
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/magic-keywords.ts +42 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/oauth-manual-input.ts +69 -0
- package/src/modes/orchestrate.ts +43 -0
- package/src/modes/print-mode.ts +282 -0
- package/src/modes/prompt-action-autocomplete.ts +322 -0
- package/src/modes/queue-input.ts +132 -0
- package/src/modes/rpc/host-tools.ts +204 -0
- package/src/modes/rpc/host-uris.ts +241 -0
- package/src/modes/rpc/rpc-client.ts +1213 -0
- package/src/modes/rpc/rpc-frame.ts +316 -0
- package/src/modes/rpc/rpc-input.ts +38 -0
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +1519 -0
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +544 -0
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/runtime-init.ts +144 -0
- package/src/modes/session-observer-registry.ts +223 -0
- package/src/modes/session-teardown.ts +82 -0
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +103 -0
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
- package/src/modes/setup-wizard/scenes/model.ts +132 -0
- package/src/modes/setup-wizard/scenes/outro.ts +35 -0
- package/src/modes/setup-wizard/scenes/providers.ts +105 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
- package/src/modes/setup-wizard/scenes/splash.ts +201 -0
- package/src/modes/setup-wizard/scenes/theme.ts +330 -0
- package/src/modes/setup-wizard/scenes/types.ts +65 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
- package/src/modes/setup-wizard/startup-splash.ts +107 -0
- package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
- package/src/modes/shared.ts +49 -0
- package/src/modes/skill-command.ts +91 -0
- package/src/modes/theme/dark.json +95 -0
- package/src/modes/theme/defaults/alabaster.json +93 -0
- package/src/modes/theme/defaults/amethyst.json +96 -0
- package/src/modes/theme/defaults/anthracite.json +93 -0
- package/src/modes/theme/defaults/basalt.json +91 -0
- package/src/modes/theme/defaults/birch.json +95 -0
- package/src/modes/theme/defaults/dark-abyss.json +91 -0
- package/src/modes/theme/defaults/dark-arctic.json +104 -0
- package/src/modes/theme/defaults/dark-aurora.json +95 -0
- package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
- package/src/modes/theme/defaults/dark-cavern.json +91 -0
- package/src/modes/theme/defaults/dark-copper.json +95 -0
- package/src/modes/theme/defaults/dark-cosmos.json +90 -0
- package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/modes/theme/defaults/dark-dracula.json +98 -0
- package/src/modes/theme/defaults/dark-eclipse.json +91 -0
- package/src/modes/theme/defaults/dark-ember.json +95 -0
- package/src/modes/theme/defaults/dark-equinox.json +90 -0
- package/src/modes/theme/defaults/dark-forest.json +96 -0
- package/src/modes/theme/defaults/dark-github.json +105 -0
- package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
- package/src/modes/theme/defaults/dark-lavender.json +95 -0
- package/src/modes/theme/defaults/dark-lunar.json +89 -0
- package/src/modes/theme/defaults/dark-midnight.json +95 -0
- package/src/modes/theme/defaults/dark-monochrome.json +94 -0
- package/src/modes/theme/defaults/dark-monokai.json +98 -0
- package/src/modes/theme/defaults/dark-nebula.json +90 -0
- package/src/modes/theme/defaults/dark-nord.json +97 -0
- package/src/modes/theme/defaults/dark-ocean.json +101 -0
- package/src/modes/theme/defaults/dark-one.json +100 -0
- package/src/modes/theme/defaults/dark-poimandres.json +143 -0
- package/src/modes/theme/defaults/dark-rainforest.json +91 -0
- package/src/modes/theme/defaults/dark-reef.json +91 -0
- package/src/modes/theme/defaults/dark-retro.json +92 -0
- package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
- package/src/modes/theme/defaults/dark-sakura.json +95 -0
- package/src/modes/theme/defaults/dark-slate.json +95 -0
- package/src/modes/theme/defaults/dark-solarized.json +97 -0
- package/src/modes/theme/defaults/dark-solstice.json +90 -0
- package/src/modes/theme/defaults/dark-starfall.json +91 -0
- package/src/modes/theme/defaults/dark-sunset.json +99 -0
- package/src/modes/theme/defaults/dark-swamp.json +90 -0
- package/src/modes/theme/defaults/dark-synthwave.json +103 -0
- package/src/modes/theme/defaults/dark-taiga.json +91 -0
- package/src/modes/theme/defaults/dark-terminal.json +95 -0
- package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/modes/theme/defaults/dark-tundra.json +91 -0
- package/src/modes/theme/defaults/dark-twilight.json +91 -0
- package/src/modes/theme/defaults/dark-volcanic.json +91 -0
- package/src/modes/theme/defaults/graphite.json +92 -0
- package/src/modes/theme/defaults/index.ts +199 -0
- package/src/modes/theme/defaults/light-arctic.json +107 -0
- package/src/modes/theme/defaults/light-aurora-day.json +91 -0
- package/src/modes/theme/defaults/light-canyon.json +91 -0
- package/src/modes/theme/defaults/light-catppuccin.json +106 -0
- package/src/modes/theme/defaults/light-cirrus.json +90 -0
- package/src/modes/theme/defaults/light-coral.json +95 -0
- package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
- package/src/modes/theme/defaults/light-dawn.json +90 -0
- package/src/modes/theme/defaults/light-dunes.json +91 -0
- package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
- package/src/modes/theme/defaults/light-forest.json +100 -0
- package/src/modes/theme/defaults/light-frost.json +95 -0
- package/src/modes/theme/defaults/light-github.json +115 -0
- package/src/modes/theme/defaults/light-glacier.json +91 -0
- package/src/modes/theme/defaults/light-gruvbox.json +108 -0
- package/src/modes/theme/defaults/light-haze.json +90 -0
- package/src/modes/theme/defaults/light-honeycomb.json +95 -0
- package/src/modes/theme/defaults/light-lagoon.json +91 -0
- package/src/modes/theme/defaults/light-lavender.json +95 -0
- package/src/modes/theme/defaults/light-meadow.json +91 -0
- package/src/modes/theme/defaults/light-mint.json +95 -0
- package/src/modes/theme/defaults/light-monochrome.json +101 -0
- package/src/modes/theme/defaults/light-ocean.json +99 -0
- package/src/modes/theme/defaults/light-one.json +99 -0
- package/src/modes/theme/defaults/light-opal.json +91 -0
- package/src/modes/theme/defaults/light-orchard.json +91 -0
- package/src/modes/theme/defaults/light-paper.json +95 -0
- package/src/modes/theme/defaults/light-poimandres.json +143 -0
- package/src/modes/theme/defaults/light-prism.json +90 -0
- package/src/modes/theme/defaults/light-retro.json +98 -0
- package/src/modes/theme/defaults/light-sand.json +95 -0
- package/src/modes/theme/defaults/light-savanna.json +91 -0
- package/src/modes/theme/defaults/light-solarized.json +102 -0
- package/src/modes/theme/defaults/light-soleil.json +90 -0
- package/src/modes/theme/defaults/light-sunset.json +99 -0
- package/src/modes/theme/defaults/light-synthwave.json +98 -0
- package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
- package/src/modes/theme/defaults/light-wetland.json +91 -0
- package/src/modes/theme/defaults/light-zenith.json +89 -0
- package/src/modes/theme/defaults/limestone.json +94 -0
- package/src/modes/theme/defaults/mahogany.json +97 -0
- package/src/modes/theme/defaults/marble.json +93 -0
- package/src/modes/theme/defaults/obsidian.json +91 -0
- package/src/modes/theme/defaults/onyx.json +91 -0
- package/src/modes/theme/defaults/pearl.json +93 -0
- package/src/modes/theme/defaults/porcelain.json +91 -0
- package/src/modes/theme/defaults/quartz.json +96 -0
- package/src/modes/theme/defaults/sandstone.json +95 -0
- package/src/modes/theme/defaults/titanium.json +90 -0
- package/src/modes/theme/light.json +93 -0
- package/src/modes/theme/mermaid-cache.ts +92 -0
- package/src/modes/theme/shimmer.ts +305 -0
- package/src/modes/theme/theme-schema.json +463 -0
- package/src/modes/theme/theme.ts +3171 -0
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +494 -0
- package/src/modes/ultrathink.ts +42 -0
- package/src/modes/utils/context-usage.ts +518 -0
- package/src/modes/utils/copy-targets.ts +378 -0
- package/src/modes/utils/hotkeys-markdown.ts +65 -0
- package/src/modes/utils/interactive-context-helpers.ts +31 -0
- package/src/modes/utils/keybinding-matchers.ts +86 -0
- package/src/modes/utils/tools-markdown.ts +31 -0
- package/src/modes/utils/transcript-render-helpers.ts +253 -0
- package/src/modes/utils/ui-helpers.ts +976 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/modes/workflow.ts +55 -0
- package/src/plan-mode/approved-plan.ts +194 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/plan-mode/plan-handoff.ts +37 -0
- package/src/plan-mode/plan-protection.ts +31 -0
- package/src/plan-mode/state.ts +6 -0
- package/src/priority.json +60 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/advise-tool.md +3 -0
- package/src/prompts/advisor/context-files.md +8 -0
- package/src/prompts/advisor/system.md +98 -0
- package/src/prompts/agents/designer.md +74 -0
- package/src/prompts/agents/frontmatter.md +12 -0
- package/src/prompts/agents/init.md +33 -0
- package/src/prompts/agents/librarian.md +119 -0
- package/src/prompts/agents/reviewer.md +139 -0
- package/src/prompts/agents/scout.md +58 -0
- package/src/prompts/agents/security-reviewer.md +75 -0
- package/src/prompts/agents/task.md +17 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/bench.md +6 -0
- package/src/prompts/ci-green-request.md +36 -0
- package/src/prompts/dry-balance-bench.md +8 -0
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/goals/goal-mode-context.md +4 -0
- package/src/prompts/goals/goal-todo-context.md +12 -0
- package/src/prompts/goals/guided-goal-interview.md +43 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/consolidation_system.md +4 -0
- package/src/prompts/memories/read-path.md +17 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-custom-request.md +21 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +68 -0
- package/src/prompts/security/scan-coordinator.md +7 -0
- package/src/prompts/security/scan-request.md +21 -0
- package/src/prompts/security/validate-request.md +8 -0
- package/src/prompts/session/launch-completion.md +1 -0
- package/src/prompts/skills/autoload.md +8 -0
- package/src/prompts/skills/user-invocation.md +11 -0
- package/src/prompts/steering/parent-irc.md +5 -0
- package/src/prompts/steering/user-interjection.md +6 -0
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/agent-creation-architect.md +50 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/auto-continue.md +1 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +14 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/commit-message-system.md +14 -0
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/custom-system-prompt.md +64 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +18 -0
- package/src/prompts/system/empty-stop-retry.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/irc-autoreply.md +6 -0
- package/src/prompts/system/irc-incoming.md +9 -0
- package/src/prompts/system/manual-continue.md +7 -0
- package/src/prompts/system/mcp-xdev-guidance.md +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/system/mid-run-todo-nudge.md +3 -0
- package/src/prompts/system/omfg-user.md +50 -0
- package/src/prompts/system/orchestrate-notice.md +40 -0
- package/src/prompts/system/personalities/default.md +18 -0
- package/src/prompts/system/personalities/friendly.md +17 -0
- package/src/prompts/system/personalities/pragmatic.md +15 -0
- package/src/prompts/system/plan-mode-active.md +125 -0
- package/src/prompts/system/plan-mode-approved.md +22 -0
- package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
- package/src/prompts/system/plan-mode-reference.md +10 -0
- package/src/prompts/system/plan-mode-subagent.md +33 -0
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
- package/src/prompts/system/plan-yolo-handoff.md +5 -0
- package/src/prompts/system/prewalk-checklist.md +7 -0
- package/src/prompts/system/prewalk-continue.md +1 -0
- package/src/prompts/system/prewalk-plan.md +13 -0
- package/src/prompts/system/project-prompt.md +61 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/resolve-device-reminder.md +3 -0
- package/src/prompts/system/rewind-report.md +6 -0
- package/src/prompts/system/side-channel-no-tools.md +3 -0
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-system-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
- package/src/prompts/system/speech-rewrite.md +15 -0
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/subagent-system-prompt.md +73 -0
- package/src/prompts/system/subagent-user-prompt.md +3 -0
- package/src/prompts/system/subagent-yield-reminder.md +23 -0
- package/src/prompts/system/system-prompt.md +263 -0
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/system/task-label.md +23 -0
- package/src/prompts/system/thinking-loop-redirect.md +10 -0
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system.md +16 -0
- package/src/prompts/system/tool-call-loop-redirect.md +8 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/system/ultrathink-notice.md +3 -0
- package/src/prompts/system/unexpected-stop-classifier.md +17 -0
- package/src/prompts/system/unexpected-stop-retry.md +4 -0
- package/src/prompts/system/vibe-mode-active.md +26 -0
- package/src/prompts/system/web-search.md +25 -0
- package/src/prompts/system/workflow-notice.md +112 -0
- package/src/prompts/system/xdev-mount-notice.md +20 -0
- package/src/prompts/tools/apply-patch.md +65 -0
- package/src/prompts/tools/ask.md +22 -0
- package/src/prompts/tools/ast-edit.md +11 -0
- package/src/prompts/tools/ast-grep.md +19 -0
- package/src/prompts/tools/async-result.md +8 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/browser.md +29 -0
- package/src/prompts/tools/checkpoint.md +15 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/debug.md +3 -0
- package/src/prompts/tools/eval.md +45 -0
- package/src/prompts/tools/github.md +22 -0
- package/src/prompts/tools/glob.md +16 -0
- package/src/prompts/tools/goal.md +11 -0
- package/src/prompts/tools/grep.md +13 -0
- package/src/prompts/tools/hub.md +34 -0
- package/src/prompts/tools/image-attachment-describe-system.md +8 -0
- package/src/prompts/tools/image-attachment-describe.md +10 -0
- package/src/prompts/tools/image-gen.md +7 -0
- package/src/prompts/tools/inspect-image-system.md +20 -0
- package/src/prompts/tools/inspect-image.md +22 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
- package/src/prompts/tools/lsp.md +19 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/memory-edit.md +12 -0
- package/src/prompts/tools/patch.md +57 -0
- package/src/prompts/tools/read.md +27 -0
- package/src/prompts/tools/recall.md +7 -0
- package/src/prompts/tools/reflect.md +5 -0
- package/src/prompts/tools/replace.md +30 -0
- package/src/prompts/tools/retain.md +6 -0
- package/src/prompts/tools/rewind.md +14 -0
- package/src/prompts/tools/security-publish.md +1 -0
- package/src/prompts/tools/security-scan.md +1 -0
- package/src/prompts/tools/task-async-contract.md +1 -0
- package/src/prompts/tools/task-summary.md +20 -0
- package/src/prompts/tools/task.md +83 -0
- package/src/prompts/tools/todo.md +42 -0
- package/src/prompts/tools/vibe-kill.md +3 -0
- package/src/prompts/tools/vibe-list.md +3 -0
- package/src/prompts/tools/vibe-send.md +9 -0
- package/src/prompts/tools/vibe-spawn.md +10 -0
- package/src/prompts/tools/vibe-turn-result.md +19 -0
- package/src/prompts/tools/vibe-wait.md +8 -0
- package/src/prompts/tools/web-search.md +8 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/registry/agent-lifecycle.ts +505 -0
- package/src/registry/agent-registry.ts +287 -0
- package/src/registry/persisted-agents.ts +423 -0
- package/src/sdk.ts +3820 -0
- package/src/secrets/index.ts +375 -0
- package/src/secrets/obfuscator.ts +2629 -0
- package/src/secrets/regex.ts +21 -0
- package/src/security/auth.ts +98 -0
- package/src/security/cloud.ts +686 -0
- package/src/security/comparison.ts +255 -0
- package/src/security/contracts/ids.ts +111 -0
- package/src/security/contracts/index.ts +4 -0
- package/src/security/contracts/schemas.ts +219 -0
- package/src/security/contracts/types.ts +254 -0
- package/src/security/contracts/validation.ts +69 -0
- package/src/security/coordinator.ts +708 -0
- package/src/security/importers/codex-security.ts +387 -0
- package/src/security/importers/index.ts +2 -0
- package/src/security/importers/sarif.ts +357 -0
- package/src/security/index.ts +13 -0
- package/src/security/preflight.ts +405 -0
- package/src/security/provenance.ts +106 -0
- package/src/security/publication.ts +326 -0
- package/src/security/remediation.ts +93 -0
- package/src/security/resource-output.ts +50 -0
- package/src/security/sarif.ts +78 -0
- package/src/security/store.ts +430 -0
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-events.ts +67 -0
- package/src/session/agent-session-types.ts +407 -0
- package/src/session/agent-session.ts +9000 -0
- package/src/session/agent-storage.ts +807 -0
- package/src/session/artifacts.ts +154 -0
- package/src/session/async-job-delivery.ts +82 -0
- package/src/session/auth-broker-config.ts +92 -0
- package/src/session/auth-storage.ts +25 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.ts +295 -0
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/client-bridge.ts +85 -0
- package/src/session/codex-auto-reset.ts +673 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/compact-modes.ts +105 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/eval-runner.ts +219 -0
- package/src/session/exit-diagnostics.ts +310 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/history-storage.ts +329 -0
- package/src/session/indexed-session-storage.ts +553 -0
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/launch-completion.ts +37 -0
- package/src/session/messages.ts +1304 -0
- package/src/session/model-controls.ts +757 -0
- package/src/session/prewalk.ts +279 -0
- package/src/session/provider-image-budget.ts +86 -0
- package/src/session/queued-messages.ts +99 -0
- package/src/session/redis-session-storage.ts +257 -0
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1896 -0
- package/src/session/session-context.ts +584 -0
- package/src/session/session-dump-format.ts +216 -0
- package/src/session/session-entries.ts +306 -0
- package/src/session/session-handoff.ts +325 -0
- package/src/session/session-history-format.ts +459 -0
- package/src/session/session-listing.ts +715 -0
- package/src/session/session-loader.ts +362 -0
- package/src/session/session-maintenance.ts +3115 -0
- package/src/session/session-manager.ts +2747 -0
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-metadata.ts +53 -0
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +280 -0
- package/src/session/session-persistence.ts +293 -0
- package/src/session/session-provider-boundary.ts +306 -0
- package/src/session/session-stats.ts +349 -0
- package/src/session/session-storage.ts +774 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/session-tools.ts +1385 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/settings-stream-fn.ts +79 -0
- package/src/session/shake-types.ts +43 -0
- package/src/session/snapcompact-inline.ts +545 -0
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/sql-session-storage.ts +374 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +1459 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/tool-choice-queue.ts +305 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-persistence.ts +142 -0
- package/src/session/turn-recovery.ts +2005 -0
- package/src/session/unexpected-stop-classifier.ts +141 -0
- package/src/session/yield-queue.ts +288 -0
- package/src/slash-commands/acp-builtins.ts +70 -0
- package/src/slash-commands/available-commands.ts +105 -0
- package/src/slash-commands/builtin-registry.ts +3149 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
- package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
- package/src/slash-commands/helpers/context-report.ts +66 -0
- package/src/slash-commands/helpers/format.ts +46 -0
- package/src/slash-commands/helpers/logout.ts +108 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +533 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/reset-usage.ts +68 -0
- package/src/slash-commands/helpers/security.ts +451 -0
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/ssh.ts +196 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
- package/src/slash-commands/helpers/todo.ts +285 -0
- package/src/slash-commands/helpers/usage-report.ts +198 -0
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/slash-commands/types.ts +139 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +667 -0
- package/src/ssh/file-transfer.ts +214 -0
- package/src/ssh/sshfs-mount.ts +163 -0
- package/src/ssh/utils.ts +51 -0
- package/src/startup-splash.ts +19 -0
- package/src/stt/asr-client.ts +401 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +603 -0
- package/src/stt/downloader.ts +142 -0
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +6 -0
- package/src/stt/models.ts +150 -0
- package/src/stt/sherpa-runtime.ts +71 -0
- package/src/stt/stt-controller.ts +320 -0
- package/src/stt/submit-trigger.ts +74 -0
- package/src/subprocess/worker-client.ts +463 -0
- package/src/subprocess/worker-runtime.ts +494 -0
- package/src/system-prompt.ts +921 -0
- package/src/task/agents.ts +170 -0
- package/src/task/commands.ts +132 -0
- package/src/task/discovery.ts +145 -0
- package/src/task/executor.ts +3431 -0
- package/src/task/index.ts +1515 -0
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/isolation-runner.ts +444 -0
- package/src/task/label.ts +40 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/omp-command.ts +26 -0
- package/src/task/output-manager.ts +115 -0
- package/src/task/parallel.ts +221 -0
- package/src/task/persisted-revive.ts +173 -0
- package/src/task/prewalk.ts +6 -0
- package/src/task/prompt-policy.ts +8 -0
- package/src/task/provider-concurrency.ts +100 -0
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/render.ts +1830 -0
- package/src/task/renderer.ts +14 -0
- package/src/task/repair-args.ts +118 -0
- package/src/task/spawn-policy.ts +72 -0
- package/src/task/structured-subagent.ts +676 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/types.ts +555 -0
- package/src/task/worktree.ts +967 -0
- package/src/task/yield-assembly.ts +198 -0
- package/src/telemetry-export.ts +504 -0
- package/src/thinking.ts +377 -0
- package/src/tiny/device.ts +111 -0
- package/src/tiny/dtype.ts +101 -0
- package/src/tiny/message-preproc.ts +155 -0
- package/src/tiny/models.ts +268 -0
- package/src/tiny/text.ts +290 -0
- package/src/tiny/title-client.ts +460 -0
- package/src/tiny/title-protocol.ts +56 -0
- package/src/tiny/worker.ts +354 -0
- package/src/tools/acp-bridge.ts +125 -0
- package/src/tools/approval.ts +245 -0
- package/src/tools/ask.ts +1459 -0
- package/src/tools/ast-edit.ts +718 -0
- package/src/tools/ast-grep.ts +526 -0
- package/src/tools/auto-generated-guard.ts +335 -0
- package/src/tools/bash-interactive.ts +435 -0
- package/src/tools/bash-interceptor.ts +148 -0
- package/src/tools/bash-pty-selection.ts +14 -0
- package/src/tools/bash-skill-urls.ts +350 -0
- package/src/tools/bash.ts +1826 -0
- package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
- package/src/tools/browser/aria/aria-snapshot.ts +131 -0
- package/src/tools/browser/attach.ts +219 -0
- package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
- package/src/tools/browser/cmux/rpc.ts +200 -0
- package/src/tools/browser/cmux/socket-client.ts +445 -0
- package/src/tools/browser/launch.ts +939 -0
- package/src/tools/browser/readable.ts +112 -0
- package/src/tools/browser/registry.ts +447 -0
- package/src/tools/browser/relay/bridge.ts +945 -0
- package/src/tools/browser/relay/daemon.ts +117 -0
- package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
- package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
- package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
- package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
- package/src/tools/browser/relay/kind.ts +41 -0
- package/src/tools/browser/relay/protocol.ts +54 -0
- package/src/tools/browser/relay/server.ts +141 -0
- package/src/tools/browser/render.ts +229 -0
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/shared-daemon.ts +139 -0
- package/src/tools/browser/tab-protocol.ts +123 -0
- package/src/tools/browser/tab-supervisor.ts +1077 -0
- package/src/tools/browser/tab-worker-entry.ts +29 -0
- package/src/tools/browser/tab-worker.ts +1982 -0
- package/src/tools/browser.ts +487 -0
- package/src/tools/builtin-names.ts +67 -0
- package/src/tools/checkpoint.ts +137 -0
- package/src/tools/computer/exposure.ts +14 -0
- package/src/tools/computer/protocol.ts +69 -0
- package/src/tools/computer/supervisor.ts +409 -0
- package/src/tools/computer/worker-entry.ts +39 -0
- package/src/tools/computer/worker.ts +745 -0
- package/src/tools/computer-renderer.ts +147 -0
- package/src/tools/computer.ts +222 -0
- package/src/tools/conflict-detect.ts +815 -0
- package/src/tools/context.ts +49 -0
- package/src/tools/debug.ts +1121 -0
- package/src/tools/default-renderer.ts +154 -0
- package/src/tools/essential-tools.ts +46 -0
- package/src/tools/eval-backends.ts +34 -0
- package/src/tools/eval-format/index.ts +24 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/julia.ts +446 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval-format/ruby.ts +380 -0
- package/src/tools/eval-render.ts +784 -0
- package/src/tools/eval.ts +774 -0
- package/src/tools/fetch.ts +1889 -0
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gh-cache-invalidation.ts +175 -0
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +484 -0
- package/src/tools/gh.ts +3958 -0
- package/src/tools/github-cache.ts +663 -0
- package/src/tools/glob.ts +691 -0
- package/src/tools/grep.ts +1920 -0
- package/src/tools/grouped-file-output.ts +210 -0
- package/src/tools/hub/index.ts +579 -0
- package/src/tools/hub/jobs.ts +714 -0
- package/src/tools/hub/launch.ts +690 -0
- package/src/tools/hub/messaging.ts +735 -0
- package/src/tools/hub/types.ts +117 -0
- package/src/tools/image-gen.ts +1689 -0
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +736 -0
- package/src/tools/inspect-image-renderer.ts +133 -0
- package/src/tools/inspect-image.ts +316 -0
- package/src/tools/json-tree.ts +260 -0
- package/src/tools/jtd-to-json-schema.ts +219 -0
- package/src/tools/jtd-to-typescript.ts +136 -0
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/learn.ts +141 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/manage-skill.ts +102 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/memory-edit.ts +61 -0
- package/src/tools/memory-recall.ts +102 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +211 -0
- package/src/tools/memory-retain.ts +89 -0
- package/src/tools/output-meta.ts +860 -0
- package/src/tools/output-schema-validator.ts +307 -0
- package/src/tools/path-utils.ts +1489 -0
- package/src/tools/plan-mode-guard.ts +155 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
- package/src/tools/read.ts +3762 -0
- package/src/tools/render-utils.ts +926 -0
- package/src/tools/renderers.ts +133 -0
- package/src/tools/report-tool-issue.ts +568 -0
- package/src/tools/resolve.ts +423 -0
- package/src/tools/review.ts +103 -0
- package/src/tools/run-scope.ts +417 -0
- package/src/tools/security-scan.ts +287 -0
- package/src/tools/shell-tokenize.ts +213 -0
- package/src/tools/sqlite-reader.ts +884 -0
- package/src/tools/terminal-output.ts +141 -0
- package/src/tools/todo.ts +1226 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +102 -0
- package/src/tools/tool-timeouts.ts +38 -0
- package/src/tools/tts.ts +266 -0
- package/src/tools/vibe.ts +608 -0
- package/src/tools/write.ts +1646 -0
- package/src/tools/xdev.ts +560 -0
- package/src/tools/yield.ts +486 -0
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +10 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/speakable.ts +392 -0
- package/src/tts/speech-enhancer.ts +206 -0
- package/src/tts/streaming-player.ts +120 -0
- package/src/tts/tts-client.ts +475 -0
- package/src/tts/tts-protocol.ts +69 -0
- package/src/tts/tts-worker.ts +434 -0
- package/src/tts/vocalizer.ts +419 -0
- package/src/tts/wav.ts +58 -0
- package/src/tui/code-cell.ts +268 -0
- package/src/tui/file-list.ts +55 -0
- package/src/tui/hyperlink.ts +178 -0
- package/src/tui/index.ts +13 -0
- package/src/tui/output-block.ts +268 -0
- package/src/tui/status-line.ts +54 -0
- package/src/tui/tree-list.ts +172 -0
- package/src/tui/types.ts +15 -0
- package/src/tui/utils.ts +103 -0
- package/src/tui/width-aware-text.ts +58 -0
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/block-context.ts +312 -0
- package/src/utils/changelog.ts +373 -0
- package/src/utils/clipboard.ts +361 -0
- package/src/utils/command-args.ts +74 -0
- package/src/utils/commit-message-generator.ts +148 -0
- package/src/utils/cpuprofile.ts +235 -0
- package/src/utils/edit-mode.ts +61 -0
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +80 -0
- package/src/utils/fetch-timeout.ts +10 -0
- package/src/utils/file-display-mode.ts +44 -0
- package/src/utils/file-mentions.ts +293 -0
- package/src/utils/git.ts +2472 -0
- package/src/utils/image-loading.ts +236 -0
- package/src/utils/image-resize.ts +420 -0
- package/src/utils/image-vision-fallback.ts +196 -0
- package/src/utils/inspect-image-mode.ts +39 -0
- package/src/utils/ipc.ts +38 -0
- package/src/utils/jj.ts +422 -0
- package/src/utils/lang-from-path.ts +251 -0
- package/src/utils/late-cleanup.ts +17 -0
- package/src/utils/local-date.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +223 -0
- package/src/utils/mupdf-wasm-embed.ts +12 -0
- package/src/utils/open.ts +126 -0
- package/src/utils/profile-tree.ts +111 -0
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/qrcode.ts +535 -0
- package/src/utils/sample-profile.ts +437 -0
- package/src/utils/session-color.ts +142 -0
- package/src/utils/shell-snapshot-fn-env.sh +63 -0
- package/src/utils/shell-snapshot.ts +326 -0
- package/src/utils/sixel.ts +69 -0
- package/src/utils/thinking-display.ts +163 -0
- package/src/utils/title-generator.ts +597 -0
- package/src/utils/token-rate.ts +72 -0
- package/src/utils/tool-choice.ts +50 -0
- package/src/utils/tools-manager.ts +411 -0
- package/src/utils/turndown.ts +82 -0
- package/src/utils/zip.ts +1106 -0
- package/src/vibe/runtime.ts +1684 -0
- package/src/vibe/state.ts +4 -0
- package/src/web/kagi.ts +305 -0
- package/src/web/parallel.ts +354 -0
- package/src/web/scrapers/artifacthub.ts +207 -0
- package/src/web/scrapers/arxiv.ts +83 -0
- package/src/web/scrapers/aur.ts +162 -0
- package/src/web/scrapers/biorxiv.ts +133 -0
- package/src/web/scrapers/bluesky.ts +262 -0
- package/src/web/scrapers/brew.ts +172 -0
- package/src/web/scrapers/cheatsh.ts +68 -0
- package/src/web/scrapers/chocolatey.ts +196 -0
- package/src/web/scrapers/choosealicense.ts +95 -0
- package/src/web/scrapers/cisa-kev.ts +87 -0
- package/src/web/scrapers/clojars.ts +154 -0
- package/src/web/scrapers/coingecko.ts +177 -0
- package/src/web/scrapers/crates-io.ts +97 -0
- package/src/web/scrapers/crossref.ts +136 -0
- package/src/web/scrapers/devto.ts +147 -0
- package/src/web/scrapers/discogs.ts +306 -0
- package/src/web/scrapers/discourse.ts +197 -0
- package/src/web/scrapers/dockerhub.ts +138 -0
- package/src/web/scrapers/docs-rs.ts +663 -0
- package/src/web/scrapers/fdroid.ts +134 -0
- package/src/web/scrapers/firefox-addons.ts +191 -0
- package/src/web/scrapers/flathub.ts +223 -0
- package/src/web/scrapers/github-gist.ts +58 -0
- package/src/web/scrapers/github.ts +800 -0
- package/src/web/scrapers/gitlab.ts +401 -0
- package/src/web/scrapers/go-pkg.ts +266 -0
- package/src/web/scrapers/hackage.ts +129 -0
- package/src/web/scrapers/hackernews.ts +189 -0
- package/src/web/scrapers/hex.ts +105 -0
- package/src/web/scrapers/huggingface.ts +321 -0
- package/src/web/scrapers/iacr.ts +89 -0
- package/src/web/scrapers/index.ts +252 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
- package/src/web/scrapers/lemmy.ts +203 -0
- package/src/web/scrapers/lobsters.ts +175 -0
- package/src/web/scrapers/mastodon.ts +292 -0
- package/src/web/scrapers/maven.ts +138 -0
- package/src/web/scrapers/mdn.ts +173 -0
- package/src/web/scrapers/metacpan.ts +222 -0
- package/src/web/scrapers/musicbrainz.ts +250 -0
- package/src/web/scrapers/npm.ts +98 -0
- package/src/web/scrapers/nuget.ts +183 -0
- package/src/web/scrapers/nvd.ts +222 -0
- package/src/web/scrapers/ollama.ts +239 -0
- package/src/web/scrapers/open-vsx.ts +106 -0
- package/src/web/scrapers/opencorporates.ts +292 -0
- package/src/web/scrapers/openlibrary.ts +336 -0
- package/src/web/scrapers/orcid.ts +286 -0
- package/src/web/scrapers/osv.ts +176 -0
- package/src/web/scrapers/packagist.ts +160 -0
- package/src/web/scrapers/pub-dev.ts +143 -0
- package/src/web/scrapers/pubmed.ts +211 -0
- package/src/web/scrapers/pypi.ts +112 -0
- package/src/web/scrapers/rawg.ts +110 -0
- package/src/web/scrapers/readthedocs.ts +121 -0
- package/src/web/scrapers/reddit.ts +95 -0
- package/src/web/scrapers/repology.ts +251 -0
- package/src/web/scrapers/rfc.ts +201 -0
- package/src/web/scrapers/rubygems.ts +103 -0
- package/src/web/scrapers/searchcode.ts +189 -0
- package/src/web/scrapers/sec-edgar.ts +261 -0
- package/src/web/scrapers/semantic-scholar.ts +171 -0
- package/src/web/scrapers/snapcraft.ts +187 -0
- package/src/web/scrapers/sourcegraph.ts +336 -0
- package/src/web/scrapers/spdx.ts +108 -0
- package/src/web/scrapers/spotify.ts +198 -0
- package/src/web/scrapers/stackoverflow.ts +120 -0
- package/src/web/scrapers/terraform.ts +277 -0
- package/src/web/scrapers/tldr.ts +47 -0
- package/src/web/scrapers/twitter.ts +94 -0
- package/src/web/scrapers/types.ts +354 -0
- package/src/web/scrapers/utils.ts +109 -0
- package/src/web/scrapers/vimeo.ts +133 -0
- package/src/web/scrapers/vscode-marketplace.ts +187 -0
- package/src/web/scrapers/w3c.ts +156 -0
- package/src/web/scrapers/wikidata.ts +344 -0
- package/src/web/scrapers/wikipedia.ts +84 -0
- package/src/web/scrapers/youtube.ts +325 -0
- package/src/web/search/index.ts +393 -0
- package/src/web/search/provider.ts +272 -0
- package/src/web/search/providers/anthropic.ts +406 -0
- package/src/web/search/providers/base.ts +112 -0
- package/src/web/search/providers/brave.ts +181 -0
- package/src/web/search/providers/browser-headers.ts +82 -0
- package/src/web/search/providers/browser-page.ts +125 -0
- package/src/web/search/providers/codex.ts +788 -0
- package/src/web/search/providers/duckduckgo.ts +382 -0
- package/src/web/search/providers/ecosia.ts +183 -0
- package/src/web/search/providers/exa.ts +513 -0
- package/src/web/search/providers/firecrawl.ts +213 -0
- package/src/web/search/providers/gemini.ts +634 -0
- package/src/web/search/providers/google.ts +195 -0
- package/src/web/search/providers/jina.ts +144 -0
- package/src/web/search/providers/kagi.ts +98 -0
- package/src/web/search/providers/kimi.ts +221 -0
- package/src/web/search/providers/mojeek.ts +220 -0
- package/src/web/search/providers/parallel.ts +186 -0
- package/src/web/search/providers/perplexity-auth.ts +142 -0
- package/src/web/search/providers/perplexity.ts +997 -0
- package/src/web/search/providers/public.ts +199 -0
- package/src/web/search/providers/searxng.ts +467 -0
- package/src/web/search/providers/startpage.ts +225 -0
- package/src/web/search/providers/synthetic.ts +126 -0
- package/src/web/search/providers/tavily.ts +244 -0
- package/src/web/search/providers/tinyfish.ts +166 -0
- package/src/web/search/providers/utils.ts +130 -0
- package/src/web/search/providers/xai.ts +372 -0
- package/src/web/search/providers/zai.ts +452 -0
- package/src/web/search/query.ts +850 -0
- package/src/web/search/render.ts +262 -0
- package/src/web/search/types.ts +518 -0
- package/src/web/search/utils.ts +17 -0
- package/src/workspace-tree.ts +326 -0
|
@@ -0,0 +1,3431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process execution for subagents.
|
|
3
|
+
*
|
|
4
|
+
* Runs each subagent on the main thread and forwards AgentEvents for progress tracking.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import type { AgentEvent, AgentIdentity, AgentMessage, AgentTelemetryConfig } from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import { recordHandoff, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
|
|
10
|
+
import type { Api, Model, ServiceTierByFamily, Usage } from "@oh-my-pi/pi-ai";
|
|
11
|
+
import { logger, popLoopPhase, prompt, pushLoopPhase, untilAborted } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { AsyncJobManager } from "../async";
|
|
13
|
+
import type { Rule } from "../capability/rule";
|
|
14
|
+
import { ModelRegistry } from "../config/model-registry";
|
|
15
|
+
import {
|
|
16
|
+
formatModelSelectorValue,
|
|
17
|
+
formatModelStringWithRouting,
|
|
18
|
+
resolveAgentPrewalkPattern,
|
|
19
|
+
resolveConfiguredModelPatterns,
|
|
20
|
+
resolveExplicitModelRole,
|
|
21
|
+
resolveModelOverride,
|
|
22
|
+
resolveModelOverrideWithAuthFallback,
|
|
23
|
+
} from "../config/model-resolver";
|
|
24
|
+
import type { PromptTemplate } from "../config/prompt-templates";
|
|
25
|
+
import { buildServiceTierByFamily, resolveSubagentServiceTier } from "../config/service-tier";
|
|
26
|
+
import { Settings } from "../config/settings";
|
|
27
|
+
import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
|
|
28
|
+
import type { ToolPathWithSource } from "../extensibility/custom-tools";
|
|
29
|
+
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
30
|
+
import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
|
|
31
|
+
import { getSessionSlashCommands } from "../extensibility/extensions/get-commands-handler";
|
|
32
|
+
import { buildSkillPromptMessage, type Skill } from "../extensibility/skills";
|
|
33
|
+
import type { HindsightSessionState } from "../hindsight/state";
|
|
34
|
+
import type { LocalProtocolOptions } from "../internal-urls";
|
|
35
|
+
import type { MCPManager } from "../mcp/manager";
|
|
36
|
+
import type { MnemopiSessionState } from "../mnemopi/state";
|
|
37
|
+
import subagentAsyncPendingTemplate from "../prompts/system/subagent-async-pending.md" with { type: "text" };
|
|
38
|
+
import subagentSystemPromptTemplate from "../prompts/system/subagent-system-prompt.md" with { type: "text" };
|
|
39
|
+
import submitReminderTemplate from "../prompts/system/subagent-yield-reminder.md" with { type: "text" };
|
|
40
|
+
import { AgentLifecycleManager, type AgentReviver } from "../registry/agent-lifecycle";
|
|
41
|
+
import { AgentRegistry } from "../registry/agent-registry";
|
|
42
|
+
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage } from "../sdk";
|
|
43
|
+
import type { AgentSession, AgentSessionEvent, Prewalk } from "../session/agent-session";
|
|
44
|
+
import type { ArtifactManager } from "../session/artifacts";
|
|
45
|
+
import { ASYNC_RESULT_MESSAGE_TYPE } from "../session/async-job-delivery";
|
|
46
|
+
import type { AuthStorage } from "../session/auth-storage";
|
|
47
|
+
import { SKILL_PROMPT_MESSAGE_TYPE, USER_INTERRUPT_LABEL } from "../session/messages";
|
|
48
|
+
import { SessionManager } from "../session/session-manager";
|
|
49
|
+
import { truncateTail } from "../session/streaming-output";
|
|
50
|
+
import { type ConfiguredThinkingLevel, prewalkWouldBeNoop, resolveTaskEffortLevel, type TaskEffort } from "../thinking";
|
|
51
|
+
import type { ContextFileEntry, ToolSession } from "../tools";
|
|
52
|
+
import { resolveEvalBackends } from "../tools/eval-backends";
|
|
53
|
+
import { isIrcEnabled } from "../tools/hub";
|
|
54
|
+
import { normalizeSchema } from "../tools/jtd-to-json-schema";
|
|
55
|
+
import { buildOutputValidator, summarizeValidationFailure } from "../tools/output-schema-validator";
|
|
56
|
+
import { ToolAbortError } from "../tools/tool-errors";
|
|
57
|
+
import type { EventBus } from "../utils/event-bus";
|
|
58
|
+
import { trackLateCleanup } from "../utils/late-cleanup";
|
|
59
|
+
import { buildNamedToolChoice } from "../utils/tool-choice";
|
|
60
|
+
import type { WorkspaceTree } from "../workspace-tree";
|
|
61
|
+
import { generateTaskLabel } from "./label";
|
|
62
|
+
import { resolveAgentPrewalkDefault } from "./prewalk";
|
|
63
|
+
import { isReadOnlyAgent } from "./read-only-policy";
|
|
64
|
+
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
65
|
+
import {
|
|
66
|
+
type AgentDefinition,
|
|
67
|
+
type AgentProgress,
|
|
68
|
+
MAX_OUTPUT_BYTES,
|
|
69
|
+
MAX_OUTPUT_LINES,
|
|
70
|
+
type SingleResult,
|
|
71
|
+
type StructuredSubagentOutput,
|
|
72
|
+
type StructuredSubagentSchemaMode,
|
|
73
|
+
type StructuredSubagentSchemaSource,
|
|
74
|
+
TASK_SUBAGENT_EVENT_CHANNEL,
|
|
75
|
+
TASK_SUBAGENT_LIFECYCLE_CHANNEL,
|
|
76
|
+
TASK_SUBAGENT_PROGRESS_CHANNEL,
|
|
77
|
+
type TaskToolDetails,
|
|
78
|
+
type YieldItem,
|
|
79
|
+
} from "./types";
|
|
80
|
+
import { arrayValuedLabels, assembleYieldResult } from "./yield-assembly";
|
|
81
|
+
|
|
82
|
+
export type { YieldItem } from "./types";
|
|
83
|
+
|
|
84
|
+
const MCP_CALL_TIMEOUT_MS = 60_000;
|
|
85
|
+
const TASK_ABORT_CLEANUP_GRACE_MS = 10_000;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Soft per-agent request budgets (assistant requests per run). Crossing the
|
|
89
|
+
* budget injects a wrap-up steering notice (`task.softRequestBudgetNotice`,
|
|
90
|
+
* on by default). At 1.5x the budget the free-running turn is stopped and the
|
|
91
|
+
* agent is driven to one forced final `yield` so partial findings come back
|
|
92
|
+
* as a real report; only if it still refuses to yield within
|
|
93
|
+
* {@link BUDGET_STOP_GRACE_REQUESTS} more requests is the run hard-aborted.
|
|
94
|
+
* Entries are ceilings, not fixed values: the `default` key applies to agents
|
|
95
|
+
* without an explicit entry, and the `task.softRequestBudget` setting can only
|
|
96
|
+
* lower an agent's budget, never raise it above its bundled entry (0 disables
|
|
97
|
+
* the guard entirely).
|
|
98
|
+
*/
|
|
99
|
+
export const SOFT_REQUEST_BUDGET: Record<string, number> = {
|
|
100
|
+
scout: 100,
|
|
101
|
+
sonic: 100,
|
|
102
|
+
default: 200,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolves the effective soft request budget for an agent. The configured
|
|
107
|
+
* `task.softRequestBudget` and the agent's bundled entry are both upper
|
|
108
|
+
* bounds, so the tighter one wins; a configured budget of 0 disables the
|
|
109
|
+
* guard regardless of the bundled entry.
|
|
110
|
+
*/
|
|
111
|
+
export function resolveSoftRequestBudget(agentName: string, configuredBudget: number): number {
|
|
112
|
+
const normalized = Math.max(0, Math.trunc(configuredBudget));
|
|
113
|
+
if (normalized === 0) return 0;
|
|
114
|
+
return Math.min(normalized, SOFT_REQUEST_BUDGET[agentName] ?? normalized);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Extra requests allowed after a budget stop for the forced yield to land before the run is hard-aborted. */
|
|
118
|
+
export const BUDGET_STOP_GRACE_REQUESTS = 5;
|
|
119
|
+
|
|
120
|
+
/** Steering notice injected when a subagent crosses its soft request budget. */
|
|
121
|
+
export function buildBudgetNotice(requests: number, budget: number): string {
|
|
122
|
+
return `[budget notice] You have used ${requests} requests in this run (soft budget: ${budget}). Wrap up now: finish the current step and yield your final report. At ${Math.ceil(budget * 1.5)} requests the run is force-stopped and you will be asked to yield whatever you have.`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Flatten whitespace and clip salvage text for the cancelled-child summary line. */
|
|
126
|
+
function formatSalvageSnippet(text: string, maxLength = 500): string {
|
|
127
|
+
const flattened = text.replace(/\s+/g, " ").trim();
|
|
128
|
+
return flattened.length > maxLength ? `${flattened.slice(0, maxLength - 1)}…` : flattened;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Agent event types to forward for progress tracking. */
|
|
132
|
+
const agentEventTypes = new Set<AgentEvent["type"]>([
|
|
133
|
+
"agent_start",
|
|
134
|
+
"agent_end",
|
|
135
|
+
"turn_start",
|
|
136
|
+
"turn_end",
|
|
137
|
+
"message_start",
|
|
138
|
+
"message_update",
|
|
139
|
+
"message_end",
|
|
140
|
+
"tool_execution_start",
|
|
141
|
+
"tool_execution_update",
|
|
142
|
+
"tool_execution_end",
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const isAgentEvent = (event: AgentSessionEvent): event is AgentEvent =>
|
|
146
|
+
agentEventTypes.has(event.type as AgentEvent["type"]);
|
|
147
|
+
|
|
148
|
+
function normalizeModelPatterns(value: string | string[] | undefined): string[] {
|
|
149
|
+
if (!value) return [];
|
|
150
|
+
if (Array.isArray(value)) {
|
|
151
|
+
return value.map(entry => entry.trim()).filter(Boolean);
|
|
152
|
+
}
|
|
153
|
+
return value
|
|
154
|
+
.split(",")
|
|
155
|
+
.map(entry => entry.trim())
|
|
156
|
+
.filter(Boolean);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const SUBAGENT_RETRY_FALLBACK_ROLE_PREFIX = "subagent:";
|
|
160
|
+
|
|
161
|
+
interface SubagentRetryFallbackCandidate {
|
|
162
|
+
model: Model<Api>;
|
|
163
|
+
selector: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function resolveSubagentRetryFallbackCandidates(
|
|
167
|
+
modelPatterns: string[],
|
|
168
|
+
modelRegistry: ModelRegistry,
|
|
169
|
+
settings: Settings,
|
|
170
|
+
): SubagentRetryFallbackCandidate[] {
|
|
171
|
+
const candidates: SubagentRetryFallbackCandidate[] = [];
|
|
172
|
+
const seen = new Set<string>();
|
|
173
|
+
const disabledProviders = new Set(settings.get("disabledProviders"));
|
|
174
|
+
for (const pattern of modelPatterns) {
|
|
175
|
+
const resolved = resolveModelOverride([pattern], modelRegistry, settings);
|
|
176
|
+
if (!resolved.model) continue;
|
|
177
|
+
if (disabledProviders.has(resolved.model.provider)) continue;
|
|
178
|
+
const selector = resolved.explicitThinkingLevel
|
|
179
|
+
? formatModelSelectorValue(formatModelStringWithRouting(resolved.model), resolved.thinkingLevel)
|
|
180
|
+
: formatModelStringWithRouting(resolved.model);
|
|
181
|
+
if (seen.has(selector)) continue;
|
|
182
|
+
seen.add(selector);
|
|
183
|
+
candidates.push({ model: resolved.model, selector });
|
|
184
|
+
}
|
|
185
|
+
return candidates;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Chain a single-model subagent inherits when its own model patterns supply no
|
|
190
|
+
* fallbacks of their own. The child is pinned to a `subagent:<id>` role whose
|
|
191
|
+
* chain shadows every configured role chain (see
|
|
192
|
+
* {@link installSubagentRetryFallbackChain}), so a role-alias request (`@smol`)
|
|
193
|
+
* MUST inherit that role's chain — otherwise the pin silently re-routes the
|
|
194
|
+
* child onto the `default` role's chain. Explicit model selectors keep
|
|
195
|
+
* inheriting `default`: they carry no role identity, and a role that happens to
|
|
196
|
+
* be assigned the same model must not capture the child's fallback routing.
|
|
197
|
+
*/
|
|
198
|
+
function resolveSubagentInheritedRetryFallbackChain(
|
|
199
|
+
settings: Settings,
|
|
200
|
+
modelRegistry: ModelRegistry,
|
|
201
|
+
modelPatterns: string[],
|
|
202
|
+
): string[] | undefined {
|
|
203
|
+
const configuredChains = settings.get("retry.fallbackChains");
|
|
204
|
+
const role = resolveExplicitModelRole(modelPatterns, settings);
|
|
205
|
+
// An explicitly emptied role chain means "no fallbacks", not "inherit
|
|
206
|
+
// default" — mirrors expandDefaultRetryFallbackChains.
|
|
207
|
+
const fallbackChain = (role !== undefined ? configuredChains?.[role] : undefined) ?? configuredChains?.default;
|
|
208
|
+
if (
|
|
209
|
+
!Array.isArray(fallbackChain) ||
|
|
210
|
+
fallbackChain.length === 0 ||
|
|
211
|
+
!fallbackChain.every(entry => typeof entry === "string")
|
|
212
|
+
) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const disabledProviders = new Set(settings.get("disabledProviders"));
|
|
216
|
+
return fallbackChain.filter(entry => {
|
|
217
|
+
const resolved = resolveModelOverride([entry], modelRegistry, settings);
|
|
218
|
+
return !resolved.model || !disabledProviders.has(resolved.model.provider);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function installSubagentRetryFallbackChain(args: {
|
|
223
|
+
settings: Settings;
|
|
224
|
+
id: string;
|
|
225
|
+
candidates: SubagentRetryFallbackCandidate[];
|
|
226
|
+
inheritedFallbackChain: string[] | undefined;
|
|
227
|
+
model: Model<Api> | undefined;
|
|
228
|
+
authFallbackUsed: boolean;
|
|
229
|
+
}): string | undefined {
|
|
230
|
+
const { settings, id, candidates, inheritedFallbackChain, model, authFallbackUsed } = args;
|
|
231
|
+
if (!model || authFallbackUsed || candidates.length === 0) return undefined;
|
|
232
|
+
|
|
233
|
+
const selectedIndex = candidates.findIndex(
|
|
234
|
+
candidate => candidate.model.provider === model.provider && candidate.model.id === model.id,
|
|
235
|
+
);
|
|
236
|
+
if (selectedIndex < 0) return undefined;
|
|
237
|
+
const fallbackSelectors = candidates.slice(selectedIndex + 1).map(candidate => candidate.selector);
|
|
238
|
+
const existingFallbackChains = settings.get("retry.fallbackChains");
|
|
239
|
+
// A single configured model may reuse its role's (or the default) configured chain, but never an implicit parent fallback.
|
|
240
|
+
const fallbackChain = fallbackSelectors.length > 0 ? fallbackSelectors : inheritedFallbackChain;
|
|
241
|
+
if (
|
|
242
|
+
!Array.isArray(fallbackChain) ||
|
|
243
|
+
fallbackChain.length === 0 ||
|
|
244
|
+
!fallbackChain.every(entry => typeof entry === "string")
|
|
245
|
+
) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const role = `${SUBAGENT_RETRY_FALLBACK_ROLE_PREFIX}${id}`;
|
|
250
|
+
const modelRoles: Record<string, string> = {};
|
|
251
|
+
const existingRoles = settings.getModelRoles();
|
|
252
|
+
for (const existingRole in existingRoles) {
|
|
253
|
+
const selector = existingRoles[existingRole];
|
|
254
|
+
if (selector) {
|
|
255
|
+
modelRoles[existingRole] = selector;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
modelRoles[role] = candidates[selectedIndex].selector;
|
|
259
|
+
settings.override("modelRoles", modelRoles);
|
|
260
|
+
// Insert the task-specific role first so another role assigned to the same model cannot capture fallback routing.
|
|
261
|
+
const fallbackChains: Record<string, string[]> = {
|
|
262
|
+
[role]: fallbackChain,
|
|
263
|
+
};
|
|
264
|
+
for (const existingRole in existingFallbackChains) {
|
|
265
|
+
if (existingRole !== role) {
|
|
266
|
+
fallbackChains[existingRole] = existingFallbackChains[existingRole];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
settings.override("retry.fallbackChains", fallbackChains);
|
|
270
|
+
return role;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function renderIrcPeerRoster(selfId: string): string {
|
|
274
|
+
const peers = AgentRegistry.global()
|
|
275
|
+
.list()
|
|
276
|
+
.filter(ref => ref.id !== selfId && ref.status !== "aborted" && ref.kind !== "advisor");
|
|
277
|
+
if (peers.length === 0) return "- (no other agents)";
|
|
278
|
+
const lines = peers.map(
|
|
279
|
+
peer =>
|
|
280
|
+
`- \`${peer.id}\` — ${peer.displayName} (${peer.kind}, ${peer.status})${peer.activity ? `: ${peer.activity}` : ""}`,
|
|
281
|
+
);
|
|
282
|
+
if (peers.some(peer => peer.status === "idle" || peer.status === "parked")) {
|
|
283
|
+
lines.push("Idle/parked peers are not gone: messaging them wakes (or revives) them.");
|
|
284
|
+
}
|
|
285
|
+
return lines.join("\n");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function withAbortTimeout<T>(
|
|
289
|
+
promise: Promise<T>,
|
|
290
|
+
timeoutMs: number,
|
|
291
|
+
signal?: AbortSignal,
|
|
292
|
+
timeoutController?: AbortController,
|
|
293
|
+
): Promise<T> {
|
|
294
|
+
if (signal?.aborted) {
|
|
295
|
+
return Promise.reject(new ToolAbortError());
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const { promise: wrappedPromise, resolve, reject } = Promise.withResolvers<T>();
|
|
299
|
+
let settled = false;
|
|
300
|
+
const timeoutId = setTimeout(() => {
|
|
301
|
+
if (settled) return;
|
|
302
|
+
settled = true;
|
|
303
|
+
timeoutController?.abort(new DOMException(`MCP tool call timed out after ${timeoutMs}ms`, "TimeoutError"));
|
|
304
|
+
reject(new Error(`MCP tool call timed out after ${timeoutMs}ms`));
|
|
305
|
+
}, timeoutMs);
|
|
306
|
+
|
|
307
|
+
const onAbort = () => {
|
|
308
|
+
if (settled) return;
|
|
309
|
+
settled = true;
|
|
310
|
+
clearTimeout(timeoutId);
|
|
311
|
+
timeoutController?.abort();
|
|
312
|
+
reject(new ToolAbortError());
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
if (signal) {
|
|
316
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
promise.then(resolve, reject).finally(() => {
|
|
320
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
321
|
+
clearTimeout(timeoutId);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return wrappedPromise;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
328
|
+
if (!value || typeof value !== "object") return false;
|
|
329
|
+
return !Array.isArray(value);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Options for subagent execution */
|
|
333
|
+
export interface ExecutorOptions {
|
|
334
|
+
cwd: string;
|
|
335
|
+
/** Additional workspace directories to seed on the subagent session (multi-root). */
|
|
336
|
+
additionalDirectories?: string[];
|
|
337
|
+
/** Exact provider credential resolver inherited from the parent session. */
|
|
338
|
+
getApiKey?: CreateAgentSessionOptions["getApiKey"];
|
|
339
|
+
worktree?: string;
|
|
340
|
+
agent: AgentDefinition;
|
|
341
|
+
task: string;
|
|
342
|
+
assignment?: string;
|
|
343
|
+
/** Shared background from the task call (`task.batch`), rendered into the subagent's system prompt. */
|
|
344
|
+
context?: string;
|
|
345
|
+
/**
|
|
346
|
+
* The session's active overall plan, handed off so subagents spawned during
|
|
347
|
+
* plan execution share the same plan context as the main agent. Omitted when
|
|
348
|
+
* the session did not start with a plan (or while plan mode is still active).
|
|
349
|
+
*/
|
|
350
|
+
planReference?: { path: string; content: string };
|
|
351
|
+
/** Pre-set UI label (e.g. eval bridge label). When absent, a tiny-model label is generated from the assignment. */
|
|
352
|
+
description?: string;
|
|
353
|
+
index: number;
|
|
354
|
+
id: string;
|
|
355
|
+
parentToolCallId?: string;
|
|
356
|
+
/**
|
|
357
|
+
* Spawn runs as a detached background job (parent turn not blocked on it).
|
|
358
|
+
* Rides the subagent lifecycle/progress payloads so HUD-style surfaces can
|
|
359
|
+
* skip spawns the transcript already renders inline. See
|
|
360
|
+
* {@link SubagentLifecyclePayload.detached}.
|
|
361
|
+
*/
|
|
362
|
+
detached?: boolean;
|
|
363
|
+
modelOverride?: string | string[];
|
|
364
|
+
/** Explicit pre-expansion model role alias selected for this run. */
|
|
365
|
+
modelRole?: string;
|
|
366
|
+
/**
|
|
367
|
+
* Active model selector of the parent session, used as an auth-aware fallback
|
|
368
|
+
* if the resolved subagent model has no working credentials. See #985.
|
|
369
|
+
*/
|
|
370
|
+
parentActiveModelPattern?: string;
|
|
371
|
+
thinkingLevel?: ConfiguredThinkingLevel;
|
|
372
|
+
/** Caller-requested coarse effort (`lo`/`med`/`hi`); maps onto the resolved model's supported thinking range and wins over {@link thinkingLevel}. */
|
|
373
|
+
effort?: TaskEffort;
|
|
374
|
+
/** Schema used to validate the final structured completion. */
|
|
375
|
+
outputSchema?: unknown;
|
|
376
|
+
/** Enforcement policy for {@link outputSchema}; defaults to legacy permissive behavior. */
|
|
377
|
+
outputSchemaMode?: StructuredSubagentSchemaMode;
|
|
378
|
+
/** Origin of the selected schema, preserved in {@link SingleResult.structuredOutput}. */
|
|
379
|
+
outputSchemaSource?: StructuredSubagentSchemaSource;
|
|
380
|
+
/**
|
|
381
|
+
* Caller supplied a schema that supersedes the agent's native output prompt.
|
|
382
|
+
* Eval `agent(..., schema=...)` sets this so built-in agents ignore stale yield labels.
|
|
383
|
+
*/
|
|
384
|
+
outputSchemaOverridesAgent?: boolean;
|
|
385
|
+
/** Parent task recursion depth (0 = top-level, 1 = first child, etc.) */
|
|
386
|
+
taskDepth?: number;
|
|
387
|
+
/**
|
|
388
|
+
* Override the `task.maxRuntimeMs` wall-clock cap for this run. When provided
|
|
389
|
+
* it wins over the settings value; `0` disables the per-subagent wall-clock
|
|
390
|
+
* limit entirely. Used by the eval `agent()` bridge, whose parent cell
|
|
391
|
+
* watchdog is already suspended for the call's duration.
|
|
392
|
+
*/
|
|
393
|
+
maxRuntimeMs?: number;
|
|
394
|
+
/** Include IRC only when the invocation policy permits collaboration. */
|
|
395
|
+
enableIrc?: boolean;
|
|
396
|
+
enableLsp?: boolean;
|
|
397
|
+
/**
|
|
398
|
+
* Enable MCP capabilities for this child. `false` suppresses both inherited
|
|
399
|
+
* MCP proxy tools and session MCP discovery; it never consults the
|
|
400
|
+
* process-global MCP manager. Defaults to `true`.
|
|
401
|
+
*/
|
|
402
|
+
enableMCP?: boolean;
|
|
403
|
+
/**
|
|
404
|
+
* Limit the child to its explicit host tool names and the required yield
|
|
405
|
+
* tool, suppressing discovered and always-included capabilities.
|
|
406
|
+
*/
|
|
407
|
+
restrictToolNames?: boolean;
|
|
408
|
+
signal?: AbortSignal;
|
|
409
|
+
onProgress?: (progress: AgentProgress) => void;
|
|
410
|
+
/**
|
|
411
|
+
* Epochs (ms, `Date.now()`) bracketing the concurrency-semaphore wait:
|
|
412
|
+
* `invokedAt` is stamped at the spawn boundary before `acquire()`,
|
|
413
|
+
* `acquiredAt` immediately after. {@link runSubprocess} reports true queue
|
|
414
|
+
* wait (`acquiredAt - invokedAt`) and pre-run setup (`startTime - acquiredAt`)
|
|
415
|
+
* separately in the launch-timing debug log. Undefined for callers that
|
|
416
|
+
* bypass the semaphore path.
|
|
417
|
+
*/
|
|
418
|
+
invokedAt?: number;
|
|
419
|
+
acquiredAt?: number;
|
|
420
|
+
sessionFile?: string | null;
|
|
421
|
+
persistArtifacts?: boolean;
|
|
422
|
+
artifactsDir?: string;
|
|
423
|
+
eventBus?: EventBus;
|
|
424
|
+
contextFiles?: ContextFileEntry[];
|
|
425
|
+
skills?: Skill[];
|
|
426
|
+
promptTemplates?: PromptTemplate[];
|
|
427
|
+
workspaceTree?: WorkspaceTree;
|
|
428
|
+
/** Parent-discovered rules, forwarded to skip rule discovery in the subagent. */
|
|
429
|
+
rules?: Rule[];
|
|
430
|
+
/**
|
|
431
|
+
* Parent's discovered extension source paths. Forwarded to skip the
|
|
432
|
+
* extension FS scan in the subagent; the subagent then re-binds each
|
|
433
|
+
* extension against its own `ExtensionAPI` (cwd, eventBus, runtime).
|
|
434
|
+
*/
|
|
435
|
+
preloadedExtensionPaths?: string[];
|
|
436
|
+
/**
|
|
437
|
+
* Parent's discovered custom-tool source paths. Forwarded to skip the
|
|
438
|
+
* `.omp/tools/` FS scan in the subagent; the subagent then re-binds each
|
|
439
|
+
* tool against its own `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
440
|
+
*/
|
|
441
|
+
preloadedCustomToolPaths?: ToolPathWithSource[];
|
|
442
|
+
mcpManager?: MCPManager;
|
|
443
|
+
authStorage?: AuthStorage;
|
|
444
|
+
modelRegistry?: ModelRegistry;
|
|
445
|
+
settings?: Settings;
|
|
446
|
+
/**
|
|
447
|
+
* Parent session's live per-family service tiers, the source of truth for a
|
|
448
|
+
* subagent whose `tier.subagent` is `"inherit"`. `null` = the parent
|
|
449
|
+
* explicitly has no tier (e.g. `/fast off`); omitted = no live session, so
|
|
450
|
+
* inherit falls back to the subagent's configured `tier.*` settings.
|
|
451
|
+
*/
|
|
452
|
+
parentServiceTier?: ServiceTierByFamily | null;
|
|
453
|
+
/** Override local:// protocol options so subagent shares parent's local:// root */
|
|
454
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
455
|
+
/**
|
|
456
|
+
* Parent session's ArtifactManager. Subagent adopts it so artifact IDs are
|
|
457
|
+
* unique across the whole agent tree and all artifacts land in the parent's
|
|
458
|
+
* artifacts directory (no per-subagent subdir).
|
|
459
|
+
*/
|
|
460
|
+
parentArtifactManager?: ArtifactManager;
|
|
461
|
+
parentHindsightSessionState?: HindsightSessionState;
|
|
462
|
+
parentMnemopiSessionState?: MnemopiSessionState;
|
|
463
|
+
/** Parent agent's eval executor session id. Subagents reuse it so eval state is shared. */
|
|
464
|
+
parentEvalSessionId?: string;
|
|
465
|
+
/**
|
|
466
|
+
* Parent agent's OpenTelemetry configuration. When defined, the subagent's
|
|
467
|
+
* loop is started with the same tracer/hooks but its own agent identity
|
|
468
|
+
* stamped, so its `invoke_agent` / `chat` / `execute_tool` spans appear as
|
|
469
|
+
* a sub-tree under the parent's active `execute_tool task` span. A
|
|
470
|
+
* `handoff` span is emitted on dispatch to mark the parent → subagent
|
|
471
|
+
* transition explicitly.
|
|
472
|
+
*/
|
|
473
|
+
parentTelemetry?: AgentTelemetryConfig;
|
|
474
|
+
/** Skills to autoload via sendCustomMessage before the first prompt */
|
|
475
|
+
autoloadSkills?: Skill[];
|
|
476
|
+
/**
|
|
477
|
+
* Registry id of the spawning agent, recorded as this subagent's parent.
|
|
478
|
+
* Forwarded verbatim to the SDK; the executor never derives it (the spawner
|
|
479
|
+
* passes its own `getAgentId()`).
|
|
480
|
+
*/
|
|
481
|
+
parentAgentId?: string;
|
|
482
|
+
/**
|
|
483
|
+
* Keep the finished subagent addressable in the registry for IRC/revival.
|
|
484
|
+
* Defaults to true. Eval bridge agents are programmatic one-shot helpers and
|
|
485
|
+
* set this false so disposal unregisters them instead of leaving idle peers.
|
|
486
|
+
*/
|
|
487
|
+
keepAlive?: boolean;
|
|
488
|
+
/** Internal ownership handoff for cleanup that outlives the visible Task result. */
|
|
489
|
+
onCleanupDeferred?: (completion: Promise<void>) => void;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function parseStringifiedJson(value: unknown): unknown {
|
|
493
|
+
if (typeof value !== "string") return value;
|
|
494
|
+
const trimmed = value.trim();
|
|
495
|
+
if (!trimmed) return value;
|
|
496
|
+
if (!(trimmed.startsWith("{") || trimmed.startsWith("["))) return value;
|
|
497
|
+
try {
|
|
498
|
+
return JSON.parse(trimmed);
|
|
499
|
+
} catch {
|
|
500
|
+
return value;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function previewOffendingData(value: unknown, maxLength = 500): string {
|
|
505
|
+
let serialized: string;
|
|
506
|
+
try {
|
|
507
|
+
serialized = JSON.stringify(value) ?? "null";
|
|
508
|
+
} catch {
|
|
509
|
+
serialized = String(value);
|
|
510
|
+
}
|
|
511
|
+
return serialized.length > maxLength ? `${serialized.slice(0, maxLength)}…` : serialized;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function tryParseJsonOutput(text: string): unknown | undefined {
|
|
515
|
+
const trimmed = text.trim();
|
|
516
|
+
if (!trimmed) return undefined;
|
|
517
|
+
try {
|
|
518
|
+
return JSON.parse(trimmed);
|
|
519
|
+
} catch {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function extractCompletionData(parsed: unknown): unknown {
|
|
525
|
+
if (!parsed || typeof parsed !== "object") return parsed;
|
|
526
|
+
const record = parsed as Record<string, unknown>;
|
|
527
|
+
if ("data" in record) {
|
|
528
|
+
return record.data;
|
|
529
|
+
}
|
|
530
|
+
return parsed;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function resolveFallbackCompletion(rawOutput: string, outputSchema: unknown): { data: unknown } | null {
|
|
534
|
+
const parsed = tryParseJsonOutput(rawOutput);
|
|
535
|
+
if (parsed === undefined) return null;
|
|
536
|
+
const candidate = parseStringifiedJson(extractCompletionData(parsed));
|
|
537
|
+
if (candidate === undefined) return null;
|
|
538
|
+
const { validator, error } = buildOutputValidator(outputSchema);
|
|
539
|
+
if (error) return null;
|
|
540
|
+
if (validator && !validator.validate(candidate).success) return null;
|
|
541
|
+
return { data: candidate };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
interface FinalizeSubprocessOutputArgs {
|
|
545
|
+
rawOutput: string;
|
|
546
|
+
exitCode: number;
|
|
547
|
+
stderr: string;
|
|
548
|
+
doneAborted: boolean;
|
|
549
|
+
signalAborted: boolean;
|
|
550
|
+
yieldItems?: YieldItem[];
|
|
551
|
+
outputSchema: unknown;
|
|
552
|
+
outputSchemaMode?: StructuredSubagentSchemaMode;
|
|
553
|
+
outputSchemaSource?: StructuredSubagentSchemaSource;
|
|
554
|
+
lastAssistantText?: string;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
interface FinalizeSubprocessOutputResult {
|
|
558
|
+
rawOutput: string;
|
|
559
|
+
exitCode: number;
|
|
560
|
+
stderr: string;
|
|
561
|
+
abortedViaYield: boolean;
|
|
562
|
+
hasYield: boolean;
|
|
563
|
+
structuredOutput?: StructuredSubagentOutput;
|
|
564
|
+
}
|
|
565
|
+
export const SUBAGENT_WARNING_SCHEMA_OVERRIDDEN =
|
|
566
|
+
"SYSTEM WARNING: Subagent exhausted schema-retry budget; result was accepted despite failing the output schema.";
|
|
567
|
+
export const SUBAGENT_WARNING_NULL_YIELD = "SYSTEM WARNING: Subagent called yield with null data.";
|
|
568
|
+
export const SUBAGENT_WARNING_MISSING_YIELD =
|
|
569
|
+
"SYSTEM WARNING: Subagent exited without calling yield tool after 3 reminders.";
|
|
570
|
+
|
|
571
|
+
/** Build a schema_violation outcome — surfaced as a non-zero exit so callers treat it as a failure. */
|
|
572
|
+
function buildSchemaViolationOutcome(
|
|
573
|
+
failure: { message: string; missingRequired: string[] },
|
|
574
|
+
data: unknown,
|
|
575
|
+
): { rawOutput: string; stderr: string; exitCode: number } {
|
|
576
|
+
const missing = failure.missingRequired;
|
|
577
|
+
const headline =
|
|
578
|
+
missing.length > 0
|
|
579
|
+
? `schema_violation: missing required fields: ${missing.join(", ")}`
|
|
580
|
+
: `schema_violation: ${failure.message}`;
|
|
581
|
+
const payload = {
|
|
582
|
+
error: "schema_violation",
|
|
583
|
+
message: failure.message,
|
|
584
|
+
missingRequired: missing,
|
|
585
|
+
data: previewOffendingData(data),
|
|
586
|
+
};
|
|
587
|
+
let rawOutput: string;
|
|
588
|
+
try {
|
|
589
|
+
rawOutput = JSON.stringify(payload, null, 2);
|
|
590
|
+
} catch {
|
|
591
|
+
rawOutput = `{"error":"schema_violation","message":${JSON.stringify(headline)}}`;
|
|
592
|
+
}
|
|
593
|
+
return { rawOutput, stderr: headline, exitCode: 1 };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): FinalizeSubprocessOutputResult {
|
|
597
|
+
let { rawOutput, exitCode, stderr } = args;
|
|
598
|
+
const { yieldItems, doneAborted, signalAborted, outputSchema, lastAssistantText } = args;
|
|
599
|
+
const mode = args.outputSchemaMode ?? "permissive";
|
|
600
|
+
const source = args.outputSchemaSource ?? (outputSchema === undefined ? "none" : "session");
|
|
601
|
+
const includeStructuredOutput = source !== "none";
|
|
602
|
+
let structuredOutput: StructuredSubagentOutput | undefined;
|
|
603
|
+
let abortedViaYield = false;
|
|
604
|
+
const hasYield = Array.isArray(yieldItems) && yieldItems.length > 0;
|
|
605
|
+
const hadFailureBeforeYield = exitCode !== 0 && stderr.trim().length > 0;
|
|
606
|
+
|
|
607
|
+
if (hasYield) {
|
|
608
|
+
const lastYield = yieldItems[yieldItems.length - 1];
|
|
609
|
+
if (lastYield?.status === "aborted") {
|
|
610
|
+
abortedViaYield = true;
|
|
611
|
+
exitCode = 0;
|
|
612
|
+
stderr = lastYield.error || "Subagent aborted task";
|
|
613
|
+
try {
|
|
614
|
+
rawOutput = JSON.stringify({ aborted: true, error: lastYield.error }, null, 2);
|
|
615
|
+
} catch {
|
|
616
|
+
rawOutput = `{"aborted":true,"error":"${lastYield.error || "Unknown error"}"}`;
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
const assembled = assembleYieldResult(yieldItems, lastAssistantText, arrayValuedLabels(outputSchema));
|
|
620
|
+
if (!assembled || assembled.missingData) {
|
|
621
|
+
rawOutput = rawOutput ? `${SUBAGENT_WARNING_NULL_YIELD}\n\n${rawOutput}` : SUBAGENT_WARNING_NULL_YIELD;
|
|
622
|
+
} else {
|
|
623
|
+
const { validator, error: schemaError, normalized } = buildOutputValidator(outputSchema);
|
|
624
|
+
const completeData = assembled.rawText ? assembled.data : parseStringifiedJson(assembled.data ?? null);
|
|
625
|
+
const validation = validator?.validate(completeData);
|
|
626
|
+
const failure =
|
|
627
|
+
validation && !validation.success
|
|
628
|
+
? summarizeValidationFailure(validation, completeData, validator?.requiredFields ?? [])
|
|
629
|
+
: assembled.schemaOverridden
|
|
630
|
+
? { message: SUBAGENT_WARNING_SCHEMA_OVERRIDDEN, missingRequired: [] }
|
|
631
|
+
: schemaError
|
|
632
|
+
? { message: `invalid output schema: ${schemaError}`, missingRequired: [] }
|
|
633
|
+
: undefined;
|
|
634
|
+
if (includeStructuredOutput) {
|
|
635
|
+
structuredOutput =
|
|
636
|
+
schemaError || normalized === undefined
|
|
637
|
+
? {
|
|
638
|
+
source,
|
|
639
|
+
mode,
|
|
640
|
+
status: "unavailable",
|
|
641
|
+
data: completeData,
|
|
642
|
+
error: schemaError ? `invalid output schema: ${schemaError}` : undefined,
|
|
643
|
+
}
|
|
644
|
+
: failure
|
|
645
|
+
? { source, mode, status: "invalid", data: completeData, error: failure.message }
|
|
646
|
+
: { source, mode, status: "valid", data: completeData };
|
|
647
|
+
}
|
|
648
|
+
const mustReject =
|
|
649
|
+
failure !== undefined && (mode === "strict" || (!assembled.schemaOverridden && !schemaError));
|
|
650
|
+
if (mustReject && failure) {
|
|
651
|
+
const outcome = buildSchemaViolationOutcome(failure, completeData);
|
|
652
|
+
rawOutput = outcome.rawOutput;
|
|
653
|
+
stderr = outcome.stderr;
|
|
654
|
+
exitCode = outcome.exitCode;
|
|
655
|
+
} else {
|
|
656
|
+
try {
|
|
657
|
+
rawOutput =
|
|
658
|
+
assembled.rawText && typeof completeData === "string"
|
|
659
|
+
? completeData
|
|
660
|
+
: (JSON.stringify(completeData, null, 2) ?? "null");
|
|
661
|
+
} catch (err) {
|
|
662
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
663
|
+
rawOutput = `{"error":"Failed to serialize yield data: ${errorMessage}"}`;
|
|
664
|
+
}
|
|
665
|
+
if (!hadFailureBeforeYield) {
|
|
666
|
+
exitCode = 0;
|
|
667
|
+
stderr = assembled.schemaOverridden
|
|
668
|
+
? SUBAGENT_WARNING_SCHEMA_OVERRIDDEN
|
|
669
|
+
: (structuredOutput?.error ?? "");
|
|
670
|
+
} else if (!stderr) {
|
|
671
|
+
stderr = "Subagent failed after yielding a result.";
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
const allowFallback = exitCode === 0 && !doneAborted && !signalAborted;
|
|
678
|
+
const { normalized: normalizedSchema, error: schemaError } = normalizeSchema(outputSchema);
|
|
679
|
+
const hasOutputSchema = normalizedSchema !== undefined && !schemaError;
|
|
680
|
+
const fallback = allowFallback ? resolveFallbackCompletion(rawOutput, outputSchema) : null;
|
|
681
|
+
if (fallback) {
|
|
682
|
+
const { validator } = buildOutputValidator(outputSchema);
|
|
683
|
+
const completeData = parseStringifiedJson(fallback.data ?? null);
|
|
684
|
+
const result = validator?.validate(completeData) ?? { success: true as const };
|
|
685
|
+
if (!result.success) {
|
|
686
|
+
const summary = summarizeValidationFailure(result, completeData, validator?.requiredFields ?? []);
|
|
687
|
+
if (includeStructuredOutput) {
|
|
688
|
+
structuredOutput = { source, mode, status: "invalid", data: completeData, error: summary.message };
|
|
689
|
+
}
|
|
690
|
+
const outcome = buildSchemaViolationOutcome(summary, completeData);
|
|
691
|
+
rawOutput = outcome.rawOutput;
|
|
692
|
+
stderr = outcome.stderr;
|
|
693
|
+
exitCode = outcome.exitCode;
|
|
694
|
+
} else {
|
|
695
|
+
if (includeStructuredOutput) {
|
|
696
|
+
structuredOutput = {
|
|
697
|
+
source,
|
|
698
|
+
mode,
|
|
699
|
+
status: "valid",
|
|
700
|
+
data: completeData,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
try {
|
|
704
|
+
rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
|
|
705
|
+
} catch (err) {
|
|
706
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
707
|
+
rawOutput = `{"error":"Failed to serialize fallback completion: ${errorMessage}"}`;
|
|
708
|
+
}
|
|
709
|
+
exitCode = 0;
|
|
710
|
+
stderr = "";
|
|
711
|
+
}
|
|
712
|
+
} else if (!hasOutputSchema && allowFallback && rawOutput.trim().length > 0) {
|
|
713
|
+
exitCode = 0;
|
|
714
|
+
stderr = "";
|
|
715
|
+
} else if (exitCode === 0) {
|
|
716
|
+
const hasRawOutput = rawOutput.trim().length > 0;
|
|
717
|
+
rawOutput = rawOutput ? `${SUBAGENT_WARNING_MISSING_YIELD}\n\n${rawOutput}` : SUBAGENT_WARNING_MISSING_YIELD;
|
|
718
|
+
if (hasOutputSchema || !hasRawOutput) {
|
|
719
|
+
exitCode = 1;
|
|
720
|
+
stderr = SUBAGENT_WARNING_MISSING_YIELD;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return { rawOutput, exitCode, stderr, abortedViaYield, hasYield, structuredOutput };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Extract a short preview from tool args for display.
|
|
730
|
+
*/
|
|
731
|
+
function extractToolArgsPreview(args: Record<string, unknown>): string {
|
|
732
|
+
// Priority order for preview
|
|
733
|
+
const previewKeys = ["command", "file_path", "path", "pattern", "query", "url", "task", "prompt"];
|
|
734
|
+
|
|
735
|
+
for (const key of previewKeys) {
|
|
736
|
+
if (args[key] && typeof args[key] === "string") {
|
|
737
|
+
const value = args[key] as string;
|
|
738
|
+
return value.length > 60 ? `${value.slice(0, 59)}…` : value;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return "";
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function getNumberField(record: Record<string, unknown>, key: string): number | undefined {
|
|
746
|
+
if (!Object.hasOwn(record, key)) return undefined;
|
|
747
|
+
const value = record[key];
|
|
748
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function firstNumberField(record: Record<string, unknown>, keys: string[]): number | undefined {
|
|
752
|
+
for (const key of keys) {
|
|
753
|
+
const value = getNumberField(record, key);
|
|
754
|
+
if (value !== undefined) return value;
|
|
755
|
+
}
|
|
756
|
+
return undefined;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Tokens for progress display: input + output + cacheWrite per turn.
|
|
761
|
+
*
|
|
762
|
+
* Deliberately excludes cacheRead. With prompt caching, cacheRead in each turn
|
|
763
|
+
* equals the full cached context (potentially hundreds of KB), so summing it
|
|
764
|
+
* across all turns produces a cumulative total that is N×context_size — far
|
|
765
|
+
* larger than the context window and misleading as a "work done" metric.
|
|
766
|
+
* cacheWrite is kept because each byte is written once, not repeated per turn.
|
|
767
|
+
* The cost segment handles billing; dedicated cache_read/cache_write segments
|
|
768
|
+
* handle cache-specific monitoring.
|
|
769
|
+
*/
|
|
770
|
+
function getUsageTokens(usage: unknown): number {
|
|
771
|
+
if (!usage || typeof usage !== "object") return 0;
|
|
772
|
+
const record = usage as Record<string, unknown>;
|
|
773
|
+
|
|
774
|
+
const input = firstNumberField(record, ["input", "input_tokens", "inputTokens"]) ?? 0;
|
|
775
|
+
const output = firstNumberField(record, ["output", "output_tokens", "outputTokens"]) ?? 0;
|
|
776
|
+
const cacheWrite = firstNumberField(record, ["cacheWrite", "cache_write", "cacheWriteTokens"]) ?? 0;
|
|
777
|
+
const computed = input + output + cacheWrite;
|
|
778
|
+
if (computed > 0) return computed;
|
|
779
|
+
// Fallback for providers that only surface a pre-summed total without individual
|
|
780
|
+
// field breakdown. This total includes cacheRead, but returning it is still better
|
|
781
|
+
// than silently showing 0 for those providers.
|
|
782
|
+
return firstNumberField(record, ["totalTokens", "total_tokens"]) ?? 0;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Create proxy tools that reuse the parent's MCP connections.
|
|
787
|
+
*
|
|
788
|
+
* Each proxy delegates to the current source `MCPTool`/`DeferredMCPTool` rather
|
|
789
|
+
* than rebuilding a raw `tools/call` request, so the Task/subagent path shares
|
|
790
|
+
* the source tool's authoritative outbound boundary: harness-intent (`i`)
|
|
791
|
+
* stripping, optional-placeholder pruning, local-URL resolution, reconnect
|
|
792
|
+
* retry, abort handling, and result/provider metadata. The source tool is
|
|
793
|
+
* re-resolved on every call by raw MCP server/tool metadata (not the normalized
|
|
794
|
+
* display name), so a reconnect that swaps the instance in `getTools()` is
|
|
795
|
+
* always honored. The proxy adds only the Task-specific 60s call timeout,
|
|
796
|
+
* combining its abort signal with the caller's around source execution.
|
|
797
|
+
*/
|
|
798
|
+
export function createMCPProxyTools(mcpManager: MCPManager): CustomTool[] {
|
|
799
|
+
return mcpManager.getTools().map(tool => {
|
|
800
|
+
const serverName = tool.mcpServerName ?? "";
|
|
801
|
+
const mcpToolName = tool.mcpToolName ?? "";
|
|
802
|
+
return {
|
|
803
|
+
name: tool.name,
|
|
804
|
+
label: tool.label ?? tool.name,
|
|
805
|
+
description: tool.description ?? "",
|
|
806
|
+
parameters: tool.parameters,
|
|
807
|
+
strict: tool.strict,
|
|
808
|
+
mcpServerName: serverName,
|
|
809
|
+
mcpToolName,
|
|
810
|
+
execute: async (toolCallId, params, onUpdate, ctx, signal) => {
|
|
811
|
+
if (signal?.aborted) {
|
|
812
|
+
throw new ToolAbortError();
|
|
813
|
+
}
|
|
814
|
+
// Re-resolve by raw MCP metadata so a reconnect that replaced the
|
|
815
|
+
// source instance is picked up; the display name alone is not enough.
|
|
816
|
+
const source = mcpManager
|
|
817
|
+
.getTools()
|
|
818
|
+
.find(t => t.mcpServerName === serverName && t.mcpToolName === mcpToolName);
|
|
819
|
+
if (!source?.execute) {
|
|
820
|
+
return {
|
|
821
|
+
content: [{ type: "text" as const, text: `MCP error: tool ${mcpToolName} no longer available` }],
|
|
822
|
+
details: { serverName, mcpToolName, isError: true },
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
const timeoutController = new AbortController();
|
|
827
|
+
const timeoutSignal = timeoutController.signal;
|
|
828
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
829
|
+
return await withAbortTimeout(
|
|
830
|
+
Promise.resolve(source.execute(toolCallId, params, onUpdate, ctx, combinedSignal)),
|
|
831
|
+
MCP_CALL_TIMEOUT_MS,
|
|
832
|
+
signal,
|
|
833
|
+
timeoutController,
|
|
834
|
+
);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
if (error instanceof ToolAbortError) {
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
content: [
|
|
841
|
+
{
|
|
842
|
+
type: "text" as const,
|
|
843
|
+
text: `MCP error: ${error instanceof Error ? error.message : String(error)}`,
|
|
844
|
+
},
|
|
845
|
+
],
|
|
846
|
+
details: { serverName, mcpToolName, isError: true },
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
};
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export function createSubagentSettings(
|
|
855
|
+
baseSettings: Settings,
|
|
856
|
+
overrides?: Partial<Record<SettingPath, unknown>>,
|
|
857
|
+
inheritedServiceTier?: ServiceTierByFamily | null,
|
|
858
|
+
): Settings {
|
|
859
|
+
const snapshot: Partial<Record<SettingPath, unknown>> = {};
|
|
860
|
+
for (const key of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
|
|
861
|
+
snapshot[key] = baseSettings.get(key);
|
|
862
|
+
}
|
|
863
|
+
// Resolve the subagent's per-family tiers from `tier.subagent` ("inherit" =
|
|
864
|
+
// match the parent's live tiers when a live session supplied them, else the
|
|
865
|
+
// subagent's own configured tier.* settings). The result is stamped back onto
|
|
866
|
+
// the snapshot so createAgentSession's tier.* reads pick it up.
|
|
867
|
+
const inheritedTiers =
|
|
868
|
+
inheritedServiceTier === undefined
|
|
869
|
+
? buildServiceTierByFamily(
|
|
870
|
+
baseSettings.get("tier.openai"),
|
|
871
|
+
baseSettings.get("tier.anthropic"),
|
|
872
|
+
baseSettings.get("tier.google"),
|
|
873
|
+
)
|
|
874
|
+
: (inheritedServiceTier ?? {});
|
|
875
|
+
const subagentTiers = resolveSubagentServiceTier(baseSettings.get("tier.subagent"), inheritedTiers);
|
|
876
|
+
snapshot["tier.openai"] = subagentTiers.openai ?? "none";
|
|
877
|
+
snapshot["tier.anthropic"] = subagentTiers.anthropic ?? "none";
|
|
878
|
+
snapshot["tier.google"] = subagentTiers.google ?? "none";
|
|
879
|
+
return Settings.isolated({
|
|
880
|
+
...snapshot,
|
|
881
|
+
// Async jobs and bash auto-backgrounding are inherited from the parent:
|
|
882
|
+
// background jobs are owner-routed to the subagent's own session, and
|
|
883
|
+
// the run driver's quiescence barrier + teardown reap guarantee no
|
|
884
|
+
// owner job outlives the run, so worktree capture/cleanup stays
|
|
885
|
+
// race-free (previously both were force-disabled here).
|
|
886
|
+
|
|
887
|
+
// Subagents run headless — there is no UI to confirm prompts against, so
|
|
888
|
+
// the parent task approval is the authorization boundary. Use yolo mode
|
|
889
|
+
// to preserve unattended subagent execution. User `tools.approval` policies still apply.
|
|
890
|
+
"tools.approvalMode": "yolo",
|
|
891
|
+
...overrides,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export type AbortReason = "signal" | "terminate" | "timeout" | "budget";
|
|
896
|
+
|
|
897
|
+
const MAX_YIELD_TOOL_ERRORS = 6;
|
|
898
|
+
|
|
899
|
+
/** Inputs for the run monitor driving one subagent assignment. */
|
|
900
|
+
interface RunMonitorArgs {
|
|
901
|
+
index: number;
|
|
902
|
+
id: string;
|
|
903
|
+
agent: AgentDefinition;
|
|
904
|
+
task: string;
|
|
905
|
+
assignment?: string;
|
|
906
|
+
description?: string;
|
|
907
|
+
/** Parent model registry for tiny-model label generation; absent → skip labeling. */
|
|
908
|
+
modelRegistry?: ModelRegistry;
|
|
909
|
+
/** Parent settings for tiny-model label generation. */
|
|
910
|
+
settings?: Settings;
|
|
911
|
+
modelOverride?: string | string[];
|
|
912
|
+
/** Explicit pre-expansion model role alias selected for this run. */
|
|
913
|
+
modelRole?: string;
|
|
914
|
+
signal?: AbortSignal;
|
|
915
|
+
onProgress?: (progress: AgentProgress) => void;
|
|
916
|
+
eventBus?: EventBus;
|
|
917
|
+
parentToolCallId?: string;
|
|
918
|
+
detached?: boolean;
|
|
919
|
+
sessionFile?: string;
|
|
920
|
+
/** Soft assistant-request budget; 0 disables the guard. */
|
|
921
|
+
softRequestBudget: number;
|
|
922
|
+
/** Whether crossing the soft budget injects a wrap-up steering notice. */
|
|
923
|
+
softRequestBudgetNotice: boolean;
|
|
924
|
+
/** Wall-clock cap in ms; 0 disables the timer. */
|
|
925
|
+
maxRuntimeMs: number;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* The run-monitoring core of {@link runSubprocess}: progress tracking, event
|
|
930
|
+
* processing, abort/budget machinery, usage accumulation, and output capture
|
|
931
|
+
* for one assignment run.
|
|
932
|
+
*/
|
|
933
|
+
interface SubagentRunMonitor {
|
|
934
|
+
readonly progress: AgentProgress;
|
|
935
|
+
/** Fires when the run was asked to stop (caller signal, timeout, budget, terminate). */
|
|
936
|
+
readonly abortSignal: AbortSignal;
|
|
937
|
+
readonly accumulatedUsage: Usage;
|
|
938
|
+
hasUsage(): boolean;
|
|
939
|
+
yieldCalled(): boolean;
|
|
940
|
+
runtimeLimitExceeded(): boolean;
|
|
941
|
+
/** True once the soft-budget stop fired: the free-running turn was aborted and the run is being driven to a forced final yield. */
|
|
942
|
+
budgetStopRequested(): boolean;
|
|
943
|
+
/** Resolves when the budget-stop session abort has settled (immediately when no stop fired). */
|
|
944
|
+
waitForBudgetStop(): Promise<void>;
|
|
945
|
+
/**
|
|
946
|
+
* True when a recorded yield was invalidated by a later async-result
|
|
947
|
+
* injection and no fresh yield has landed since: the yield payload
|
|
948
|
+
* predates background job outcomes the model was shown.
|
|
949
|
+
*/
|
|
950
|
+
yieldInvalidatedByAsync(): boolean;
|
|
951
|
+
/**
|
|
952
|
+
* True once a terminal yield with pending owner async work stopped the
|
|
953
|
+
* free-running turn (recoverable, like a budget stop) instead of
|
|
954
|
+
* terminating the run. Cleared when {@link waitForYieldTurnStop} settles.
|
|
955
|
+
*/
|
|
956
|
+
yieldTurnStopRequested(): boolean;
|
|
957
|
+
/** Resolves when the yield turn-stop session abort has settled (immediately when none fired). */
|
|
958
|
+
waitForYieldTurnStop(): Promise<void>;
|
|
959
|
+
/** The abort kind for this run, when an abort was requested. */
|
|
960
|
+
abortKind(): AbortReason | undefined;
|
|
961
|
+
terminalError(): string | undefined;
|
|
962
|
+
/** True when the abort carries a precise external reason (signal / wall-clock / budget). */
|
|
963
|
+
hasExplicitAbortReason(): boolean;
|
|
964
|
+
/** Whether the (attempted) abort counts as a cancelled run rather than an internal failure. */
|
|
965
|
+
isAbortedRun(): boolean;
|
|
966
|
+
requestAbort(reason: AbortReason): void;
|
|
967
|
+
failWithError(message: string): void;
|
|
968
|
+
abortActiveSession(): Promise<void>;
|
|
969
|
+
waitForActiveSessionAbort(): Promise<void>;
|
|
970
|
+
resolveSignalAbortReason(): string;
|
|
971
|
+
resolveAbortReasonText(): string;
|
|
972
|
+
setActiveSession(session: AgentSession | null): void;
|
|
973
|
+
/** Return and clear the active session reference. */
|
|
974
|
+
takeActiveSession(): AgentSession | null;
|
|
975
|
+
/** Subscribe the monitor to a session's events. Returns the unsubscribe function. */
|
|
976
|
+
attach(session: AgentSession): () => void;
|
|
977
|
+
/** Best-effort capture of the last assistant text for cancelled-run salvage. */
|
|
978
|
+
captureSalvage(session: AgentSession): void;
|
|
979
|
+
lastAssistantSalvageText(): string | undefined;
|
|
980
|
+
/** Final raw output: end-of-run assistant text when available, else accumulated chunks. */
|
|
981
|
+
rawOutput(): string;
|
|
982
|
+
scheduleProgress(flush?: boolean): void;
|
|
983
|
+
/** Stop processing events and clear listeners/timers. Call once the run settled. */
|
|
984
|
+
finish(): void;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* True when `message` is the session-injected async-result follow-up
|
|
989
|
+
* ({@link ASYNC_RESULT_MESSAGE_TYPE}): the transcript-ordered signal that a
|
|
990
|
+
* background job outcome landed after whatever the model said before it.
|
|
991
|
+
*/
|
|
992
|
+
function isAsyncResultInjection(message: AgentMessage | undefined): boolean {
|
|
993
|
+
return message?.role === "custom" && message.customType === ASYNC_RESULT_MESSAGE_TYPE;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
997
|
+
const {
|
|
998
|
+
index,
|
|
999
|
+
id,
|
|
1000
|
+
agent,
|
|
1001
|
+
task,
|
|
1002
|
+
assignment,
|
|
1003
|
+
signal,
|
|
1004
|
+
onProgress,
|
|
1005
|
+
softRequestBudget,
|
|
1006
|
+
softRequestBudgetNotice,
|
|
1007
|
+
maxRuntimeMs,
|
|
1008
|
+
} = args;
|
|
1009
|
+
const startTime = Date.now();
|
|
1010
|
+
|
|
1011
|
+
const progress: AgentProgress = {
|
|
1012
|
+
index,
|
|
1013
|
+
id,
|
|
1014
|
+
agent: agent.name,
|
|
1015
|
+
agentSource: agent.source,
|
|
1016
|
+
status: "running",
|
|
1017
|
+
task,
|
|
1018
|
+
assignment,
|
|
1019
|
+
description: args.description,
|
|
1020
|
+
lastIntent: undefined,
|
|
1021
|
+
recentTools: [],
|
|
1022
|
+
recentOutput: [],
|
|
1023
|
+
toolCount: 0,
|
|
1024
|
+
requests: 0,
|
|
1025
|
+
tokens: 0,
|
|
1026
|
+
cost: 0,
|
|
1027
|
+
durationMs: 0,
|
|
1028
|
+
modelOverride: args.modelOverride,
|
|
1029
|
+
modelRole: args.modelRole,
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
const outputChunks: string[] = [];
|
|
1033
|
+
const finalOutputChunks: string[] = [];
|
|
1034
|
+
const RECENT_OUTPUT_TAIL_BYTES = 8 * 1024;
|
|
1035
|
+
let recentOutputTail = "";
|
|
1036
|
+
let recentOutputDirty = false;
|
|
1037
|
+
let resolved = false;
|
|
1038
|
+
let abortSent = false;
|
|
1039
|
+
let abortReason: AbortReason | undefined;
|
|
1040
|
+
let runtimeLimitExceeded = false;
|
|
1041
|
+
const listenerController = new AbortController();
|
|
1042
|
+
const listenerSignal = listenerController.signal;
|
|
1043
|
+
const abortController = new AbortController();
|
|
1044
|
+
const abortSignal = abortController.signal;
|
|
1045
|
+
let activeSession: AgentSession | null = null;
|
|
1046
|
+
let yieldCalled = false;
|
|
1047
|
+
let yieldCallPending = false;
|
|
1048
|
+
let yieldInvalidatedByAsync = false;
|
|
1049
|
+
let yieldTurnStopRequested = false;
|
|
1050
|
+
let yieldTurnStopPromise: Promise<void> | null = null;
|
|
1051
|
+
|
|
1052
|
+
// Accumulate usage incrementally from message_end events (no memory for streaming events)
|
|
1053
|
+
const accumulatedUsage: Usage = {
|
|
1054
|
+
input: 0,
|
|
1055
|
+
output: 0,
|
|
1056
|
+
cacheRead: 0,
|
|
1057
|
+
cacheWrite: 0,
|
|
1058
|
+
totalTokens: 0,
|
|
1059
|
+
reasoningTokens: 0,
|
|
1060
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
1061
|
+
};
|
|
1062
|
+
let hasUsage = false;
|
|
1063
|
+
let budgetSteerSent = false;
|
|
1064
|
+
let budgetLimitExceeded = false;
|
|
1065
|
+
let budgetStopRequested = false;
|
|
1066
|
+
let budgetStopAbortPromise: Promise<void> | undefined;
|
|
1067
|
+
let terminalError: string | undefined;
|
|
1068
|
+
let consecutiveYieldToolErrors = 0;
|
|
1069
|
+
let lastAssistantSalvageText: string | undefined;
|
|
1070
|
+
let activeSessionAbortPromise: Promise<void> | undefined;
|
|
1071
|
+
|
|
1072
|
+
const abortActiveSession = (): Promise<void> => {
|
|
1073
|
+
const session = activeSession;
|
|
1074
|
+
if (!session) return Promise.resolve();
|
|
1075
|
+
activeSessionAbortPromise ??= session.abort().catch(error => {
|
|
1076
|
+
logger.debug("Subagent session abort cleanup failed", {
|
|
1077
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
return activeSessionAbortPromise;
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
const waitForActiveSessionAbort = async (): Promise<void> => {
|
|
1084
|
+
if (activeSessionAbortPromise) await activeSessionAbortPromise;
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
const requestAbort = (reason: AbortReason) => {
|
|
1088
|
+
if (reason === "timeout") {
|
|
1089
|
+
runtimeLimitExceeded = true;
|
|
1090
|
+
}
|
|
1091
|
+
if (reason === "budget") {
|
|
1092
|
+
budgetLimitExceeded = true;
|
|
1093
|
+
}
|
|
1094
|
+
if (abortSent) {
|
|
1095
|
+
if (reason === "signal" && abortReason !== "signal" && abortReason !== "timeout") {
|
|
1096
|
+
abortReason = "signal";
|
|
1097
|
+
}
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (resolved) return;
|
|
1101
|
+
abortSent = true;
|
|
1102
|
+
abortReason = reason;
|
|
1103
|
+
abortController.abort();
|
|
1104
|
+
void abortActiveSession();
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
// Soft-budget stop: cancel the free-running turn WITHOUT aborting the
|
|
1108
|
+
// monitor, so driveSessionToYield can still drive one forced final yield.
|
|
1109
|
+
// Deliberately not routed through abortActiveSession(): that memoizes its
|
|
1110
|
+
// promise, and a later hard abort (grace exhausted) must be able to abort
|
|
1111
|
+
// the session again.
|
|
1112
|
+
const requestBudgetStop = () => {
|
|
1113
|
+
if (budgetStopRequested || abortSent || resolved) return;
|
|
1114
|
+
budgetStopRequested = true;
|
|
1115
|
+
const session = activeSession;
|
|
1116
|
+
budgetStopAbortPromise = session
|
|
1117
|
+
? session.abort().catch(error => {
|
|
1118
|
+
logger.debug("Subagent budget-stop abort failed", {
|
|
1119
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1120
|
+
});
|
|
1121
|
+
})
|
|
1122
|
+
: Promise.resolve();
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
const failWithError = (message: string) => {
|
|
1126
|
+
terminalError ??= message;
|
|
1127
|
+
requestAbort("terminate");
|
|
1128
|
+
};
|
|
1129
|
+
// Yield turn-stop: a terminal yield recorded while owner async work is
|
|
1130
|
+
// still pending is a scheduling pause, not run completion. Stop the
|
|
1131
|
+
// free-running turn exactly like a budget stop (session abort, monitor
|
|
1132
|
+
// signal untouched) so driveSessionToYield's quiescence barrier can settle
|
|
1133
|
+
// the jobs, fold their results in, and demand a fresh yield. Terminating
|
|
1134
|
+
// here instead would abort the run signal and make the barrier
|
|
1135
|
+
// unreachable, completing the run with a payload that predates the job
|
|
1136
|
+
// outcomes.
|
|
1137
|
+
const requestYieldTurnStop = () => {
|
|
1138
|
+
if (yieldTurnStopRequested || abortSent || resolved) return;
|
|
1139
|
+
yieldTurnStopRequested = true;
|
|
1140
|
+
const session = activeSession;
|
|
1141
|
+
yieldTurnStopPromise = session
|
|
1142
|
+
? session.abort().catch(error => {
|
|
1143
|
+
logger.debug("Subagent yield turn-stop abort failed", {
|
|
1144
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1145
|
+
});
|
|
1146
|
+
})
|
|
1147
|
+
: Promise.resolve();
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
/** Owner async work that can still re-wake the run (quiescence barrier predicate). */
|
|
1151
|
+
const sessionHasPendingAsyncWork = (): boolean => activeSession?.hasPendingAsyncWork?.() ?? false;
|
|
1152
|
+
|
|
1153
|
+
// Handle abort signal
|
|
1154
|
+
if (signal) {
|
|
1155
|
+
signal.addEventListener(
|
|
1156
|
+
"abort",
|
|
1157
|
+
() => {
|
|
1158
|
+
if (!resolved) requestAbort("signal");
|
|
1159
|
+
},
|
|
1160
|
+
{ once: true, signal: listenerSignal },
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Wall-clock hard limit. Defense-in-depth for the case where a provider stream
|
|
1165
|
+
// hang escapes the inference-layer watchdog (see openai-completions
|
|
1166
|
+
// `isOpenAICompletionsProgressChunk`). Disabled by default; set
|
|
1167
|
+
// `task.maxRuntimeMs > 0` to cap each subagent's lifetime.
|
|
1168
|
+
let runtimeTimeoutId: NodeJS.Timeout | undefined;
|
|
1169
|
+
if (maxRuntimeMs > 0) {
|
|
1170
|
+
runtimeTimeoutId = setTimeout(() => {
|
|
1171
|
+
if (!resolved) {
|
|
1172
|
+
logger.warn("Subagent runtime limit exceeded; aborting", {
|
|
1173
|
+
id,
|
|
1174
|
+
agent: agent.name,
|
|
1175
|
+
maxRuntimeMs,
|
|
1176
|
+
});
|
|
1177
|
+
requestAbort("timeout");
|
|
1178
|
+
}
|
|
1179
|
+
}, maxRuntimeMs);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const resolveSignalAbortReason = (): string => {
|
|
1183
|
+
const reason = signal?.reason;
|
|
1184
|
+
if (reason instanceof Error) {
|
|
1185
|
+
const message = reason.message.trim();
|
|
1186
|
+
if (message.length > 0) return message;
|
|
1187
|
+
} else if (typeof reason === "string") {
|
|
1188
|
+
const message = reason.trim();
|
|
1189
|
+
if (message.length > 0) return message;
|
|
1190
|
+
}
|
|
1191
|
+
return "Cancelled by caller";
|
|
1192
|
+
};
|
|
1193
|
+
const resolveAbortReasonText = (): string => {
|
|
1194
|
+
if (runtimeLimitExceeded) {
|
|
1195
|
+
return `Subagent runtime limit exceeded (task.maxRuntimeMs=${maxRuntimeMs})`;
|
|
1196
|
+
}
|
|
1197
|
+
if (budgetLimitExceeded) {
|
|
1198
|
+
return `Soft request budget exceeded (${progress.requests} requests; budget ${softRequestBudget}) — agent did not yield when force-stopped`;
|
|
1199
|
+
}
|
|
1200
|
+
if (budgetStopRequested) {
|
|
1201
|
+
return `Soft request budget exceeded (${progress.requests} requests; budget ${softRequestBudget})`;
|
|
1202
|
+
}
|
|
1203
|
+
return resolveSignalAbortReason();
|
|
1204
|
+
};
|
|
1205
|
+
const PROGRESS_COALESCE_MS = 150;
|
|
1206
|
+
let lastProgressEmitMs = 0;
|
|
1207
|
+
let progressTimeoutId: NodeJS.Timeout | null = null;
|
|
1208
|
+
|
|
1209
|
+
// Recompute progress.recentOutput from the capped tail. Deferred: text_delta
|
|
1210
|
+
// appends only extend the tail and mark it dirty; the (up to 8KB) split/filter
|
|
1211
|
+
// runs synchronously here, immediately before the ONLY places the progress
|
|
1212
|
+
// object is snapshotted ({...progress} for onProgress and the eventBus
|
|
1213
|
+
// progress channel, both inside emitProgressNow — including the
|
|
1214
|
+
// scheduleProgress(flush) finalize/error/cancel paths). Observers therefore
|
|
1215
|
+
// always see exact state; no staleness beyond the existing 150ms coalescing.
|
|
1216
|
+
const refreshRecentOutput = () => {
|
|
1217
|
+
if (!recentOutputDirty) return;
|
|
1218
|
+
recentOutputDirty = false;
|
|
1219
|
+
const filtered = recentOutputTail.split("\n").filter(line => line.trim());
|
|
1220
|
+
progress.recentOutput = filtered.slice(-8).reverse();
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
const emitProgressNow = () => {
|
|
1224
|
+
refreshRecentOutput();
|
|
1225
|
+
progress.durationMs = Date.now() - startTime;
|
|
1226
|
+
onProgress?.({ ...progress });
|
|
1227
|
+
const activityGist =
|
|
1228
|
+
progress.lastIntent ?? (progress.currentTool ? `running ${progress.currentTool}` : undefined);
|
|
1229
|
+
if (activityGist) AgentRegistry.global().setActivity(id, activityGist);
|
|
1230
|
+
if (args.eventBus) {
|
|
1231
|
+
args.eventBus.emit(TASK_SUBAGENT_PROGRESS_CHANNEL, {
|
|
1232
|
+
index,
|
|
1233
|
+
agent: agent.name,
|
|
1234
|
+
agentSource: agent.source,
|
|
1235
|
+
task,
|
|
1236
|
+
parentToolCallId: args.parentToolCallId,
|
|
1237
|
+
detached: args.detached,
|
|
1238
|
+
assignment,
|
|
1239
|
+
progress: { ...progress },
|
|
1240
|
+
sessionFile: args.sessionFile,
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
lastProgressEmitMs = Date.now();
|
|
1244
|
+
};
|
|
1245
|
+
|
|
1246
|
+
const scheduleProgress = (flush = false) => {
|
|
1247
|
+
if (flush) {
|
|
1248
|
+
if (progressTimeoutId) {
|
|
1249
|
+
clearTimeout(progressTimeoutId);
|
|
1250
|
+
progressTimeoutId = null;
|
|
1251
|
+
}
|
|
1252
|
+
emitProgressNow();
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
const now = Date.now();
|
|
1256
|
+
const elapsed = now - lastProgressEmitMs;
|
|
1257
|
+
if (lastProgressEmitMs === 0 || elapsed >= PROGRESS_COALESCE_MS) {
|
|
1258
|
+
if (progressTimeoutId) {
|
|
1259
|
+
clearTimeout(progressTimeoutId);
|
|
1260
|
+
progressTimeoutId = null;
|
|
1261
|
+
}
|
|
1262
|
+
emitProgressNow();
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
if (progressTimeoutId) return;
|
|
1266
|
+
progressTimeoutId = setTimeout(() => {
|
|
1267
|
+
progressTimeoutId = null;
|
|
1268
|
+
emitProgressNow();
|
|
1269
|
+
}, PROGRESS_COALESCE_MS - elapsed);
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
// The task wire schema carries no description: when the caller didn't pre-set
|
|
1273
|
+
// a UI label (e.g. the eval bridge's `label`), compress the assignment into a
|
|
1274
|
+
// tiny-model one-sentence label off the spawn's critical path. Best-effort —
|
|
1275
|
+
// a late label still lands via the finalize-time reads of `progress.description`;
|
|
1276
|
+
// failures just leave the label unset.
|
|
1277
|
+
const labelSource = assignment?.trim();
|
|
1278
|
+
if (!args.description && args.modelRegistry && args.settings && labelSource) {
|
|
1279
|
+
generateTaskLabel(labelSource, args.modelRegistry, args.settings, id, abortSignal)
|
|
1280
|
+
.then(label => {
|
|
1281
|
+
if (!label || abortSignal.aborted || progress.description) return;
|
|
1282
|
+
progress.description = label;
|
|
1283
|
+
if (!resolved) scheduleProgress();
|
|
1284
|
+
})
|
|
1285
|
+
.catch(err => {
|
|
1286
|
+
logger.debug("Subagent label generation failed", {
|
|
1287
|
+
id,
|
|
1288
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1289
|
+
});
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
const getMessageContent = (message: unknown): unknown => {
|
|
1294
|
+
if (!isRecord(message) || !("content" in message)) {
|
|
1295
|
+
return undefined;
|
|
1296
|
+
}
|
|
1297
|
+
return message.content;
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
const getMessageUsage = (message: unknown): unknown => {
|
|
1301
|
+
if (!isRecord(message) || !("usage" in message)) {
|
|
1302
|
+
return undefined;
|
|
1303
|
+
}
|
|
1304
|
+
return message.usage;
|
|
1305
|
+
};
|
|
1306
|
+
|
|
1307
|
+
const appendRecentOutputTail = (text: string) => {
|
|
1308
|
+
if (!text) return;
|
|
1309
|
+
recentOutputTail += text;
|
|
1310
|
+
if (recentOutputTail.length > RECENT_OUTPUT_TAIL_BYTES) {
|
|
1311
|
+
recentOutputTail = recentOutputTail.slice(-RECENT_OUTPUT_TAIL_BYTES);
|
|
1312
|
+
}
|
|
1313
|
+
// O(chunk) hot path: this runs on every text_delta token (hundreds/
|
|
1314
|
+
// thousands per second while streaming). Line reconstruction is deferred
|
|
1315
|
+
// to refreshRecentOutput() at the emit boundary.
|
|
1316
|
+
recentOutputDirty = true;
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
const replaceRecentOutputFromContent = (content: unknown[]) => {
|
|
1320
|
+
recentOutputTail = "";
|
|
1321
|
+
for (const block of content) {
|
|
1322
|
+
if (!block || typeof block !== "object") continue;
|
|
1323
|
+
const record = block as { type?: unknown; text?: unknown };
|
|
1324
|
+
if (record.type !== "text" || typeof record.text !== "string") continue;
|
|
1325
|
+
if (!record.text) continue;
|
|
1326
|
+
recentOutputTail += record.text;
|
|
1327
|
+
if (recentOutputTail.length > RECENT_OUTPUT_TAIL_BYTES) {
|
|
1328
|
+
recentOutputTail = recentOutputTail.slice(-RECENT_OUTPUT_TAIL_BYTES);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
recentOutputDirty = true;
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
const resetRecentOutput = () => {
|
|
1335
|
+
recentOutputTail = "";
|
|
1336
|
+
recentOutputDirty = false;
|
|
1337
|
+
progress.recentOutput = [];
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1340
|
+
const emitSubagentEvent = (event: AgentSessionEvent) => {
|
|
1341
|
+
if (!args.eventBus) return;
|
|
1342
|
+
args.eventBus.emit(TASK_SUBAGENT_EVENT_CHANNEL, {
|
|
1343
|
+
id,
|
|
1344
|
+
event,
|
|
1345
|
+
});
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
const recordExtractedToolData = (toolName: string, data: unknown): void => {
|
|
1349
|
+
progress.extractedToolData = progress.extractedToolData || {};
|
|
1350
|
+
const existing = progress.extractedToolData[toolName] || [];
|
|
1351
|
+
existing.push(data);
|
|
1352
|
+
progress.extractedToolData[toolName] = existing;
|
|
1353
|
+
if (toolName === "yield") {
|
|
1354
|
+
yieldCalled = true;
|
|
1355
|
+
yieldCallPending = false;
|
|
1356
|
+
yieldInvalidatedByAsync = false;
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
const processEvent = (event: AgentEvent) => {
|
|
1361
|
+
if (resolved) return;
|
|
1362
|
+
const now = Date.now();
|
|
1363
|
+
let flushProgress = false;
|
|
1364
|
+
|
|
1365
|
+
switch (event.type) {
|
|
1366
|
+
case "message_start":
|
|
1367
|
+
if (event.message?.role === "assistant") {
|
|
1368
|
+
resetRecentOutput();
|
|
1369
|
+
}
|
|
1370
|
+
// An async-result follow-up injected after a recorded yield
|
|
1371
|
+
// supersedes that yield: its payload predates the job outcome the
|
|
1372
|
+
// model is now being shown. Un-latch so the quiescence barrier's
|
|
1373
|
+
// reminder ladder demands a fresh yield. Guarded on the run signal:
|
|
1374
|
+
// once the run is completing, late injections must not destabilize
|
|
1375
|
+
// the settled classification.
|
|
1376
|
+
if (yieldCalled && !abortSignal.aborted && isAsyncResultInjection(event.message)) {
|
|
1377
|
+
yieldCalled = false;
|
|
1378
|
+
yieldInvalidatedByAsync = true;
|
|
1379
|
+
}
|
|
1380
|
+
break;
|
|
1381
|
+
|
|
1382
|
+
case "tool_execution_start": {
|
|
1383
|
+
progress.toolCount++;
|
|
1384
|
+
progress.currentTool = event.toolName;
|
|
1385
|
+
let startArgs: Record<string, unknown> = {};
|
|
1386
|
+
if ("toolArgs" in event && isRecord(event.toolArgs)) {
|
|
1387
|
+
startArgs = event.toolArgs;
|
|
1388
|
+
} else if (isRecord(event.args)) {
|
|
1389
|
+
startArgs = event.args;
|
|
1390
|
+
}
|
|
1391
|
+
progress.currentToolArgs = extractToolArgsPreview(startArgs);
|
|
1392
|
+
progress.currentToolStartMs = now;
|
|
1393
|
+
const intent = event.intent?.trim();
|
|
1394
|
+
if (intent) {
|
|
1395
|
+
progress.lastIntent = intent;
|
|
1396
|
+
}
|
|
1397
|
+
if (event.toolName === "yield" && !yieldCalled) {
|
|
1398
|
+
yieldCallPending = true;
|
|
1399
|
+
}
|
|
1400
|
+
// Reset any prior in-flight task snapshot so we don't show stale
|
|
1401
|
+
// nested progress when the agent enters a fresh `task` call.
|
|
1402
|
+
if (event.toolName === "task") {
|
|
1403
|
+
progress.inflightTaskDetails = undefined;
|
|
1404
|
+
}
|
|
1405
|
+
break;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
case "tool_execution_end": {
|
|
1409
|
+
if (progress.currentTool) {
|
|
1410
|
+
progress.recentTools.unshift({
|
|
1411
|
+
tool: progress.currentTool,
|
|
1412
|
+
args: progress.currentToolArgs || "",
|
|
1413
|
+
endMs: now,
|
|
1414
|
+
});
|
|
1415
|
+
// Keep only last 5
|
|
1416
|
+
if (progress.recentTools.length > 5) {
|
|
1417
|
+
progress.recentTools.pop();
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
progress.currentTool = undefined;
|
|
1421
|
+
progress.currentToolArgs = undefined;
|
|
1422
|
+
progress.currentToolStartMs = undefined;
|
|
1423
|
+
// The finalized TaskToolDetails will be captured below into
|
|
1424
|
+
// `extractedToolData.task`; drop the in-flight snapshot so the
|
|
1425
|
+
// renderer doesn't double-count it against the final entry.
|
|
1426
|
+
if (event.toolName === "task") {
|
|
1427
|
+
progress.inflightTaskDetails = undefined;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// Check for registered subagent tool handler
|
|
1431
|
+
const handler = subprocessToolRegistry.getHandler(event.toolName);
|
|
1432
|
+
const eventRecord: unknown = event;
|
|
1433
|
+
const eventArgs = isRecord(eventRecord) && isRecord(eventRecord.args) ? eventRecord.args : {};
|
|
1434
|
+
if (handler) {
|
|
1435
|
+
// Extract data using handler
|
|
1436
|
+
if (handler.extractData) {
|
|
1437
|
+
const data = handler.extractData({
|
|
1438
|
+
toolName: event.toolName,
|
|
1439
|
+
toolCallId: event.toolCallId,
|
|
1440
|
+
args: eventArgs,
|
|
1441
|
+
result: event.result,
|
|
1442
|
+
isError: event.isError,
|
|
1443
|
+
});
|
|
1444
|
+
if (data !== undefined) {
|
|
1445
|
+
recordExtractedToolData(event.toolName, data);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
if (event.toolName === "yield") {
|
|
1450
|
+
yieldCallPending = false;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Check if handler wants to terminate the session
|
|
1454
|
+
if (
|
|
1455
|
+
handler.shouldTerminate?.({
|
|
1456
|
+
toolName: event.toolName,
|
|
1457
|
+
toolCallId: event.toolCallId,
|
|
1458
|
+
args: eventArgs,
|
|
1459
|
+
result: event.result,
|
|
1460
|
+
isError: event.isError,
|
|
1461
|
+
})
|
|
1462
|
+
) {
|
|
1463
|
+
if (event.toolName === "yield" && sessionHasPendingAsyncWork()) {
|
|
1464
|
+
// Terminal yield with owner jobs still pending: park the
|
|
1465
|
+
// run behind the quiescence barrier instead of completing
|
|
1466
|
+
// it (see requestYieldTurnStop).
|
|
1467
|
+
requestYieldTurnStop();
|
|
1468
|
+
} else {
|
|
1469
|
+
requestAbort("terminate");
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (event.toolName === "yield") {
|
|
1474
|
+
if (event.isError && !abortSent) {
|
|
1475
|
+
consecutiveYieldToolErrors++;
|
|
1476
|
+
let yieldErrorText = "";
|
|
1477
|
+
const resultContent = event.result?.content;
|
|
1478
|
+
if (Array.isArray(resultContent)) {
|
|
1479
|
+
const textParts: string[] = [];
|
|
1480
|
+
for (const block of resultContent) {
|
|
1481
|
+
if (
|
|
1482
|
+
block &&
|
|
1483
|
+
typeof block === "object" &&
|
|
1484
|
+
"type" in block &&
|
|
1485
|
+
block.type === "text" &&
|
|
1486
|
+
"text" in block &&
|
|
1487
|
+
typeof block.text === "string"
|
|
1488
|
+
) {
|
|
1489
|
+
textParts.push(block.text);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
yieldErrorText = textParts.join("\n").trim();
|
|
1493
|
+
}
|
|
1494
|
+
if (consecutiveYieldToolErrors >= MAX_YIELD_TOOL_ERRORS) {
|
|
1495
|
+
const suffix = yieldErrorText ? ` Last yield error: ${yieldErrorText}` : "";
|
|
1496
|
+
failWithError(
|
|
1497
|
+
`Subagent submitted invalid yield results ${consecutiveYieldToolErrors} times; stopping to avoid an infinite submit loop.${suffix}`,
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
} else if (!event.isError) {
|
|
1501
|
+
consecutiveYieldToolErrors = 0;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
flushProgress = true;
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
case "tool_execution_update": {
|
|
1509
|
+
// Surface nested-subagent progress mid-flight. The child task
|
|
1510
|
+
// tool emits incremental `onUpdate` calls carrying its current
|
|
1511
|
+
// `TaskToolDetails` (results + progress); we stash the latest
|
|
1512
|
+
// snapshot so the parent UI can render the in-flight subtree
|
|
1513
|
+
// without waiting for the call to finish.
|
|
1514
|
+
if (event.toolName === "task") {
|
|
1515
|
+
const partial = (event as { partialResult?: { details?: unknown } }).partialResult;
|
|
1516
|
+
const details = partial && typeof partial === "object" ? partial.details : undefined;
|
|
1517
|
+
if (details && typeof details === "object" && "results" in (details as TaskToolDetails)) {
|
|
1518
|
+
progress.inflightTaskDetails = details as TaskToolDetails;
|
|
1519
|
+
flushProgress = true;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
break;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
case "message_update": {
|
|
1526
|
+
if (event.message?.role !== "assistant") break;
|
|
1527
|
+
const assistantEvent = (
|
|
1528
|
+
event as AgentEvent & {
|
|
1529
|
+
assistantMessageEvent?: { type?: string; delta?: string };
|
|
1530
|
+
}
|
|
1531
|
+
).assistantMessageEvent;
|
|
1532
|
+
if (assistantEvent?.type === "text_delta" && typeof assistantEvent.delta === "string") {
|
|
1533
|
+
appendRecentOutputTail(assistantEvent.delta);
|
|
1534
|
+
break;
|
|
1535
|
+
}
|
|
1536
|
+
if (assistantEvent && assistantEvent.type !== "text_delta") {
|
|
1537
|
+
break;
|
|
1538
|
+
}
|
|
1539
|
+
const updateContent =
|
|
1540
|
+
getMessageContent(event.message) || (event as AgentEvent & { content?: unknown }).content;
|
|
1541
|
+
if (updateContent && Array.isArray(updateContent)) {
|
|
1542
|
+
replaceRecentOutputFromContent(updateContent);
|
|
1543
|
+
}
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
case "message_end": {
|
|
1548
|
+
// Extract text from assistant and toolResult messages (not user prompts)
|
|
1549
|
+
const role = event.message?.role;
|
|
1550
|
+
if (role === "assistant") {
|
|
1551
|
+
progress.requests += 1;
|
|
1552
|
+
const eventContent = isRecord(event) && "content" in event ? event.content : undefined;
|
|
1553
|
+
const messageContent = getMessageContent(event.message) || eventContent;
|
|
1554
|
+
if (messageContent && Array.isArray(messageContent)) {
|
|
1555
|
+
for (const block of messageContent) {
|
|
1556
|
+
if (!isRecord(block)) continue;
|
|
1557
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
1558
|
+
outputChunks.push(block.text);
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
if (block.type !== "toolCall" || typeof block.name !== "string") continue;
|
|
1562
|
+
if (block.name === "yield" && !yieldCalled) {
|
|
1563
|
+
yieldCallPending = true;
|
|
1564
|
+
flushProgress = true;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
if (softRequestBudget > 0 && !abortSent && !yieldCallPending) {
|
|
1569
|
+
const stopThreshold = softRequestBudget * 1.5;
|
|
1570
|
+
if (budgetStopRequested) {
|
|
1571
|
+
// Grace window after the stop: the forced yield needs a
|
|
1572
|
+
// request or two; a child that keeps burning requests
|
|
1573
|
+
// instead of yielding is hard-aborted.
|
|
1574
|
+
if (progress.requests >= stopThreshold + BUDGET_STOP_GRACE_REQUESTS) {
|
|
1575
|
+
requestAbort("budget");
|
|
1576
|
+
}
|
|
1577
|
+
} else if (progress.requests >= stopThreshold) {
|
|
1578
|
+
requestBudgetStop();
|
|
1579
|
+
} else if (softRequestBudgetNotice && !budgetSteerSent && progress.requests >= softRequestBudget) {
|
|
1580
|
+
budgetSteerSent = true;
|
|
1581
|
+
const steerSession = activeSession;
|
|
1582
|
+
if (steerSession) {
|
|
1583
|
+
// Build the notice now (the count at crossing time), but send
|
|
1584
|
+
// behind an async boundary: a synchronously-throwing send must
|
|
1585
|
+
// never take down event processing (which escalates to terminate).
|
|
1586
|
+
const notice = buildBudgetNotice(progress.requests, softRequestBudget);
|
|
1587
|
+
void Promise.resolve()
|
|
1588
|
+
.then(() => steerSession.sendUserMessage(notice, { deliverAs: "steer" }))
|
|
1589
|
+
.catch(err => {
|
|
1590
|
+
logger.warn("Subagent budget steer failed", {
|
|
1591
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1592
|
+
});
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
// Extract and accumulate usage (prefer message.usage, fallback to event.usage)
|
|
1599
|
+
const eventUsage = isRecord(event) && "usage" in event ? event.usage : undefined;
|
|
1600
|
+
const messageUsage = getMessageUsage(event.message) || eventUsage;
|
|
1601
|
+
if (isRecord(messageUsage)) {
|
|
1602
|
+
// Only count assistant messages (not tool results, etc.)
|
|
1603
|
+
if (role === "assistant") {
|
|
1604
|
+
const costRecord = isRecord(messageUsage.cost) ? messageUsage.cost : undefined;
|
|
1605
|
+
hasUsage = true;
|
|
1606
|
+
accumulatedUsage.input += getNumberField(messageUsage, "input") ?? 0;
|
|
1607
|
+
accumulatedUsage.output += getNumberField(messageUsage, "output") ?? 0;
|
|
1608
|
+
accumulatedUsage.cacheRead += getNumberField(messageUsage, "cacheRead") ?? 0;
|
|
1609
|
+
accumulatedUsage.cacheWrite += getNumberField(messageUsage, "cacheWrite") ?? 0;
|
|
1610
|
+
accumulatedUsage.totalTokens += getNumberField(messageUsage, "totalTokens") ?? 0;
|
|
1611
|
+
accumulatedUsage.reasoningTokens =
|
|
1612
|
+
(accumulatedUsage.reasoningTokens ?? 0) + (getNumberField(messageUsage, "reasoningTokens") ?? 0);
|
|
1613
|
+
if (costRecord) {
|
|
1614
|
+
accumulatedUsage.cost.input += getNumberField(costRecord, "input") ?? 0;
|
|
1615
|
+
accumulatedUsage.cost.output += getNumberField(costRecord, "output") ?? 0;
|
|
1616
|
+
accumulatedUsage.cost.cacheRead += getNumberField(costRecord, "cacheRead") ?? 0;
|
|
1617
|
+
accumulatedUsage.cost.cacheWrite += getNumberField(costRecord, "cacheWrite") ?? 0;
|
|
1618
|
+
accumulatedUsage.cost.total += getNumberField(costRecord, "total") ?? 0;
|
|
1619
|
+
progress.cost = accumulatedUsage.cost.total;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
// Accumulate tokens for progress display
|
|
1623
|
+
progress.tokens += getUsageTokens(messageUsage);
|
|
1624
|
+
// Track latest per-turn context size so the UI can show
|
|
1625
|
+
// "current context", not just cumulative billing volume.
|
|
1626
|
+
if (role === "assistant") {
|
|
1627
|
+
const perTurnTotal = getNumberField(messageUsage, "totalTokens");
|
|
1628
|
+
if (perTurnTotal !== undefined && perTurnTotal > 0) {
|
|
1629
|
+
progress.contextTokens = perTurnTotal;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
break;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
case "agent_end":
|
|
1637
|
+
// Extract final content from assistant messages only (not user prompts)
|
|
1638
|
+
if (event.messages && Array.isArray(event.messages)) {
|
|
1639
|
+
for (const msg of event.messages) {
|
|
1640
|
+
if ((msg as { role?: string })?.role !== "assistant") continue;
|
|
1641
|
+
const messageContent = getMessageContent(msg);
|
|
1642
|
+
if (messageContent && Array.isArray(messageContent)) {
|
|
1643
|
+
for (const block of messageContent) {
|
|
1644
|
+
if (block.type === "text" && block.text) {
|
|
1645
|
+
finalOutputChunks.push(block.text);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
flushProgress = true;
|
|
1652
|
+
break;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
scheduleProgress(flushProgress);
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
const attach = (session: AgentSession): (() => void) => {
|
|
1659
|
+
let activeModel = session.model ? formatModelStringWithRouting(session.model) : undefined;
|
|
1660
|
+
return session.subscribe(event => {
|
|
1661
|
+
emitSubagentEvent(event);
|
|
1662
|
+
const nextModel = session.model ? formatModelStringWithRouting(session.model) : undefined;
|
|
1663
|
+
if (nextModel && nextModel !== activeModel) {
|
|
1664
|
+
activeModel = nextModel;
|
|
1665
|
+
progress.resolvedModel = nextModel;
|
|
1666
|
+
scheduleProgress(true);
|
|
1667
|
+
}
|
|
1668
|
+
if (event.type === "auto_retry_start") {
|
|
1669
|
+
progress.retryState = {
|
|
1670
|
+
attempt: event.attempt,
|
|
1671
|
+
maxAttempts: event.maxAttempts,
|
|
1672
|
+
delayMs: event.delayMs,
|
|
1673
|
+
errorMessage: event.errorMessage,
|
|
1674
|
+
startedAtMs: Date.now(),
|
|
1675
|
+
};
|
|
1676
|
+
progress.retryFailure = undefined;
|
|
1677
|
+
scheduleProgress(true);
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
if (event.type === "auto_retry_end") {
|
|
1681
|
+
const attempt = progress.retryState?.attempt ?? event.attempt;
|
|
1682
|
+
progress.retryState = undefined;
|
|
1683
|
+
if (!event.success) {
|
|
1684
|
+
progress.retryFailure = {
|
|
1685
|
+
attempt,
|
|
1686
|
+
errorMessage: event.finalError ?? "Auto-retry failed",
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
scheduleProgress(true);
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (isAgentEvent(event)) {
|
|
1693
|
+
// Breadcrumb the synchronous subagent event handling so the loop
|
|
1694
|
+
// watchdog can attribute any block to this in-process subagent.
|
|
1695
|
+
pushLoopPhase(`subagent:${id}`);
|
|
1696
|
+
try {
|
|
1697
|
+
processEvent(event);
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
logger.error("Subagent event processing failed", {
|
|
1700
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1701
|
+
});
|
|
1702
|
+
requestAbort("terminate");
|
|
1703
|
+
} finally {
|
|
1704
|
+
popLoopPhase();
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
if (event.type === "retry_fallback_applied") {
|
|
1708
|
+
progress.resolvedModel = event.to;
|
|
1709
|
+
progress.resolvedModelIsFallback = true;
|
|
1710
|
+
scheduleProgress(true);
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
if (event.type === "retry_fallback_succeeded") {
|
|
1714
|
+
progress.resolvedModel = event.model;
|
|
1715
|
+
progress.resolvedModelIsFallback = true;
|
|
1716
|
+
scheduleProgress(true);
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
};
|
|
1721
|
+
|
|
1722
|
+
const captureSalvage = (session: AgentSession): void => {
|
|
1723
|
+
// Best-effort salvage: capture the last assistant text so
|
|
1724
|
+
// cancelled/aborted children can surface "last activity" instead of
|
|
1725
|
+
// "(no output)".
|
|
1726
|
+
try {
|
|
1727
|
+
const lastContent = session.getLastAssistantMessage()?.content;
|
|
1728
|
+
if (Array.isArray(lastContent)) {
|
|
1729
|
+
const text = lastContent
|
|
1730
|
+
.map(block => (block.type === "text" && typeof block.text === "string" ? block.text : ""))
|
|
1731
|
+
.filter(Boolean)
|
|
1732
|
+
.join("\n");
|
|
1733
|
+
if (text.trim()) {
|
|
1734
|
+
lastAssistantSalvageText = text;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
} catch {
|
|
1738
|
+
// Salvage is best-effort; partial sessions may not implement it
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
|
|
1742
|
+
return {
|
|
1743
|
+
progress,
|
|
1744
|
+
abortSignal,
|
|
1745
|
+
accumulatedUsage,
|
|
1746
|
+
hasUsage: () => hasUsage,
|
|
1747
|
+
yieldCalled: () => yieldCalled,
|
|
1748
|
+
runtimeLimitExceeded: () => runtimeLimitExceeded,
|
|
1749
|
+
terminalError: () => terminalError,
|
|
1750
|
+
hasExplicitAbortReason: () =>
|
|
1751
|
+
abortReason === "signal" || runtimeLimitExceeded || budgetLimitExceeded || budgetStopRequested,
|
|
1752
|
+
budgetStopRequested: () => budgetStopRequested,
|
|
1753
|
+
waitForBudgetStop: () => budgetStopAbortPromise ?? Promise.resolve(),
|
|
1754
|
+
yieldInvalidatedByAsync: () => yieldInvalidatedByAsync,
|
|
1755
|
+
yieldTurnStopRequested: () => yieldTurnStopRequested,
|
|
1756
|
+
waitForYieldTurnStop: async () => {
|
|
1757
|
+
const pending = yieldTurnStopPromise;
|
|
1758
|
+
if (!pending) {
|
|
1759
|
+
yieldTurnStopRequested = false;
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
try {
|
|
1763
|
+
await pending;
|
|
1764
|
+
} finally {
|
|
1765
|
+
// Clear only after the abort settled so the idempotence gate in
|
|
1766
|
+
// requestYieldTurnStop stays closed while it is in flight.
|
|
1767
|
+
if (yieldTurnStopPromise === pending) {
|
|
1768
|
+
yieldTurnStopPromise = null;
|
|
1769
|
+
yieldTurnStopRequested = false;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
},
|
|
1773
|
+
// A soft stop that never escalated still identifies as a budget abort so
|
|
1774
|
+
// the lifecycle can park the agent as resumable instead of killing it.
|
|
1775
|
+
abortKind: () => abortReason ?? (budgetStopRequested ? "budget" : undefined),
|
|
1776
|
+
isAbortedRun: () =>
|
|
1777
|
+
abortReason === "signal" || runtimeLimitExceeded || budgetLimitExceeded || abortReason === undefined,
|
|
1778
|
+
requestAbort,
|
|
1779
|
+
failWithError,
|
|
1780
|
+
abortActiveSession,
|
|
1781
|
+
waitForActiveSessionAbort,
|
|
1782
|
+
resolveSignalAbortReason,
|
|
1783
|
+
resolveAbortReasonText,
|
|
1784
|
+
setActiveSession: session => {
|
|
1785
|
+
activeSession = session;
|
|
1786
|
+
},
|
|
1787
|
+
takeActiveSession: () => {
|
|
1788
|
+
const session = activeSession;
|
|
1789
|
+
activeSession = null;
|
|
1790
|
+
return session;
|
|
1791
|
+
},
|
|
1792
|
+
attach,
|
|
1793
|
+
captureSalvage,
|
|
1794
|
+
lastAssistantSalvageText: () => lastAssistantSalvageText,
|
|
1795
|
+
rawOutput: () => (finalOutputChunks.length > 0 ? finalOutputChunks.join("") : outputChunks.join("")),
|
|
1796
|
+
scheduleProgress,
|
|
1797
|
+
finish: () => {
|
|
1798
|
+
resolved = true;
|
|
1799
|
+
listenerController.abort();
|
|
1800
|
+
if (runtimeTimeoutId !== undefined) {
|
|
1801
|
+
clearTimeout(runtimeTimeoutId);
|
|
1802
|
+
runtimeTimeoutId = undefined;
|
|
1803
|
+
}
|
|
1804
|
+
if (progressTimeoutId) {
|
|
1805
|
+
clearTimeout(progressTimeoutId);
|
|
1806
|
+
progressTimeoutId = null;
|
|
1807
|
+
}
|
|
1808
|
+
},
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
interface DriveOutcome {
|
|
1813
|
+
exitCode: number;
|
|
1814
|
+
error?: string;
|
|
1815
|
+
aborted: boolean;
|
|
1816
|
+
abortReasonText?: string;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
const MAX_YIELD_RETRIES = 3;
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Drive one assignment through a live session: send the prompt, wait for idle,
|
|
1823
|
+
* remind the agent to `yield` (up to {@link MAX_YIELD_RETRIES} times), then
|
|
1824
|
+
* classify the terminal assistant state. A soft-budget stop short-circuits the
|
|
1825
|
+
* reminder ladder into a single forced final yield so partial findings still
|
|
1826
|
+
* come back as a real report.
|
|
1827
|
+
*/
|
|
1828
|
+
async function driveSessionToYield(
|
|
1829
|
+
session: AgentSession,
|
|
1830
|
+
monitor: SubagentRunMonitor,
|
|
1831
|
+
task: string,
|
|
1832
|
+
): Promise<DriveOutcome> {
|
|
1833
|
+
const abortSignal = monitor.abortSignal;
|
|
1834
|
+
let exitCode = 0;
|
|
1835
|
+
let error: string | undefined;
|
|
1836
|
+
let aborted = false;
|
|
1837
|
+
let abortReasonText: string | undefined;
|
|
1838
|
+
const checkAbort = () => {
|
|
1839
|
+
if (abortSignal.aborted) {
|
|
1840
|
+
aborted = monitor.isAbortedRun();
|
|
1841
|
+
if (aborted) {
|
|
1842
|
+
abortReasonText ??= monitor.resolveAbortReasonText();
|
|
1843
|
+
}
|
|
1844
|
+
exitCode = 1;
|
|
1845
|
+
throw new ToolAbortError();
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
const awaitAbortable = async <T>(promise: Promise<T>): Promise<T> => {
|
|
1849
|
+
checkAbort();
|
|
1850
|
+
const { promise: abortPromise, reject } = Promise.withResolvers<never>();
|
|
1851
|
+
const onAbort = () => {
|
|
1852
|
+
try {
|
|
1853
|
+
checkAbort();
|
|
1854
|
+
} catch (err) {
|
|
1855
|
+
reject(err);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
1859
|
+
try {
|
|
1860
|
+
return await Promise.race([promise, abortPromise]);
|
|
1861
|
+
} finally {
|
|
1862
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
|
|
1866
|
+
try {
|
|
1867
|
+
try {
|
|
1868
|
+
await awaitAbortable(session.prompt(task, { attribution: "agent" }));
|
|
1869
|
+
await awaitAbortable(session.waitForIdle());
|
|
1870
|
+
} catch (err) {
|
|
1871
|
+
// A budget stop or a yield turn-stop (terminal yield parked behind
|
|
1872
|
+
// the async quiescence barrier) cancels the free-running turn by
|
|
1873
|
+
// aborting the session, which can surface here as a rejected
|
|
1874
|
+
// prompt. Swallow it and drive the barrier/forced final yield
|
|
1875
|
+
// below; real caller/timeout aborts (monitor signal) and genuine
|
|
1876
|
+
// failures keep the old path.
|
|
1877
|
+
const recoverableStop = monitor.budgetStopRequested() || monitor.yieldTurnStopRequested();
|
|
1878
|
+
if (!recoverableStop || abortSignal.aborted) throw err;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
const reminderToolChoice = buildNamedToolChoice("yield", session.model);
|
|
1882
|
+
|
|
1883
|
+
const runYieldLadder = async (): Promise<void> => {
|
|
1884
|
+
let retryCount = 0;
|
|
1885
|
+
while (!monitor.yieldCalled() && retryCount < MAX_YIELD_RETRIES && !abortSignal.aborted) {
|
|
1886
|
+
// A budget stop collapses the reminder ladder to a single forced
|
|
1887
|
+
// final yield: wait for the stop's session abort to settle, then
|
|
1888
|
+
// prompt once with the wrap-up reminder + named tool choice.
|
|
1889
|
+
const budgetStop = monitor.budgetStopRequested();
|
|
1890
|
+
if (budgetStop) {
|
|
1891
|
+
retryCount = MAX_YIELD_RETRIES - 1;
|
|
1892
|
+
await monitor.waitForBudgetStop();
|
|
1893
|
+
if (monitor.yieldCalled() || abortSignal.aborted) break;
|
|
1894
|
+
}
|
|
1895
|
+
// Skip reminders when the model returned a terminal error (e.g.
|
|
1896
|
+
// rate-limit cap hit, auth failure). Re-prompting would just
|
|
1897
|
+
// hit the same wall, multiplying the failure noise without
|
|
1898
|
+
// any chance of producing a yield.
|
|
1899
|
+
const lastBeforeReminder = session.getLastAssistantMessage();
|
|
1900
|
+
if (lastBeforeReminder?.stopReason === "error") break;
|
|
1901
|
+
try {
|
|
1902
|
+
retryCount++;
|
|
1903
|
+
const reminder = prompt.render(submitReminderTemplate, {
|
|
1904
|
+
retryCount,
|
|
1905
|
+
maxRetries: MAX_YIELD_RETRIES,
|
|
1906
|
+
budgetStop,
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
const isFinalRetry = retryCount >= MAX_YIELD_RETRIES;
|
|
1910
|
+
await awaitAbortable(
|
|
1911
|
+
session.prompt(reminder, {
|
|
1912
|
+
attribution: "agent",
|
|
1913
|
+
synthetic: true,
|
|
1914
|
+
...(isFinalRetry && reminderToolChoice ? { toolChoice: reminderToolChoice } : {}),
|
|
1915
|
+
}),
|
|
1916
|
+
);
|
|
1917
|
+
await awaitAbortable(session.waitForIdle());
|
|
1918
|
+
} catch (err) {
|
|
1919
|
+
if (abortSignal.aborted || err instanceof ToolAbortError) {
|
|
1920
|
+
// Benign control-flow exit — user cancel (^C) or compaction aborting
|
|
1921
|
+
// pending operations both surface here as ToolAbortError. The outer
|
|
1922
|
+
// catch and finally already mark the run aborted; logging at ERROR
|
|
1923
|
+
// would spam operator dashboards with non-failures.
|
|
1924
|
+
logger.debug("Subagent prompt aborted");
|
|
1925
|
+
} else {
|
|
1926
|
+
logger.error("Subagent prompt failed", {
|
|
1927
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
|
|
1934
|
+
// Yield ladder + quiescence barrier (structured concurrency), one
|
|
1935
|
+
// loop: each iteration first demands a yield — initially, and again
|
|
1936
|
+
// whenever an async-result delivery un-latched the previous one
|
|
1937
|
+
// (including during the notice turn) — then either completes on
|
|
1938
|
+
// quiescence or settles one generation of owner async work.
|
|
1939
|
+
//
|
|
1940
|
+
// A final yield with owner background jobs still running or
|
|
1941
|
+
// undelivered is a scheduling pause, not run completion — the monitor
|
|
1942
|
+
// parks such a yield with a recoverable turn-stop instead of
|
|
1943
|
+
// terminating the run. Jobs are settled and their results folded into
|
|
1944
|
+
// the run as async-result follow-up turns; each delivered result
|
|
1945
|
+
// supersedes the yield it postdates, so the reminder ladder re-runs
|
|
1946
|
+
// to demand a fresh yield that accounts for it. Only a yield with no
|
|
1947
|
+
// pending owner work left is terminal — the isolation runner captures
|
|
1948
|
+
// and destroys the worktree right after this run resolves, so no
|
|
1949
|
+
// owner job that could still re-wake the session may outlive it.
|
|
1950
|
+
// Suppressed (acknowledged / hub-watched) jobs never re-wake the run
|
|
1951
|
+
// and are reaped at teardown.
|
|
1952
|
+
//
|
|
1953
|
+
// Before blocking on running jobs, tell the model ONCE what it is
|
|
1954
|
+
// waiting on so it can `hub` wait/cancel instead of sitting silent
|
|
1955
|
+
// until the jobs (or the runtime limit) expire. Runs that never yield
|
|
1956
|
+
// (ladder exhausted / terminal model error) skip the barrier — more
|
|
1957
|
+
// injected turns just multiply the failure noise; the teardown reap
|
|
1958
|
+
// still cancels and awaits their jobs before worktree capture.
|
|
1959
|
+
let asyncPendingNoticeSent = false;
|
|
1960
|
+
while (!abortSignal.aborted) {
|
|
1961
|
+
if (!monitor.yieldCalled()) {
|
|
1962
|
+
await runYieldLadder();
|
|
1963
|
+
// Ladder exhausted / terminal model error: classified below
|
|
1964
|
+
// (missing yield, or stale yield when one was invalidated).
|
|
1965
|
+
if (!monitor.yieldCalled()) break;
|
|
1966
|
+
}
|
|
1967
|
+
// Let the parked yield's turn-stop session abort settle before
|
|
1968
|
+
// prompting again (mirrors waitForBudgetStop).
|
|
1969
|
+
await awaitAbortable(monitor.waitForYieldTurnStop());
|
|
1970
|
+
if (!session.hasPendingAsyncWork()) break;
|
|
1971
|
+
if (!asyncPendingNoticeSent) {
|
|
1972
|
+
asyncPendingNoticeSent = true;
|
|
1973
|
+
const running = session.getAsyncJobSnapshot()?.running ?? [];
|
|
1974
|
+
if (running.length > 0) {
|
|
1975
|
+
const jobs = running.map(job => `${job.id}${job.label ? ` (${job.label})` : ""}`).join(", ");
|
|
1976
|
+
const notice = prompt.render(subagentAsyncPendingTemplate, {
|
|
1977
|
+
count: running.length,
|
|
1978
|
+
multiple: running.length > 1,
|
|
1979
|
+
jobs,
|
|
1980
|
+
});
|
|
1981
|
+
try {
|
|
1982
|
+
await awaitAbortable(session.prompt(notice, { attribution: "agent", synthetic: true }));
|
|
1983
|
+
await awaitAbortable(session.waitForIdle());
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
if (abortSignal.aborted || err instanceof ToolAbortError) throw err;
|
|
1986
|
+
// A failed notice turn must not kill the run — fall through
|
|
1987
|
+
// to the passive settle below.
|
|
1988
|
+
logger.warn("Subagent async-pending notice failed", {
|
|
1989
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
// Re-evaluate: the notice turn may have cancelled, watched, or
|
|
1993
|
+
// absorbed the jobs — or already re-yielded.
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
await awaitAbortable(session.settleAsyncWork());
|
|
1998
|
+
// Results delivered during the settle invalidated the recorded
|
|
1999
|
+
// yield: the next iteration's ladder demands a fresh one.
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
if (!monitor.yieldCalled()) {
|
|
2003
|
+
await awaitAbortable(session.waitForIdle());
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
const lastAssistant = session.getLastAssistantMessage();
|
|
2007
|
+
if (lastAssistant) {
|
|
2008
|
+
if (lastAssistant.stopReason === "aborted") {
|
|
2009
|
+
if (!monitor.yieldCalled() || monitor.runtimeLimitExceeded()) {
|
|
2010
|
+
aborted = monitor.isAbortedRun();
|
|
2011
|
+
if (aborted) {
|
|
2012
|
+
// A real caller signal or the wall-clock timer carries a precise
|
|
2013
|
+
// reason (signal.reason / "runtime limit exceeded"). An internal
|
|
2014
|
+
// turn abort does NOT — prefer the assistant message's own
|
|
2015
|
+
// errorMessage ("Request was aborted" or a specific stream error)
|
|
2016
|
+
// over the misleading "Cancelled by caller".
|
|
2017
|
+
abortReasonText ??= monitor.hasExplicitAbortReason()
|
|
2018
|
+
? monitor.resolveAbortReasonText()
|
|
2019
|
+
: lastAssistant.errorMessage?.trim() || monitor.resolveAbortReasonText();
|
|
2020
|
+
}
|
|
2021
|
+
exitCode = 1;
|
|
2022
|
+
}
|
|
2023
|
+
} else if (lastAssistant.stopReason === "error") {
|
|
2024
|
+
exitCode = 1;
|
|
2025
|
+
error ??= lastAssistant.errorMessage || "Subagent failed";
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// A budget-stopped run that still produced no yield is a budget abort:
|
|
2030
|
+
// surface the precise reason instead of a generic missing-yield failure.
|
|
2031
|
+
if (!monitor.yieldCalled() && monitor.budgetStopRequested() && !aborted) {
|
|
2032
|
+
aborted = true;
|
|
2033
|
+
abortReasonText ??= monitor.resolveAbortReasonText();
|
|
2034
|
+
exitCode = 1;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// A recorded yield that async-result deliveries superseded and the
|
|
2038
|
+
// model never refreshed is stale: fail the run instead of letting the
|
|
2039
|
+
// parent act on a payload that predates the background job outcomes
|
|
2040
|
+
// the model was shown. The stale payload still ships through
|
|
2041
|
+
// finalizeSubprocessOutput's failed-after-yield path (exit 1 + stderr,
|
|
2042
|
+
// output preserved as salvage).
|
|
2043
|
+
if (monitor.yieldInvalidatedByAsync() && !abortSignal.aborted) {
|
|
2044
|
+
exitCode = 1;
|
|
2045
|
+
error ??=
|
|
2046
|
+
"Background job results arrived after the subagent's last yield; it did not submit a refreshed yield covering them.";
|
|
2047
|
+
}
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
if (abortSignal.aborted && monitor.yieldCalled() && !monitor.runtimeLimitExceeded()) {
|
|
2050
|
+
exitCode = 0;
|
|
2051
|
+
} else {
|
|
2052
|
+
exitCode = 1;
|
|
2053
|
+
if (!abortSignal.aborted) {
|
|
2054
|
+
error = err instanceof Error ? err.stack || err.message : String(err);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
} finally {
|
|
2058
|
+
error ??= monitor.terminalError();
|
|
2059
|
+
if (abortSignal.aborted && (!monitor.yieldCalled() || monitor.runtimeLimitExceeded())) {
|
|
2060
|
+
aborted = monitor.isAbortedRun();
|
|
2061
|
+
if (aborted) {
|
|
2062
|
+
abortReasonText ??= monitor.resolveAbortReasonText();
|
|
2063
|
+
}
|
|
2064
|
+
if (exitCode === 0) exitCode = 1;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
return { exitCode, error, aborted, abortReasonText };
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
interface FinalizeRunArgs {
|
|
2072
|
+
monitor: SubagentRunMonitor;
|
|
2073
|
+
done: { exitCode: number; error?: string; aborted?: boolean; abortReason?: string; durationMs: number };
|
|
2074
|
+
index: number;
|
|
2075
|
+
id: string;
|
|
2076
|
+
agent: AgentDefinition;
|
|
2077
|
+
task: string;
|
|
2078
|
+
assignment?: string;
|
|
2079
|
+
modelOverride?: string | string[];
|
|
2080
|
+
/** Explicit pre-expansion model role alias selected for this run. */
|
|
2081
|
+
modelRole?: string;
|
|
2082
|
+
outputSchema?: unknown;
|
|
2083
|
+
outputSchemaMode?: StructuredSubagentSchemaMode;
|
|
2084
|
+
outputSchemaSource?: StructuredSubagentSchemaSource;
|
|
2085
|
+
signal?: AbortSignal;
|
|
2086
|
+
artifactsDir?: string;
|
|
2087
|
+
eventBus?: EventBus;
|
|
2088
|
+
parentToolCallId?: string;
|
|
2089
|
+
detached?: boolean;
|
|
2090
|
+
sessionFile?: string;
|
|
2091
|
+
startTime: number;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
/**
|
|
2095
|
+
* Turn a settled run into a {@link SingleResult}: resolve the yield payload via
|
|
2096
|
+
* {@link finalizeSubprocessOutput}, salvage cancelled-run output, write the
|
|
2097
|
+
* `<id>.md` output artifact, flush final progress, and emit the lifecycle end
|
|
2098
|
+
* event.
|
|
2099
|
+
*/
|
|
2100
|
+
async function finalizeRunResult(args: FinalizeRunArgs): Promise<SingleResult> {
|
|
2101
|
+
const { monitor, done, index, id, agent, task, assignment, signal, modelOverride, modelRole } = args;
|
|
2102
|
+
const progress = monitor.progress;
|
|
2103
|
+
let exitCode = done.exitCode;
|
|
2104
|
+
let stderr = done.error ?? "";
|
|
2105
|
+
|
|
2106
|
+
// Use final output if available, otherwise accumulated output
|
|
2107
|
+
let rawOutput = monitor.rawOutput();
|
|
2108
|
+
const yieldItems = progress.extractedToolData?.yield as YieldItem[] | undefined;
|
|
2109
|
+
// Breadcrumb the synchronous yield-payload shaping (O(rawOutput)) so a block
|
|
2110
|
+
// here is attributed to this subagent rather than logged as "unknown".
|
|
2111
|
+
pushLoopPhase(`subagent:${id}`);
|
|
2112
|
+
let finalized: FinalizeSubprocessOutputResult;
|
|
2113
|
+
try {
|
|
2114
|
+
finalized = finalizeSubprocessOutput({
|
|
2115
|
+
rawOutput,
|
|
2116
|
+
exitCode,
|
|
2117
|
+
stderr,
|
|
2118
|
+
doneAborted: Boolean(done.aborted),
|
|
2119
|
+
signalAborted: Boolean(signal?.aborted),
|
|
2120
|
+
yieldItems,
|
|
2121
|
+
outputSchema: args.outputSchema,
|
|
2122
|
+
outputSchemaMode: args.outputSchemaMode,
|
|
2123
|
+
outputSchemaSource: args.outputSchemaSource,
|
|
2124
|
+
lastAssistantText: monitor.lastAssistantSalvageText(),
|
|
2125
|
+
});
|
|
2126
|
+
} finally {
|
|
2127
|
+
popLoopPhase();
|
|
2128
|
+
}
|
|
2129
|
+
rawOutput = finalized.rawOutput;
|
|
2130
|
+
exitCode = finalized.exitCode;
|
|
2131
|
+
stderr = finalized.stderr;
|
|
2132
|
+
// Salvage for cancelled/aborted children that produced no completed output:
|
|
2133
|
+
// surface the last assistant text + stats instead of "(no output)" so the
|
|
2134
|
+
// parent doesn't redo work the child already finished.
|
|
2135
|
+
const salvageText = monitor.lastAssistantSalvageText();
|
|
2136
|
+
if (
|
|
2137
|
+
(done.aborted || signal?.aborted || monitor.runtimeLimitExceeded()) &&
|
|
2138
|
+
!rawOutput.trim() &&
|
|
2139
|
+
salvageText !== undefined
|
|
2140
|
+
) {
|
|
2141
|
+
rawOutput = `[cancelled after ${progress.requests} req, ${progress.tokens} tok — last activity: "${formatSalvageSnippet(salvageText)}"]`;
|
|
2142
|
+
}
|
|
2143
|
+
const lastYield = yieldItems?.[yieldItems.length - 1];
|
|
2144
|
+
const yieldAbortReason = lastYield?.status === "aborted" ? lastYield.error || "Subagent aborted task" : undefined;
|
|
2145
|
+
const { abortedViaYield, hasYield } = finalized;
|
|
2146
|
+
const { content: truncatedOutput, truncated } = truncateTail(rawOutput, {
|
|
2147
|
+
maxBytes: MAX_OUTPUT_BYTES,
|
|
2148
|
+
maxLines: MAX_OUTPUT_LINES,
|
|
2149
|
+
});
|
|
2150
|
+
|
|
2151
|
+
// Write output artifact (input and jsonl already written in real-time)
|
|
2152
|
+
// Compute output metadata for agent:// URL integration
|
|
2153
|
+
let outputMeta: { lineCount: number; charCount: number } | undefined;
|
|
2154
|
+
let outputPath: string | undefined;
|
|
2155
|
+
if (args.artifactsDir) {
|
|
2156
|
+
outputPath = path.join(args.artifactsDir, `${id}.md`);
|
|
2157
|
+
try {
|
|
2158
|
+
await Bun.write(outputPath, rawOutput);
|
|
2159
|
+
outputMeta = {
|
|
2160
|
+
lineCount: rawOutput.split("\n").length,
|
|
2161
|
+
charCount: rawOutput.length,
|
|
2162
|
+
};
|
|
2163
|
+
} catch {
|
|
2164
|
+
// Non-fatal
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// Update final progress. A wall-clock timeout always wins: if the runtime
|
|
2169
|
+
// limit fired we report aborted/failed regardless of whether a yield landed
|
|
2170
|
+
// while we were tearing the session down. The yield data is still surfaced
|
|
2171
|
+
// to the caller via `progress.extractedToolData`, but the exit status must
|
|
2172
|
+
// reflect the timeout so on-call doesn't mistake a stuck run for success.
|
|
2173
|
+
const runtimeLimitExceeded = monitor.runtimeLimitExceeded();
|
|
2174
|
+
if (runtimeLimitExceeded && exitCode === 0) {
|
|
2175
|
+
exitCode = 1;
|
|
2176
|
+
}
|
|
2177
|
+
const wasAborted =
|
|
2178
|
+
runtimeLimitExceeded || Boolean(done.aborted) || abortedViaYield || (!hasYield && Boolean(signal?.aborted));
|
|
2179
|
+
const finalAbortReason = wasAborted
|
|
2180
|
+
? runtimeLimitExceeded
|
|
2181
|
+
? monitor.resolveAbortReasonText()
|
|
2182
|
+
: done.aborted
|
|
2183
|
+
? (done.abortReason ?? monitor.resolveAbortReasonText())
|
|
2184
|
+
: abortedViaYield
|
|
2185
|
+
? yieldAbortReason
|
|
2186
|
+
: signal?.aborted
|
|
2187
|
+
? monitor.resolveSignalAbortReason()
|
|
2188
|
+
: monitor.resolveAbortReasonText()
|
|
2189
|
+
: undefined;
|
|
2190
|
+
progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
|
2191
|
+
monitor.scheduleProgress(true);
|
|
2192
|
+
|
|
2193
|
+
// Emit lifecycle end event after finalization so yield status is reflected
|
|
2194
|
+
if (args.eventBus) {
|
|
2195
|
+
args.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
|
|
2196
|
+
id,
|
|
2197
|
+
agent: agent.name,
|
|
2198
|
+
parentToolCallId: args.parentToolCallId,
|
|
2199
|
+
detached: args.detached,
|
|
2200
|
+
agentSource: agent.source,
|
|
2201
|
+
description: progress.description,
|
|
2202
|
+
status: progress.status as "completed" | "failed" | "aborted",
|
|
2203
|
+
sessionFile: args.sessionFile,
|
|
2204
|
+
index,
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
return {
|
|
2209
|
+
index,
|
|
2210
|
+
id,
|
|
2211
|
+
agent: agent.name,
|
|
2212
|
+
agentSource: agent.source,
|
|
2213
|
+
task,
|
|
2214
|
+
assignment,
|
|
2215
|
+
description: progress.description,
|
|
2216
|
+
lastIntent: progress.lastIntent,
|
|
2217
|
+
exitCode,
|
|
2218
|
+
output: truncatedOutput,
|
|
2219
|
+
stderr,
|
|
2220
|
+
truncated: Boolean(truncated),
|
|
2221
|
+
...(finalized.structuredOutput ? { structuredOutput: finalized.structuredOutput } : {}),
|
|
2222
|
+
durationMs: Date.now() - args.startTime,
|
|
2223
|
+
tokens: progress.tokens,
|
|
2224
|
+
requests: progress.requests,
|
|
2225
|
+
contextTokens: progress.contextTokens,
|
|
2226
|
+
contextWindow: progress.contextWindow,
|
|
2227
|
+
modelOverride,
|
|
2228
|
+
modelRole,
|
|
2229
|
+
resolvedModel: progress.resolvedModel,
|
|
2230
|
+
resolvedModelIsFallback: progress.resolvedModelIsFallback,
|
|
2231
|
+
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
2232
|
+
aborted: wasAborted,
|
|
2233
|
+
abortReason: finalAbortReason,
|
|
2234
|
+
usage: monitor.hasUsage() ? monitor.accumulatedUsage : undefined,
|
|
2235
|
+
outputPath,
|
|
2236
|
+
extractedToolData: progress.extractedToolData,
|
|
2237
|
+
retryFailure: progress.retryFailure,
|
|
2238
|
+
outputMeta,
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
/** Inputs for {@link attachIrcWakeTurnMonitor}. */
|
|
2243
|
+
export interface IrcWakeTurnMonitorOptions {
|
|
2244
|
+
/** Registry id of the kept-alive subagent whose autonomous IRC wake turns are monitored. */
|
|
2245
|
+
id: string;
|
|
2246
|
+
index?: number;
|
|
2247
|
+
agent: AgentDefinition;
|
|
2248
|
+
description?: string;
|
|
2249
|
+
modelOverride?: string | string[];
|
|
2250
|
+
/** Explicit pre-expansion model role alias selected for this run. */
|
|
2251
|
+
modelRole?: string;
|
|
2252
|
+
eventBus?: EventBus;
|
|
2253
|
+
parentToolCallId?: string;
|
|
2254
|
+
/** Fallback session file when the registry ref carries none. */
|
|
2255
|
+
sessionFile?: string;
|
|
2256
|
+
maxRuntimeMs?: number;
|
|
2257
|
+
outputSchema?: unknown;
|
|
2258
|
+
outputSchemaMode?: StructuredSubagentSchemaMode;
|
|
2259
|
+
outputSchemaSource?: StructuredSubagentSchemaSource;
|
|
2260
|
+
artifactsDir?: string;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
/**
|
|
2264
|
+
* Bracket a kept-alive subagent's autonomous IRC wake turns with a task run
|
|
2265
|
+
* monitor so RPC/collab subscribers see the same `subagent_lifecycle` /
|
|
2266
|
+
* `subagent_progress` frames a first run emits. Shared by the live executor
|
|
2267
|
+
* reviver and the persisted cold-revive path so a resumed process's parked
|
|
2268
|
+
* subagents are not blind spots. The observer runs after the session has
|
|
2269
|
+
* flushed its post-prompt settle (see {@link AgentSession.setIrcWakeTurnObserver}).
|
|
2270
|
+
*/
|
|
2271
|
+
export function attachIrcWakeTurnMonitor(session: AgentSession, options: IrcWakeTurnMonitorOptions): void {
|
|
2272
|
+
const { id, agent } = options;
|
|
2273
|
+
const index = options.index ?? 0;
|
|
2274
|
+
const maxRuntimeMs = options.maxRuntimeMs ?? 0;
|
|
2275
|
+
session.setIrcWakeTurnObserver(records => {
|
|
2276
|
+
const ircTask =
|
|
2277
|
+
records
|
|
2278
|
+
.map(record => {
|
|
2279
|
+
const body =
|
|
2280
|
+
record.details && typeof record.details === "object"
|
|
2281
|
+
? Reflect.get(record.details, "message")
|
|
2282
|
+
: undefined;
|
|
2283
|
+
return typeof body === "string" ? body : record.content;
|
|
2284
|
+
})
|
|
2285
|
+
.filter(Boolean)
|
|
2286
|
+
.join("\n\n") || "IRC follow-up";
|
|
2287
|
+
const turnStartTime = Date.now();
|
|
2288
|
+
const sessionFile = AgentRegistry.global().get(id)?.sessionFile ?? options.sessionFile ?? undefined;
|
|
2289
|
+
const turnMonitor = createSubagentRunMonitor({
|
|
2290
|
+
index,
|
|
2291
|
+
id,
|
|
2292
|
+
agent,
|
|
2293
|
+
task: ircTask,
|
|
2294
|
+
description: options.description,
|
|
2295
|
+
modelOverride: options.modelOverride,
|
|
2296
|
+
modelRole: options.modelRole,
|
|
2297
|
+
eventBus: options.eventBus,
|
|
2298
|
+
parentToolCallId: options.parentToolCallId,
|
|
2299
|
+
detached: true,
|
|
2300
|
+
sessionFile,
|
|
2301
|
+
softRequestBudget: 0,
|
|
2302
|
+
softRequestBudgetNotice: false,
|
|
2303
|
+
maxRuntimeMs,
|
|
2304
|
+
});
|
|
2305
|
+
|
|
2306
|
+
if (options.eventBus) {
|
|
2307
|
+
options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
|
|
2308
|
+
id,
|
|
2309
|
+
agent: agent.name,
|
|
2310
|
+
parentToolCallId: options.parentToolCallId,
|
|
2311
|
+
detached: true,
|
|
2312
|
+
agentSource: agent.source,
|
|
2313
|
+
description: options.description,
|
|
2314
|
+
status: "started",
|
|
2315
|
+
sessionFile,
|
|
2316
|
+
index,
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
turnMonitor.setActiveSession(session);
|
|
2321
|
+
const unsubscribeTurn = turnMonitor.attach(session);
|
|
2322
|
+
return async turnError => {
|
|
2323
|
+
unsubscribeTurn();
|
|
2324
|
+
const activeSession = turnMonitor.takeActiveSession();
|
|
2325
|
+
if (activeSession) turnMonitor.captureSalvage(activeSession);
|
|
2326
|
+
const lastAssistant = session.getLastAssistantMessage();
|
|
2327
|
+
const yielded = turnMonitor.yieldCalled();
|
|
2328
|
+
const runtimeLimitExceeded = turnMonitor.runtimeLimitExceeded();
|
|
2329
|
+
const aborted = runtimeLimitExceeded || (lastAssistant?.stopReason === "aborted" && !yielded);
|
|
2330
|
+
const error =
|
|
2331
|
+
lastAssistant?.stopReason === "error"
|
|
2332
|
+
? lastAssistant.errorMessage || "Subagent failed"
|
|
2333
|
+
: turnError !== undefined && !yielded
|
|
2334
|
+
? turnError instanceof Error
|
|
2335
|
+
? turnError.stack || turnError.message
|
|
2336
|
+
: String(turnError)
|
|
2337
|
+
: undefined;
|
|
2338
|
+
turnMonitor.finish();
|
|
2339
|
+
try {
|
|
2340
|
+
await finalizeRunResult({
|
|
2341
|
+
monitor: turnMonitor,
|
|
2342
|
+
done: {
|
|
2343
|
+
exitCode: aborted || error ? 1 : 0,
|
|
2344
|
+
error,
|
|
2345
|
+
aborted,
|
|
2346
|
+
abortReason: aborted ? turnMonitor.resolveAbortReasonText() : undefined,
|
|
2347
|
+
durationMs: Date.now() - turnStartTime,
|
|
2348
|
+
},
|
|
2349
|
+
index,
|
|
2350
|
+
id,
|
|
2351
|
+
agent,
|
|
2352
|
+
task: ircTask,
|
|
2353
|
+
modelOverride: options.modelOverride,
|
|
2354
|
+
modelRole: options.modelRole,
|
|
2355
|
+
outputSchema: options.outputSchema,
|
|
2356
|
+
outputSchemaMode: options.outputSchemaMode,
|
|
2357
|
+
outputSchemaSource: options.outputSchemaSource,
|
|
2358
|
+
artifactsDir: options.artifactsDir,
|
|
2359
|
+
eventBus: options.eventBus,
|
|
2360
|
+
parentToolCallId: options.parentToolCallId,
|
|
2361
|
+
detached: true,
|
|
2362
|
+
sessionFile,
|
|
2363
|
+
startTime: turnStartTime,
|
|
2364
|
+
});
|
|
2365
|
+
} catch (finalizeError) {
|
|
2366
|
+
logger.warn("IRC subagent turn finalization failed", {
|
|
2367
|
+
id,
|
|
2368
|
+
error: finalizeError instanceof Error ? finalizeError.message : String(finalizeError),
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
};
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
/**
|
|
2376
|
+
* Settle a subagent's registry lifecycle after a run: terminal teardown for
|
|
2377
|
+
* hard aborts, unregister for one-shot helpers, park for isolated runs, and
|
|
2378
|
+
* idle + lifecycle adoption for kept-alive agents. A soft-budget abort on a
|
|
2379
|
+
* kept-alive, revivable agent is treated as a self-inflicted stop rather than
|
|
2380
|
+
* a kill — the agent stays interrogable and resumable (irc wake / revival).
|
|
2381
|
+
*/
|
|
2382
|
+
export async function finalizeSubagentLifecycle(args: {
|
|
2383
|
+
id: string;
|
|
2384
|
+
session: AgentSession;
|
|
2385
|
+
aborted: boolean;
|
|
2386
|
+
/** Which watchdog (if any) requested the abort; decides revivability. */
|
|
2387
|
+
abortKind?: AbortReason;
|
|
2388
|
+
keepAlive: boolean;
|
|
2389
|
+
isolated: boolean;
|
|
2390
|
+
agentIdleTtlMs: number;
|
|
2391
|
+
reviveSession: AgentReviver | null;
|
|
2392
|
+
cleanupDeadlineAt?: number;
|
|
2393
|
+
onCleanupDeferred?: (completion: Promise<void>) => void;
|
|
2394
|
+
}): Promise<void> {
|
|
2395
|
+
const registry = AgentRegistry.global();
|
|
2396
|
+
const ref = registry.get(args.id);
|
|
2397
|
+
const ownsRef = Boolean(ref && ref.session === args.session);
|
|
2398
|
+
const cleanupDeadlineAt = args.cleanupDeadlineAt ?? Date.now() + 5000;
|
|
2399
|
+
const disposeSession = async (): Promise<void> => {
|
|
2400
|
+
const disposal = args.session.dispose();
|
|
2401
|
+
const remainingMs = Math.max(0, cleanupDeadlineAt - Date.now());
|
|
2402
|
+
try {
|
|
2403
|
+
await untilAborted(AbortSignal.timeout(remainingMs), () => disposal);
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
if (Date.now() >= cleanupDeadlineAt) {
|
|
2406
|
+
args.onCleanupDeferred?.(disposal);
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
logger.warn("Subagent session cleanup failed", {
|
|
2410
|
+
id: args.id,
|
|
2411
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
|
|
2416
|
+
// A budget abort leaves a consistent session with its transcript on disk;
|
|
2417
|
+
// caller signals, wall-clock timeouts (possible stream hang), and internal
|
|
2418
|
+
// terminations are genuine kills and stay terminal.
|
|
2419
|
+
const resumableAbort =
|
|
2420
|
+
args.abortKind === "budget" && args.keepAlive && !args.isolated && args.reviveSession !== null;
|
|
2421
|
+
if (args.aborted && !resumableAbort) {
|
|
2422
|
+
if (ref && ownsRef) {
|
|
2423
|
+
// Route hard kills through the lifecycle owner so the terminal
|
|
2424
|
+
// decision is durable and a restart cannot rediscover the transcript
|
|
2425
|
+
// as a revivable parked agent.
|
|
2426
|
+
try {
|
|
2427
|
+
await AgentLifecycleManager.global().release(args.id, ref, { tombstone: true });
|
|
2428
|
+
} catch (error) {
|
|
2429
|
+
logger.warn("runSubagent: failed to persist kill tombstone", { id: args.id, error: String(error) });
|
|
2430
|
+
registry.setStatus(args.id, "aborted", ref);
|
|
2431
|
+
registry.detachSession(args.id, ref);
|
|
2432
|
+
await disposeSession();
|
|
2433
|
+
}
|
|
2434
|
+
} else {
|
|
2435
|
+
await disposeSession();
|
|
2436
|
+
}
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
if (!args.keepAlive) {
|
|
2441
|
+
// One-shot helper: dispose and unregister. No IRC, no revival.
|
|
2442
|
+
await disposeSession();
|
|
2443
|
+
if (ref && ownsRef) registry.unregister(args.id, ref);
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
if (args.isolated) {
|
|
2448
|
+
// Isolated run: the worktree is merged + cleaned after the run, so
|
|
2449
|
+
// the session is not resumable. Park the ref WITHOUT adopting — the
|
|
2450
|
+
// transcript stays reachable (history://), but ensureLive will throw.
|
|
2451
|
+
// Status must flip to "parked" before dispose so the sdk dispose
|
|
2452
|
+
// wrapper skips unregister.
|
|
2453
|
+
if (ref && ownsRef) registry.setStatus(args.id, "parked", ref);
|
|
2454
|
+
await disposeSession();
|
|
2455
|
+
if (ref && ownsRef) registry.detachSession(args.id, ref);
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
// Keep-alive: finished and failed subagents both stay interrogable.
|
|
2460
|
+
// The lifecycle manager owns idle-TTL parking + revival from here on.
|
|
2461
|
+
if (!ref || !ownsRef || !registry.setStatus(args.id, "idle", ref)) {
|
|
2462
|
+
await disposeSession();
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
AgentLifecycleManager.global().adopt(
|
|
2466
|
+
args.id,
|
|
2467
|
+
{
|
|
2468
|
+
idleTtlMs: args.agentIdleTtlMs,
|
|
2469
|
+
revive: args.reviveSession ?? undefined,
|
|
2470
|
+
},
|
|
2471
|
+
ref,
|
|
2472
|
+
);
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
/** Options for {@link runSubagentFollowUpTurn}. */
|
|
2476
|
+
export interface FollowUpTurnOptions {
|
|
2477
|
+
/** Registry id of the (live or parked) subagent to continue. */
|
|
2478
|
+
id: string;
|
|
2479
|
+
/** Agent definition the session was originally spawned with (drives progress labels + finalize). */
|
|
2480
|
+
agent: AgentDefinition;
|
|
2481
|
+
/** The follow-up message; sent as the turn's user prompt. */
|
|
2482
|
+
message: string;
|
|
2483
|
+
index?: number;
|
|
2484
|
+
description?: string;
|
|
2485
|
+
/** Explicit pre-expansion model role alias retained from the original run. */
|
|
2486
|
+
modelRole?: string;
|
|
2487
|
+
/** Structured-output state retained from the original invocation. */
|
|
2488
|
+
outputSchema?: unknown;
|
|
2489
|
+
outputSchemaMode?: StructuredSubagentSchemaMode;
|
|
2490
|
+
outputSchemaSource?: StructuredSubagentSchemaSource;
|
|
2491
|
+
signal?: AbortSignal;
|
|
2492
|
+
onProgress?: (progress: AgentProgress) => void;
|
|
2493
|
+
eventBus?: EventBus;
|
|
2494
|
+
parentToolCallId?: string;
|
|
2495
|
+
/** When set, the turn's raw output is (re)written to `<artifactsDir>/<id>.md` so `agent://<id>` tracks the latest turn. */
|
|
2496
|
+
artifactsDir?: string;
|
|
2497
|
+
/** Wall-clock cap in ms for this turn; 0 disables. */
|
|
2498
|
+
maxRuntimeMs?: number;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
/**
|
|
2502
|
+
* Continue a previously spawned (keep-alive) subagent with one more monitored
|
|
2503
|
+
* turn: revive it if parked, send `message` as a real prompt, drive it to
|
|
2504
|
+
* `yield`, and finalize a {@link SingleResult} exactly like a first run.
|
|
2505
|
+
*
|
|
2506
|
+
* The session's full conversation history is retained (live session, or JSONL
|
|
2507
|
+
* replay through the lifecycle reviver), so the turn sees all prior context.
|
|
2508
|
+
* Unlike {@link runSubprocess}, the session is NOT torn down afterwards — it
|
|
2509
|
+
* stays adopted by the {@link AgentLifecycleManager} (idle → TTL park →
|
|
2510
|
+
* revive), and an aborted turn only aborts the in-flight turn.
|
|
2511
|
+
*/
|
|
2512
|
+
export async function runSubagentFollowUpTurn(options: FollowUpTurnOptions): Promise<SingleResult> {
|
|
2513
|
+
const { id, agent, message, signal } = options;
|
|
2514
|
+
const index = options.index ?? 0;
|
|
2515
|
+
const startTime = Date.now();
|
|
2516
|
+
const session = await AgentLifecycleManager.global().ensureLive(id);
|
|
2517
|
+
const ref = AgentRegistry.global().get(id);
|
|
2518
|
+
const sessionFile = ref?.sessionFile ?? undefined;
|
|
2519
|
+
|
|
2520
|
+
const monitor = createSubagentRunMonitor({
|
|
2521
|
+
index,
|
|
2522
|
+
id,
|
|
2523
|
+
agent,
|
|
2524
|
+
task: message,
|
|
2525
|
+
description: options.description,
|
|
2526
|
+
modelRole: options.modelRole,
|
|
2527
|
+
signal,
|
|
2528
|
+
onProgress: options.onProgress,
|
|
2529
|
+
eventBus: options.eventBus,
|
|
2530
|
+
parentToolCallId: options.parentToolCallId,
|
|
2531
|
+
detached: true,
|
|
2532
|
+
sessionFile,
|
|
2533
|
+
softRequestBudget: 0,
|
|
2534
|
+
softRequestBudgetNotice: false,
|
|
2535
|
+
maxRuntimeMs: options.maxRuntimeMs ?? 0,
|
|
2536
|
+
});
|
|
2537
|
+
|
|
2538
|
+
if (options.eventBus) {
|
|
2539
|
+
options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
|
|
2540
|
+
id,
|
|
2541
|
+
agent: agent.name,
|
|
2542
|
+
parentToolCallId: options.parentToolCallId,
|
|
2543
|
+
detached: true,
|
|
2544
|
+
agentSource: agent.source,
|
|
2545
|
+
description: options.description,
|
|
2546
|
+
status: "started",
|
|
2547
|
+
sessionFile,
|
|
2548
|
+
index,
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
monitor.setActiveSession(session);
|
|
2553
|
+
const unsubscribe = monitor.attach(session);
|
|
2554
|
+
let outcome: DriveOutcome;
|
|
2555
|
+
try {
|
|
2556
|
+
outcome = await driveSessionToYield(session, monitor, message);
|
|
2557
|
+
} finally {
|
|
2558
|
+
try {
|
|
2559
|
+
await untilAborted(AbortSignal.timeout(5000), () => monitor.waitForActiveSessionAbort());
|
|
2560
|
+
} catch {
|
|
2561
|
+
// Ignore abort cleanup timeouts; the session stays adopted either way.
|
|
2562
|
+
}
|
|
2563
|
+
unsubscribe();
|
|
2564
|
+
const active = monitor.takeActiveSession();
|
|
2565
|
+
if (active) monitor.captureSalvage(active);
|
|
2566
|
+
monitor.finish();
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
return finalizeRunResult({
|
|
2570
|
+
monitor,
|
|
2571
|
+
done: { ...outcome, abortReason: outcome.abortReasonText, durationMs: Date.now() - startTime },
|
|
2572
|
+
index,
|
|
2573
|
+
id,
|
|
2574
|
+
agent,
|
|
2575
|
+
task: message,
|
|
2576
|
+
modelRole: options.modelRole,
|
|
2577
|
+
outputSchema: options.outputSchema,
|
|
2578
|
+
outputSchemaMode: options.outputSchemaMode,
|
|
2579
|
+
outputSchemaSource: options.outputSchemaSource,
|
|
2580
|
+
signal,
|
|
2581
|
+
artifactsDir: options.artifactsDir,
|
|
2582
|
+
eventBus: options.eventBus,
|
|
2583
|
+
parentToolCallId: options.parentToolCallId,
|
|
2584
|
+
detached: true,
|
|
2585
|
+
sessionFile,
|
|
2586
|
+
startTime,
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
/**
|
|
2591
|
+
* Run a single agent in-process.
|
|
2592
|
+
*/
|
|
2593
|
+
export async function runSubprocess(options: ExecutorOptions): Promise<SingleResult> {
|
|
2594
|
+
const {
|
|
2595
|
+
cwd,
|
|
2596
|
+
agent,
|
|
2597
|
+
task,
|
|
2598
|
+
assignment,
|
|
2599
|
+
index,
|
|
2600
|
+
id,
|
|
2601
|
+
worktree,
|
|
2602
|
+
modelOverride,
|
|
2603
|
+
modelRole,
|
|
2604
|
+
thinkingLevel,
|
|
2605
|
+
outputSchema,
|
|
2606
|
+
enableLsp,
|
|
2607
|
+
signal,
|
|
2608
|
+
onProgress,
|
|
2609
|
+
} = options;
|
|
2610
|
+
const startTime = Date.now();
|
|
2611
|
+
// Set by the session's onFirstChatDispatch hook the first time the agent
|
|
2612
|
+
// loop dispatches a chat request to the provider — the launch-complete boundary.
|
|
2613
|
+
let firstChatDispatchAt: number | undefined;
|
|
2614
|
+
|
|
2615
|
+
// Check if already aborted
|
|
2616
|
+
if (signal?.aborted) {
|
|
2617
|
+
return {
|
|
2618
|
+
index,
|
|
2619
|
+
id,
|
|
2620
|
+
agent: agent.name,
|
|
2621
|
+
agentSource: agent.source,
|
|
2622
|
+
task,
|
|
2623
|
+
assignment,
|
|
2624
|
+
description: options.description,
|
|
2625
|
+
exitCode: 1,
|
|
2626
|
+
output: "",
|
|
2627
|
+
stderr: "Cancelled before start",
|
|
2628
|
+
truncated: false,
|
|
2629
|
+
durationMs: 0,
|
|
2630
|
+
tokens: 0,
|
|
2631
|
+
requests: 0,
|
|
2632
|
+
modelOverride,
|
|
2633
|
+
modelRole,
|
|
2634
|
+
error: "Cancelled before start",
|
|
2635
|
+
aborted: true,
|
|
2636
|
+
abortReason: "Cancelled before start",
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// Set up artifact paths and write input file upfront if artifacts dir provided
|
|
2641
|
+
let subtaskSessionFile: string | undefined;
|
|
2642
|
+
if (options.artifactsDir) {
|
|
2643
|
+
subtaskSessionFile = path.join(options.artifactsDir, `${id}.jsonl`);
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
const settings = options.settings ?? Settings.isolated();
|
|
2647
|
+
const subagentSettings = createSubagentSettings(
|
|
2648
|
+
settings,
|
|
2649
|
+
{
|
|
2650
|
+
...(agent.readSummarize === false ? { "read.summarize.enabled": false } : undefined),
|
|
2651
|
+
// Isolated runs must not expose roots outside the worktree.
|
|
2652
|
+
...(worktree !== undefined ? { "workspace.additionalDirectories": [] } : undefined),
|
|
2653
|
+
},
|
|
2654
|
+
options.parentServiceTier,
|
|
2655
|
+
);
|
|
2656
|
+
const maxRecursionDepth = settings.get("task.maxRecursionDepth") ?? 2;
|
|
2657
|
+
const maxRuntimeMs = Math.max(
|
|
2658
|
+
0,
|
|
2659
|
+
Math.trunc(Number(options.maxRuntimeMs ?? settings.get("task.maxRuntimeMs") ?? 0) || 0),
|
|
2660
|
+
);
|
|
2661
|
+
// TTL before an adopted idle subagent is parked by the lifecycle manager.
|
|
2662
|
+
// <= 0 disables parking (the session stays live until process teardown).
|
|
2663
|
+
const agentIdleTtlMs = Math.trunc(Number(settings.get("task.agentIdleTtlMs") ?? 420_000) || 0);
|
|
2664
|
+
const configuredDefaultBudget = Math.max(
|
|
2665
|
+
0,
|
|
2666
|
+
Math.trunc(Number(settings.get("task.softRequestBudget") ?? SOFT_REQUEST_BUDGET.default) || 0),
|
|
2667
|
+
);
|
|
2668
|
+
const softRequestBudget = resolveSoftRequestBudget(agent.name, configuredDefaultBudget);
|
|
2669
|
+
const softRequestBudgetNotice = settings.get("task.softRequestBudgetNotice") ?? false;
|
|
2670
|
+
const parentDepth = options.taskDepth ?? 0;
|
|
2671
|
+
const childDepth = parentDepth + 1;
|
|
2672
|
+
const atMaxDepth = maxRecursionDepth >= 0 && childDepth >= maxRecursionDepth;
|
|
2673
|
+
const ircEnabled = options.enableIrc !== false && isIrcEnabled(subagentSettings, childDepth);
|
|
2674
|
+
|
|
2675
|
+
// Add tools if specified
|
|
2676
|
+
let toolNames: string[] | undefined;
|
|
2677
|
+
if (agent.tools && agent.tools.length > 0) {
|
|
2678
|
+
toolNames = agent.tools;
|
|
2679
|
+
// Auto-include task tool if spawns defined but task not in tools
|
|
2680
|
+
if (agent.spawns !== undefined && !toolNames.includes("task") && !atMaxDepth) {
|
|
2681
|
+
toolNames = [...toolNames, "task"];
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
if (atMaxDepth && toolNames?.includes("task")) {
|
|
2686
|
+
toolNames = toolNames.filter(name => name !== "task");
|
|
2687
|
+
}
|
|
2688
|
+
// Ordinary agents retain the host's always-on collaboration capability.
|
|
2689
|
+
// Restricted sessions must not widen their explicit host tool list with hub.
|
|
2690
|
+
if (toolNames && !options.restrictToolNames && !toolNames.includes("hub")) {
|
|
2691
|
+
toolNames = [...toolNames, "hub"];
|
|
2692
|
+
}
|
|
2693
|
+
if (toolNames?.includes("exec")) {
|
|
2694
|
+
const backends = resolveEvalBackends({ settings } as ToolSession);
|
|
2695
|
+
const expanded = toolNames.filter(name => name !== "exec");
|
|
2696
|
+
if (backends.python || backends.js || backends.ruby || backends.julia) expanded.push("eval");
|
|
2697
|
+
expanded.push("bash");
|
|
2698
|
+
toolNames = Array.from(new Set(expanded));
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
const modelPatterns = normalizeModelPatterns(modelOverride ?? agent.model);
|
|
2702
|
+
const sessionFile = subtaskSessionFile ?? null;
|
|
2703
|
+
const spawnsEnv = atMaxDepth
|
|
2704
|
+
? ""
|
|
2705
|
+
: agent.spawns === undefined
|
|
2706
|
+
? ""
|
|
2707
|
+
: agent.spawns === "*"
|
|
2708
|
+
? "*"
|
|
2709
|
+
: agent.spawns.join(",");
|
|
2710
|
+
|
|
2711
|
+
const lspEnabled = enableLsp ?? true;
|
|
2712
|
+
const skipPythonPreflight = Array.isArray(toolNames) && !toolNames.includes("eval");
|
|
2713
|
+
|
|
2714
|
+
const monitor = createSubagentRunMonitor({
|
|
2715
|
+
index,
|
|
2716
|
+
id,
|
|
2717
|
+
agent,
|
|
2718
|
+
task,
|
|
2719
|
+
assignment,
|
|
2720
|
+
description: options.description,
|
|
2721
|
+
modelRegistry: options.modelRegistry,
|
|
2722
|
+
settings,
|
|
2723
|
+
modelOverride,
|
|
2724
|
+
modelRole,
|
|
2725
|
+
signal,
|
|
2726
|
+
onProgress,
|
|
2727
|
+
eventBus: options.eventBus,
|
|
2728
|
+
parentToolCallId: options.parentToolCallId,
|
|
2729
|
+
detached: options.detached,
|
|
2730
|
+
sessionFile: subtaskSessionFile,
|
|
2731
|
+
softRequestBudget,
|
|
2732
|
+
softRequestBudgetNotice,
|
|
2733
|
+
maxRuntimeMs,
|
|
2734
|
+
});
|
|
2735
|
+
const progress = monitor.progress;
|
|
2736
|
+
let unsubscribe: (() => void) | null = null;
|
|
2737
|
+
let reviveSession: AgentReviver | null = null;
|
|
2738
|
+
// Adopted (kept-alive) subagents flip registry status from session events on
|
|
2739
|
+
// later turns: revive/wake → running, turn drained → idle. The subscription
|
|
2740
|
+
// intentionally survives this run; a disposed session emits nothing, so it
|
|
2741
|
+
// needs no teardown.
|
|
2742
|
+
const installRegistryStatusSync = (target: AgentSession): void => {
|
|
2743
|
+
target.subscribe(event => {
|
|
2744
|
+
if (event.type === "agent_start") {
|
|
2745
|
+
AgentRegistry.global().setStatus(id, "running", target);
|
|
2746
|
+
} else if (event.type === "agent_end") {
|
|
2747
|
+
AgentRegistry.global().setStatus(id, "idle", target);
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
};
|
|
2751
|
+
const installIrcWakeTurnMonitor = (target: AgentSession): void => {
|
|
2752
|
+
attachIrcWakeTurnMonitor(target, {
|
|
2753
|
+
id,
|
|
2754
|
+
index,
|
|
2755
|
+
agent,
|
|
2756
|
+
description: options.description,
|
|
2757
|
+
modelOverride,
|
|
2758
|
+
modelRole,
|
|
2759
|
+
eventBus: options.eventBus,
|
|
2760
|
+
parentToolCallId: options.parentToolCallId,
|
|
2761
|
+
sessionFile: subtaskSessionFile,
|
|
2762
|
+
maxRuntimeMs,
|
|
2763
|
+
outputSchema,
|
|
2764
|
+
outputSchemaMode: options.outputSchemaMode,
|
|
2765
|
+
outputSchemaSource: options.outputSchemaSource,
|
|
2766
|
+
artifactsDir: options.artifactsDir,
|
|
2767
|
+
});
|
|
2768
|
+
};
|
|
2769
|
+
|
|
2770
|
+
const runSubagent = async (): Promise<{
|
|
2771
|
+
exitCode: number;
|
|
2772
|
+
error?: string;
|
|
2773
|
+
aborted?: boolean;
|
|
2774
|
+
abortReason?: string;
|
|
2775
|
+
durationMs: number;
|
|
2776
|
+
}> => {
|
|
2777
|
+
const sessionAbortController = new AbortController();
|
|
2778
|
+
const abortSignal = monitor.abortSignal;
|
|
2779
|
+
let exitCode = 0;
|
|
2780
|
+
let error: string | undefined;
|
|
2781
|
+
let aborted = false;
|
|
2782
|
+
let abortReasonText: string | undefined;
|
|
2783
|
+
const checkAbort = () => {
|
|
2784
|
+
if (abortSignal.aborted) {
|
|
2785
|
+
throw new ToolAbortError();
|
|
2786
|
+
}
|
|
2787
|
+
};
|
|
2788
|
+
const awaitAbortable = async <T>(promise: Promise<T>): Promise<T> => {
|
|
2789
|
+
checkAbort();
|
|
2790
|
+
const { promise: abortPromise, reject } = Promise.withResolvers<never>();
|
|
2791
|
+
const onAbort = () => {
|
|
2792
|
+
try {
|
|
2793
|
+
checkAbort();
|
|
2794
|
+
} catch (err) {
|
|
2795
|
+
reject(err);
|
|
2796
|
+
}
|
|
2797
|
+
};
|
|
2798
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
2799
|
+
try {
|
|
2800
|
+
return await Promise.race([promise, abortPromise]);
|
|
2801
|
+
} finally {
|
|
2802
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
2803
|
+
}
|
|
2804
|
+
};
|
|
2805
|
+
// Launch-latency phase marks (performance.now()); read by the debug log
|
|
2806
|
+
// emitted before this closure returns. Left undefined when setup throws
|
|
2807
|
+
// before reaching the phase, which itself localizes the cost.
|
|
2808
|
+
const perfStart = performance.now();
|
|
2809
|
+
let resolvedAt: number | undefined;
|
|
2810
|
+
let sessionOpenedAt: number | undefined;
|
|
2811
|
+
let sessionCreatedAt: number | undefined;
|
|
2812
|
+
let readyAt: number | undefined;
|
|
2813
|
+
|
|
2814
|
+
try {
|
|
2815
|
+
checkAbort();
|
|
2816
|
+
// Pin authStorage to modelRegistry.authStorage — mirrors the createAgentSession invariant.
|
|
2817
|
+
const registryFromParent = options.modelRegistry !== undefined;
|
|
2818
|
+
const modelRegistry =
|
|
2819
|
+
options.modelRegistry ??
|
|
2820
|
+
new ModelRegistry(options.authStorage ?? (await awaitAbortable(discoverAuthStorage())));
|
|
2821
|
+
const authStorage = modelRegistry.authStorage;
|
|
2822
|
+
if (options.authStorage && options.authStorage !== authStorage) {
|
|
2823
|
+
throw new Error(
|
|
2824
|
+
"options.authStorage and options.modelRegistry.authStorage must be the same instance when both are provided",
|
|
2825
|
+
);
|
|
2826
|
+
}
|
|
2827
|
+
checkAbort();
|
|
2828
|
+
if (!registryFromParent) {
|
|
2829
|
+
modelRegistry.refreshInBackground();
|
|
2830
|
+
} else {
|
|
2831
|
+
logger.debug("runSubagent: reusing parent modelRegistry; skipping refresh");
|
|
2832
|
+
}
|
|
2833
|
+
checkAbort();
|
|
2834
|
+
|
|
2835
|
+
const configuredModelPatterns = resolveConfiguredModelPatterns(modelPatterns, settings);
|
|
2836
|
+
const inheritedRetryFallbackChain =
|
|
2837
|
+
configuredModelPatterns.length === 1
|
|
2838
|
+
? resolveSubagentInheritedRetryFallbackChain(subagentSettings, modelRegistry, modelPatterns)
|
|
2839
|
+
: undefined;
|
|
2840
|
+
const {
|
|
2841
|
+
model,
|
|
2842
|
+
thinkingLevel: resolvedThinkingLevel,
|
|
2843
|
+
explicitThinkingLevel,
|
|
2844
|
+
authFallbackUsed,
|
|
2845
|
+
warning: modelResolutionWarning,
|
|
2846
|
+
} = await awaitAbortable(
|
|
2847
|
+
resolveModelOverrideWithAuthFallback(
|
|
2848
|
+
modelPatterns,
|
|
2849
|
+
options.parentActiveModelPattern,
|
|
2850
|
+
modelRegistry,
|
|
2851
|
+
settings,
|
|
2852
|
+
id,
|
|
2853
|
+
),
|
|
2854
|
+
);
|
|
2855
|
+
if (modelResolutionWarning) {
|
|
2856
|
+
logger.warn("Subagent model resolution warning", {
|
|
2857
|
+
warning: modelResolutionWarning,
|
|
2858
|
+
requested: modelPatterns,
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
if (authFallbackUsed && model) {
|
|
2862
|
+
logger.warn("Subagent model has no working credentials; falling back to parent session model", {
|
|
2863
|
+
requested: modelPatterns,
|
|
2864
|
+
parentModel: options.parentActiveModelPattern,
|
|
2865
|
+
resolvedProvider: model.provider,
|
|
2866
|
+
resolvedModel: model.id,
|
|
2867
|
+
});
|
|
2868
|
+
}
|
|
2869
|
+
const retryFallbackRole = installSubagentRetryFallbackChain({
|
|
2870
|
+
settings: subagentSettings,
|
|
2871
|
+
id,
|
|
2872
|
+
candidates: resolveSubagentRetryFallbackCandidates(modelPatterns, modelRegistry, subagentSettings),
|
|
2873
|
+
inheritedFallbackChain: inheritedRetryFallbackChain,
|
|
2874
|
+
model,
|
|
2875
|
+
authFallbackUsed,
|
|
2876
|
+
});
|
|
2877
|
+
if (retryFallbackRole) {
|
|
2878
|
+
logger.debug("Configured subagent runtime model fallback chain", {
|
|
2879
|
+
role: retryFallbackRole,
|
|
2880
|
+
requested: modelPatterns,
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
if (model?.contextWindow && model.contextWindow > 0) {
|
|
2884
|
+
progress.contextWindow = model.contextWindow;
|
|
2885
|
+
}
|
|
2886
|
+
// Caller-requested coarse effort maps onto the resolved model's
|
|
2887
|
+
// supported range, then respects the operator-configured ceiling.
|
|
2888
|
+
// Undefined (no effort, or no controllable effort surface) falls
|
|
2889
|
+
// through to the normal selectors below.
|
|
2890
|
+
// The ceiling outlives initial resolution: it rides into the session so
|
|
2891
|
+
// retry-fallback recovery can never clamp effort back up past it.
|
|
2892
|
+
const spawnEffortCeiling = options.effort !== undefined ? settings.get("task.maxEffort") : undefined;
|
|
2893
|
+
const effortLevel =
|
|
2894
|
+
options.effort !== undefined
|
|
2895
|
+
? resolveTaskEffortLevel(model, options.effort, spawnEffortCeiling)
|
|
2896
|
+
: undefined;
|
|
2897
|
+
if (model) {
|
|
2898
|
+
const displayLevel = effortLevel ?? (explicitThinkingLevel ? resolvedThinkingLevel : undefined);
|
|
2899
|
+
progress.resolvedModel =
|
|
2900
|
+
displayLevel !== undefined
|
|
2901
|
+
? formatModelSelectorValue(formatModelStringWithRouting(model), displayLevel)
|
|
2902
|
+
: formatModelStringWithRouting(model);
|
|
2903
|
+
}
|
|
2904
|
+
// Precedence: caller `effort` > explicit `:level` suffix on the resolved
|
|
2905
|
+
// model pattern > agent-definition default (e.g. task's `auto`) >
|
|
2906
|
+
// pattern-derived level.
|
|
2907
|
+
const effectiveThinkingLevel =
|
|
2908
|
+
effortLevel ?? (explicitThinkingLevel ? resolvedThinkingLevel : (thinkingLevel ?? resolvedThinkingLevel));
|
|
2909
|
+
resolvedAt = performance.now();
|
|
2910
|
+
const effectiveCwd = worktree ?? cwd;
|
|
2911
|
+
const sessionManagerPromise = sessionFile
|
|
2912
|
+
? SessionManager.open(sessionFile, undefined, undefined, {
|
|
2913
|
+
initialCwd: effectiveCwd,
|
|
2914
|
+
suppressBreadcrumb: true,
|
|
2915
|
+
})
|
|
2916
|
+
: Promise.resolve(SessionManager.inMemory(effectiveCwd));
|
|
2917
|
+
// Setup below can fail before this promise's consumption boundary.
|
|
2918
|
+
// Observe rejection immediately while preserving it for the later await.
|
|
2919
|
+
sessionManagerPromise.catch(() => {});
|
|
2920
|
+
// Per-agent prewalk: the agent definition's `prewalk` frontmatter or the
|
|
2921
|
+
// `task.agentPrewalk` settings override hands the subagent off to a
|
|
2922
|
+
// fast/cheap target at its first edit/write — the same mechanism as the
|
|
2923
|
+
// session-level --prewalk. The bundled generic `task` agent has no
|
|
2924
|
+
// frontmatter default; the `task.prewalk` toggle (default off) arms it.
|
|
2925
|
+
// Resolution failures skip prewalk instead of failing the spawn.
|
|
2926
|
+
let prewalk: Prewalk | undefined;
|
|
2927
|
+
const prewalkPattern = resolveAgentPrewalkPattern({
|
|
2928
|
+
settingsOverride: settings.get("task.agentPrewalk")[agent.name],
|
|
2929
|
+
agentPrewalk: resolveAgentPrewalkDefault(agent, settings.get("task.prewalk")),
|
|
2930
|
+
});
|
|
2931
|
+
if (prewalkPattern) {
|
|
2932
|
+
await awaitAbortable(modelRegistry.awaitBackgroundRefresh());
|
|
2933
|
+
const resolvedPrewalk = resolveModelOverride([prewalkPattern], modelRegistry, settings);
|
|
2934
|
+
const target = resolvedPrewalk.model;
|
|
2935
|
+
if (!target || !modelRegistry.hasConfiguredAuth(target)) {
|
|
2936
|
+
logger.warn("Subagent prewalk target unavailable; skipping prewalk", {
|
|
2937
|
+
agent: agent.name,
|
|
2938
|
+
pattern: prewalkPattern,
|
|
2939
|
+
warning: resolvedPrewalk.warning,
|
|
2940
|
+
});
|
|
2941
|
+
} else if (prewalkWouldBeNoop(model, effectiveThinkingLevel, target, resolvedPrewalk.thinkingLevel)) {
|
|
2942
|
+
// Same model AND same effective thinking level: switching would only
|
|
2943
|
+
// inject the plan/checklist nudges for no gain — skip. An effort-only
|
|
2944
|
+
// delta on the same model still arms (it is a real cheapening hand-off).
|
|
2945
|
+
logger.debug("Subagent prewalk target matches starting model and thinking level; skipping prewalk", {
|
|
2946
|
+
agent: agent.name,
|
|
2947
|
+
pattern: prewalkPattern,
|
|
2948
|
+
});
|
|
2949
|
+
} else {
|
|
2950
|
+
prewalk = { target, thinkingLevel: resolvedPrewalk.thinkingLevel };
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
const restrictToolNames = options.restrictToolNames === true;
|
|
2955
|
+
const enableMCP = !restrictToolNames && (options.enableMCP ?? true);
|
|
2956
|
+
const mcpManager = enableMCP ? options.mcpManager : undefined;
|
|
2957
|
+
const mcpProxyTools = mcpManager ? createMCPProxyTools(mcpManager) : [];
|
|
2958
|
+
|
|
2959
|
+
// Derive subagent-scoped telemetry from the parent's config so the
|
|
2960
|
+
// child loop's spans nest under the parent's active execute_tool span
|
|
2961
|
+
// (OTEL context propagation handles parent linkage automatically),
|
|
2962
|
+
// carry the subagent's own agent identity, and use the subagent's
|
|
2963
|
+
// own session id for `gen_ai.conversation.id`.
|
|
2964
|
+
const subagentAgentIdentity: AgentIdentity | undefined = options.parentTelemetry
|
|
2965
|
+
? {
|
|
2966
|
+
id,
|
|
2967
|
+
name: agent.name,
|
|
2968
|
+
description: agent.description,
|
|
2969
|
+
}
|
|
2970
|
+
: undefined;
|
|
2971
|
+
const subagentTelemetry: AgentTelemetryConfig | undefined =
|
|
2972
|
+
options.parentTelemetry && subagentAgentIdentity
|
|
2973
|
+
? {
|
|
2974
|
+
...options.parentTelemetry,
|
|
2975
|
+
agent: subagentAgentIdentity,
|
|
2976
|
+
// Clear parent's conversationId; the child loop falls back to
|
|
2977
|
+
// its own AgentLoopConfig.sessionId.
|
|
2978
|
+
conversationId: undefined,
|
|
2979
|
+
}
|
|
2980
|
+
: undefined;
|
|
2981
|
+
|
|
2982
|
+
if (options.parentTelemetry && subagentAgentIdentity) {
|
|
2983
|
+
const parentTelemetryHandle = resolveTelemetry(
|
|
2984
|
+
options.parentTelemetry,
|
|
2985
|
+
options.parentTelemetry.conversationId,
|
|
2986
|
+
);
|
|
2987
|
+
recordHandoff(parentTelemetryHandle, {
|
|
2988
|
+
fromAgent: options.parentTelemetry.agent,
|
|
2989
|
+
toAgent: subagentAgentIdentity,
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
const { normalized: normalizedOutputSchema } = normalizeSchema(outputSchema);
|
|
2994
|
+
|
|
2995
|
+
// Captured by the lifecycle reviver: rebuilding an equivalent session from
|
|
2996
|
+
// the same JSONL file re-invokes createAgentSession with the exact options
|
|
2997
|
+
// of the original run (same agent id, tools, model, system prompt,
|
|
2998
|
+
// artifacts dir) — only the SessionManager differs.
|
|
2999
|
+
const buildSubagentSessionOptions = (
|
|
3000
|
+
sessionManagerForRun: SessionManager,
|
|
3001
|
+
expectedAgentRef: CreateAgentSessionOptions["expectedAgentRef"],
|
|
3002
|
+
): CreateAgentSessionOptions => ({
|
|
3003
|
+
cwd: worktree ?? cwd,
|
|
3004
|
+
additionalDirectories: worktree !== undefined ? undefined : options.additionalDirectories,
|
|
3005
|
+
authStorage,
|
|
3006
|
+
modelRegistry,
|
|
3007
|
+
getApiKey: options.getApiKey,
|
|
3008
|
+
settings: subagentSettings,
|
|
3009
|
+
model,
|
|
3010
|
+
modelPattern: model || modelOverride === undefined ? undefined : modelPatterns,
|
|
3011
|
+
modelPatternAuthFallback:
|
|
3012
|
+
model || modelOverride === undefined ? undefined : options.parentActiveModelPattern,
|
|
3013
|
+
modelPatternFallbackRole:
|
|
3014
|
+
model || modelOverride === undefined ? undefined : `${SUBAGENT_RETRY_FALLBACK_ROLE_PREFIX}${id}`,
|
|
3015
|
+
modelPatternDefaultFallbackChain:
|
|
3016
|
+
model || modelOverride === undefined ? undefined : inheritedRetryFallbackChain,
|
|
3017
|
+
thinkingLevel: effectiveThinkingLevel,
|
|
3018
|
+
thinkingLevelCeiling: spawnEffortCeiling,
|
|
3019
|
+
toolNames,
|
|
3020
|
+
outputSchema,
|
|
3021
|
+
outputSchemaMode: options.outputSchemaMode,
|
|
3022
|
+
restrictToolNames: options.restrictToolNames,
|
|
3023
|
+
requireYieldTool: true,
|
|
3024
|
+
contextFiles: options.contextFiles,
|
|
3025
|
+
skills: options.skills,
|
|
3026
|
+
promptTemplates: options.promptTemplates,
|
|
3027
|
+
workspaceTree: options.workspaceTree,
|
|
3028
|
+
rules: options.rules,
|
|
3029
|
+
preloadedExtensionPaths: restrictToolNames ? [] : options.preloadedExtensionPaths,
|
|
3030
|
+
preloadedCustomToolPaths: restrictToolNames ? [] : options.preloadedCustomToolPaths,
|
|
3031
|
+
systemPrompt: defaultPrompt => {
|
|
3032
|
+
const subagentPrompt = prompt.render(subagentSystemPromptTemplate, {
|
|
3033
|
+
agent: agent.systemPrompt,
|
|
3034
|
+
context: options.context?.trim() ?? "",
|
|
3035
|
+
planReference: options.planReference?.content ?? "",
|
|
3036
|
+
planReferencePath: options.planReference?.path ?? "",
|
|
3037
|
+
worktree: worktree ?? "",
|
|
3038
|
+
outputSchema: normalizedOutputSchema,
|
|
3039
|
+
outputSchemaOverridesAgent: options.outputSchemaOverridesAgent === true,
|
|
3040
|
+
ircPeers: ircEnabled ? renderIrcPeerRoster(id) : "",
|
|
3041
|
+
ircSelfId: ircEnabled ? id : "",
|
|
3042
|
+
});
|
|
3043
|
+
return defaultPrompt.length === 0
|
|
3044
|
+
? [subagentPrompt]
|
|
3045
|
+
: [...defaultPrompt.slice(0, -1), subagentPrompt, defaultPrompt[defaultPrompt.length - 1]];
|
|
3046
|
+
},
|
|
3047
|
+
sessionManager: sessionManagerForRun,
|
|
3048
|
+
hasUI: false,
|
|
3049
|
+
prewalk,
|
|
3050
|
+
spawns: spawnsEnv,
|
|
3051
|
+
taskDepth: childDepth,
|
|
3052
|
+
parentHindsightSessionState: options.parentHindsightSessionState,
|
|
3053
|
+
parentMnemopiSessionState: options.parentMnemopiSessionState,
|
|
3054
|
+
parentTaskPrefix: id,
|
|
3055
|
+
parentAgentId: options.parentAgentId,
|
|
3056
|
+
agentId: id,
|
|
3057
|
+
agentDisplayName: agent.name,
|
|
3058
|
+
expectedAgentRef,
|
|
3059
|
+
enableLsp: lspEnabled,
|
|
3060
|
+
enableIrc: options.enableIrc,
|
|
3061
|
+
skipPythonPreflight,
|
|
3062
|
+
enableMCP,
|
|
3063
|
+
mcpManager,
|
|
3064
|
+
customTools: mcpProxyTools.length > 0 ? mcpProxyTools : undefined,
|
|
3065
|
+
localProtocolOptions: options.localProtocolOptions,
|
|
3066
|
+
telemetry: subagentTelemetry,
|
|
3067
|
+
parentEvalSessionId: options.parentEvalSessionId,
|
|
3068
|
+
onFirstChatDispatch: () => {
|
|
3069
|
+
firstChatDispatchAt ??= performance.now();
|
|
3070
|
+
},
|
|
3071
|
+
});
|
|
3072
|
+
|
|
3073
|
+
const sessionManager = await awaitAbortable(sessionManagerPromise);
|
|
3074
|
+
if (options.parentArtifactManager) {
|
|
3075
|
+
sessionManager.adoptArtifactManager(options.parentArtifactManager);
|
|
3076
|
+
}
|
|
3077
|
+
sessionOpenedAt = performance.now();
|
|
3078
|
+
|
|
3079
|
+
const sessionPromise = createAgentSession(buildSubagentSessionOptions(sessionManager, null));
|
|
3080
|
+
let session: AgentSession;
|
|
3081
|
+
try {
|
|
3082
|
+
({ session } = await awaitAbortable(sessionPromise));
|
|
3083
|
+
} catch (err) {
|
|
3084
|
+
// Abort raced session startup. The session may still resolve later
|
|
3085
|
+
// holding live LSP/MCP child processes — dispose it when it does so
|
|
3086
|
+
// a cancelled subagent cannot leak them.
|
|
3087
|
+
void sessionPromise.then(created => created.session.dispose()).catch(() => {});
|
|
3088
|
+
throw err;
|
|
3089
|
+
}
|
|
3090
|
+
sessionCreatedAt = performance.now();
|
|
3091
|
+
|
|
3092
|
+
monitor.setActiveSession(session);
|
|
3093
|
+
installRegistryStatusSync(session);
|
|
3094
|
+
if (sessionFile !== null && worktree === undefined) {
|
|
3095
|
+
// Lifecycle reviver: park closed the JSONL writer, so reopening takes
|
|
3096
|
+
// the single-writer lock cleanly and restores the full message history
|
|
3097
|
+
// (createAgentSession → agent.replaceMessages). Isolated runs are not
|
|
3098
|
+
// resumable (worktree is merged + cleaned) and never get a reviver.
|
|
3099
|
+
reviveSession = async expectedAgentRef => {
|
|
3100
|
+
const reopened = await SessionManager.open(sessionFile, undefined, undefined, {
|
|
3101
|
+
suppressBreadcrumb: true,
|
|
3102
|
+
});
|
|
3103
|
+
if (options.parentArtifactManager) {
|
|
3104
|
+
reopened.adoptArtifactManager(options.parentArtifactManager);
|
|
3105
|
+
}
|
|
3106
|
+
const { session: revived } = await createAgentSession(
|
|
3107
|
+
buildSubagentSessionOptions(reopened, expectedAgentRef),
|
|
3108
|
+
);
|
|
3109
|
+
installRegistryStatusSync(revived);
|
|
3110
|
+
installIrcWakeTurnMonitor(revived);
|
|
3111
|
+
return revived;
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
// Emit lifecycle start event
|
|
3116
|
+
if (options.eventBus) {
|
|
3117
|
+
options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
|
|
3118
|
+
id,
|
|
3119
|
+
agent: agent.name,
|
|
3120
|
+
parentToolCallId: options.parentToolCallId,
|
|
3121
|
+
detached: options.detached,
|
|
3122
|
+
agentSource: agent.source,
|
|
3123
|
+
description: options.description,
|
|
3124
|
+
status: "started",
|
|
3125
|
+
sessionFile: subtaskSessionFile,
|
|
3126
|
+
index,
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
// Todos are parent-owned bookkeeping and stripped from subagents —
|
|
3131
|
+
// except under prewalk, whose plan nudge + todo gate require the
|
|
3132
|
+
// subagent to commit its own todo list before the hand-off.
|
|
3133
|
+
const isParentOwnedTool = (name: string): boolean => !prewalk && name === "todo";
|
|
3134
|
+
const subagentToolNames = session.getEnabledToolNames();
|
|
3135
|
+
const filteredSubagentTools = subagentToolNames.filter(name => !isParentOwnedTool(name));
|
|
3136
|
+
if (filteredSubagentTools.length !== subagentToolNames.length) {
|
|
3137
|
+
await awaitAbortable(session.setActiveToolsByName(filteredSubagentTools));
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
session.sessionManager.appendSessionInit({
|
|
3141
|
+
systemPrompt: session.agent.state.systemPrompt.join("\n\n"),
|
|
3142
|
+
task,
|
|
3143
|
+
tools: session.getActiveToolNames(),
|
|
3144
|
+
agent: agent.name,
|
|
3145
|
+
modelRole: modelRole ?? resolveExplicitModelRole(modelOverride ?? agent.model, subagentSettings),
|
|
3146
|
+
resolvedModel: progress.resolvedModel,
|
|
3147
|
+
readOnly: isReadOnlyAgent(agent),
|
|
3148
|
+
spawns: spawnsEnv,
|
|
3149
|
+
readSummarize: agent.readSummarize,
|
|
3150
|
+
outputSchema,
|
|
3151
|
+
outputSchemaMode: options.outputSchemaMode,
|
|
3152
|
+
restrictToolNames: restrictToolNames || undefined,
|
|
3153
|
+
});
|
|
3154
|
+
|
|
3155
|
+
abortSignal.addEventListener(
|
|
3156
|
+
"abort",
|
|
3157
|
+
() => {
|
|
3158
|
+
void monitor.abortActiveSession();
|
|
3159
|
+
},
|
|
3160
|
+
{ once: true, signal: sessionAbortController.signal },
|
|
3161
|
+
);
|
|
3162
|
+
// Defensive: if the wall-clock timer (or external signal) fired during
|
|
3163
|
+
// the awaited setup above, the listener registration races the dispatch
|
|
3164
|
+
// and may not observe the already-fired abort event. Mirror it manually.
|
|
3165
|
+
if (abortSignal.aborted) {
|
|
3166
|
+
void monitor.abortActiveSession();
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
const pendingExtensionMessages: Array<Promise<unknown>> = [];
|
|
3170
|
+
const extensionRunner = session.extensionRunner;
|
|
3171
|
+
if (extensionRunner) {
|
|
3172
|
+
extensionRunner.initialize(
|
|
3173
|
+
{
|
|
3174
|
+
sendMessage: (message, options) => {
|
|
3175
|
+
const sendPromise = session.sendCustomMessage(message, options).catch(e => {
|
|
3176
|
+
logger.error("Extension sendMessage failed", {
|
|
3177
|
+
error: e instanceof Error ? e.message : String(e),
|
|
3178
|
+
});
|
|
3179
|
+
});
|
|
3180
|
+
pendingExtensionMessages.push(sendPromise);
|
|
3181
|
+
},
|
|
3182
|
+
sendUserMessage: (content, options) => {
|
|
3183
|
+
const sendPromise = session.sendUserMessage(content, options).catch(e => {
|
|
3184
|
+
logger.error("Extension sendUserMessage failed", {
|
|
3185
|
+
error: e instanceof Error ? e.message : String(e),
|
|
3186
|
+
});
|
|
3187
|
+
});
|
|
3188
|
+
pendingExtensionMessages.push(sendPromise);
|
|
3189
|
+
},
|
|
3190
|
+
appendEntry: (customType, data) => {
|
|
3191
|
+
session.sessionManager.appendCustomEntry(customType, data);
|
|
3192
|
+
},
|
|
3193
|
+
setLabel: (targetId, label) => {
|
|
3194
|
+
session.sessionManager.appendLabelChange(targetId, label);
|
|
3195
|
+
},
|
|
3196
|
+
getActiveTools: () => session.getEnabledToolNames(),
|
|
3197
|
+
getAllTools: () => session.getAllToolInfos(),
|
|
3198
|
+
setActiveTools: (toolNames: string[]) =>
|
|
3199
|
+
session.setActiveToolsByName(toolNames.filter(name => !isParentOwnedTool(name))),
|
|
3200
|
+
getCommands: () => getSessionSlashCommands(session),
|
|
3201
|
+
setModel: model => runExtensionSetModel(session, model),
|
|
3202
|
+
getThinkingLevel: () => session.thinkingLevel,
|
|
3203
|
+
setThinkingLevel: level => session.setThinkingLevel(level),
|
|
3204
|
+
getServiceTiers: () => session.serviceTierByFamily,
|
|
3205
|
+
setServiceTier: (family, tier) => session.setServiceTierFamily(family, tier),
|
|
3206
|
+
getSessionName: () => session.sessionManager.getSessionName(),
|
|
3207
|
+
setSessionName: async name => {
|
|
3208
|
+
await session.sessionManager.setSessionName(name, "user");
|
|
3209
|
+
},
|
|
3210
|
+
},
|
|
3211
|
+
{
|
|
3212
|
+
getModel: () => session.model,
|
|
3213
|
+
isIdle: () => !session.isStreaming,
|
|
3214
|
+
abort: () => session.abort({ reason: USER_INTERRUPT_LABEL }),
|
|
3215
|
+
hasPendingMessages: () => session.queuedMessageCount > 0,
|
|
3216
|
+
shutdown: () => {},
|
|
3217
|
+
getContextUsage: () => session.getContextUsage(),
|
|
3218
|
+
getSystemPrompt: () => session.systemPrompt,
|
|
3219
|
+
compact: instructionsOrOptions => runExtensionCompact(session, instructionsOrOptions),
|
|
3220
|
+
},
|
|
3221
|
+
);
|
|
3222
|
+
extensionRunner.onError(err => {
|
|
3223
|
+
logger.error("Extension error", { path: err.extensionPath, error: err.error });
|
|
3224
|
+
});
|
|
3225
|
+
await awaitAbortable(extensionRunner.emit({ type: "session_start" }));
|
|
3226
|
+
while (pendingExtensionMessages.length > 0) {
|
|
3227
|
+
await awaitAbortable(Promise.all(pendingExtensionMessages.splice(0)));
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
unsubscribe = monitor.attach(session);
|
|
3232
|
+
|
|
3233
|
+
checkAbort();
|
|
3234
|
+
// Autoload skills via sendCustomMessage (same mechanic as /skill:<name>)
|
|
3235
|
+
if (options.autoloadSkills?.length) {
|
|
3236
|
+
for (const skill of options.autoloadSkills) {
|
|
3237
|
+
const { message } = await buildSkillPromptMessage(skill, "", "autoload");
|
|
3238
|
+
await session.sendCustomMessage(
|
|
3239
|
+
{
|
|
3240
|
+
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
3241
|
+
content: message,
|
|
3242
|
+
display: false,
|
|
3243
|
+
details: { name: skill.name, path: skill.filePath },
|
|
3244
|
+
},
|
|
3245
|
+
{ triggerTurn: false },
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
readyAt = performance.now();
|
|
3251
|
+
const outcome = await driveSessionToYield(session, monitor, task);
|
|
3252
|
+
exitCode = outcome.exitCode;
|
|
3253
|
+
error = outcome.error;
|
|
3254
|
+
aborted = outcome.aborted;
|
|
3255
|
+
abortReasonText = outcome.abortReasonText;
|
|
3256
|
+
} catch (err) {
|
|
3257
|
+
exitCode = 1;
|
|
3258
|
+
if (!abortSignal.aborted) {
|
|
3259
|
+
error = err instanceof Error ? err.stack || err.message : String(err);
|
|
3260
|
+
}
|
|
3261
|
+
} finally {
|
|
3262
|
+
const cleanupDeadlineAt = Date.now() + TASK_ABORT_CLEANUP_GRACE_MS;
|
|
3263
|
+
const cleanupChangeStatus =
|
|
3264
|
+
worktree === undefined
|
|
3265
|
+
? "This task was not isolated, so its changes may remain in the working directory."
|
|
3266
|
+
: "No isolated changes were applied.";
|
|
3267
|
+
const lateCleanups: Promise<void>[] = [];
|
|
3268
|
+
let deferredSessionShutdown: Promise<void> | undefined;
|
|
3269
|
+
const deferCleanup = (completion: Promise<void>): void => {
|
|
3270
|
+
lateCleanups.push(completion);
|
|
3271
|
+
exitCode = 1;
|
|
3272
|
+
aborted = true;
|
|
3273
|
+
abortReasonText = `cleanup exceeded ${TASK_ABORT_CLEANUP_GRACE_MS} ms`;
|
|
3274
|
+
error ??= `Task aborted. Cleanup did not finish within ${TASK_ABORT_CLEANUP_GRACE_MS} ms. ${cleanupChangeStatus}`;
|
|
3275
|
+
};
|
|
3276
|
+
if (abortSignal.aborted) {
|
|
3277
|
+
aborted = monitor.isAbortedRun();
|
|
3278
|
+
if (aborted) {
|
|
3279
|
+
abortReasonText ??= monitor.resolveAbortReasonText();
|
|
3280
|
+
}
|
|
3281
|
+
if (exitCode === 0) exitCode = 1;
|
|
3282
|
+
}
|
|
3283
|
+
sessionAbortController.abort();
|
|
3284
|
+
const activeSessionAbort = monitor.waitForActiveSessionAbort();
|
|
3285
|
+
try {
|
|
3286
|
+
await untilAborted(
|
|
3287
|
+
AbortSignal.timeout(Math.max(0, cleanupDeadlineAt - Date.now())),
|
|
3288
|
+
() => activeSessionAbort,
|
|
3289
|
+
);
|
|
3290
|
+
} catch (cleanupError) {
|
|
3291
|
+
if (Date.now() >= cleanupDeadlineAt) {
|
|
3292
|
+
deferCleanup(activeSessionAbort);
|
|
3293
|
+
} else {
|
|
3294
|
+
logger.warn("Subagent abort cleanup failed", {
|
|
3295
|
+
id,
|
|
3296
|
+
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
if (unsubscribe) {
|
|
3301
|
+
try {
|
|
3302
|
+
unsubscribe();
|
|
3303
|
+
} catch {
|
|
3304
|
+
// Ignore unsubscribe errors
|
|
3305
|
+
}
|
|
3306
|
+
unsubscribe = null;
|
|
3307
|
+
}
|
|
3308
|
+
const jobManager = AsyncJobManager.instance();
|
|
3309
|
+
if (jobManager) {
|
|
3310
|
+
const reap = await jobManager.cancelAndReapOwnerJobs(id, cleanupDeadlineAt);
|
|
3311
|
+
if (!reap.settled) {
|
|
3312
|
+
deferCleanup(reap.completion);
|
|
3313
|
+
logger.warn("Subagent async job cleanup exceeded its deadline", {
|
|
3314
|
+
id,
|
|
3315
|
+
pendingJobIds: reap.pendingJobIds,
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
const session = monitor.takeActiveSession();
|
|
3320
|
+
if (session) {
|
|
3321
|
+
monitor.captureSalvage(session);
|
|
3322
|
+
if (options.keepAlive !== false && worktree === undefined) {
|
|
3323
|
+
installIrcWakeTurnMonitor(session);
|
|
3324
|
+
}
|
|
3325
|
+
await finalizeSubagentLifecycle({
|
|
3326
|
+
id,
|
|
3327
|
+
session,
|
|
3328
|
+
aborted,
|
|
3329
|
+
abortKind: monitor.abortKind(),
|
|
3330
|
+
keepAlive: options.keepAlive !== false,
|
|
3331
|
+
isolated: worktree !== undefined,
|
|
3332
|
+
agentIdleTtlMs,
|
|
3333
|
+
reviveSession,
|
|
3334
|
+
cleanupDeadlineAt,
|
|
3335
|
+
onCleanupDeferred: completion => {
|
|
3336
|
+
deferredSessionShutdown = completion;
|
|
3337
|
+
deferCleanup(completion);
|
|
3338
|
+
},
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
if (jobManager) {
|
|
3342
|
+
if (deferredSessionShutdown) {
|
|
3343
|
+
const finalReap = Promise.allSettled([deferredSessionShutdown]).then(async () => {
|
|
3344
|
+
const reap = await jobManager.cancelAndReapOwnerJobs(id, Date.now());
|
|
3345
|
+
await reap.completion;
|
|
3346
|
+
});
|
|
3347
|
+
lateCleanups.push(finalReap);
|
|
3348
|
+
} else {
|
|
3349
|
+
const reap = await jobManager.cancelAndReapOwnerJobs(id, cleanupDeadlineAt);
|
|
3350
|
+
if (!reap.settled) {
|
|
3351
|
+
deferCleanup(reap.completion);
|
|
3352
|
+
logger.warn("Subagent async job cleanup exceeded its deadline after session shutdown", {
|
|
3353
|
+
id,
|
|
3354
|
+
pendingJobIds: reap.pendingJobIds,
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
if (lateCleanups.length > 0) {
|
|
3360
|
+
const completion = Promise.allSettled(lateCleanups).then(() => {});
|
|
3361
|
+
trackLateCleanup(completion, { id, resource: "subagent" });
|
|
3362
|
+
options.onCleanupDeferred?.(completion);
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
// Launch-latency breakdown (subagent invocation → first chat dispatch).
|
|
3367
|
+
// Phase deltas are performance.now() spans; the task-tool concurrency
|
|
3368
|
+
// brackets use the Date.now epochs captured by the spawn site
|
|
3369
|
+
// (invokedAt before acquire, acquiredAt after) so queue wait and
|
|
3370
|
+
// pre-run setup are reported apart.
|
|
3371
|
+
const span = (from: number | undefined, to: number | undefined): number | undefined =>
|
|
3372
|
+
from !== undefined && to !== undefined ? Math.round(to - from) : undefined;
|
|
3373
|
+
const queueMs =
|
|
3374
|
+
options.invokedAt !== undefined && options.acquiredAt !== undefined
|
|
3375
|
+
? Math.round(options.acquiredAt - options.invokedAt)
|
|
3376
|
+
: undefined;
|
|
3377
|
+
const preRunMs = options.acquiredAt !== undefined ? Math.round(startTime - options.acquiredAt) : undefined;
|
|
3378
|
+
const setupToFirstChatMs = span(perfStart, firstChatDispatchAt);
|
|
3379
|
+
const invokeToFirstChatMs =
|
|
3380
|
+
options.invokedAt !== undefined && setupToFirstChatMs !== undefined
|
|
3381
|
+
? Math.round(startTime - options.invokedAt) + setupToFirstChatMs
|
|
3382
|
+
: undefined;
|
|
3383
|
+
logger.debug("subagent launch timing", {
|
|
3384
|
+
id,
|
|
3385
|
+
agent: agent.name,
|
|
3386
|
+
queueMs,
|
|
3387
|
+
preRunMs,
|
|
3388
|
+
resolveMs: span(perfStart, resolvedAt),
|
|
3389
|
+
sessionOpenMs: span(resolvedAt, sessionOpenedAt),
|
|
3390
|
+
createSessionMs: span(sessionOpenedAt, sessionCreatedAt),
|
|
3391
|
+
readyMs: span(sessionCreatedAt, readyAt),
|
|
3392
|
+
promptToFirstChatMs: span(readyAt, firstChatDispatchAt),
|
|
3393
|
+
setupToFirstChatMs,
|
|
3394
|
+
invokeToFirstChatMs,
|
|
3395
|
+
});
|
|
3396
|
+
return {
|
|
3397
|
+
exitCode,
|
|
3398
|
+
error,
|
|
3399
|
+
aborted,
|
|
3400
|
+
abortReason: aborted ? abortReasonText : undefined,
|
|
3401
|
+
durationMs: Date.now() - startTime,
|
|
3402
|
+
};
|
|
3403
|
+
};
|
|
3404
|
+
|
|
3405
|
+
const done = await runSubagent();
|
|
3406
|
+
monitor.finish();
|
|
3407
|
+
|
|
3408
|
+
const result = await finalizeRunResult({
|
|
3409
|
+
monitor,
|
|
3410
|
+
done,
|
|
3411
|
+
index,
|
|
3412
|
+
id,
|
|
3413
|
+
agent,
|
|
3414
|
+
task,
|
|
3415
|
+
assignment,
|
|
3416
|
+
modelOverride,
|
|
3417
|
+
modelRole,
|
|
3418
|
+
outputSchema,
|
|
3419
|
+
outputSchemaMode: options.outputSchemaMode,
|
|
3420
|
+
outputSchemaSource: options.outputSchemaSource,
|
|
3421
|
+
signal,
|
|
3422
|
+
artifactsDir: options.artifactsDir,
|
|
3423
|
+
eventBus: options.eventBus,
|
|
3424
|
+
parentToolCallId: options.parentToolCallId,
|
|
3425
|
+
detached: options.detached,
|
|
3426
|
+
sessionFile: subtaskSessionFile,
|
|
3427
|
+
startTime,
|
|
3428
|
+
});
|
|
3429
|
+
AgentRegistry.global().setHistory(id, { outputPath: result.outputPath });
|
|
3430
|
+
return result;
|
|
3431
|
+
}
|