@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
package/src/sdk.ts
ADDED
|
@@ -0,0 +1,3820 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Agent,
|
|
3
|
+
type AgentEvent,
|
|
4
|
+
type AgentMessage,
|
|
5
|
+
type AgentOptions,
|
|
6
|
+
type AgentTelemetryConfig,
|
|
7
|
+
type AgentTool,
|
|
8
|
+
AppendOnlyContextManager,
|
|
9
|
+
filterProviderReplayMessages,
|
|
10
|
+
type ThinkingLevel,
|
|
11
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
12
|
+
import type {
|
|
13
|
+
Context,
|
|
14
|
+
CredentialDisabledEvent,
|
|
15
|
+
Effort,
|
|
16
|
+
Message,
|
|
17
|
+
Model,
|
|
18
|
+
ModelUsageHealth,
|
|
19
|
+
ProviderSessionState,
|
|
20
|
+
ServiceTier,
|
|
21
|
+
SimpleStreamOptions,
|
|
22
|
+
} from "@oh-my-pi/pi-ai";
|
|
23
|
+
import { resolveApiKeyOnce } from "@oh-my-pi/pi-ai/auth-retry";
|
|
24
|
+
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
25
|
+
import {
|
|
26
|
+
getOpenAICodexTransportDetails,
|
|
27
|
+
prewarmOpenAICodexResponses,
|
|
28
|
+
} from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
29
|
+
import { FALLBACK_DIALECT, preferredDialect } from "@oh-my-pi/pi-catalog/identity";
|
|
30
|
+
import type { Component } from "@oh-my-pi/pi-tui";
|
|
31
|
+
import { $env, $flag, getAgentDir, getProjectDir, logger, postmortem, prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
32
|
+
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
33
|
+
import {
|
|
34
|
+
discoverAdvisorConfigs,
|
|
35
|
+
discoverWatchdogFiles,
|
|
36
|
+
formatActiveRepoWatchdogPrompt,
|
|
37
|
+
formatAdvisorContextPrompt,
|
|
38
|
+
loadAdvisorTranscriptCosts,
|
|
39
|
+
} from "./advisor";
|
|
40
|
+
import { AsyncJobManager } from "./async";
|
|
41
|
+
import { AutoLearnController, buildAutoLearnInstructions } from "./autolearn/controller";
|
|
42
|
+
import { createAutoresearchExtension } from "./autoresearch";
|
|
43
|
+
import { loadCapability } from "./capability";
|
|
44
|
+
import { type Rule, ruleCapability, setActiveRules } from "./capability/rule";
|
|
45
|
+
import { bucketRules } from "./capability/rule-buckets";
|
|
46
|
+
import { shouldEnableAppendOnlyContext } from "./config/append-only-context-mode";
|
|
47
|
+
import { shouldInlineToolDescriptors } from "./config/inline-tool-descriptors-mode";
|
|
48
|
+
import { isAuthenticated, kNoAuth, ModelRegistry } from "./config/model-registry";
|
|
49
|
+
import {
|
|
50
|
+
formatModelSelectorValue,
|
|
51
|
+
formatModelString,
|
|
52
|
+
formatModelStringWithRouting,
|
|
53
|
+
getModelMatchPreferences,
|
|
54
|
+
parseModelPattern,
|
|
55
|
+
parseModelString,
|
|
56
|
+
pickDefaultAvailableModel,
|
|
57
|
+
resolveAllowedModels,
|
|
58
|
+
resolveCliModel,
|
|
59
|
+
resolveConfiguredModelPatterns,
|
|
60
|
+
resolveModelRoleValue,
|
|
61
|
+
} from "./config/model-resolver";
|
|
62
|
+
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
|
|
63
|
+
import { applyProviderGlobalsFromSettings } from "./config/provider-globals";
|
|
64
|
+
import { buildServiceTierByFamily } from "./config/service-tier";
|
|
65
|
+
import { Settings, type SkillsSettings } from "./config/settings";
|
|
66
|
+
import { CursorExecHandlers, type CursorMcpResourceAdapter } from "./cursor";
|
|
67
|
+
import { createBridgeEditTool, createBridgeGrepFactory } from "./cursor-bridge-tools";
|
|
68
|
+
import "./discovery";
|
|
69
|
+
import { initializeWithSettings } from "./discovery";
|
|
70
|
+
import { withOmpExtensionRootScope } from "./discovery/omp-extension-roots";
|
|
71
|
+
import { disposeAllJuliaKernelSessions, disposeJuliaKernelSessionsByOwner } from "./eval/jl/executor";
|
|
72
|
+
import { disposeVmContextsByOwner } from "./eval/js/context-manager";
|
|
73
|
+
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./eval/py/executor";
|
|
74
|
+
import { disposeAllRubyKernelSessions, disposeRubyKernelSessionsByOwner } from "./eval/rb/executor";
|
|
75
|
+
import { defaultEvalSessionId } from "./eval/session-id";
|
|
76
|
+
import {
|
|
77
|
+
type CustomCommandsLoadResult,
|
|
78
|
+
type LoadedCustomCommand,
|
|
79
|
+
loadCustomCommands as loadCustomCommandsInternal,
|
|
80
|
+
} from "./extensibility/custom-commands";
|
|
81
|
+
import { discoverCustomToolPaths, loadCustomTools, type ToolPathWithSource } from "./extensibility/custom-tools";
|
|
82
|
+
import type { CustomTool, CustomToolContext, CustomToolSessionEvent } from "./extensibility/custom-tools/types";
|
|
83
|
+
import {
|
|
84
|
+
discoverAndLoadExtensions,
|
|
85
|
+
discoverExtensionPaths,
|
|
86
|
+
type ExtensionContext,
|
|
87
|
+
type ExtensionFactory,
|
|
88
|
+
ExtensionRunner,
|
|
89
|
+
ExtensionToolWrapper,
|
|
90
|
+
type ExtensionUIContext,
|
|
91
|
+
type LoadExtensionsResult,
|
|
92
|
+
loadExtensionFromFactory,
|
|
93
|
+
loadExtensions,
|
|
94
|
+
type ToolDefinition,
|
|
95
|
+
wrapRegisteredTools,
|
|
96
|
+
} from "./extensibility/extensions";
|
|
97
|
+
import {
|
|
98
|
+
loadSkills as loadSkillsInternal,
|
|
99
|
+
type Skill,
|
|
100
|
+
type SkillWarning,
|
|
101
|
+
setActiveSkills,
|
|
102
|
+
} from "./extensibility/skills";
|
|
103
|
+
import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./extensibility/slash-commands";
|
|
104
|
+
import type { HindsightSessionState } from "./hindsight/state";
|
|
105
|
+
import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls";
|
|
106
|
+
import { setSharedLspEnabled } from "./lsp/client";
|
|
107
|
+
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
|
|
108
|
+
import {
|
|
109
|
+
deduplicateMCPToolsByName,
|
|
110
|
+
discoverAndLoadMCPTools,
|
|
111
|
+
type MCPLoadResult,
|
|
112
|
+
MCPManager,
|
|
113
|
+
MCPToolCache,
|
|
114
|
+
type MCPToolsLoadResult,
|
|
115
|
+
parseMCPToolName,
|
|
116
|
+
} from "./mcp";
|
|
117
|
+
import { MCP_CONNECTION_STATUS_EVENT_CHANNEL, type McpConnectionStatusEvent } from "./mcp/startup-events";
|
|
118
|
+
import { createSessionMemoryRuntimeContext, resolveMemoryBackend } from "./memory-backend";
|
|
119
|
+
import { MEMORY_BACKEND_TOOL_NAMES } from "./memory-backend/tool-names";
|
|
120
|
+
import type { MnemopiSessionState } from "./mnemopi/state";
|
|
121
|
+
import mcpXdevGuidanceTemplate from "./prompts/system/mcp-xdev-guidance.md" with { type: "text" };
|
|
122
|
+
import lateDiagnosticTemplate from "./prompts/tools/lsp-late-diagnostic.md" with { type: "text" };
|
|
123
|
+
import { AgentLifecycleManager } from "./registry/agent-lifecycle";
|
|
124
|
+
import { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
|
|
125
|
+
import {
|
|
126
|
+
buildSecretObfuscator,
|
|
127
|
+
deobfuscateSessionContext,
|
|
128
|
+
deobfuscateToolArguments,
|
|
129
|
+
obfuscateMessages,
|
|
130
|
+
obfuscateProviderContext,
|
|
131
|
+
type SecretObfuscator,
|
|
132
|
+
} from "./secrets";
|
|
133
|
+
import { AgentSession, type InitialRetryFallbackState, type PlanYolo, type Prewalk } from "./session/agent-session";
|
|
134
|
+
import { discoverAuthStorage as discoverAuthStorageFromConfig } from "./session/auth-broker-config";
|
|
135
|
+
import type { AuthStorage } from "./session/auth-storage";
|
|
136
|
+
import { createInterruptedTurnAbortMessage } from "./session/exit-diagnostics";
|
|
137
|
+
import {
|
|
138
|
+
type CustomMessage,
|
|
139
|
+
convertToLlm,
|
|
140
|
+
LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
|
|
141
|
+
replaceLlmImagesWithText,
|
|
142
|
+
USER_INTERRUPT_LABEL,
|
|
143
|
+
wrapSteeringForModel,
|
|
144
|
+
} from "./session/messages";
|
|
145
|
+
import { clampProviderContextImages } from "./session/provider-image-budget";
|
|
146
|
+
import {
|
|
147
|
+
expandDefaultRetryFallbackChains,
|
|
148
|
+
findRetryFallbackCandidates,
|
|
149
|
+
type RetryFallbackResolutionContext,
|
|
150
|
+
resolveRetryFallbackChainKey,
|
|
151
|
+
} from "./session/retry-fallback-chains";
|
|
152
|
+
import { getRestorableSessionModels } from "./session/session-context";
|
|
153
|
+
import { SessionManager } from "./session/session-manager";
|
|
154
|
+
import { collectMountedMCPToolRoutes, projectMountedMCPXdevGuidance } from "./session/session-tools";
|
|
155
|
+
import { createSettingsAwareStreamFn } from "./session/settings-stream-fn";
|
|
156
|
+
import { SnapcompactInlineTransformer } from "./session/snapcompact-inline";
|
|
157
|
+
import { createSnapcompactSavingsRecorder } from "./session/snapcompact-savings-journal";
|
|
158
|
+
import { closeAllConnections } from "./ssh/connection-manager";
|
|
159
|
+
import { unmountAll } from "./ssh/sshfs-mount";
|
|
160
|
+
import {
|
|
161
|
+
type BuildSystemPromptResult,
|
|
162
|
+
buildSystemPrompt as buildSystemPromptInternal,
|
|
163
|
+
loadProjectContextFiles as loadContextFilesInternal,
|
|
164
|
+
projectSystemPromptToolMetadata,
|
|
165
|
+
} from "./system-prompt";
|
|
166
|
+
import { AgentOutputManager } from "./task/output-manager";
|
|
167
|
+
import { wrapStreamFnWithProviderConcurrency } from "./task/provider-concurrency";
|
|
168
|
+
import { isScoutSpawnable } from "./task/spawn-policy";
|
|
169
|
+
import type { StructuredSubagentSchemaMode } from "./task/types";
|
|
170
|
+
import {
|
|
171
|
+
AUTO_THINKING,
|
|
172
|
+
type ConfiguredThinkingLevel,
|
|
173
|
+
concreteThinkingLevel,
|
|
174
|
+
parseConfiguredThinkingLevel,
|
|
175
|
+
parseThinkingLevel,
|
|
176
|
+
resolveProvisionalAutoLevel,
|
|
177
|
+
resolveThinkingLevelForModel,
|
|
178
|
+
shouldDisableReasoning,
|
|
179
|
+
toReasoningEffort,
|
|
180
|
+
} from "./thinking";
|
|
181
|
+
import {
|
|
182
|
+
BashTool,
|
|
183
|
+
BUILTIN_TOOLS,
|
|
184
|
+
createTools,
|
|
185
|
+
createVibeTools,
|
|
186
|
+
type DeferredDiagnosticsEntry,
|
|
187
|
+
defaultLoadModeForToolName,
|
|
188
|
+
discoverStartupLspServers,
|
|
189
|
+
EditTool,
|
|
190
|
+
EvalTool,
|
|
191
|
+
GlobTool,
|
|
192
|
+
GrepTool,
|
|
193
|
+
getSearchTools,
|
|
194
|
+
HIDDEN_TOOLS,
|
|
195
|
+
isMountableUnderXdev,
|
|
196
|
+
type LspStartupServerInfo,
|
|
197
|
+
listXdevTools,
|
|
198
|
+
ReadTool,
|
|
199
|
+
releaseComputerSessionsForOwner,
|
|
200
|
+
resolveMountedXdevExecutable,
|
|
201
|
+
type Tool,
|
|
202
|
+
type ToolSession,
|
|
203
|
+
WebSearchTool,
|
|
204
|
+
WriteTool,
|
|
205
|
+
warmupLspServers,
|
|
206
|
+
xdevDocsAll,
|
|
207
|
+
xdevEntries,
|
|
208
|
+
} from "./tools";
|
|
209
|
+
import { isMCPToolName, normalizeToolNames } from "./tools/builtin-names";
|
|
210
|
+
import { ToolContextStore } from "./tools/context";
|
|
211
|
+
import { isIrcEnabled } from "./tools/hub";
|
|
212
|
+
import { getImageGenTools } from "./tools/image-gen";
|
|
213
|
+
import { wrapToolWithMetaNotice } from "./tools/output-meta";
|
|
214
|
+
import { isAutoQaEnabled } from "./tools/report-tool-issue";
|
|
215
|
+
import { queueResolveHandler } from "./tools/resolve";
|
|
216
|
+
import { USER_TODO_EDIT_CUSTOM_TYPE } from "./tools/todo";
|
|
217
|
+
import { ttsTool } from "./tools/tts";
|
|
218
|
+
import { resolveActiveRepoContext } from "./utils/active-repo-context";
|
|
219
|
+
import { EventBus } from "./utils/event-bus";
|
|
220
|
+
import { buildNamedToolChoice } from "./utils/tool-choice";
|
|
221
|
+
import { VibeSessionRegistry } from "./vibe/runtime";
|
|
222
|
+
import { buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
|
|
223
|
+
|
|
224
|
+
type McpNotificationEntry = {
|
|
225
|
+
serverName: string;
|
|
226
|
+
uri: string;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
type LateDiagnosticsDetails = {
|
|
230
|
+
files: Array<{ path: string; summary: string; errored: boolean; messages: string[] }>;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
function buildLateDiagnosticsBatchMessage(
|
|
234
|
+
entries: DeferredDiagnosticsEntry[],
|
|
235
|
+
): CustomMessage<LateDiagnosticsDetails> | null {
|
|
236
|
+
if (entries.length === 0) return null;
|
|
237
|
+
const files = entries.map(entry => ({
|
|
238
|
+
path: entry.path,
|
|
239
|
+
summary: entry.summary,
|
|
240
|
+
messages: entry.messages,
|
|
241
|
+
errored: entry.errored,
|
|
242
|
+
}));
|
|
243
|
+
const details: LateDiagnosticsDetails = {
|
|
244
|
+
files: files.map(file => ({
|
|
245
|
+
path: file.path,
|
|
246
|
+
summary: file.summary,
|
|
247
|
+
errored: file.errored,
|
|
248
|
+
messages: file.messages,
|
|
249
|
+
})),
|
|
250
|
+
};
|
|
251
|
+
return {
|
|
252
|
+
role: "custom",
|
|
253
|
+
customType: LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE,
|
|
254
|
+
content: prompt.render(lateDiagnosticTemplate, {
|
|
255
|
+
multiple: files.length > 1,
|
|
256
|
+
files,
|
|
257
|
+
}),
|
|
258
|
+
display: true,
|
|
259
|
+
attribution: "agent",
|
|
260
|
+
details,
|
|
261
|
+
timestamp: Date.now(),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function buildMcpNotificationBatchMessage(entries: McpNotificationEntry[]): AgentMessage | null {
|
|
266
|
+
const resources: McpNotificationEntry[] = [];
|
|
267
|
+
const seen = new Set<string>();
|
|
268
|
+
for (const entry of entries) {
|
|
269
|
+
const key = `${entry.serverName}\0${entry.uri}`;
|
|
270
|
+
if (seen.has(key)) continue;
|
|
271
|
+
seen.add(key);
|
|
272
|
+
resources.push(entry);
|
|
273
|
+
}
|
|
274
|
+
if (resources.length === 0) return null;
|
|
275
|
+
const lines = [`[MCP notification] ${resources.length} resource(s) updated:`];
|
|
276
|
+
for (const resource of resources) {
|
|
277
|
+
lines.push(`- server="${resource.serverName}" uri=${resource.uri}`);
|
|
278
|
+
}
|
|
279
|
+
lines.push('Use read(path="mcp://<uri>") to inspect if relevant.');
|
|
280
|
+
return {
|
|
281
|
+
role: "user",
|
|
282
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
283
|
+
attribution: "agent",
|
|
284
|
+
timestamp: Date.now(),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function createPendingMCPTool(name: string): Tool {
|
|
289
|
+
const parsed = parseMCPToolName(name);
|
|
290
|
+
const serverName = parsed?.serverName;
|
|
291
|
+
const mcpToolName = parsed?.toolName ?? name;
|
|
292
|
+
const label = serverName ? `${serverName}/${mcpToolName}` : name;
|
|
293
|
+
const message = serverName
|
|
294
|
+
? `MCP server "${serverName}" is still connecting; tool "${name}" is not yet available. Retry after the MCP connection completes.`
|
|
295
|
+
: `MCP discovery is still in progress; tool "${name}" is not yet available. Retry after MCP connection completes.`;
|
|
296
|
+
const tool: Tool & { mcpServerName?: string; mcpToolName?: string } = {
|
|
297
|
+
name,
|
|
298
|
+
label,
|
|
299
|
+
description: `Pending MCP tool. ${message}`,
|
|
300
|
+
parameters: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {},
|
|
303
|
+
additionalProperties: true,
|
|
304
|
+
},
|
|
305
|
+
approval: "write",
|
|
306
|
+
intent: "omit",
|
|
307
|
+
mcpServerName: serverName,
|
|
308
|
+
mcpToolName,
|
|
309
|
+
async execute() {
|
|
310
|
+
return {
|
|
311
|
+
content: [{ type: "text", text: message }],
|
|
312
|
+
details: { serverName, mcpToolName, isError: true },
|
|
313
|
+
isError: true,
|
|
314
|
+
};
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
return tool;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function collectPendingMCPToolNames(explicitToolNames: readonly string[] | undefined): string[] {
|
|
321
|
+
const names = new Set<string>();
|
|
322
|
+
for (const name of explicitToolNames ?? []) {
|
|
323
|
+
const normalized = name.toLowerCase();
|
|
324
|
+
if (isMCPToolName(normalized)) names.add(normalized);
|
|
325
|
+
}
|
|
326
|
+
return [...names];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function logMCPLoadErrors(errors: MCPLoadResult["errors"]): void {
|
|
330
|
+
for (const [serverName, error] of errors) {
|
|
331
|
+
logger.error("MCP tool load failed", { path: `mcp:${serverName}`, error });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function applyMCPEnvironment(result: { exaApiKeys: string[] }): void {
|
|
336
|
+
if (result.exaApiKeys.length > 0 && !$env.EXA_API_KEY) {
|
|
337
|
+
Bun.env.EXA_API_KEY = result.exaApiKeys[0];
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Types
|
|
342
|
+
export interface CreateAgentSessionOptions {
|
|
343
|
+
/** Working directory for project-local discovery. Default: getProjectDir() */
|
|
344
|
+
cwd?: string;
|
|
345
|
+
/** Additional workspace directories beyond cwd (multi-root), absolute or cwd-relative. */
|
|
346
|
+
additionalDirectories?: string[];
|
|
347
|
+
/** Global config directory. Default: ~/.omp/agent */
|
|
348
|
+
agentDir?: string;
|
|
349
|
+
/** Spawns to allow. Default: "*" */
|
|
350
|
+
spawns?: string;
|
|
351
|
+
|
|
352
|
+
/** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
|
|
353
|
+
authStorage?: AuthStorage;
|
|
354
|
+
/** Model registry. Default: discoverModels(authStorage, agentDir) */
|
|
355
|
+
modelRegistry?: ModelRegistry;
|
|
356
|
+
/**
|
|
357
|
+
* Request credential resolver. Defaults to the model registry's normal
|
|
358
|
+
* session-affine resolver. Security scans use this narrow seam to keep one
|
|
359
|
+
* durable OAuth row pinned for the operation without changing ordinary
|
|
360
|
+
* provider routing.
|
|
361
|
+
*/
|
|
362
|
+
getApiKey?: AgentOptions["getApiKey"];
|
|
363
|
+
|
|
364
|
+
/** Model to use. Default: from settings, else first available */
|
|
365
|
+
model?: Model;
|
|
366
|
+
/** Raw model pattern(s) (e.g. from --model CLI flag) to resolve after extensions load.
|
|
367
|
+
* Used when model lookup is deferred because extension-provided models aren't registered yet. */
|
|
368
|
+
modelPattern?: string | string[];
|
|
369
|
+
/** Authenticated fallback selector for deferred subagent model patterns. */
|
|
370
|
+
modelPatternAuthFallback?: string;
|
|
371
|
+
/** Role name used to install retry fallbacks after deferred subagent patterns resolve. */
|
|
372
|
+
modelPatternFallbackRole?: string;
|
|
373
|
+
/** Validated default retry chain to install when a deferred singleton pattern resolves. */
|
|
374
|
+
modelPatternDefaultFallbackChain?: string[];
|
|
375
|
+
/** Thinking selector. Default: from settings, else unset */
|
|
376
|
+
thinkingLevel?: ConfiguredThinkingLevel;
|
|
377
|
+
/** Hard ceiling on the session's thinking effort (e.g. a task spawn's `task.maxEffort`-capped hint); retry-fallback recovery re-clamps to it. */
|
|
378
|
+
thinkingLevelCeiling?: Effort;
|
|
379
|
+
/** OpenAI service-tier override for this session. `null` omits `service_tier`. */
|
|
380
|
+
openAIServiceTier?: ServiceTier | null;
|
|
381
|
+
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
382
|
+
scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
383
|
+
/** Prewalk from the starting model to a fast/cheap target at the first edit/write once the todo list exists. */
|
|
384
|
+
prewalk?: Prewalk;
|
|
385
|
+
/** Force read-only plan mode at start, auto-approve on the model's first resolve call, then switch to execute. */
|
|
386
|
+
planYolo?: PlanYolo;
|
|
387
|
+
|
|
388
|
+
/** Provider-facing system prompt override. Replaces the fully rendered default blocks. */
|
|
389
|
+
systemPrompt?: string | string[] | ((defaultPrompt: string[]) => string | string[]);
|
|
390
|
+
/** Already-loaded custom prompt text rendered through the bundled custom system prompt template. */
|
|
391
|
+
customSystemPrompt?: string;
|
|
392
|
+
/** Already-loaded text appended through the bundled system prompt templates. */
|
|
393
|
+
appendSystemPrompt?: string;
|
|
394
|
+
/**
|
|
395
|
+
* Already-loaded title-generation system prompt override (typically
|
|
396
|
+
* {@link discoverTitleSystemPromptFile} → {@link resolvePromptInput}). When
|
|
397
|
+
* set, every automatic session-title generation path on this session — the
|
|
398
|
+
* first-input title and the replan-driven refresh — uses this prompt
|
|
399
|
+
* instead of the bundled default. Refresh on cwd change via
|
|
400
|
+
* {@link AgentSession.setTitleSystemPrompt}.
|
|
401
|
+
*/
|
|
402
|
+
titleSystemPrompt?: string;
|
|
403
|
+
/** Optional provider-facing session identifier for prompt caches and sticky auth selection.
|
|
404
|
+
* Keeps persisted session files isolated while reusing provider-side caches. */
|
|
405
|
+
providerSessionId?: string;
|
|
406
|
+
/** Optional provider-facing prompt cache key, distinct from request lineage. */
|
|
407
|
+
providerPromptCacheKey?: string;
|
|
408
|
+
/** Whether `providerPromptCacheKey` is caller-pinned or inherited from a full fork. */
|
|
409
|
+
providerPromptCacheKeySource?: "explicit" | "fork";
|
|
410
|
+
/** Absolute wall-clock deadline in Unix epoch milliseconds. */
|
|
411
|
+
deadline?: number;
|
|
412
|
+
|
|
413
|
+
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
414
|
+
customTools?: (CustomTool | ToolDefinition)[];
|
|
415
|
+
/** Inline extensions (merged with discovery). */
|
|
416
|
+
extensions?: ExtensionFactory[];
|
|
417
|
+
/** Additional extension paths to load (merged with discovery). */
|
|
418
|
+
additionalExtensionPaths?: string[];
|
|
419
|
+
/** Disable extension discovery (explicit paths still load). */
|
|
420
|
+
disableExtensionDiscovery?: boolean;
|
|
421
|
+
/**
|
|
422
|
+
* Pre-loaded extensions (skips file discovery and the per-session factory
|
|
423
|
+
* call). Used by the CLI when extensions are loaded early to parse custom
|
|
424
|
+
* flags — the same process owns the returned instances, so reusing them is
|
|
425
|
+
* safe.
|
|
426
|
+
*
|
|
427
|
+
* NEVER pass this across session boundaries (e.g. parent → subagent).
|
|
428
|
+
* `Extension` instances close over a parent-bound `ExtensionAPI` (cwd,
|
|
429
|
+
* eventBus, runtime), and reusing them would route tools/handlers/commands
|
|
430
|
+
* back through the parent. For subagents, forward
|
|
431
|
+
* {@link preloadedExtensionPaths} instead.
|
|
432
|
+
*
|
|
433
|
+
* @internal
|
|
434
|
+
*/
|
|
435
|
+
preloadedExtensions?: LoadExtensionsResult;
|
|
436
|
+
/**
|
|
437
|
+
* Pre-discovered extension source paths. When provided, the filesystem-scan
|
|
438
|
+
* inside `discoverExtensionPaths()` is skipped — the session still calls
|
|
439
|
+
* `loadExtensions()` itself so each `Extension` is bound to THIS session's
|
|
440
|
+
* `ExtensionAPI` (cwd, eventBus, runtime).
|
|
441
|
+
*
|
|
442
|
+
* This is the safe pass-through for parent → subagent forwarding.
|
|
443
|
+
*/
|
|
444
|
+
preloadedExtensionPaths?: string[];
|
|
445
|
+
/**
|
|
446
|
+
* Pre-discovered custom-tool source paths from `.omp/tools/`, `.claude/tools/`,
|
|
447
|
+
* plugins, etc. When provided, the filesystem-scan inside
|
|
448
|
+
* `discoverCustomToolPaths()` is skipped — subagents inherit the parent's
|
|
449
|
+
* scan result and call `loadCustomTools()` themselves so each session binds
|
|
450
|
+
* tools to its OWN `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
451
|
+
*
|
|
452
|
+
* Forwarding the loaded `LoadedCustomTool[]` instances directly would reuse
|
|
453
|
+
* the parent's session-bound API and route tool execution back through the
|
|
454
|
+
* parent — wrong for isolated tasks and for pending-action routing.
|
|
455
|
+
*/
|
|
456
|
+
preloadedCustomToolPaths?: ToolPathWithSource[];
|
|
457
|
+
|
|
458
|
+
/** Shared event bus for tool/extension communication. Default: creates new bus. */
|
|
459
|
+
eventBus?: EventBus;
|
|
460
|
+
|
|
461
|
+
/** Skills. Default: discovered from multiple locations */
|
|
462
|
+
skills?: Skill[];
|
|
463
|
+
/** Rules. Default: discovered from multiple locations */
|
|
464
|
+
rules?: Rule[];
|
|
465
|
+
/** Context files (AGENTS.md content). Default: discovered walking up from cwd */
|
|
466
|
+
contextFiles?: Array<{ path: string; content: string }>;
|
|
467
|
+
/** Pre-built workspace tree (skips re-scanning; passed by parents to subagents). */
|
|
468
|
+
workspaceTree?: WorkspaceTree;
|
|
469
|
+
/** Prompt templates. Default: discovered from cwd/.omp/prompts/ + agentDir/prompts/ */
|
|
470
|
+
promptTemplates?: PromptTemplate[];
|
|
471
|
+
/** File-based slash commands. Default: discovered from commands/ directories */
|
|
472
|
+
slashCommands?: FileSlashCommand[];
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Enable MCP capabilities. `false` skips MCP discovery and ignores
|
|
476
|
+
* `mcpManager`, preventing process-global or inherited MCP access. Default:
|
|
477
|
+
* true.
|
|
478
|
+
*/
|
|
479
|
+
enableMCP?: boolean;
|
|
480
|
+
/** Existing MCP manager to reuse when MCP is enabled (skips discovery, propagates to toolSession). */
|
|
481
|
+
mcpManager?: MCPManager;
|
|
482
|
+
|
|
483
|
+
/** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */
|
|
484
|
+
enableLsp?: boolean;
|
|
485
|
+
/** Restrict LSP to navigation and diagnostics even when enabled. Defaults to true for restricted sessions. */
|
|
486
|
+
lspReadOnly?: boolean;
|
|
487
|
+
/** Whether this invocation may expose IRC. `false` removes it even for subagents. */
|
|
488
|
+
enableIrc?: boolean;
|
|
489
|
+
/** Skip subprocess-kernel availability checks and prelude warmup */
|
|
490
|
+
skipPythonPreflight?: boolean;
|
|
491
|
+
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
492
|
+
toolNames?: string[];
|
|
493
|
+
/** Limit the session to explicitly supplied tool names, without discovered extras. */
|
|
494
|
+
restrictToolNames?: boolean;
|
|
495
|
+
/**
|
|
496
|
+
* Permit only caller-supplied SDK custom tools inside a restricted session.
|
|
497
|
+
* They must still be named in {@link toolNames}; discovered extensions, MCP,
|
|
498
|
+
* and ambient custom tools remain disabled. Default: false.
|
|
499
|
+
*/
|
|
500
|
+
allowRestrictedCustomTools?: boolean;
|
|
501
|
+
|
|
502
|
+
/** Output schema for structured completion (subagents). */
|
|
503
|
+
outputSchema?: unknown;
|
|
504
|
+
/** Enforcement policy for {@link outputSchema}; defaults to legacy permissive behavior. */
|
|
505
|
+
outputSchemaMode?: StructuredSubagentSchemaMode;
|
|
506
|
+
/** Whether to include the yield tool by default */
|
|
507
|
+
requireYieldTool?: boolean;
|
|
508
|
+
/** Task recursion depth (for subagent sessions). Default: 0 */
|
|
509
|
+
taskDepth?: number;
|
|
510
|
+
/** Parent Hindsight state to alias for subagent memory tools. */
|
|
511
|
+
parentHindsightSessionState?: HindsightSessionState;
|
|
512
|
+
/** Parent Mnemopi state to alias for subagent memory tools. */
|
|
513
|
+
parentMnemopiSessionState?: MnemopiSessionState;
|
|
514
|
+
/** Pre-allocated agent identity for IRC routing. Default: "Main" for top-level, parentTaskPrefix-derived for sub. */
|
|
515
|
+
agentId?: string;
|
|
516
|
+
/** Display name for the agent in IRC. Default: "main" or "sub". */
|
|
517
|
+
agentDisplayName?: string;
|
|
518
|
+
/** Optional shared agent registry for IRC routing. Default: AgentRegistry.global(). */
|
|
519
|
+
agentRegistry?: AgentRegistry;
|
|
520
|
+
/**
|
|
521
|
+
* Registry generation authorized for this creation. `null` requires the id
|
|
522
|
+
* to be absent; an AgentRef allows a parked revival to reuse only that ref.
|
|
523
|
+
* Undefined preserves legacy unconditional registration for external SDK callers.
|
|
524
|
+
* @internal
|
|
525
|
+
*/
|
|
526
|
+
expectedAgentRef?: AgentRef | null;
|
|
527
|
+
/** Parent task ID prefix for nested artifact naming (e.g., "Extensions") */
|
|
528
|
+
parentTaskPrefix?: string;
|
|
529
|
+
/**
|
|
530
|
+
* Registry id of the spawning agent, recorded as this subagent's parent in
|
|
531
|
+
* the agent registry. Distinct from `parentTaskPrefix`, which is this agent's
|
|
532
|
+
* own artifact/output-id prefix (the executor passes the child's own id
|
|
533
|
+
* there, so it must never double as the parent link). Undefined for the
|
|
534
|
+
* top-level "Main" session, which has no parent.
|
|
535
|
+
*/
|
|
536
|
+
parentAgentId?: string;
|
|
537
|
+
/** Inherited eval executor session id for subagents sharing parent eval state. */
|
|
538
|
+
parentEvalSessionId?: string;
|
|
539
|
+
|
|
540
|
+
/** Session manager. Default: session stored under the configured agentDir sessions root */
|
|
541
|
+
sessionManager?: SessionManager;
|
|
542
|
+
|
|
543
|
+
/** Override local:// protocol options for subagent local:// sharing. Default: uses the session's own artifacts dir and session ID. */
|
|
544
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
545
|
+
|
|
546
|
+
/** Settings instance. Default: Settings.init({ cwd, agentDir }) */
|
|
547
|
+
settings?: Settings;
|
|
548
|
+
/**
|
|
549
|
+
* Legacy alias for `settings`. Older Pi extensions pass SettingsManager.create(...)
|
|
550
|
+
* through this field; accept it so their SDK calls keep the configured settings.
|
|
551
|
+
*/
|
|
552
|
+
settingsManager?: Settings | Promise<Settings>;
|
|
553
|
+
|
|
554
|
+
/** Whether UI is available (enables interactive tools like ask). Default: false */
|
|
555
|
+
hasUI?: boolean;
|
|
556
|
+
/**
|
|
557
|
+
* Defer `confirm` reserve-policy fallback until AgentSession prompt-time UI is configured.
|
|
558
|
+
* ACP uses this while capabilities are negotiated without enabling UI-only tools.
|
|
559
|
+
*/
|
|
560
|
+
deferUsageReserveConfirmation?: boolean;
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Opt-in OpenTelemetry instrumentation forwarded to the underlying Agent.
|
|
564
|
+
* Passing `{}` enables the loop's GenAI-semantic-convention spans. See
|
|
565
|
+
* {@link AgentTelemetryConfig} for the full surface (hooks, content capture,
|
|
566
|
+
* cost estimator, agent identity).
|
|
567
|
+
*
|
|
568
|
+
* Safe to enable without an OTEL SDK registered in the host: the
|
|
569
|
+
* `@opentelemetry/api` package returns a no-op tracer in that case.
|
|
570
|
+
*/
|
|
571
|
+
telemetry?: AgentTelemetryConfig;
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Fired once, when the agent loop hands its first request to the provider
|
|
575
|
+
* transport (i.e. the `streamFn` wrapper is first invoked). Used to measure
|
|
576
|
+
* subagent launch latency — the boundary between "session built" and "model
|
|
577
|
+
* call dispatched". This is the loop's dispatch point, slightly before the
|
|
578
|
+
* actual provider HTTP call (per-request prep, identical across all
|
|
579
|
+
* requests, follows it), which is the right granularity for launch timing.
|
|
580
|
+
*/
|
|
581
|
+
onFirstChatDispatch?: () => void;
|
|
582
|
+
|
|
583
|
+
/** Whether to auto-approve all tool calls (--auto-approve CLI flag). Default: false */
|
|
584
|
+
autoApprove?: boolean;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** Result from createAgentSession */
|
|
588
|
+
export interface CreateAgentSessionResult {
|
|
589
|
+
/** The created session */
|
|
590
|
+
session: AgentSession;
|
|
591
|
+
/** Extensions result (loaded extensions + runtime) */
|
|
592
|
+
extensionsResult: LoadExtensionsResult;
|
|
593
|
+
/** Update tool UI context (interactive mode) */
|
|
594
|
+
setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
|
|
595
|
+
/** MCP manager for server lifecycle management (undefined if MCP disabled) */
|
|
596
|
+
mcpManager?: MCPManager;
|
|
597
|
+
/** Warning if session was restored with a different model than saved */
|
|
598
|
+
modelFallbackMessage?: string;
|
|
599
|
+
/** LSP servers detected for startup; warmup may continue in the background */
|
|
600
|
+
lspServers?: LspStartupServerInfo[];
|
|
601
|
+
/** Shared event bus for tool/extension communication */
|
|
602
|
+
eventBus: EventBus;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export type DialectFormat = "auto" | "native" | Dialect;
|
|
606
|
+
|
|
607
|
+
export function resolveDialect(
|
|
608
|
+
format: DialectFormat,
|
|
609
|
+
model: (Pick<Model, "supportsTools"> & Partial<Pick<Model, "id">>) | undefined,
|
|
610
|
+
): Dialect | undefined {
|
|
611
|
+
if (format === "native") return undefined;
|
|
612
|
+
if (format === "auto") {
|
|
613
|
+
if (model?.supportsTools !== false) return undefined;
|
|
614
|
+
if (!model.id) return "glm";
|
|
615
|
+
const preferred = preferredDialect(model.id);
|
|
616
|
+
return preferred === FALLBACK_DIALECT ? "glm" : preferred;
|
|
617
|
+
}
|
|
618
|
+
return format;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Re-exports
|
|
622
|
+
|
|
623
|
+
export type { PromptTemplate } from "./config/prompt-templates";
|
|
624
|
+
export { Settings, type SkillsSettings } from "./config/settings";
|
|
625
|
+
export type { CustomCommand, CustomCommandFactory } from "./extensibility/custom-commands/types";
|
|
626
|
+
export type { CustomTool, CustomToolFactory } from "./extensibility/custom-tools/types";
|
|
627
|
+
export type * from "./extensibility/extensions";
|
|
628
|
+
export type { Skill } from "./extensibility/skills";
|
|
629
|
+
export type { FileSlashCommand } from "./extensibility/slash-commands";
|
|
630
|
+
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp";
|
|
631
|
+
// Agent registry: pass a private instance per `createAgentSession` when
|
|
632
|
+
// embedding several concurrent top-level sessions in one process (the default
|
|
633
|
+
// global registry admits only one "Main" per process generation).
|
|
634
|
+
export { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
|
|
635
|
+
export type { Tool } from "./tools";
|
|
636
|
+
export { buildDirectoryTree, buildWorkspaceTree, type DirectoryTree, type WorkspaceTree } from "./workspace-tree";
|
|
637
|
+
|
|
638
|
+
export {
|
|
639
|
+
// Individual tool classes (for custom usage)
|
|
640
|
+
BashTool,
|
|
641
|
+
// Tool classes and factories
|
|
642
|
+
BUILTIN_TOOLS,
|
|
643
|
+
createTools,
|
|
644
|
+
EditTool,
|
|
645
|
+
EvalTool,
|
|
646
|
+
GlobTool,
|
|
647
|
+
GrepTool,
|
|
648
|
+
HIDDEN_TOOLS,
|
|
649
|
+
ReadTool,
|
|
650
|
+
type ToolSession,
|
|
651
|
+
WebSearchTool,
|
|
652
|
+
WriteTool,
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
// Helper Functions
|
|
656
|
+
|
|
657
|
+
// Discovery Functions
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Create an AuthStorage instance.
|
|
661
|
+
*
|
|
662
|
+
* Default: local SQLite store at `<agentDir>/agent.db`.
|
|
663
|
+
*
|
|
664
|
+
* Broker mode: when `OMP_AUTH_BROKER_URL` is set, credentials are pulled from
|
|
665
|
+
* a remote auth-broker over the wire. Refresh tokens never leave the broker;
|
|
666
|
+
* the client receives access tokens with `refresh = "__remote__"` and calls
|
|
667
|
+
* back into the broker through the {@link AuthStorageOptions.refreshOAuthCredential}
|
|
668
|
+
* override to re-mint access tokens when needed.
|
|
669
|
+
*
|
|
670
|
+
* Delegates to {@link ./session/auth-broker-config} so the TUI and the catalog
|
|
671
|
+
* generator share the same credential-discovery logic.
|
|
672
|
+
*/
|
|
673
|
+
export async function discoverAuthStorage(agentDir: string = getAgentDir()): Promise<AuthStorage> {
|
|
674
|
+
return discoverAuthStorageFromConfig(agentDir);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Discover extensions from cwd.
|
|
679
|
+
*/
|
|
680
|
+
export async function discoverExtensions(cwd?: string): Promise<LoadExtensionsResult> {
|
|
681
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
682
|
+
|
|
683
|
+
return discoverAndLoadExtensions([], resolvedCwd);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Path-only counterpart of {@link loadSessionExtensions}: the FS-heavy scan
|
|
688
|
+
* without the per-session module load. Subagents reuse the parent's path list
|
|
689
|
+
* (cached on {@link ToolSession.extensionPaths}) and rebuild Extension
|
|
690
|
+
* instances themselves so each session's `ExtensionAPI` (cwd, eventBus,
|
|
691
|
+
* runtime) is its own.
|
|
692
|
+
*/
|
|
693
|
+
export async function discoverSessionExtensionPaths(
|
|
694
|
+
options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">,
|
|
695
|
+
cwd: string,
|
|
696
|
+
settings: Settings,
|
|
697
|
+
): Promise<string[]> {
|
|
698
|
+
const configuredPaths = options.disableExtensionDiscovery
|
|
699
|
+
? (options.additionalExtensionPaths ?? [])
|
|
700
|
+
: [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
|
|
701
|
+
const disabledExtensionIds = options.disableExtensionDiscovery
|
|
702
|
+
? undefined
|
|
703
|
+
: (settings.get("disabledExtensions") ?? []);
|
|
704
|
+
return discoverExtensionPaths(configuredPaths, cwd, disabledExtensionIds, {
|
|
705
|
+
ambient: !options.disableExtensionDiscovery,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Load the discovered/configured extensions for a session — everything {@link
|
|
711
|
+
* createAgentSession} would load except the inline factory extensions it appends
|
|
712
|
+
* itself. Extracted so the CLI can resolve extension-registered flags (and thus
|
|
713
|
+
* classify `@file` arguments extension-aware) *before* a session — and its
|
|
714
|
+
* terminal breadcrumb — is created, then hand the result back through
|
|
715
|
+
* {@link CreateAgentSessionOptions.preloadedExtensions} so the work is not
|
|
716
|
+
* repeated. Keep this the single source of the discovery branch logic.
|
|
717
|
+
*/
|
|
718
|
+
export async function loadSessionExtensions(
|
|
719
|
+
options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">,
|
|
720
|
+
cwd: string,
|
|
721
|
+
settings: Settings,
|
|
722
|
+
eventBus: EventBus,
|
|
723
|
+
): Promise<LoadExtensionsResult> {
|
|
724
|
+
const paths = await discoverSessionExtensionPaths(options, cwd, settings);
|
|
725
|
+
const result = await logger.time("loadExtensions", loadExtensions, paths, cwd, eventBus);
|
|
726
|
+
for (const { path, error } of result.errors) {
|
|
727
|
+
logger.error("Failed to load extension", { path, error });
|
|
728
|
+
}
|
|
729
|
+
return result;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* Load discovered/configured extensions and register their providers into
|
|
734
|
+
* `modelRegistry`, then discover the dynamic provider catalogs. One-shot CLIs
|
|
735
|
+
* (`omp bench`, dry-balance) build a bare {@link ModelRegistry} that only knows
|
|
736
|
+
* built-in catalog providers; without this, providers contributed by an
|
|
737
|
+
* extension (e.g. a custom OpenAI-compatible provider under
|
|
738
|
+
* `~/.omp/agent/extensions/`) never reach model resolution. Mirrors the
|
|
739
|
+
* session / `omp models` path: drain the queued provider registrations, then
|
|
740
|
+
* `refreshRuntimeProviders` so dynamically-discovered models exist before
|
|
741
|
+
* selectors are resolved.
|
|
742
|
+
*/
|
|
743
|
+
export async function loadCliExtensionProviders(
|
|
744
|
+
modelRegistry: ModelRegistry,
|
|
745
|
+
settings: Settings,
|
|
746
|
+
cwd: string,
|
|
747
|
+
options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths"> = {},
|
|
748
|
+
): Promise<void> {
|
|
749
|
+
const eventBus = new EventBus();
|
|
750
|
+
const extensionsResult = await loadSessionExtensions(options, cwd, settings, eventBus);
|
|
751
|
+
const activeSources = extensionsResult.extensions.map(extension => extension.path);
|
|
752
|
+
modelRegistry.syncExtensionSources(activeSources);
|
|
753
|
+
for (const sourceId of new Set(activeSources)) {
|
|
754
|
+
modelRegistry.clearSourceRegistrations(sourceId);
|
|
755
|
+
}
|
|
756
|
+
for (const { name, config, sourceId } of extensionsResult.runtime.pendingProviderRegistrations) {
|
|
757
|
+
modelRegistry.registerProvider(name, config, sourceId);
|
|
758
|
+
}
|
|
759
|
+
extensionsResult.runtime.pendingProviderRegistrations = [];
|
|
760
|
+
await modelRegistry.refreshRuntimeProviders();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Discover skills from cwd and agentDir.
|
|
765
|
+
*/
|
|
766
|
+
export async function discoverSkills(
|
|
767
|
+
cwd?: string,
|
|
768
|
+
_agentDir?: string,
|
|
769
|
+
settings?: SkillsSettings,
|
|
770
|
+
): Promise<{ skills: Skill[]; warnings: SkillWarning[] }> {
|
|
771
|
+
return await loadSkillsInternal({
|
|
772
|
+
...settings,
|
|
773
|
+
cwd: cwd ?? getProjectDir(),
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Discover context files (AGENTS.md) walking up from cwd.
|
|
779
|
+
* Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
|
|
780
|
+
*/
|
|
781
|
+
export async function discoverContextFiles(
|
|
782
|
+
cwd?: string,
|
|
783
|
+
_agentDir?: string,
|
|
784
|
+
disabledExtensions?: string[],
|
|
785
|
+
): Promise<Array<{ path: string; content: string; depth?: number }>> {
|
|
786
|
+
return await loadContextFilesInternal({
|
|
787
|
+
cwd: cwd ?? getProjectDir(),
|
|
788
|
+
disabledExtensions,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Discover prompt templates from cwd and agentDir.
|
|
794
|
+
*/
|
|
795
|
+
export async function discoverPromptTemplates(cwd?: string, agentDir?: string): Promise<PromptTemplate[]> {
|
|
796
|
+
return await loadPromptTemplatesInternal({
|
|
797
|
+
cwd: cwd ?? getProjectDir(),
|
|
798
|
+
agentDir: agentDir ?? getAgentDir(),
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Discover file-based slash commands from commands/ directories.
|
|
804
|
+
*/
|
|
805
|
+
export async function discoverSlashCommands(cwd?: string): Promise<FileSlashCommand[]> {
|
|
806
|
+
return loadSlashCommandsInternal({ cwd: cwd ?? getProjectDir() });
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Discover custom commands (TypeScript slash commands) from cwd and agentDir.
|
|
811
|
+
*/
|
|
812
|
+
export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
|
|
813
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
814
|
+
const resolvedAgentDir = agentDir ?? getAgentDir();
|
|
815
|
+
|
|
816
|
+
return loadCustomCommandsInternal({
|
|
817
|
+
cwd: resolvedCwd,
|
|
818
|
+
agentDir: resolvedAgentDir,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Discover MCP servers from .mcp.json files.
|
|
824
|
+
* Returns the manager and loaded tools.
|
|
825
|
+
*/
|
|
826
|
+
export async function discoverMCPServers(cwd?: string): Promise<MCPToolsLoadResult> {
|
|
827
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
828
|
+
return discoverAndLoadMCPTools(resolvedCwd);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// API Key Helpers
|
|
832
|
+
|
|
833
|
+
// System Prompt
|
|
834
|
+
|
|
835
|
+
export interface BuildSystemPromptOptions {
|
|
836
|
+
tools?: Tool[];
|
|
837
|
+
skills?: Skill[];
|
|
838
|
+
contextFiles?: Array<{ path: string; content: string }>;
|
|
839
|
+
cwd?: string;
|
|
840
|
+
customPrompt?: string;
|
|
841
|
+
appendPrompt?: string;
|
|
842
|
+
inlineToolDescriptors?: boolean;
|
|
843
|
+
includeWorkspaceTree?: boolean;
|
|
844
|
+
/** Include the read-only security:// resource inventory entry. Default: false. */
|
|
845
|
+
securityEnabled?: boolean;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Build the default provider-facing system prompt blocks.
|
|
850
|
+
*
|
|
851
|
+
* The returned `systemPrompt` preserves the stable harness prompt and dynamic project context
|
|
852
|
+
* as separate entries so providers can cache prompt prefixes without concatenating blocks.
|
|
853
|
+
*/
|
|
854
|
+
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<BuildSystemPromptResult> {
|
|
855
|
+
const toolNames = options.tools?.map(tool => tool.name);
|
|
856
|
+
const toolMap = options.tools ? new Map(options.tools.map(tool => [tool.name, tool])) : undefined;
|
|
857
|
+
const promptTools = toolMap
|
|
858
|
+
? projectSystemPromptToolMetadata(
|
|
859
|
+
toolMap,
|
|
860
|
+
options.inlineToolDescriptors ? { mode: "full" } : { mode: "compact", toolNames: toolNames ?? [] },
|
|
861
|
+
)
|
|
862
|
+
: undefined;
|
|
863
|
+
return await buildSystemPromptInternal({
|
|
864
|
+
cwd: options.cwd,
|
|
865
|
+
customPrompt: options.customPrompt,
|
|
866
|
+
skills: options.skills,
|
|
867
|
+
contextFiles: options.contextFiles,
|
|
868
|
+
appendSystemPrompt: options.appendPrompt,
|
|
869
|
+
inlineToolDescriptors: options.inlineToolDescriptors,
|
|
870
|
+
includeWorkspaceTree: options.includeWorkspaceTree,
|
|
871
|
+
securityEnabled: options.securityEnabled,
|
|
872
|
+
toolNames,
|
|
873
|
+
tools: promptTools,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Internal Helpers
|
|
878
|
+
|
|
879
|
+
function createCustomToolContext(ctx: ExtensionContext): CustomToolContext {
|
|
880
|
+
return {
|
|
881
|
+
sessionManager: ctx.sessionManager,
|
|
882
|
+
modelRegistry: ctx.modelRegistry,
|
|
883
|
+
model: ctx.model,
|
|
884
|
+
isIdle: ctx.isIdle,
|
|
885
|
+
hasQueuedMessages: ctx.hasPendingMessages,
|
|
886
|
+
abort: ctx.abort,
|
|
887
|
+
localProtocolOptions: ctx.localProtocolOptions,
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
function isCustomTool(tool: CustomTool | ToolDefinition): tool is CustomTool {
|
|
892
|
+
// To distinguish, we mark converted tools with a hidden symbol property.
|
|
893
|
+
// If the tool doesn't have this marker, it's a CustomTool that needs conversion.
|
|
894
|
+
return !(tool as any).__isToolDefinition;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function isLegacyBuiltinToolDefinition(tool: CustomTool | ToolDefinition): boolean {
|
|
898
|
+
return !isCustomTool(tool) && "__ompLegacyBuiltinTool" in tool && tool.__ompLegacyBuiltinTool === true;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const TOOL_DEFINITION_MARKER = Symbol("__isToolDefinition");
|
|
902
|
+
/** Matches the truncation applied to per-server instructions inside `rebuildSystemPrompt`. */
|
|
903
|
+
const MAX_MCP_INSTRUCTIONS_LENGTH = 4000;
|
|
904
|
+
|
|
905
|
+
let sshCleanupRegistered = false;
|
|
906
|
+
|
|
907
|
+
async function cleanupSshResources(): Promise<void> {
|
|
908
|
+
const results = await Promise.allSettled([closeAllConnections(), unmountAll()]);
|
|
909
|
+
for (const result of results) {
|
|
910
|
+
if (result.status === "rejected") {
|
|
911
|
+
logger.warn("SSH cleanup failed", { error: String(result.reason) });
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function registerSshCleanup(): void {
|
|
917
|
+
if (sshCleanupRegistered) return;
|
|
918
|
+
sshCleanupRegistered = true;
|
|
919
|
+
postmortem.register("ssh-cleanup", cleanupSshResources);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
let evalCleanupRegistered = false;
|
|
923
|
+
|
|
924
|
+
function registerEvalCleanup(): void {
|
|
925
|
+
if (evalCleanupRegistered) return;
|
|
926
|
+
evalCleanupRegistered = true;
|
|
927
|
+
postmortem.register("python-cleanup", disposeAllKernelSessions);
|
|
928
|
+
postmortem.register("ruby-cleanup", disposeAllRubyKernelSessions);
|
|
929
|
+
postmortem.register("julia-cleanup", disposeAllJuliaKernelSessions);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
export function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
933
|
+
const definition: ToolDefinition & { [TOOL_DEFINITION_MARKER]: true } = {
|
|
934
|
+
name: tool.name,
|
|
935
|
+
label: tool.label,
|
|
936
|
+
description: tool.description,
|
|
937
|
+
parameters: tool.parameters,
|
|
938
|
+
hidden: tool.hidden,
|
|
939
|
+
loadMode: defaultLoadModeForToolName(tool.name, tool.loadMode),
|
|
940
|
+
deferrable: tool.deferrable,
|
|
941
|
+
approval: typeof tool.approval === "function" ? tool.approval.bind(tool) : tool.approval,
|
|
942
|
+
// Preserved through RegisteredToolAdapter so MCP-backed tools' explicit
|
|
943
|
+
// `strict: false` (#4336/#4340) survives the custom-tool → definition bridge.
|
|
944
|
+
strict: tool.strict,
|
|
945
|
+
mcpServerName: tool.mcpServerName,
|
|
946
|
+
mcpToolName: tool.mcpToolName,
|
|
947
|
+
execute: (toolCallId, params, signal, onUpdate, ctx) =>
|
|
948
|
+
tool.execute(toolCallId, params, onUpdate, createCustomToolContext(ctx), signal),
|
|
949
|
+
onSession: tool.onSession ? (event, ctx) => tool.onSession?.(event, createCustomToolContext(ctx)) : undefined,
|
|
950
|
+
renderCall: tool.renderCall,
|
|
951
|
+
renderResult: tool.renderResult
|
|
952
|
+
? (result, options, theme): Component => {
|
|
953
|
+
const component = tool.renderResult?.(
|
|
954
|
+
result,
|
|
955
|
+
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
|
|
956
|
+
theme,
|
|
957
|
+
);
|
|
958
|
+
// Return empty component if undefined to match Component type requirement
|
|
959
|
+
return component ?? ({ render: () => [] } as unknown as Component);
|
|
960
|
+
}
|
|
961
|
+
: undefined,
|
|
962
|
+
[TOOL_DEFINITION_MARKER]: true,
|
|
963
|
+
};
|
|
964
|
+
return definition;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
968
|
+
const uniqueTools = deduplicateMCPToolsByName(tools);
|
|
969
|
+
return api => {
|
|
970
|
+
for (const tool of uniqueTools) {
|
|
971
|
+
api.registerTool(customToolToDefinition(tool));
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const runOnSession = async (event: CustomToolSessionEvent, ctx: ExtensionContext) => {
|
|
975
|
+
for (const tool of uniqueTools) {
|
|
976
|
+
if (!tool.onSession) continue;
|
|
977
|
+
try {
|
|
978
|
+
await tool.onSession(event, createCustomToolContext(ctx));
|
|
979
|
+
} catch (err) {
|
|
980
|
+
logger.warn("Custom tool onSession error", { tool: tool.name, error: String(err) });
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
api.on("session_start", async (_event, ctx) =>
|
|
986
|
+
runOnSession({ reason: "start", previousSessionFile: undefined }, ctx),
|
|
987
|
+
);
|
|
988
|
+
api.on("session_switch", async (event, ctx) =>
|
|
989
|
+
runOnSession({ reason: "switch", previousSessionFile: event.previousSessionFile }, ctx),
|
|
990
|
+
);
|
|
991
|
+
api.on("session_branch", async (event, ctx) =>
|
|
992
|
+
runOnSession({ reason: "branch", previousSessionFile: event.previousSessionFile }, ctx),
|
|
993
|
+
);
|
|
994
|
+
api.on("session_tree", async (_event, ctx) =>
|
|
995
|
+
runOnSession({ reason: "tree", previousSessionFile: undefined }, ctx),
|
|
996
|
+
);
|
|
997
|
+
api.on("session_shutdown", async (_event, ctx) =>
|
|
998
|
+
runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx),
|
|
999
|
+
);
|
|
1000
|
+
api.on("auto_compaction_start", async (event, ctx) =>
|
|
1001
|
+
runOnSession({ reason: "auto_compaction_start", trigger: event.reason, action: event.action }, ctx),
|
|
1002
|
+
);
|
|
1003
|
+
api.on("auto_compaction_end", async (event, ctx) =>
|
|
1004
|
+
runOnSession(
|
|
1005
|
+
{
|
|
1006
|
+
reason: "auto_compaction_end",
|
|
1007
|
+
action: event.action,
|
|
1008
|
+
result: event.result,
|
|
1009
|
+
aborted: event.aborted,
|
|
1010
|
+
willRetry: event.willRetry,
|
|
1011
|
+
errorMessage: event.errorMessage,
|
|
1012
|
+
},
|
|
1013
|
+
ctx,
|
|
1014
|
+
),
|
|
1015
|
+
);
|
|
1016
|
+
api.on("auto_retry_start", async (event, ctx) =>
|
|
1017
|
+
runOnSession(
|
|
1018
|
+
{
|
|
1019
|
+
reason: "auto_retry_start",
|
|
1020
|
+
attempt: event.attempt,
|
|
1021
|
+
maxAttempts: event.maxAttempts,
|
|
1022
|
+
delayMs: event.delayMs,
|
|
1023
|
+
errorMessage: event.errorMessage,
|
|
1024
|
+
errorId: event.errorId,
|
|
1025
|
+
},
|
|
1026
|
+
ctx,
|
|
1027
|
+
),
|
|
1028
|
+
);
|
|
1029
|
+
api.on("auto_retry_end", async (event, ctx) =>
|
|
1030
|
+
runOnSession(
|
|
1031
|
+
{
|
|
1032
|
+
reason: "auto_retry_end",
|
|
1033
|
+
success: event.success,
|
|
1034
|
+
attempt: event.attempt,
|
|
1035
|
+
finalError: event.finalError,
|
|
1036
|
+
recoveredErrors: event.recoveredErrors,
|
|
1037
|
+
},
|
|
1038
|
+
ctx,
|
|
1039
|
+
),
|
|
1040
|
+
);
|
|
1041
|
+
api.on("ttsr_triggered", async (event, ctx) =>
|
|
1042
|
+
runOnSession({ reason: "ttsr_triggered", rules: event.rules }, ctx),
|
|
1043
|
+
);
|
|
1044
|
+
api.on("todo_reminder", async (event, ctx) =>
|
|
1045
|
+
runOnSession(
|
|
1046
|
+
{
|
|
1047
|
+
reason: "todo_reminder",
|
|
1048
|
+
todos: event.todos,
|
|
1049
|
+
attempt: event.attempt,
|
|
1050
|
+
maxAttempts: event.maxAttempts,
|
|
1051
|
+
},
|
|
1052
|
+
ctx,
|
|
1053
|
+
),
|
|
1054
|
+
);
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Factory
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Build LoadedCustomCommand entries for all MCP prompts across connected servers.
|
|
1062
|
+
* These are re-created whenever prompts change (setOnPromptsChanged callback).
|
|
1063
|
+
*/
|
|
1064
|
+
function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
|
|
1065
|
+
const commands: LoadedCustomCommand[] = [];
|
|
1066
|
+
for (const serverName of manager.getConnectedServers()) {
|
|
1067
|
+
const prompts = manager.getServerPrompts(serverName);
|
|
1068
|
+
if (!prompts?.length) continue;
|
|
1069
|
+
for (const prompt of prompts) {
|
|
1070
|
+
const commandName = `${serverName}:${prompt.name}`;
|
|
1071
|
+
commands.push({
|
|
1072
|
+
path: `mcp:${commandName}`,
|
|
1073
|
+
resolvedPath: `mcp:${commandName}`,
|
|
1074
|
+
source: "bundled",
|
|
1075
|
+
command: {
|
|
1076
|
+
name: commandName,
|
|
1077
|
+
description: prompt.description ?? `MCP prompt from ${serverName}`,
|
|
1078
|
+
async execute(args: string[]) {
|
|
1079
|
+
const promptArgs: Record<string, string> = {};
|
|
1080
|
+
for (const arg of args) {
|
|
1081
|
+
const eqIdx = arg.indexOf("=");
|
|
1082
|
+
if (eqIdx > 0) {
|
|
1083
|
+
promptArgs[arg.slice(0, eqIdx)] = arg.slice(eqIdx + 1);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const result = await manager.executePrompt(serverName, prompt.name, promptArgs);
|
|
1087
|
+
if (!result) return "";
|
|
1088
|
+
const parts: string[] = [];
|
|
1089
|
+
for (const msg of result.messages) {
|
|
1090
|
+
const contentItems = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
1091
|
+
for (const item of contentItems) {
|
|
1092
|
+
if (item.type === "text") {
|
|
1093
|
+
parts.push(item.text);
|
|
1094
|
+
} else if (item.type === "resource") {
|
|
1095
|
+
const resource = item.resource;
|
|
1096
|
+
if (resource.text) parts.push(resource.text);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return parts.join("\n\n");
|
|
1101
|
+
},
|
|
1102
|
+
},
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return commands;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/** Dependencies used to construct an isolated auto-learn capture agent. */
|
|
1110
|
+
export interface AutoLearnCaptureRunnerOptions {
|
|
1111
|
+
sourceAgent: Agent;
|
|
1112
|
+
captureTools: AgentTool[];
|
|
1113
|
+
createAgent: (options: AgentOptions) => Agent;
|
|
1114
|
+
onPayload?: SimpleStreamOptions["onPayload"];
|
|
1115
|
+
onResponse?: SimpleStreamOptions["onResponse"];
|
|
1116
|
+
createSessionId?: () => string;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/** Build a private capture runner over a detached message snapshot and provider session. */
|
|
1120
|
+
export function createAutoLearnCaptureRunner(
|
|
1121
|
+
options: AutoLearnCaptureRunnerOptions,
|
|
1122
|
+
): (content: string, signal?: AbortSignal) => Promise<void> {
|
|
1123
|
+
return async (content, signal) => {
|
|
1124
|
+
if (options.captureTools.length === 0 || signal?.aborted) return;
|
|
1125
|
+
const captureModel = options.sourceAgent.state.model;
|
|
1126
|
+
if (!captureModel) return;
|
|
1127
|
+
|
|
1128
|
+
const captureSessionId = options.createSessionId?.() ?? Bun.randomUUIDv7();
|
|
1129
|
+
const captureProviderSessionState = new Map<string, ProviderSessionState>();
|
|
1130
|
+
const captureMessages = options.sourceAgent.state.messages.map((message): AgentMessage => {
|
|
1131
|
+
if (message.role === "assistant") {
|
|
1132
|
+
return { ...message, responseId: undefined, providerPayload: undefined };
|
|
1133
|
+
}
|
|
1134
|
+
if (message.role === "user" || message.role === "developer") {
|
|
1135
|
+
return { ...message, providerPayload: undefined };
|
|
1136
|
+
}
|
|
1137
|
+
return message;
|
|
1138
|
+
});
|
|
1139
|
+
const captureAgent = options.createAgent({
|
|
1140
|
+
initialState: {
|
|
1141
|
+
systemPrompt: [...options.sourceAgent.state.systemPrompt],
|
|
1142
|
+
model: captureModel,
|
|
1143
|
+
thinkingLevel: options.sourceAgent.state.thinkingLevel,
|
|
1144
|
+
disableReasoning: options.sourceAgent.state.disableReasoning,
|
|
1145
|
+
tools: options.captureTools,
|
|
1146
|
+
messages: captureMessages,
|
|
1147
|
+
},
|
|
1148
|
+
sessionId: captureSessionId,
|
|
1149
|
+
promptCacheKey: captureSessionId,
|
|
1150
|
+
providerSessionState: captureProviderSessionState,
|
|
1151
|
+
getApiKey: requestModel => options.sourceAgent.getApiKey?.(requestModel),
|
|
1152
|
+
onPayload: options.onPayload,
|
|
1153
|
+
onResponse: options.onResponse,
|
|
1154
|
+
});
|
|
1155
|
+
captureAgent.setMetadataResolver(provider => options.sourceAgent.metadataForProvider(provider));
|
|
1156
|
+
const captureMessage: CustomMessage = {
|
|
1157
|
+
role: "custom",
|
|
1158
|
+
customType: "autolearn-nudge",
|
|
1159
|
+
content,
|
|
1160
|
+
display: false,
|
|
1161
|
+
attribution: "agent",
|
|
1162
|
+
timestamp: Date.now(),
|
|
1163
|
+
};
|
|
1164
|
+
const abortCapture = () => captureAgent.abort(signal?.reason);
|
|
1165
|
+
signal?.addEventListener("abort", abortCapture, { once: true });
|
|
1166
|
+
try {
|
|
1167
|
+
if (signal?.aborted) {
|
|
1168
|
+
abortCapture();
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
await captureAgent.prompt(captureMessage);
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
if (!signal?.aborted) throw error;
|
|
1174
|
+
} finally {
|
|
1175
|
+
signal?.removeEventListener("abort", abortCapture);
|
|
1176
|
+
for (const [providerKey, state] of captureProviderSessionState) {
|
|
1177
|
+
try {
|
|
1178
|
+
state.close();
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
logger.warn("Failed to close auto-learn capture provider state", {
|
|
1181
|
+
providerKey,
|
|
1182
|
+
error: String(error),
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
captureProviderSessionState.clear();
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Create an AgentSession with the specified options.
|
|
1192
|
+
*
|
|
1193
|
+
* @example
|
|
1194
|
+
* ```typescript
|
|
1195
|
+
* // Minimal - uses defaults
|
|
1196
|
+
* const { session } = await createAgentSession();
|
|
1197
|
+
*
|
|
1198
|
+
* // With explicit model
|
|
1199
|
+
* import { getModel } from '@oh-my-pi/pi-ai';
|
|
1200
|
+
* const { session } = await createAgentSession({
|
|
1201
|
+
* model: getModel('anthropic', 'claude-opus-4-5'),
|
|
1202
|
+
* thinkingLevel: 'high',
|
|
1203
|
+
* });
|
|
1204
|
+
*
|
|
1205
|
+
* // Continue previous session
|
|
1206
|
+
* const { session, modelFallbackMessage } = await createAgentSession({
|
|
1207
|
+
* continueSession: true,
|
|
1208
|
+
* });
|
|
1209
|
+
*
|
|
1210
|
+
* // Full control
|
|
1211
|
+
* const { session } = await createAgentSession({
|
|
1212
|
+
* model: myModel,
|
|
1213
|
+
* getApiKey: async () => Bun.env.MY_KEY,
|
|
1214
|
+
* systemPrompt: ['You are helpful.'],
|
|
1215
|
+
* tools: codingTools({ cwd: getProjectDir() }),
|
|
1216
|
+
* skills: [],
|
|
1217
|
+
* sessionManager: SessionManager.inMemory(),
|
|
1218
|
+
* });
|
|
1219
|
+
* ```
|
|
1220
|
+
*/
|
|
1221
|
+
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
|
1222
|
+
const rootMode = options.disableExtensionDiscovery ? "explicit-only" : "merge";
|
|
1223
|
+
return await withOmpExtensionRootScope(options.additionalExtensionPaths ?? [], rootMode, () =>
|
|
1224
|
+
createAgentSessionScoped(options),
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
async function createAgentSessionScoped(options: CreateAgentSessionOptions): Promise<CreateAgentSessionResult> {
|
|
1229
|
+
const cwd = options.cwd ?? getProjectDir();
|
|
1230
|
+
const agentDir = options.agentDir ?? getAgentDir();
|
|
1231
|
+
const eventBus = options.eventBus ?? new EventBus();
|
|
1232
|
+
|
|
1233
|
+
registerSshCleanup();
|
|
1234
|
+
registerEvalCleanup();
|
|
1235
|
+
|
|
1236
|
+
// Pin authStorage to modelRegistry.authStorage: ModelRegistry.getApiKey() routes refresh
|
|
1237
|
+
// failures through that instance, so any divergent storage handed to the bridge / mcpManager
|
|
1238
|
+
// / session would silently miss credential_disabled events.
|
|
1239
|
+
const modelRegistry =
|
|
1240
|
+
options.modelRegistry ??
|
|
1241
|
+
new ModelRegistry(options.authStorage ?? (await logger.time("discoverModels", discoverAuthStorage, agentDir)));
|
|
1242
|
+
// Track whether we internally created the authStorage so we can close it
|
|
1243
|
+
// if construction fails before the session takes ownership.
|
|
1244
|
+
const ownsAuthStorage = !options.authStorage && !options.modelRegistry;
|
|
1245
|
+
const authStorage = modelRegistry.authStorage;
|
|
1246
|
+
if (options.authStorage && options.authStorage !== authStorage) {
|
|
1247
|
+
throw new Error(
|
|
1248
|
+
"options.authStorage and options.modelRegistry.authStorage must be the same instance when both are provided",
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
// Subscribe before any getApiKey() call so startup model probes can't fire a
|
|
1252
|
+
// credential_disabled event past us. An embedder's constructor handler makes the
|
|
1253
|
+
// listener set non-empty from construction, which defeats AuthStorage's no-listener
|
|
1254
|
+
// buffer — so we can't rely on it to catch startup events for the extension runner.
|
|
1255
|
+
const startupCredentialDisabledEvents: CredentialDisabledEvent[] = [];
|
|
1256
|
+
let credentialDisabledTarget: ExtensionRunner | undefined;
|
|
1257
|
+
const unsubscribeCredentialDisabled: (() => void) | undefined = authStorage.onCredentialDisabled(event => {
|
|
1258
|
+
if (credentialDisabledTarget) {
|
|
1259
|
+
// Discard return: any handler error is routed through runner.onError listeners.
|
|
1260
|
+
void credentialDisabledTarget.emitCredentialDisabled(event);
|
|
1261
|
+
} else {
|
|
1262
|
+
startupCredentialDisabledEvents.push(event);
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
const settings = await (options.settings ??
|
|
1266
|
+
options.settingsManager ??
|
|
1267
|
+
logger.time("settings", Settings.init, { cwd, agentDir }));
|
|
1268
|
+
logger.time("initializeWithSettings", initializeWithSettings, settings);
|
|
1269
|
+
if (!options.modelRegistry) {
|
|
1270
|
+
modelRegistry.refreshInBackground();
|
|
1271
|
+
}
|
|
1272
|
+
// Kick off workspace tree discovery early. The native workspace scan returns
|
|
1273
|
+
// both the rendered-tree input and the AGENTS.md directory-context index, so
|
|
1274
|
+
// startup does not perform a second recursive filesystem search. Subagents
|
|
1275
|
+
// inherit the parent's resolved values via options.
|
|
1276
|
+
const STARTUP_SCAN_DEADLINE_MS = 5000;
|
|
1277
|
+
const includeWorkspaceTree = settings.get("includeWorkspaceTree") ?? false;
|
|
1278
|
+
const workspaceTreePromise: Promise<WorkspaceTree> = options.workspaceTree
|
|
1279
|
+
? Promise.resolve(options.workspaceTree)
|
|
1280
|
+
: includeWorkspaceTree
|
|
1281
|
+
? logger.time("buildWorkspaceTree", () => buildWorkspaceTree(cwd, { timeoutMs: STARTUP_SCAN_DEADLINE_MS }))
|
|
1282
|
+
: Promise.resolve({ rootPath: cwd, rendered: "", truncated: false, totalLines: 0, agentsMdFiles: [] });
|
|
1283
|
+
workspaceTreePromise.catch(() => {});
|
|
1284
|
+
|
|
1285
|
+
// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
|
|
1286
|
+
// at their respective consumer sites. Their work can overlap with model resolution, secret loading,
|
|
1287
|
+
// session-context build, tool creation, MCP discovery, and extension discovery.
|
|
1288
|
+
const contextFilesPromise = options.contextFiles
|
|
1289
|
+
? Promise.resolve(options.contextFiles)
|
|
1290
|
+
: logger.time("discoverContextFiles", discoverContextFiles, cwd, agentDir);
|
|
1291
|
+
contextFilesPromise.catch(() => {});
|
|
1292
|
+
const resolveRepoContext = async (repoCwd: string) => {
|
|
1293
|
+
try {
|
|
1294
|
+
return await resolveActiveRepoContext(repoCwd);
|
|
1295
|
+
} catch (err) {
|
|
1296
|
+
logger.debug("Failed to resolve active repo context", { err: String(err) });
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
const activeRepoContextPromise = logger.time("resolveActiveRepoContext", resolveRepoContext, cwd);
|
|
1301
|
+
activeRepoContextPromise.catch(() => {});
|
|
1302
|
+
const watchdogFilesPromise = logger.time("discoverWatchdogFiles", () => discoverWatchdogFiles(cwd, agentDir));
|
|
1303
|
+
watchdogFilesPromise.catch(() => {});
|
|
1304
|
+
const advisorConfigsPromise = logger.time("discoverAdvisorConfigs", () => discoverAdvisorConfigs(cwd, agentDir));
|
|
1305
|
+
advisorConfigsPromise.catch(() => {});
|
|
1306
|
+
const promptTemplatesPromise = options.promptTemplates
|
|
1307
|
+
? Promise.resolve(options.promptTemplates)
|
|
1308
|
+
: logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir);
|
|
1309
|
+
promptTemplatesPromise.catch(() => {});
|
|
1310
|
+
const slashCommandsPromise = options.slashCommands
|
|
1311
|
+
? Promise.resolve(options.slashCommands)
|
|
1312
|
+
: logger.time("discoverSlashCommands", discoverSlashCommands, cwd);
|
|
1313
|
+
slashCommandsPromise.catch(() => {});
|
|
1314
|
+
const skillsSettings = settings.getGroup("skills");
|
|
1315
|
+
const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
|
|
1316
|
+
const discoveredSkillsPromise =
|
|
1317
|
+
options.skills === undefined
|
|
1318
|
+
? logger.time("discoverSkills", discoverSkills, cwd, agentDir, {
|
|
1319
|
+
...skillsSettings,
|
|
1320
|
+
disabledExtensions: disabledExtensionIds,
|
|
1321
|
+
})
|
|
1322
|
+
: undefined;
|
|
1323
|
+
discoveredSkillsPromise?.catch(() => {});
|
|
1324
|
+
|
|
1325
|
+
// Initialize provider preferences from settings
|
|
1326
|
+
applyProviderGlobalsFromSettings(settings);
|
|
1327
|
+
|
|
1328
|
+
const sessionManager =
|
|
1329
|
+
options.sessionManager ??
|
|
1330
|
+
logger.time("sessionManager", () =>
|
|
1331
|
+
SessionManager.create(cwd, SessionManager.getDefaultSessionDir(cwd, agentDir)),
|
|
1332
|
+
);
|
|
1333
|
+
const configuredDirs = options.additionalDirectories
|
|
1334
|
+
? options.additionalDirectories
|
|
1335
|
+
: settings.get("workspace.additionalDirectories");
|
|
1336
|
+
if (configuredDirs.length > 0) {
|
|
1337
|
+
// Merge with any roots restored from the session header (resume/fork), not replace.
|
|
1338
|
+
const existing = sessionManager.getAdditionalDirectories();
|
|
1339
|
+
const merged = [...new Set([...existing, ...configuredDirs])];
|
|
1340
|
+
await sessionManager.setAdditionalDirectories(merged);
|
|
1341
|
+
}
|
|
1342
|
+
const providerSessionId = options.providerSessionId ?? sessionManager.getSessionId();
|
|
1343
|
+
const forkCacheShapeChanged =
|
|
1344
|
+
options.model !== undefined ||
|
|
1345
|
+
options.modelPattern !== undefined ||
|
|
1346
|
+
options.thinkingLevel !== undefined ||
|
|
1347
|
+
options.systemPrompt !== undefined ||
|
|
1348
|
+
options.customSystemPrompt !== undefined ||
|
|
1349
|
+
options.appendSystemPrompt !== undefined ||
|
|
1350
|
+
options.toolNames !== undefined ||
|
|
1351
|
+
options.customTools !== undefined;
|
|
1352
|
+
const inheritedPromptCacheKey = forkCacheShapeChanged
|
|
1353
|
+
? undefined
|
|
1354
|
+
: sessionManager.getHeader()?.providerPromptCacheKey;
|
|
1355
|
+
const providerPromptCacheKey = options.providerPromptCacheKey ?? inheritedPromptCacheKey;
|
|
1356
|
+
const providerPromptCacheKeySource =
|
|
1357
|
+
options.providerPromptCacheKey !== undefined
|
|
1358
|
+
? (options.providerPromptCacheKeySource ?? "explicit")
|
|
1359
|
+
: providerPromptCacheKey !== undefined
|
|
1360
|
+
? "fork"
|
|
1361
|
+
: undefined;
|
|
1362
|
+
// Startup model *selection* only needs to know whether auth is configured for
|
|
1363
|
+
// a candidate's provider — never the resolved key bytes. Use the synchronous,
|
|
1364
|
+
// side-effect-free probe (`hasConfiguredAuth`): it refreshes no OAuth tokens,
|
|
1365
|
+
// executes no `!command` keys, and issues no auth-broker requests. Resolving the
|
|
1366
|
+
// real key here (`getApiKey`) blocks resume on those network paths — a slow or
|
|
1367
|
+
// unreachable OAuth/broker endpoint stalls startup for the full ~10s refresh
|
|
1368
|
+
// timeout per candidate (observed as a hang in `restoreSessionModel`). The real
|
|
1369
|
+
// key is resolved lazily per request via ModelRegistry.resolver.
|
|
1370
|
+
const hasModelAuth = (candidate: Model): boolean => modelRegistry.hasConfiguredAuth(candidate);
|
|
1371
|
+
|
|
1372
|
+
// Load and create secret obfuscator early so resumed session state and prompt warnings
|
|
1373
|
+
// reflect actual loaded secrets, not just the setting toggle.
|
|
1374
|
+
const obfuscator: SecretObfuscator | undefined = settings.get("secrets.enabled")
|
|
1375
|
+
? await buildSecretObfuscator(cwd, agentDir, options.agentDir)
|
|
1376
|
+
: undefined;
|
|
1377
|
+
const secretsEnabled = obfuscator?.hasSecrets() === true;
|
|
1378
|
+
|
|
1379
|
+
// An abnormal process exit after a non-terminal message tail is durable
|
|
1380
|
+
// evidence that the old process can no longer finish that turn. Preserve the
|
|
1381
|
+
// partial transcript and append one terminal aborted assistant record before
|
|
1382
|
+
// rebuilding runtime context. The helper is idempotent once that record exists.
|
|
1383
|
+
let existingBranch = logger.time("getSessionBranch", () => sessionManager.getBranch());
|
|
1384
|
+
const interruptedTurnAbort = createInterruptedTurnAbortMessage(existingBranch);
|
|
1385
|
+
if (interruptedTurnAbort) {
|
|
1386
|
+
sessionManager.appendMessage(interruptedTurnAbort);
|
|
1387
|
+
existingBranch = logger.time("getRecoveredSessionBranch", () => sessionManager.getBranch());
|
|
1388
|
+
}
|
|
1389
|
+
let existingSession = logger.time("loadSessionContext", () =>
|
|
1390
|
+
deobfuscateSessionContext(sessionManager.buildSessionContext(), obfuscator),
|
|
1391
|
+
);
|
|
1392
|
+
const hasExistingSession = existingBranch.length > 0;
|
|
1393
|
+
const hasThinkingEntry = existingBranch.some(entry => entry.type === "thinking_level_change");
|
|
1394
|
+
const hasServiceTierEntry = existingBranch.some(entry => entry.type === "service_tier_change");
|
|
1395
|
+
|
|
1396
|
+
const deferredModelPatterns = Array.isArray(options.modelPattern)
|
|
1397
|
+
? options.modelPattern.map(pattern => pattern.trim()).filter(Boolean)
|
|
1398
|
+
: options.modelPattern?.trim()
|
|
1399
|
+
? [options.modelPattern.trim()]
|
|
1400
|
+
: [];
|
|
1401
|
+
const hasExplicitModel = options.model !== undefined || deferredModelPatterns.length > 0;
|
|
1402
|
+
const modelMatchPreferences = getModelMatchPreferences(settings);
|
|
1403
|
+
const allowedModels = await logger.time("resolveAllowedModels", () =>
|
|
1404
|
+
resolveAllowedModels(modelRegistry, settings, modelMatchPreferences),
|
|
1405
|
+
);
|
|
1406
|
+
let defaultRoleSpec = logger.time("resolveDefaultModelRole", () =>
|
|
1407
|
+
resolveModelRoleValue(settings.getModelRole("default"), allowedModels, {
|
|
1408
|
+
settings,
|
|
1409
|
+
matchPreferences: modelMatchPreferences,
|
|
1410
|
+
}),
|
|
1411
|
+
);
|
|
1412
|
+
let model = options.model;
|
|
1413
|
+
let modelFallbackMessage: string | undefined;
|
|
1414
|
+
let initialRetryFallback: InitialRetryFallbackState | undefined;
|
|
1415
|
+
// Identify session model strings to restore in fallback order. We do an
|
|
1416
|
+
// initial pass here so model-dependent setup (thinking-level resolution,
|
|
1417
|
+
// host preconnect) can use the restored model; extension-registered
|
|
1418
|
+
// providers aren't visible yet, so we retry the preferred candidates once
|
|
1419
|
+
// extensions register below.
|
|
1420
|
+
const sessionModelStrings =
|
|
1421
|
+
!hasExplicitModel && hasExistingSession
|
|
1422
|
+
? getRestorableSessionModels(existingSession.models, sessionManager.getLastModelChangeRole())
|
|
1423
|
+
: [];
|
|
1424
|
+
let restoredSessionModelIndex = -1;
|
|
1425
|
+
let restoredSessionThinkingLevel: ConfiguredThinkingLevel | undefined;
|
|
1426
|
+
if (!hasExplicitModel && !model && sessionModelStrings.length > 0) {
|
|
1427
|
+
logger.time("restoreSessionModel", () => {
|
|
1428
|
+
let failedSessionModel: string | undefined;
|
|
1429
|
+
for (let i = 0; i < sessionModelStrings.length; i++) {
|
|
1430
|
+
const sessionModelStr = sessionModelStrings[i];
|
|
1431
|
+
const parsedModel = parseModelString(sessionModelStr, {
|
|
1432
|
+
allowMaxSuffix: true,
|
|
1433
|
+
allowAutoAlias: true,
|
|
1434
|
+
isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
|
|
1435
|
+
});
|
|
1436
|
+
if (!parsedModel) {
|
|
1437
|
+
failedSessionModel ??= sessionModelStr;
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
const restoredModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
|
|
1442
|
+
if (restoredModel && hasModelAuth(restoredModel)) {
|
|
1443
|
+
model = restoredModel;
|
|
1444
|
+
restoredSessionModelIndex = i;
|
|
1445
|
+
restoredSessionThinkingLevel = parsedModel.thinkingLevel;
|
|
1446
|
+
break;
|
|
1447
|
+
}
|
|
1448
|
+
failedSessionModel ??= sessionModelStr;
|
|
1449
|
+
}
|
|
1450
|
+
if (failedSessionModel) {
|
|
1451
|
+
modelFallbackMessage = `Could not restore model ${failedSessionModel}`;
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// If still no model, try settings default.
|
|
1457
|
+
// Skip settings fallback when an explicit model was requested.
|
|
1458
|
+
if (!hasExplicitModel && !model && defaultRoleSpec.model) {
|
|
1459
|
+
const settingsDefaultModel = defaultRoleSpec.model;
|
|
1460
|
+
logger.time("resolveSettingsDefaultModel", () => {
|
|
1461
|
+
// defaultRoleSpec.model already comes from modelRegistry.getAvailable(),
|
|
1462
|
+
// so re-validating auth here just repeats the expensive lookup path.
|
|
1463
|
+
model = settingsDefaultModel;
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const taskDepth = options.taskDepth ?? 0;
|
|
1468
|
+
|
|
1469
|
+
// Resolves the session/agent thinking level using the same precedence we
|
|
1470
|
+
// apply at startup: explicit option → persisted session entry → restored
|
|
1471
|
+
// model selector suffix → default role's explicit selector → selected
|
|
1472
|
+
// model's defaultLevel → global settings default. Run again after extension
|
|
1473
|
+
// role reclaim so the final model's own defaults aren't masked by an earlier
|
|
1474
|
+
// fallback model's.
|
|
1475
|
+
const pickInitialThinkingLevel = (selectedModel: Model | undefined): ConfiguredThinkingLevel | undefined => {
|
|
1476
|
+
let level = options.thinkingLevel;
|
|
1477
|
+
if (level === undefined && hasExistingSession && hasThinkingEntry) {
|
|
1478
|
+
level =
|
|
1479
|
+
parseConfiguredThinkingLevel(existingSession.configuredThinkingLevel) ??
|
|
1480
|
+
parseThinkingLevel(existingSession.thinkingLevel);
|
|
1481
|
+
}
|
|
1482
|
+
if (level === undefined && !hasThinkingEntry && restoredSessionThinkingLevel !== undefined) {
|
|
1483
|
+
level = restoredSessionThinkingLevel;
|
|
1484
|
+
}
|
|
1485
|
+
if (level === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
|
|
1486
|
+
level = defaultRoleSpec.thinkingLevel;
|
|
1487
|
+
}
|
|
1488
|
+
if (level === undefined && selectedModel?.thinking?.defaultLevel !== undefined) {
|
|
1489
|
+
level = selectedModel.thinking.defaultLevel;
|
|
1490
|
+
}
|
|
1491
|
+
if (level === undefined) {
|
|
1492
|
+
level = parseConfiguredThinkingLevel(settings.get("defaultThinkingLevel"));
|
|
1493
|
+
}
|
|
1494
|
+
return level;
|
|
1495
|
+
};
|
|
1496
|
+
let thinkingLevel = pickInitialThinkingLevel(model);
|
|
1497
|
+
let autoThinking = thinkingLevel === AUTO_THINKING;
|
|
1498
|
+
// Concrete level the agent/session start with. With `auto` this is the
|
|
1499
|
+
// provisional level shown until the first per-turn classification resolves;
|
|
1500
|
+
// `auto` itself stays a session-only concept handled by AgentSession.
|
|
1501
|
+
let effectiveThinkingLevel: ThinkingLevel | undefined = concreteThinkingLevel(thinkingLevel);
|
|
1502
|
+
if (model) {
|
|
1503
|
+
const resolvedModel = model;
|
|
1504
|
+
effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
1505
|
+
autoThinking
|
|
1506
|
+
? resolveProvisionalAutoLevel(resolvedModel)
|
|
1507
|
+
: resolveThinkingLevelForModel(resolvedModel, effectiveThinkingLevel),
|
|
1508
|
+
);
|
|
1509
|
+
// Fire-and-forget TLS+H2 handshake to the model's host so it overlaps
|
|
1510
|
+
// with the rest of session setup (extension/skill load, tool registry,
|
|
1511
|
+
// system prompt build). Without this, the first `fetch(...)` pays the
|
|
1512
|
+
// full handshake serially — 100–300 ms transcontinental for
|
|
1513
|
+
// api.anthropic.com from a residential IP. Every mode benefits
|
|
1514
|
+
// (interactive, print, rpc, acp).
|
|
1515
|
+
preconnectModelHost(model.baseUrl);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
let skills: Skill[];
|
|
1519
|
+
let skillWarnings: SkillWarning[];
|
|
1520
|
+
if (options.skills !== undefined) {
|
|
1521
|
+
skills = options.skills;
|
|
1522
|
+
skillWarnings = [];
|
|
1523
|
+
} else {
|
|
1524
|
+
const discovered = await (discoveredSkillsPromise ?? Promise.resolve({ skills: [], warnings: [] }));
|
|
1525
|
+
skills = discovered.skills;
|
|
1526
|
+
skillWarnings = discovered.warnings;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
// Discover rules and bucket them in one pass to avoid repeated scans over large rule sets.
|
|
1530
|
+
const { ttsrManager, rulebookRules, alwaysApplyRules, allRules } = await logger.time(
|
|
1531
|
+
"discoverTtsrRules",
|
|
1532
|
+
async () => {
|
|
1533
|
+
const { TtsrManager } = await import("./export/ttsr");
|
|
1534
|
+
const ttsrSettings = settings.getGroup("ttsr");
|
|
1535
|
+
const ttsrManager = new TtsrManager(ttsrSettings);
|
|
1536
|
+
const rulesResult =
|
|
1537
|
+
options.rules !== undefined
|
|
1538
|
+
? { items: options.rules, warnings: undefined }
|
|
1539
|
+
: await loadCapability<Rule>(ruleCapability.id, { cwd });
|
|
1540
|
+
const { rulebookRules, alwaysApplyRules } = bucketRules(rulesResult.items, ttsrManager, {
|
|
1541
|
+
builtinRules: ttsrSettings.builtinRules,
|
|
1542
|
+
disabledRules: ttsrSettings.disabledRules,
|
|
1543
|
+
});
|
|
1544
|
+
if (existingSession.injectedTtsrRules.length > 0) {
|
|
1545
|
+
ttsrManager.restoreInjected(existingSession.injectedTtsrRules);
|
|
1546
|
+
}
|
|
1547
|
+
return { ttsrManager, rulebookRules, alwaysApplyRules, allRules: rulesResult.items };
|
|
1548
|
+
},
|
|
1549
|
+
);
|
|
1550
|
+
|
|
1551
|
+
// Resolve contextFiles up-front (it's needed before tool creation). The
|
|
1552
|
+
// workspace tree scan is slow on large repos and we MUST NOT block startup on
|
|
1553
|
+
// it. On timeout we forward `undefined` to ToolSession; buildSystemPromptInternal
|
|
1554
|
+
// will re-race the same promise through its own withDeadline path. Background
|
|
1555
|
+
// work continues so caches still warm.
|
|
1556
|
+
const raceWithDeadline = async <T>(name: string, work: Promise<T>): Promise<T | undefined> => {
|
|
1557
|
+
let timedOut = false;
|
|
1558
|
+
const result = await Promise.race([
|
|
1559
|
+
work,
|
|
1560
|
+
Bun.sleep(STARTUP_SCAN_DEADLINE_MS).then(() => {
|
|
1561
|
+
timedOut = true;
|
|
1562
|
+
return undefined;
|
|
1563
|
+
}),
|
|
1564
|
+
]);
|
|
1565
|
+
if (timedOut) {
|
|
1566
|
+
logger.warn("Startup scan exceeded deadline; deferring to system prompt fallback", {
|
|
1567
|
+
name,
|
|
1568
|
+
timeoutMs: STARTUP_SCAN_DEADLINE_MS,
|
|
1569
|
+
cwd,
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
return result;
|
|
1573
|
+
};
|
|
1574
|
+
const [initialContextFiles, resolvedWorkspaceTree, watchdogFiles, initialActiveRepoContext, discoveredAdvisors] =
|
|
1575
|
+
await Promise.all([
|
|
1576
|
+
contextFilesPromise,
|
|
1577
|
+
raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
|
|
1578
|
+
watchdogFilesPromise,
|
|
1579
|
+
activeRepoContextPromise,
|
|
1580
|
+
advisorConfigsPromise,
|
|
1581
|
+
]);
|
|
1582
|
+
let contextFiles = initialContextFiles;
|
|
1583
|
+
|
|
1584
|
+
let agent: Agent;
|
|
1585
|
+
let session!: AgentSession;
|
|
1586
|
+
let hasSession = false;
|
|
1587
|
+
let hasRegistered = false;
|
|
1588
|
+
const restrictToolNames = options.restrictToolNames === true;
|
|
1589
|
+
const enableLsp = options.enableLsp ?? !restrictToolNames;
|
|
1590
|
+
const lspReadOnly = options.lspReadOnly ?? restrictToolNames;
|
|
1591
|
+
const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
|
|
1592
|
+
// Only the first top-level session in a process owns an AsyncJobManager.
|
|
1593
|
+
// Subagents inherit the parent's manager via `AsyncJobManager.instance()`
|
|
1594
|
+
// (set below), and any additional top-level session spun up in-process
|
|
1595
|
+
// (e.g. the agent-creation architect in `agent-dashboard.ts`) must share
|
|
1596
|
+
// the live singleton — otherwise its dispose path would clobber the
|
|
1597
|
+
// owning session's manager and break the `task`/`bash` async paths
|
|
1598
|
+
// (issue #1923). The `instance()` guard means later sessions also skip
|
|
1599
|
+
// constructing an orphaned manager that nothing would ever route to.
|
|
1600
|
+
// Delivery is owner-routed: every AgentSession registers its own sink
|
|
1601
|
+
// (see session/async-job-delivery.ts), so the manager takes no default
|
|
1602
|
+
// onJobComplete here.
|
|
1603
|
+
const asyncJobManager =
|
|
1604
|
+
!options.parentTaskPrefix && !AsyncJobManager.instance()
|
|
1605
|
+
? new AsyncJobManager({ maxRunningJobs: asyncMaxJobs })
|
|
1606
|
+
: undefined;
|
|
1607
|
+
|
|
1608
|
+
const scopedAsyncJobManager = asyncJobManager ?? (options.parentTaskPrefix ? AsyncJobManager.instance() : undefined);
|
|
1609
|
+
|
|
1610
|
+
const agentRegistry = options.agentRegistry ?? AgentRegistry.global();
|
|
1611
|
+
const resolvedAgentId = options.agentId ?? options.parentTaskPrefix ?? MAIN_AGENT_ID;
|
|
1612
|
+
const resolvedAgentDisplayName =
|
|
1613
|
+
options.agentDisplayName ?? ((options.taskDepth ?? 0) > 0 || options.parentTaskPrefix ? "sub" : "main");
|
|
1614
|
+
const agentKind = (options.taskDepth ?? 0) > 0 || options.parentTaskPrefix ? ("sub" as const) : ("main" as const);
|
|
1615
|
+
let registeredAgentRef: AgentRef | undefined;
|
|
1616
|
+
/**
|
|
1617
|
+
* Forget the agent ref on teardown — unless it is a retained terminal ref.
|
|
1618
|
+
* Parking disposes the session but keeps the ref addressable (history://,
|
|
1619
|
+
* revive); a hard kill leaves it as a terminal `aborted` tombstone. Both are
|
|
1620
|
+
* detached (session === null) by the time dispose runs, per the AgentRef
|
|
1621
|
+
* invariant, so preserving them never keeps a disposed session reachable — an
|
|
1622
|
+
* aborted ref that still holds a live session is a bug and is unregistered
|
|
1623
|
+
* rather than handed to ensureLive. Only process teardown / a plain release
|
|
1624
|
+
* unregisters.
|
|
1625
|
+
*/
|
|
1626
|
+
const unregisterUnlessParked = (): void => {
|
|
1627
|
+
const ref = registeredAgentRef;
|
|
1628
|
+
if (!ref || agentRegistry.get(resolvedAgentId) !== ref) return;
|
|
1629
|
+
if (ref.status === "parked" || (ref.status === "aborted" && !ref.session)) return;
|
|
1630
|
+
if (AgentLifecycleManager.global().isParking(resolvedAgentId, ref)) return;
|
|
1631
|
+
agentRegistry.unregister(resolvedAgentId, ref);
|
|
1632
|
+
};
|
|
1633
|
+
const evalKernelOwnerId = `agent-session:${Snowflake.next()}`;
|
|
1634
|
+
|
|
1635
|
+
try {
|
|
1636
|
+
const getActiveModelString = (): string | undefined => {
|
|
1637
|
+
const activeModel = agent?.state.model;
|
|
1638
|
+
if (activeModel) return formatModelString(activeModel);
|
|
1639
|
+
if (model) return formatModelString(model);
|
|
1640
|
+
return undefined;
|
|
1641
|
+
};
|
|
1642
|
+
// Per-path mutation counter shared across edit/write tools. Late-diagnostics
|
|
1643
|
+
// entries capture it at fetch time and are dropped at injection if a newer
|
|
1644
|
+
// mutation (any tool) bumped it in the meantime.
|
|
1645
|
+
const fileMutationVersions = new Map<string, number>();
|
|
1646
|
+
const disposeCallbacks = new Set<() => void>();
|
|
1647
|
+
const activeToolNames = new Set<string>();
|
|
1648
|
+
const toolRegistry = new Map<string, Tool>();
|
|
1649
|
+
const setActiveToolNames = (names: Iterable<string>): void => {
|
|
1650
|
+
activeToolNames.clear();
|
|
1651
|
+
for (const name of names) {
|
|
1652
|
+
activeToolNames.add(name);
|
|
1653
|
+
}
|
|
1654
|
+
};
|
|
1655
|
+
const toolSession: ToolSession = {
|
|
1656
|
+
get cwd() {
|
|
1657
|
+
return sessionManager.getCwd();
|
|
1658
|
+
},
|
|
1659
|
+
isToolActive: name => activeToolNames.has(name),
|
|
1660
|
+
setActiveToolNames,
|
|
1661
|
+
toolRegistry,
|
|
1662
|
+
hasUI: options.hasUI ?? false,
|
|
1663
|
+
getApiKey: options.getApiKey,
|
|
1664
|
+
get additionalDirectories() {
|
|
1665
|
+
return sessionManager.getAdditionalDirectories();
|
|
1666
|
+
},
|
|
1667
|
+
enableLsp,
|
|
1668
|
+
lspReadOnly,
|
|
1669
|
+
enableIrc: restrictToolNames ? false : options.enableIrc,
|
|
1670
|
+
restrictToolNames,
|
|
1671
|
+
get hasEditTool() {
|
|
1672
|
+
const requestedToolNames = options.toolNames ? normalizeToolNames(options.toolNames) : undefined;
|
|
1673
|
+
return restrictToolNames
|
|
1674
|
+
? requestedToolNames?.includes("edit") === true
|
|
1675
|
+
: !requestedToolNames || requestedToolNames.includes("edit");
|
|
1676
|
+
},
|
|
1677
|
+
skipPythonPreflight: options.skipPythonPreflight,
|
|
1678
|
+
contextFiles,
|
|
1679
|
+
workspaceTree: resolvedWorkspaceTree,
|
|
1680
|
+
get skills() {
|
|
1681
|
+
return session?.skills ?? skills;
|
|
1682
|
+
},
|
|
1683
|
+
refreshSkills: () => session.refreshSkills(),
|
|
1684
|
+
rules: allRules,
|
|
1685
|
+
eventBus,
|
|
1686
|
+
outputSchema: options.outputSchema,
|
|
1687
|
+
outputSchemaMode: options.outputSchemaMode,
|
|
1688
|
+
requireYieldTool: options.requireYieldTool,
|
|
1689
|
+
prewalkArmed: options.prewalk !== undefined,
|
|
1690
|
+
taskDepth: options.taskDepth ?? 0,
|
|
1691
|
+
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
1692
|
+
sessionManager,
|
|
1693
|
+
getEvalKernelOwnerId: () => evalKernelOwnerId,
|
|
1694
|
+
getEvalSessionId: () =>
|
|
1695
|
+
session?.getEvalSessionId() ?? options.parentEvalSessionId ?? defaultEvalSessionId(toolSession),
|
|
1696
|
+
assertEvalExecutionAllowed: () => session?.assertEvalExecutionAllowed(),
|
|
1697
|
+
trackEvalExecution: (execution, abortController) =>
|
|
1698
|
+
session ? session.trackEvalExecution(execution, abortController) : execution,
|
|
1699
|
+
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
1700
|
+
isDisposed: () => session?.isDisposed ?? false,
|
|
1701
|
+
getHindsightSessionState: () => session?.getHindsightSessionState(),
|
|
1702
|
+
getMnemopiSessionState: () => session?.getMnemopiSessionState(),
|
|
1703
|
+
getAgentId: () => resolvedAgentId,
|
|
1704
|
+
getToolByName: name => session?.getToolByName(name),
|
|
1705
|
+
agentRegistry,
|
|
1706
|
+
// The global lifecycle releases through AgentRegistry.global(); wiring it
|
|
1707
|
+
// onto a caller-supplied registry would report a cancel while releasing an
|
|
1708
|
+
// unrelated global ref. With no lifecycle, hub cancel falls back to
|
|
1709
|
+
// dispose + unregister on the session's own registry.
|
|
1710
|
+
agentLifecycle: options.agentRegistry ? undefined : () => AgentLifecycleManager.global(),
|
|
1711
|
+
getSessionSpawns: () => options.spawns ?? "*",
|
|
1712
|
+
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
1713
|
+
getActiveModelString,
|
|
1714
|
+
getActiveModel: () => agent?.state.model ?? model,
|
|
1715
|
+
getInspectImageModeOverride: () => session?.getInspectImageModeOverride(),
|
|
1716
|
+
getServiceTierByFamily: () => session?.serviceTierByFamily,
|
|
1717
|
+
getImageAttachments: () => session?.getImageAttachments() ?? [],
|
|
1718
|
+
getPlanModeState: () => session?.getPlanModeState(),
|
|
1719
|
+
getPlanReferencePath: () => session?.getPlanReferencePath() ?? "local://PLAN.md",
|
|
1720
|
+
getGoalModeState: () => session?.getGoalModeState(),
|
|
1721
|
+
getGoalRuntime: () => session?.goalRuntime,
|
|
1722
|
+
getUsageStatistics: () => sessionManager.getUsageStatistics(),
|
|
1723
|
+
getTurnBudget: () => sessionManager.getTurnBudget(),
|
|
1724
|
+
recordEvalSubagentUsage: output => sessionManager.recordEvalSubagentOutput(output),
|
|
1725
|
+
getClientBridge: () => session?.clientBridge,
|
|
1726
|
+
queueDeferredDiagnostics: entry => session?.yieldQueue.enqueue(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, entry),
|
|
1727
|
+
queueLaunchCompletion: notification =>
|
|
1728
|
+
session?.queueLaunchCompletion(notification) ??
|
|
1729
|
+
Promise.reject(new Error("Session unavailable for launch completion delivery")),
|
|
1730
|
+
registerDisposeCallback: callback => {
|
|
1731
|
+
disposeCallbacks.add(callback);
|
|
1732
|
+
return () => disposeCallbacks.delete(callback);
|
|
1733
|
+
},
|
|
1734
|
+
registerSessionChangeCallback: callback => session?.registerSessionChangeCallback(callback),
|
|
1735
|
+
bumpFileMutationVersion: path => {
|
|
1736
|
+
const next = (fileMutationVersions.get(path) ?? 0) + 1;
|
|
1737
|
+
fileMutationVersions.set(path, next);
|
|
1738
|
+
return next;
|
|
1739
|
+
},
|
|
1740
|
+
getFileMutationVersion: path => fileMutationVersions.get(path) ?? 0,
|
|
1741
|
+
getTodoPhases: () => session.getTodoPhases(),
|
|
1742
|
+
setTodoPhases: phases => session.setTodoPhases(phases),
|
|
1743
|
+
getCheckpointState: () => session.getCheckpointState(),
|
|
1744
|
+
setCheckpointState: state => session.setCheckpointState(state ?? undefined),
|
|
1745
|
+
getLastCompletedRewind: () => session.getLastCompletedRewind(),
|
|
1746
|
+
getToolChoiceQueue: () => session.toolChoiceQueue,
|
|
1747
|
+
buildToolChoice: name => {
|
|
1748
|
+
const m = session.model;
|
|
1749
|
+
return m ? buildNamedToolChoice(name, m) : undefined;
|
|
1750
|
+
},
|
|
1751
|
+
steer: msg =>
|
|
1752
|
+
session.agent.steer({
|
|
1753
|
+
role: "custom",
|
|
1754
|
+
customType: msg.customType,
|
|
1755
|
+
content: msg.content,
|
|
1756
|
+
display: false,
|
|
1757
|
+
details: msg.details,
|
|
1758
|
+
attribution: "agent",
|
|
1759
|
+
timestamp: Date.now(),
|
|
1760
|
+
}),
|
|
1761
|
+
peekQueueInvoker: () => session.peekQueueInvoker(),
|
|
1762
|
+
peekPendingInvoker: () => session.peekPendingInvoker(),
|
|
1763
|
+
clearPendingInvokers: () => session.clearPendingInvokers(),
|
|
1764
|
+
peekPlanProposalHandler: () => session.peekPlanProposalHandler(),
|
|
1765
|
+
setPlanProposalHandler: handler => session.setPlanProposalHandler(handler),
|
|
1766
|
+
allocateOutputArtifact: async toolType => {
|
|
1767
|
+
try {
|
|
1768
|
+
return await sessionManager.allocateArtifactPath(toolType);
|
|
1769
|
+
} catch {
|
|
1770
|
+
return {};
|
|
1771
|
+
}
|
|
1772
|
+
},
|
|
1773
|
+
getArtifactManager: () => sessionManager.getArtifactManager(),
|
|
1774
|
+
settings,
|
|
1775
|
+
authStorage,
|
|
1776
|
+
modelRegistry,
|
|
1777
|
+
getTelemetry: () => agent?.telemetry,
|
|
1778
|
+
// Subagents inherit the singleton (the parent's manager) so their bash/task
|
|
1779
|
+
// completions still flow into the spawning conversation's yieldQueue.
|
|
1780
|
+
// Secondary in-process top-level sessions (no parentTaskPrefix, no
|
|
1781
|
+
// constructed manager because the singleton was already installed) leave
|
|
1782
|
+
// this undefined so tools and session job snapshots refuse async work
|
|
1783
|
+
// instead of silently routing into the owning session (issue #1923).
|
|
1784
|
+
asyncJobManager: scopedAsyncJobManager,
|
|
1785
|
+
};
|
|
1786
|
+
|
|
1787
|
+
// Wire process-wide internal URL singletons owned by their real classes.
|
|
1788
|
+
// Top-level sessions install the active snapshots; subagents inherit them.
|
|
1789
|
+
// Artifact and agent-output URLs resolve via `AgentRegistry.global()` —
|
|
1790
|
+
// the protocol handlers walk each ref's `sessionManager.getArtifactsDir()`,
|
|
1791
|
+
// which collapses to the parent's dir for subagents (they adopt the
|
|
1792
|
+
// parent's ArtifactManager) so one lookup hits everything.
|
|
1793
|
+
const getArtifactsDir = () => sessionManager.getArtifactsDir();
|
|
1794
|
+
if (!options.parentTaskPrefix) {
|
|
1795
|
+
setActiveSkills(skills);
|
|
1796
|
+
// Include TTSR rules so `rule://<name>` can resolve them too. They are
|
|
1797
|
+
// registered with the manager and bucketed out before rulebook/always,
|
|
1798
|
+
// so without this a TTSR-only rule (e.g. a triggered builtin) is not
|
|
1799
|
+
// addressable and `rule://` reports "Available: none".
|
|
1800
|
+
setActiveRules([...rulebookRules, ...alwaysApplyRules, ...ttsrManager.getRules()]);
|
|
1801
|
+
if (asyncJobManager) AsyncJobManager.setInstance(asyncJobManager);
|
|
1802
|
+
}
|
|
1803
|
+
const localProtocolOptions = options.localProtocolOptions ?? {
|
|
1804
|
+
getArtifactsDir,
|
|
1805
|
+
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
1806
|
+
};
|
|
1807
|
+
if (options.localProtocolOptions && !options.parentTaskPrefix) {
|
|
1808
|
+
LocalProtocolHandler.setOverride(options.localProtocolOptions);
|
|
1809
|
+
}
|
|
1810
|
+
toolSession.getArtifactsDir = getArtifactsDir;
|
|
1811
|
+
toolSession.localProtocolOptions = localProtocolOptions;
|
|
1812
|
+
toolSession.agentOutputManager = new AgentOutputManager(
|
|
1813
|
+
getArtifactsDir,
|
|
1814
|
+
options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
|
|
1815
|
+
);
|
|
1816
|
+
|
|
1817
|
+
// Create built-in tools (already wrapped with meta notice formatting)
|
|
1818
|
+
await logger.time("createAllTools", createTools, toolSession, options.toolNames);
|
|
1819
|
+
|
|
1820
|
+
// Restricted sessions cannot inherit or discover MCP capabilities.
|
|
1821
|
+
const enableMCP = !restrictToolNames && (options.enableMCP ?? true);
|
|
1822
|
+
let mcpManager: MCPManager | undefined = enableMCP ? options.mcpManager : undefined;
|
|
1823
|
+
toolSession.mcpManager = mcpManager;
|
|
1824
|
+
toolSession.enableMCP = enableMCP;
|
|
1825
|
+
const deferMCPDiscoveryForUI = enableMCP && !mcpManager && options.hasUI === true;
|
|
1826
|
+
const customTools: CustomTool[] = [];
|
|
1827
|
+
let startDeferredMCPDiscovery: ((liveSession: AgentSession) => void) | undefined;
|
|
1828
|
+
const startupQuiet = settings.get("startup.quiet");
|
|
1829
|
+
const onMCPStatus = (event: McpConnectionStatusEvent) => {
|
|
1830
|
+
if (!options.hasUI || startupQuiet) return;
|
|
1831
|
+
if (event.type === "connecting" && event.serverNames.length === 0) return;
|
|
1832
|
+
eventBus.emit(MCP_CONNECTION_STATUS_EVENT_CHANNEL, event);
|
|
1833
|
+
};
|
|
1834
|
+
const mcpDiscoverOptions = {
|
|
1835
|
+
onStatus: onMCPStatus,
|
|
1836
|
+
enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
|
|
1837
|
+
// Always filter Exa - we have native integration
|
|
1838
|
+
filterExa: true,
|
|
1839
|
+
// Filter browser MCP servers when builtin browser tool is active
|
|
1840
|
+
filterBrowser: settings.get("browser.enabled") ?? false,
|
|
1841
|
+
};
|
|
1842
|
+
if (enableMCP && !mcpManager) {
|
|
1843
|
+
if (deferMCPDiscoveryForUI) {
|
|
1844
|
+
const cacheStorage = settings.getStorage();
|
|
1845
|
+
mcpManager = new MCPManager(cwd, cacheStorage ? new MCPToolCache(cacheStorage) : null);
|
|
1846
|
+
mcpManager.setAuthStorage(authStorage);
|
|
1847
|
+
toolSession.mcpManager = mcpManager;
|
|
1848
|
+
|
|
1849
|
+
if (settings.get("mcp.notifications")) {
|
|
1850
|
+
mcpManager.setNotificationsEnabled(true);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
const deferredMCPManager = mcpManager;
|
|
1854
|
+
startDeferredMCPDiscovery = liveSession => {
|
|
1855
|
+
void (async () => {
|
|
1856
|
+
try {
|
|
1857
|
+
const mcpResult = await logger.time("discoverAndLoadMCPTools", () =>
|
|
1858
|
+
deferredMCPManager.discoverAndConnect(mcpDiscoverOptions),
|
|
1859
|
+
);
|
|
1860
|
+
// The session can be torn down while servers are still connecting.
|
|
1861
|
+
// Don't resurrect tools on a disposed session, and don't leak the
|
|
1862
|
+
// transports/subprocesses the connect just spawned.
|
|
1863
|
+
if (liveSession.isDisposed) {
|
|
1864
|
+
await deferredMCPManager.disconnectAll();
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
applyMCPEnvironment(mcpResult);
|
|
1868
|
+
logMCPLoadErrors(mcpResult.errors);
|
|
1869
|
+
// Connected MCP tools are enabled and mounted under xd:// devices.
|
|
1870
|
+
await liveSession.refreshMCPTools(mcpResult.tools);
|
|
1871
|
+
} catch (error) {
|
|
1872
|
+
logger.error("MCP tool load failed", {
|
|
1873
|
+
path: ".mcp.json",
|
|
1874
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
})();
|
|
1878
|
+
};
|
|
1879
|
+
} else {
|
|
1880
|
+
const mcpResult = await logger.time("discoverAndLoadMCPTools", discoverAndLoadMCPTools, cwd, {
|
|
1881
|
+
...mcpDiscoverOptions,
|
|
1882
|
+
cacheStorage: settings.getStorage(),
|
|
1883
|
+
authStorage,
|
|
1884
|
+
});
|
|
1885
|
+
mcpManager = mcpResult.manager;
|
|
1886
|
+
toolSession.mcpManager = mcpManager;
|
|
1887
|
+
|
|
1888
|
+
if (settings.get("mcp.notifications")) {
|
|
1889
|
+
mcpManager.setNotificationsEnabled(true);
|
|
1890
|
+
}
|
|
1891
|
+
applyMCPEnvironment(mcpResult);
|
|
1892
|
+
|
|
1893
|
+
// Log MCP errors
|
|
1894
|
+
for (const { path, error } of mcpResult.errors) {
|
|
1895
|
+
logger.error("MCP tool load failed", { path, error });
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
if (mcpResult.tools.length > 0) {
|
|
1899
|
+
// MCP tools are LoadedCustomTool, extract the tool property
|
|
1900
|
+
customTools.push(...mcpResult.tools.map(loaded => loaded.tool));
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
// Only top-level sessions own the global MCPManager. Subagents already
|
|
1905
|
+
// receive the parent's manager via `options.mcpManager`, and reassigning
|
|
1906
|
+
// the singleton to the same value is a no-op — keep the gate explicit
|
|
1907
|
+
// to mirror the AsyncJobManager ownership rule.
|
|
1908
|
+
if (mcpManager && !options.parentTaskPrefix) MCPManager.setInstance(mcpManager);
|
|
1909
|
+
|
|
1910
|
+
const builtInToolNames = [...toolRegistry.keys()];
|
|
1911
|
+
let customToolPaths: ToolPathWithSource[] = [];
|
|
1912
|
+
const inlineExtensions: ExtensionFactory[] = [];
|
|
1913
|
+
if (!restrictToolNames) {
|
|
1914
|
+
// Add image tools when generation is enabled and either no explicit tool
|
|
1915
|
+
// whitelist was given or it names `generate_image`. Unlike built-in tools
|
|
1916
|
+
// (filtered in `createTools`), custom tools are force-activated via
|
|
1917
|
+
// `alwaysInclude` below, so an explicit `--no-tools`/whitelist must be
|
|
1918
|
+
// honored here or image-gen would leak past every filter (issue #5305).
|
|
1919
|
+
const imageGenRequested = !options.toolNames || options.toolNames.includes("generate_image");
|
|
1920
|
+
if (settings.get("generate_image.enabled") && imageGenRequested) {
|
|
1921
|
+
const imageGenTools = await logger.time("getImageGenTools", () => getImageGenTools(modelRegistry, model));
|
|
1922
|
+
if (imageGenTools.length > 0) {
|
|
1923
|
+
customTools.push(...(imageGenTools as unknown as CustomTool[]));
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
if (settings.get("speechgen.enabled")) {
|
|
1928
|
+
customTools.push(ttsTool as unknown as CustomTool);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// Add web search tools
|
|
1932
|
+
if (options.toolNames?.includes("web_search")) {
|
|
1933
|
+
customTools.push(...getSearchTools());
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// Discover custom tools from `.omp/tools/`, `.claude/tools/`, plugins, etc.
|
|
1937
|
+
// Subagents reuse the parent's scan via `preloadedCustomToolPaths` to skip
|
|
1938
|
+
// the FS walk, but ALWAYS re-call `loadCustomTools` here so factories bind
|
|
1939
|
+
// to THIS session's `CustomToolAPI` (cwd, exec, pushPendingAction, UI).
|
|
1940
|
+
// Forwarding the parent's `LoadedCustomTool[]` directly would route tool
|
|
1941
|
+
// execution back through the parent — wrong for isolated tasks and for
|
|
1942
|
+
// pending-action queueing.
|
|
1943
|
+
customToolPaths =
|
|
1944
|
+
options.preloadedCustomToolPaths ??
|
|
1945
|
+
(await logger.time("discoverCustomToolPaths", () => discoverCustomToolPaths([], cwd)));
|
|
1946
|
+
const customToolsLoadResult = await logger.time("loadCustomTools", () =>
|
|
1947
|
+
loadCustomTools(customToolPaths, cwd, builtInToolNames, action => queueResolveHandler(toolSession, action)),
|
|
1948
|
+
);
|
|
1949
|
+
for (const { path, error } of customToolsLoadResult.errors) {
|
|
1950
|
+
logger.error("Custom tool load failed", { path, error });
|
|
1951
|
+
}
|
|
1952
|
+
if (customToolsLoadResult.tools.length > 0) {
|
|
1953
|
+
customTools.push(...customToolsLoadResult.tools.map(loaded => loaded.tool));
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
inlineExtensions.push(...(options.extensions ?? []));
|
|
1957
|
+
inlineExtensions.push(createAutoresearchExtension);
|
|
1958
|
+
if (customTools.length > 0) {
|
|
1959
|
+
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
// Forward the path list (NOT the loaded tools) to subagents so they
|
|
1963
|
+
// re-bind under their own `CustomToolAPI` while skipping the FS scan.
|
|
1964
|
+
toolSession.customToolPaths = customToolPaths;
|
|
1965
|
+
|
|
1966
|
+
// Load extensions. Three paths:
|
|
1967
|
+
// 1. `preloadedExtensions` (CLI): caller already loaded — reuse the
|
|
1968
|
+
// Extension instances. Shallow-clone `extensions` so the inline
|
|
1969
|
+
// push below cannot mutate the caller's array. `runtime` is shared
|
|
1970
|
+
// so flag values set pre-creation flow into the live session.
|
|
1971
|
+
// 2. `preloadedExtensionPaths` (subagent): caller resolved paths;
|
|
1972
|
+
// skip the FS scan but always re-call `loadExtensions` here so
|
|
1973
|
+
// each `Extension` binds to THIS session's `ExtensionAPI`
|
|
1974
|
+
// (cwd, eventBus, runtime).
|
|
1975
|
+
// 3. No preload: run the full session discovery.
|
|
1976
|
+
// `disableExtensionDiscovery` is honored implicitly: a caller that set
|
|
1977
|
+
// the flag and pre-resolved the result already reflects that choice.
|
|
1978
|
+
let extensionPaths: string[];
|
|
1979
|
+
let extensionsResult: LoadExtensionsResult;
|
|
1980
|
+
if (restrictToolNames) {
|
|
1981
|
+
// Allocate a session runtime without evaluating caller-provided extension
|
|
1982
|
+
// instances, paths, or factories.
|
|
1983
|
+
extensionPaths = [];
|
|
1984
|
+
extensionsResult = await loadExtensions([], cwd, eventBus);
|
|
1985
|
+
} else if (options.preloadedExtensions) {
|
|
1986
|
+
extensionsResult = {
|
|
1987
|
+
...options.preloadedExtensions,
|
|
1988
|
+
extensions: [...options.preloadedExtensions.extensions],
|
|
1989
|
+
};
|
|
1990
|
+
// Capture paths for downstream forwarding; filter inline-factory
|
|
1991
|
+
// entries (`<inline-N>`) — those are per-session, not source paths.
|
|
1992
|
+
extensionPaths = extensionsResult.extensions
|
|
1993
|
+
.map(ext => ext.resolvedPath)
|
|
1994
|
+
.filter(p => !p.startsWith("<inline"));
|
|
1995
|
+
} else if (options.preloadedExtensionPaths) {
|
|
1996
|
+
extensionPaths = options.preloadedExtensionPaths;
|
|
1997
|
+
extensionsResult = await logger.time("loadExtensions", loadExtensions, extensionPaths, cwd, eventBus);
|
|
1998
|
+
for (const { path, error } of extensionsResult.errors) {
|
|
1999
|
+
logger.error("Failed to load extension", { path, error });
|
|
2000
|
+
}
|
|
2001
|
+
} else {
|
|
2002
|
+
extensionPaths = await logger.time("discoverSessionExtensionPaths", () =>
|
|
2003
|
+
discoverSessionExtensionPaths(options, cwd, settings),
|
|
2004
|
+
);
|
|
2005
|
+
extensionsResult = await logger.time("loadExtensions", loadExtensions, extensionPaths, cwd, eventBus);
|
|
2006
|
+
for (const { path, error } of extensionsResult.errors) {
|
|
2007
|
+
logger.error("Failed to load extension", { path, error });
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
// Forward the source-path list (NOT the loaded instances) so subagents
|
|
2011
|
+
// rebuild their own session-scoped extensions.
|
|
2012
|
+
toolSession.extensionPaths = extensionPaths;
|
|
2013
|
+
|
|
2014
|
+
// Load inline extensions from factories
|
|
2015
|
+
if (inlineExtensions.length > 0) {
|
|
2016
|
+
for (let i = 0; i < inlineExtensions.length; i++) {
|
|
2017
|
+
const factory = inlineExtensions[i];
|
|
2018
|
+
const loaded = await loadExtensionFromFactory(
|
|
2019
|
+
factory,
|
|
2020
|
+
cwd,
|
|
2021
|
+
eventBus,
|
|
2022
|
+
extensionsResult.runtime,
|
|
2023
|
+
`<inline-${i}>`,
|
|
2024
|
+
);
|
|
2025
|
+
extensionsResult.extensions.push(loaded);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// Process provider registrations queued during extension loading.
|
|
2030
|
+
// This must happen before the runner is created so that models registered by
|
|
2031
|
+
// extensions are available for model selection on session resume / fallback.
|
|
2032
|
+
if (!restrictToolNames) {
|
|
2033
|
+
const activeExtensionSources = extensionsResult.extensions.map(extension => extension.path);
|
|
2034
|
+
modelRegistry.syncExtensionSources(activeExtensionSources);
|
|
2035
|
+
for (const sourceId of new Set(activeExtensionSources)) {
|
|
2036
|
+
modelRegistry.clearSourceRegistrations(sourceId);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
if (extensionsResult.runtime.pendingProviderRegistrations.length > 0) {
|
|
2040
|
+
for (const { name, config, sourceId } of extensionsResult.runtime.pendingProviderRegistrations) {
|
|
2041
|
+
modelRegistry.registerProvider(name, config, sourceId);
|
|
2042
|
+
}
|
|
2043
|
+
extensionsResult.runtime.pendingProviderRegistrations = [];
|
|
2044
|
+
}
|
|
2045
|
+
// Hydrate cached runtime (extension) provider catalogs before model
|
|
2046
|
+
// resolution. Dynamic-only providers have no synchronous registration side
|
|
2047
|
+
// effect, so a cold --model/provider resume must see the same fresh SQLite
|
|
2048
|
+
// cache that `omp models find` uses before the online refresh continues in
|
|
2049
|
+
// the background.
|
|
2050
|
+
await modelRegistry.refreshRuntimeProviders("offline");
|
|
2051
|
+
// Continue runtime discovery in the background (cache-aware) so startup is
|
|
2052
|
+
// only blocked on local cache reads, not provider network fetches. Stash
|
|
2053
|
+
// the promise so the deferred `--model` retry below can await it instead
|
|
2054
|
+
// of starting a second concurrent discovery pass (the unfiltered
|
|
2055
|
+
// `refresh()` also covers runtime model managers).
|
|
2056
|
+
const runtimeDiscoveryPromise = modelRegistry.refreshRuntimeProviders().catch(error => {
|
|
2057
|
+
logger.warn("runtime provider discovery failed", {
|
|
2058
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2059
|
+
});
|
|
2060
|
+
});
|
|
2061
|
+
|
|
2062
|
+
// Retry session-model candidates now that extension providers are
|
|
2063
|
+
// registered. The initial restore runs before extensions load, so a role
|
|
2064
|
+
// model supplied by an extension would have either fallen back to the
|
|
2065
|
+
// saved default (`restoredSessionModelIndex > 0`) or failed entirely
|
|
2066
|
+
// (`restoredSessionModelIndex === -1`, with the settings default or
|
|
2067
|
+
// downstream fallback filling `model`). Reclaim it here so resume
|
|
2068
|
+
// honors the last active role in either case.
|
|
2069
|
+
const sessionRetryLimit = restoredSessionModelIndex >= 0 ? restoredSessionModelIndex : sessionModelStrings.length;
|
|
2070
|
+
if (!hasExplicitModel && sessionRetryLimit > 0) {
|
|
2071
|
+
for (let i = 0; i < sessionRetryLimit; i++) {
|
|
2072
|
+
const sessionModelStr = sessionModelStrings[i];
|
|
2073
|
+
const parsedModel = parseModelString(sessionModelStr, {
|
|
2074
|
+
allowMaxSuffix: true,
|
|
2075
|
+
allowAutoAlias: true,
|
|
2076
|
+
isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
|
|
2077
|
+
});
|
|
2078
|
+
if (!parsedModel) continue;
|
|
2079
|
+
const restoredModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
|
|
2080
|
+
if (restoredModel && hasModelAuth(restoredModel)) {
|
|
2081
|
+
model = restoredModel;
|
|
2082
|
+
modelFallbackMessage = undefined;
|
|
2083
|
+
restoredSessionModelIndex = i;
|
|
2084
|
+
restoredSessionThinkingLevel = parsedModel.thinkingLevel;
|
|
2085
|
+
// Recompute thinking-level from scratch against the reclaimed
|
|
2086
|
+
// model: any value derived from the earlier fallback model's
|
|
2087
|
+
// `thinking.defaultLevel` must not become sticky.
|
|
2088
|
+
thinkingLevel = pickInitialThinkingLevel(restoredModel);
|
|
2089
|
+
autoThinking = thinkingLevel === AUTO_THINKING;
|
|
2090
|
+
effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
|
|
2091
|
+
effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
2092
|
+
autoThinking
|
|
2093
|
+
? resolveProvisionalAutoLevel(restoredModel)
|
|
2094
|
+
: resolveThinkingLevelForModel(restoredModel, effectiveThinkingLevel),
|
|
2095
|
+
);
|
|
2096
|
+
preconnectModelHost(restoredModel.baseUrl);
|
|
2097
|
+
break;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
// Resolve deferred --model/subagent patterns now that extension models are
|
|
2102
|
+
// registered. Use the same CLI resolver as the immediate path so bare role
|
|
2103
|
+
// names, exact model names, and provider selectors keep one precedence rule.
|
|
2104
|
+
if (!model && deferredModelPatterns.length > 0) {
|
|
2105
|
+
// Deferred `--model` patterns almost always failed at the immediate
|
|
2106
|
+
// path (main.ts:881) precisely because discovery-backed providers
|
|
2107
|
+
// hadn't populated yet. Await the in-flight runtime discovery
|
|
2108
|
+
// already kicked off above (stash + reuse avoids a second concurrent
|
|
2109
|
+
// `#refreshRuntimeDiscoveries` pass for the same runtime model
|
|
2110
|
+
// managers; it resolves instantly when no runtime managers are
|
|
2111
|
+
// registered). `refreshRuntimeProviders()` only covers runtime model
|
|
2112
|
+
// managers, not config-discovery providers (e.g. user-configured
|
|
2113
|
+
// ollama); fall back to a full cache-aware refresh only when the
|
|
2114
|
+
// runtime pass didn't surface a match AND config-discovery providers
|
|
2115
|
+
// exist to fetch from. By then runtime managers short-circuit on the
|
|
2116
|
+
// fresh cache written by the awaited pass, closing the double-fetch
|
|
2117
|
+
// window.
|
|
2118
|
+
await logger.time("resolveModelDiscoveryDeferredRetry", () => runtimeDiscoveryPromise);
|
|
2119
|
+
const matchPreferences = getModelMatchPreferences(settings);
|
|
2120
|
+
const runtimeResolved = deferredModelPatterns.some(pattern =>
|
|
2121
|
+
pattern.split(",").some(selector => {
|
|
2122
|
+
const trimmedSelector = selector.trim();
|
|
2123
|
+
if (!trimmedSelector) return false;
|
|
2124
|
+
const resolved = resolveCliModel({
|
|
2125
|
+
cliModel: trimmedSelector,
|
|
2126
|
+
modelRegistry,
|
|
2127
|
+
settings,
|
|
2128
|
+
preferences: matchPreferences,
|
|
2129
|
+
});
|
|
2130
|
+
return Boolean(
|
|
2131
|
+
resolved.model || (resolved.configuredPatterns && resolved.configuredPatterns.length > 0),
|
|
2132
|
+
);
|
|
2133
|
+
}),
|
|
2134
|
+
);
|
|
2135
|
+
if (!runtimeResolved && modelRegistry.getDiscoverableProviders().length > 0) {
|
|
2136
|
+
await logger.time("resolveModelDiscoveryFallbackNonRuntime", () =>
|
|
2137
|
+
modelRegistry.refresh("online-if-uncached"),
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
const allModels = modelRegistry.getAll();
|
|
2141
|
+
const availableModels = modelRegistry.getAvailable();
|
|
2142
|
+
const expandedModelPatterns = deferredModelPatterns.flatMap(pattern =>
|
|
2143
|
+
pattern.split(",").flatMap(selector => {
|
|
2144
|
+
const trimmedSelector = selector.trim();
|
|
2145
|
+
if (!trimmedSelector) return [];
|
|
2146
|
+
const resolved = resolveCliModel({
|
|
2147
|
+
cliModel: trimmedSelector,
|
|
2148
|
+
modelRegistry,
|
|
2149
|
+
settings,
|
|
2150
|
+
preferences: matchPreferences,
|
|
2151
|
+
});
|
|
2152
|
+
if (resolved.configuredPatterns && resolved.configuredPatterns.length > 0) {
|
|
2153
|
+
const primaryPatterns: Array<{
|
|
2154
|
+
pattern: string;
|
|
2155
|
+
retryFallback: InitialRetryFallbackState | undefined;
|
|
2156
|
+
}> = resolved.configuredPatterns.map(pattern => ({
|
|
2157
|
+
pattern,
|
|
2158
|
+
retryFallback: undefined,
|
|
2159
|
+
}));
|
|
2160
|
+
if (!resolved.configuredRole || !settings.get("retry.modelFallback")) {
|
|
2161
|
+
return primaryPatterns;
|
|
2162
|
+
}
|
|
2163
|
+
const fallbackContext: RetryFallbackResolutionContext = {
|
|
2164
|
+
chains: expandDefaultRetryFallbackChains(settings.get("retry.fallbackChains"), [
|
|
2165
|
+
...Object.keys(settings.getModelRoles()),
|
|
2166
|
+
resolved.configuredRole,
|
|
2167
|
+
]),
|
|
2168
|
+
getModelRole: role => settings.getModelRole(role),
|
|
2169
|
+
modelLookup: modelRegistry,
|
|
2170
|
+
};
|
|
2171
|
+
const originalSelector = resolved.configuredPatterns[0];
|
|
2172
|
+
const availableOriginal = parseModelPattern(originalSelector, availableModels, matchPreferences);
|
|
2173
|
+
const originalModel =
|
|
2174
|
+
availableOriginal.model ?? parseModelPattern(originalSelector, allModels, matchPreferences).model;
|
|
2175
|
+
const chainKey = resolveRetryFallbackChainKey(
|
|
2176
|
+
fallbackContext,
|
|
2177
|
+
originalSelector,
|
|
2178
|
+
originalModel,
|
|
2179
|
+
resolved.configuredRole,
|
|
2180
|
+
);
|
|
2181
|
+
if (!chainKey) return primaryPatterns;
|
|
2182
|
+
const parsedOriginal = parseModelString(originalSelector, {
|
|
2183
|
+
allowMaxSuffix: true,
|
|
2184
|
+
allowAutoAlias: true,
|
|
2185
|
+
isLiteralModelId: (provider, id) => modelRegistry.find(provider, id) !== undefined,
|
|
2186
|
+
});
|
|
2187
|
+
const retryFallback: InitialRetryFallbackState = {
|
|
2188
|
+
role: chainKey,
|
|
2189
|
+
originalSelector,
|
|
2190
|
+
originalThinkingLevel: parsedOriginal?.thinkingLevel,
|
|
2191
|
+
};
|
|
2192
|
+
return [
|
|
2193
|
+
...primaryPatterns,
|
|
2194
|
+
...findRetryFallbackCandidates(fallbackContext, chainKey, originalSelector, originalModel, {
|
|
2195
|
+
allowMissingPrimary: true,
|
|
2196
|
+
}).map(candidate => ({ pattern: candidate.raw, retryFallback })),
|
|
2197
|
+
];
|
|
2198
|
+
}
|
|
2199
|
+
if (resolved.model) {
|
|
2200
|
+
return [
|
|
2201
|
+
{
|
|
2202
|
+
pattern: formatModelSelectorValue(
|
|
2203
|
+
resolved.selector ?? formatModelStringWithRouting(resolved.model),
|
|
2204
|
+
resolved.thinkingLevel,
|
|
2205
|
+
),
|
|
2206
|
+
retryFallback: undefined,
|
|
2207
|
+
},
|
|
2208
|
+
];
|
|
2209
|
+
}
|
|
2210
|
+
return resolveConfiguredModelPatterns([trimmedSelector], settings).map(pattern => ({
|
|
2211
|
+
pattern,
|
|
2212
|
+
retryFallback: undefined,
|
|
2213
|
+
}));
|
|
2214
|
+
}),
|
|
2215
|
+
);
|
|
2216
|
+
const resolutionModels = expandedModelPatterns.some(
|
|
2217
|
+
({ pattern }) => parseModelPattern(pattern, availableModels, matchPreferences).model,
|
|
2218
|
+
)
|
|
2219
|
+
? availableModels
|
|
2220
|
+
: allModels;
|
|
2221
|
+
let usageFallbackTriggered = false;
|
|
2222
|
+
for (let patternIndex = 0; patternIndex < expandedModelPatterns.length; patternIndex += 1) {
|
|
2223
|
+
const { pattern, retryFallback } = expandedModelPatterns[patternIndex];
|
|
2224
|
+
const primary = parseModelPattern(pattern, resolutionModels, matchPreferences);
|
|
2225
|
+
if (!primary.model || (retryFallback && !hasModelAuth(primary.model))) continue;
|
|
2226
|
+
let hasUsageFallbackCandidate = false;
|
|
2227
|
+
for (
|
|
2228
|
+
let candidateIndex = patternIndex + 1;
|
|
2229
|
+
candidateIndex < expandedModelPatterns.length;
|
|
2230
|
+
candidateIndex += 1
|
|
2231
|
+
) {
|
|
2232
|
+
const candidate = parseModelPattern(
|
|
2233
|
+
expandedModelPatterns[candidateIndex].pattern,
|
|
2234
|
+
resolutionModels,
|
|
2235
|
+
matchPreferences,
|
|
2236
|
+
);
|
|
2237
|
+
if (candidate.model && hasModelAuth(candidate.model)) {
|
|
2238
|
+
hasUsageFallbackCandidate = true;
|
|
2239
|
+
break;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
const usageReservePolicy = settings.get("retry.usageReservePolicy");
|
|
2243
|
+
const modelFallbackEnabled = settings.get("retry.modelFallback");
|
|
2244
|
+
if (
|
|
2245
|
+
((modelFallbackEnabled && (hasUsageFallbackCandidate || usageFallbackTriggered)) ||
|
|
2246
|
+
usageReservePolicy === "fail-closed") &&
|
|
2247
|
+
settings.get("retry.usageAwareFallback")
|
|
2248
|
+
) {
|
|
2249
|
+
let usageHealth: ModelUsageHealth | undefined;
|
|
2250
|
+
try {
|
|
2251
|
+
usageHealth = await modelRegistry.authStorage.getModelUsageHealth(primary.model.provider, {
|
|
2252
|
+
modelId: primary.model.id,
|
|
2253
|
+
baseUrl: primary.model.baseUrl,
|
|
2254
|
+
reserveFraction: settings.get("retry.usageReservePct") / 100,
|
|
2255
|
+
});
|
|
2256
|
+
} catch (error) {
|
|
2257
|
+
logger.debug("Usage-aware model preflight failed open", {
|
|
2258
|
+
provider: primary.model.provider,
|
|
2259
|
+
model: primary.model.id,
|
|
2260
|
+
error: String(error),
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
if (usageHealth?.state === "depleted") {
|
|
2264
|
+
if (usageReservePolicy === "fail-closed") {
|
|
2265
|
+
throw new Error(
|
|
2266
|
+
`Usage depleted for ${primary.model.provider}/${primary.model.id}; reserve policy is fail-closed.`,
|
|
2267
|
+
);
|
|
2268
|
+
}
|
|
2269
|
+
if (modelFallbackEnabled) {
|
|
2270
|
+
usageFallbackTriggered = true;
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
if (usageHealth?.state === "reserve") {
|
|
2275
|
+
if (usageReservePolicy === "fail-closed") {
|
|
2276
|
+
throw new Error(
|
|
2277
|
+
`Usage reserve reached for ${primary.model.provider}/${primary.model.id}; reserve policy is fail-closed.`,
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
if (
|
|
2281
|
+
modelFallbackEnabled &&
|
|
2282
|
+
(usageReservePolicy === "auto" || (!options.hasUI && !options.deferUsageReserveConfirmation))
|
|
2283
|
+
) {
|
|
2284
|
+
usageFallbackTriggered = true;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
let selectedModel = primary.model;
|
|
2290
|
+
let selectedThinkingLevel = primary.thinkingLevel;
|
|
2291
|
+
let selectedExplicitThinkingLevel = primary.explicitThinkingLevel;
|
|
2292
|
+
// A chain entry without its own `:level` suffix inherits the
|
|
2293
|
+
// unavailable primary's configured thinking level, matching
|
|
2294
|
+
// runtime fallback-chain semantics.
|
|
2295
|
+
if (retryFallback && !selectedExplicitThinkingLevel && retryFallback.originalThinkingLevel !== undefined) {
|
|
2296
|
+
selectedThinkingLevel = retryFallback.originalThinkingLevel;
|
|
2297
|
+
selectedExplicitThinkingLevel = true;
|
|
2298
|
+
}
|
|
2299
|
+
let authFallbackUsed = false;
|
|
2300
|
+
if (options.modelPatternAuthFallback) {
|
|
2301
|
+
const primaryKey = await modelRegistry.getApiKey(primary.model);
|
|
2302
|
+
if (primaryKey !== kNoAuth && !isAuthenticated(primaryKey)) {
|
|
2303
|
+
const fallback = parseModelPattern(
|
|
2304
|
+
options.modelPatternAuthFallback,
|
|
2305
|
+
resolutionModels,
|
|
2306
|
+
matchPreferences,
|
|
2307
|
+
);
|
|
2308
|
+
if (fallback.model) {
|
|
2309
|
+
const fallbackKey = await modelRegistry.getApiKey(fallback.model);
|
|
2310
|
+
if (isAuthenticated(fallbackKey)) {
|
|
2311
|
+
selectedModel = fallback.model;
|
|
2312
|
+
selectedThinkingLevel = fallback.thinkingLevel;
|
|
2313
|
+
selectedExplicitThinkingLevel = fallback.explicitThinkingLevel;
|
|
2314
|
+
authFallbackUsed = true;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
if (!authFallbackUsed && options.modelPatternFallbackRole) {
|
|
2320
|
+
const primarySelector = formatModelSelectorValue(
|
|
2321
|
+
formatModelStringWithRouting(primary.model),
|
|
2322
|
+
primary.thinkingLevel,
|
|
2323
|
+
);
|
|
2324
|
+
const seenSelectors = new Set<string>([primarySelector]);
|
|
2325
|
+
const fallbackSelectors: string[] = [];
|
|
2326
|
+
for (const fallbackEntry of expandedModelPatterns.slice(patternIndex + 1)) {
|
|
2327
|
+
const fallback = parseModelPattern(fallbackEntry.pattern, resolutionModels, matchPreferences);
|
|
2328
|
+
if (!fallback.model) continue;
|
|
2329
|
+
const fallbackSelector = formatModelSelectorValue(
|
|
2330
|
+
formatModelStringWithRouting(fallback.model),
|
|
2331
|
+
fallback.thinkingLevel,
|
|
2332
|
+
);
|
|
2333
|
+
if (seenSelectors.has(fallbackSelector)) continue;
|
|
2334
|
+
seenSelectors.add(fallbackSelector);
|
|
2335
|
+
fallbackSelectors.push(fallbackSelector);
|
|
2336
|
+
}
|
|
2337
|
+
if (fallbackSelectors.length === 0) {
|
|
2338
|
+
for (const selector of options.modelPatternDefaultFallbackChain ?? []) {
|
|
2339
|
+
if (typeof selector !== "string" || seenSelectors.has(selector)) continue;
|
|
2340
|
+
seenSelectors.add(selector);
|
|
2341
|
+
fallbackSelectors.push(selector);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
if (fallbackSelectors.length > 0) {
|
|
2345
|
+
const modelRoles: Record<string, string> = {};
|
|
2346
|
+
const existingRoles = settings.getModelRoles();
|
|
2347
|
+
for (const role in existingRoles) {
|
|
2348
|
+
const selector = existingRoles[role];
|
|
2349
|
+
if (selector) {
|
|
2350
|
+
modelRoles[role] = selector;
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
modelRoles[options.modelPatternFallbackRole] = primarySelector;
|
|
2354
|
+
settings.override("modelRoles", modelRoles);
|
|
2355
|
+
const fallbackChains: Record<string, string[]> = {
|
|
2356
|
+
[options.modelPatternFallbackRole]: fallbackSelectors,
|
|
2357
|
+
};
|
|
2358
|
+
const existingFallbackChains = settings.get("retry.fallbackChains");
|
|
2359
|
+
for (const role in existingFallbackChains) {
|
|
2360
|
+
if (role !== options.modelPatternFallbackRole) {
|
|
2361
|
+
fallbackChains[role] = existingFallbackChains[role];
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
settings.override("retry.fallbackChains", fallbackChains);
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
model = selectedModel;
|
|
2368
|
+
initialRetryFallback =
|
|
2369
|
+
retryFallback && usageFallbackTriggered ? { ...retryFallback, pinned: true } : retryFallback;
|
|
2370
|
+
modelFallbackMessage = undefined;
|
|
2371
|
+
if (selectedExplicitThinkingLevel) {
|
|
2372
|
+
restoredSessionThinkingLevel = selectedThinkingLevel;
|
|
2373
|
+
}
|
|
2374
|
+
thinkingLevel = pickInitialThinkingLevel(selectedModel);
|
|
2375
|
+
autoThinking = thinkingLevel === AUTO_THINKING;
|
|
2376
|
+
effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
|
|
2377
|
+
effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
2378
|
+
autoThinking
|
|
2379
|
+
? resolveProvisionalAutoLevel(selectedModel)
|
|
2380
|
+
: resolveThinkingLevelForModel(selectedModel, effectiveThinkingLevel),
|
|
2381
|
+
);
|
|
2382
|
+
preconnectModelHost(selectedModel.baseUrl);
|
|
2383
|
+
break;
|
|
2384
|
+
}
|
|
2385
|
+
if (!model) {
|
|
2386
|
+
const requested =
|
|
2387
|
+
deferredModelPatterns.length === 1
|
|
2388
|
+
? `"${deferredModelPatterns[0]}"`
|
|
2389
|
+
: `one of ${deferredModelPatterns.map(pattern => `"${pattern}"`).join(", ")}`;
|
|
2390
|
+
modelFallbackMessage = `Model ${requested} not found`;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
// Fall back to first available model with a valid API key, honoring the
|
|
2395
|
+
// path-scoped `enabledModels` allow-list when configured. Skip when the
|
|
2396
|
+
// user explicitly requested a model via --model that wasn't found.
|
|
2397
|
+
if (!model && deferredModelPatterns.length === 0) {
|
|
2398
|
+
// Retry the configured default role against the current catalog,
|
|
2399
|
+
// setting `model` (+ thinking level) when it resolves. Extension
|
|
2400
|
+
// factories register providers AFTER the early `defaultRoleSpec`
|
|
2401
|
+
// resolution, and configured discovery providers may still be
|
|
2402
|
+
// mid-discovery, so a role pointing at such a model (an openai-compat
|
|
2403
|
+
// plugin's `posthog/claude-opus-4-8`, a models.yml `openai-models-list`
|
|
2404
|
+
// endpoint) returned `undefined` there. Without this retry the
|
|
2405
|
+
// `pickDefaultAvailableModel` fallback below happily replaces the
|
|
2406
|
+
// user's configured default with a bundled provider's default whenever
|
|
2407
|
+
// a stray `OPENAI_API_KEY`/`ANTHROPIC_API_KEY` is in the environment.
|
|
2408
|
+
// (issues #3569, #6162)
|
|
2409
|
+
const tryResolveDefaultRole = async (): Promise<boolean> => {
|
|
2410
|
+
if (hasExplicitModel) return false;
|
|
2411
|
+
// Re-resolve the allowed set: extension factories and discovery
|
|
2412
|
+
// refreshes above may have registered models not visible earlier.
|
|
2413
|
+
const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
|
|
2414
|
+
const reResolvedRoleSpec = resolveModelRoleValue(settings.getModelRole("default"), fallbackCandidates, {
|
|
2415
|
+
settings,
|
|
2416
|
+
matchPreferences: modelMatchPreferences,
|
|
2417
|
+
});
|
|
2418
|
+
if (!reResolvedRoleSpec.model) return false;
|
|
2419
|
+
defaultRoleSpec = reResolvedRoleSpec;
|
|
2420
|
+
const resolvedDefaultModel = reResolvedRoleSpec.model;
|
|
2421
|
+
model = resolvedDefaultModel;
|
|
2422
|
+
modelFallbackMessage = undefined;
|
|
2423
|
+
// Recompute the thinking level against the now-real model.
|
|
2424
|
+
// `pickInitialThinkingLevel` closes over `defaultRoleSpec`,
|
|
2425
|
+
// so the role's explicit selector (e.g. `:max`) now applies.
|
|
2426
|
+
thinkingLevel = pickInitialThinkingLevel(resolvedDefaultModel);
|
|
2427
|
+
autoThinking = thinkingLevel === AUTO_THINKING;
|
|
2428
|
+
effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
|
|
2429
|
+
effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
2430
|
+
autoThinking
|
|
2431
|
+
? resolveProvisionalAutoLevel(resolvedDefaultModel)
|
|
2432
|
+
: resolveThinkingLevelForModel(resolvedDefaultModel, effectiveThinkingLevel),
|
|
2433
|
+
);
|
|
2434
|
+
preconnectModelHost(resolvedDefaultModel.baseUrl);
|
|
2435
|
+
return true;
|
|
2436
|
+
};
|
|
2437
|
+
|
|
2438
|
+
await tryResolveDefaultRole();
|
|
2439
|
+
|
|
2440
|
+
if (!model) {
|
|
2441
|
+
const fallbackCandidates = await resolveAllowedModels(modelRegistry, settings, modelMatchPreferences);
|
|
2442
|
+
let pick = pickDefaultAvailableModel(fallbackCandidates.filter(hasModelAuth));
|
|
2443
|
+
|
|
2444
|
+
// Cold-cache discovery race (issues #6114, #6162): a discovery
|
|
2445
|
+
// provider (models.yml `openai-models-list`, LM Studio/Ollama/
|
|
2446
|
+
// llama.cpp, or an openai-compat proxy) ships no static models, so
|
|
2447
|
+
// the static+cached catalog resolved nothing above. Background
|
|
2448
|
+
// discovery in main.ts fires only AFTER createAgentSession returns,
|
|
2449
|
+
// so on a cache-cold boot the configured default stays unresolved
|
|
2450
|
+
// and `pick` silently degrades to an unrelated authed provider's
|
|
2451
|
+
// default (#6162) or "No models available" (#6114) — even though
|
|
2452
|
+
// `omp models` (which awaits discovery) lists the model. Await one
|
|
2453
|
+
// cache-aware discovery pass and retry when a default role is
|
|
2454
|
+
// configured (must win over `pick`) or nothing resolved at all.
|
|
2455
|
+
// The common path — role already resolved, or a `pick` with no
|
|
2456
|
+
// configured default — never pays for it.
|
|
2457
|
+
const defaultRoleConfigured = Boolean(settings.getModelRole("default"));
|
|
2458
|
+
if (
|
|
2459
|
+
!hasExplicitModel &&
|
|
2460
|
+
(defaultRoleConfigured || !pick) &&
|
|
2461
|
+
modelRegistry.getDiscoverableProviders().length > 0
|
|
2462
|
+
) {
|
|
2463
|
+
await logger.time("resolveModelDiscoveryFallback", () => modelRegistry.refresh("online-if-uncached"));
|
|
2464
|
+
if (!(await tryResolveDefaultRole()) && !model) {
|
|
2465
|
+
const refreshedCandidates = await resolveAllowedModels(
|
|
2466
|
+
modelRegistry,
|
|
2467
|
+
settings,
|
|
2468
|
+
modelMatchPreferences,
|
|
2469
|
+
);
|
|
2470
|
+
pick = pickDefaultAvailableModel(refreshedCandidates.filter(hasModelAuth));
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
if (!model && pick) {
|
|
2475
|
+
model = pick;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
if (model) {
|
|
2479
|
+
if (modelFallbackMessage) {
|
|
2480
|
+
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
|
2481
|
+
}
|
|
2482
|
+
} else {
|
|
2483
|
+
const patterns = settings.get("enabledModels");
|
|
2484
|
+
modelFallbackMessage =
|
|
2485
|
+
patterns && patterns.length > 0
|
|
2486
|
+
? `No model available matching enabledModels (${patterns.join(", ")}) with usable credentials. Configure auth for an allowed provider or adjust enabledModels.`
|
|
2487
|
+
: "No models available. Use /login or set an API key environment variable. Then use /model to select a model.";
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
if (model) {
|
|
2492
|
+
const selectedModel = model;
|
|
2493
|
+
const refreshedModel = await logger.time("refreshInitialModelMetadata", () =>
|
|
2494
|
+
modelRegistry.refreshSelectedModelMetadata(selectedModel),
|
|
2495
|
+
);
|
|
2496
|
+
if (refreshedModel !== selectedModel) {
|
|
2497
|
+
model = refreshedModel;
|
|
2498
|
+
thinkingLevel = pickInitialThinkingLevel(refreshedModel);
|
|
2499
|
+
autoThinking = thinkingLevel === AUTO_THINKING;
|
|
2500
|
+
effectiveThinkingLevel = concreteThinkingLevel(thinkingLevel);
|
|
2501
|
+
effectiveThinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
2502
|
+
autoThinking
|
|
2503
|
+
? resolveProvisionalAutoLevel(refreshedModel)
|
|
2504
|
+
: resolveThinkingLevelForModel(refreshedModel, effectiveThinkingLevel),
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// A first-turn user tail has no assistant metadata to copy. Once startup
|
|
2510
|
+
// has selected its final model, use that model to terminate the
|
|
2511
|
+
// interrupted turn before the live agent consumes the restored context.
|
|
2512
|
+
if (model) {
|
|
2513
|
+
const selectedModelAbort = createInterruptedTurnAbortMessage(existingBranch, {
|
|
2514
|
+
api: model.api,
|
|
2515
|
+
provider: model.provider,
|
|
2516
|
+
model: model.id,
|
|
2517
|
+
});
|
|
2518
|
+
if (selectedModelAbort) {
|
|
2519
|
+
sessionManager.appendMessage(selectedModelAbort);
|
|
2520
|
+
existingBranch = logger.time("getRecoveredUserTailBranch", () => sessionManager.getBranch());
|
|
2521
|
+
existingSession = logger.time("loadRecoveredUserTailContext", () =>
|
|
2522
|
+
deobfuscateSessionContext(sessionManager.buildSessionContext(), obfuscator),
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
// Restricted sessions do not discover or evaluate custom command modules.
|
|
2528
|
+
const customCommandsResult: CustomCommandsLoadResult =
|
|
2529
|
+
options.disableExtensionDiscovery || restrictToolNames
|
|
2530
|
+
? { commands: [], errors: [] }
|
|
2531
|
+
: await logger.time("discoverCustomCommands", loadCustomCommandsInternal, { cwd, agentDir });
|
|
2532
|
+
if (!options.disableExtensionDiscovery && !restrictToolNames) {
|
|
2533
|
+
for (const { path, error } of customCommandsResult.errors) {
|
|
2534
|
+
logger.error("Failed to load custom command", { path, error });
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// The runner is created unconditionally — even with zero extensions loaded — because the
|
|
2539
|
+
// `ExtensionToolWrapper` installed below is the only place the per-tool approval gate runs.
|
|
2540
|
+
// A conditional runner means the approval system silently disappears for users with no
|
|
2541
|
+
// extensions, contradicting non-yolo `tools.approvalMode` settings without feedback.
|
|
2542
|
+
// (The builtin autoresearch extension is unconditionally loaded above, so this scenario
|
|
2543
|
+
// is unreachable; unconditional runner construction keeps that invariant explicit and
|
|
2544
|
+
// prevents future optional extensions from silently re-opening the hole.)
|
|
2545
|
+
const extensionRunner: ExtensionRunner = new ExtensionRunner(
|
|
2546
|
+
extensionsResult.extensions,
|
|
2547
|
+
extensionsResult.runtime,
|
|
2548
|
+
cwd,
|
|
2549
|
+
sessionManager,
|
|
2550
|
+
modelRegistry,
|
|
2551
|
+
() => (hasSession ? createSessionMemoryRuntimeContext(session, agentDir, cwd) : undefined),
|
|
2552
|
+
settings,
|
|
2553
|
+
localProtocolOptions,
|
|
2554
|
+
() => (hasSession ? session.getAsyncJobSnapshot() : null),
|
|
2555
|
+
);
|
|
2556
|
+
|
|
2557
|
+
credentialDisabledTarget = extensionRunner;
|
|
2558
|
+
for (const event of startupCredentialDisabledEvents.splice(0)) {
|
|
2559
|
+
// Discard return: any handler error is routed through runner.onError listeners.
|
|
2560
|
+
void extensionRunner.emitCredentialDisabled(event);
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
const getSessionContext = () => ({
|
|
2564
|
+
sessionManager,
|
|
2565
|
+
modelRegistry,
|
|
2566
|
+
model: agent.state.model,
|
|
2567
|
+
isIdle: () => !session.isStreaming,
|
|
2568
|
+
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
2569
|
+
abort: () => {
|
|
2570
|
+
session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
2571
|
+
},
|
|
2572
|
+
settings,
|
|
2573
|
+
localProtocolOptions,
|
|
2574
|
+
autoApprove: options.autoApprove ?? false,
|
|
2575
|
+
});
|
|
2576
|
+
const toolContextStore = new ToolContextStore(getSessionContext);
|
|
2577
|
+
// Native built-in implementations backing same-tool `ctx.invokeTool`, so a tool that
|
|
2578
|
+
// re-registers a built-in (e.g. wrapping `write`) can delegate to the original — reaching the
|
|
2579
|
+
// unwrapped native execute, which inherits the caller's already-granted approval rather than
|
|
2580
|
+
// re-running the gate. Seeded from the xdev registry when present (it retains discoverable
|
|
2581
|
+
// built-ins like `browser` that xdev partitioning removes from the active tool array), else
|
|
2582
|
+
// from the built-in registry; captured before the ExtensionToolWrapper pass so the natives
|
|
2583
|
+
// stay unwrapped. The extension runner exposes it to re-registered tools via createContext.
|
|
2584
|
+
const nativeToolsByName = new Map<string, Tool>(toolSession.xdev?.tools ?? undefined);
|
|
2585
|
+
|
|
2586
|
+
const registeredTools = restrictToolNames ? [] : extensionRunner.getAllRegisteredTools();
|
|
2587
|
+
const sdkCustomTools =
|
|
2588
|
+
restrictToolNames && options.allowRestrictedCustomTools !== true
|
|
2589
|
+
? []
|
|
2590
|
+
: (options.customTools?.filter(tool => !isLegacyBuiltinToolDefinition(tool)) ?? []);
|
|
2591
|
+
const allCustomTools = [
|
|
2592
|
+
...registeredTools,
|
|
2593
|
+
...sdkCustomTools.map(tool => {
|
|
2594
|
+
const definition = isCustomTool(tool) ? customToolToDefinition(tool) : tool;
|
|
2595
|
+
return { definition, extensionPath: "<sdk>" };
|
|
2596
|
+
}),
|
|
2597
|
+
];
|
|
2598
|
+
// `wrapToolWithMetaNotice` runs the centralized large-output → artifact spill.
|
|
2599
|
+
// Built-in tools get it in `createTools`; extension, SDK-custom, image-gen,
|
|
2600
|
+
// TTS, and startup (non-deferred) MCP tools all funnel through here, so apply
|
|
2601
|
+
// it once at this adapter boundary (idempotent — a no-op if already wrapped).
|
|
2602
|
+
const wrappedExtensionTools: Tool[] = deduplicateMCPToolsByName(
|
|
2603
|
+
wrapRegisteredTools(allCustomTools, extensionRunner).map(wrapToolWithMetaNotice),
|
|
2604
|
+
);
|
|
2605
|
+
|
|
2606
|
+
// All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
|
|
2607
|
+
const builtInRegistryToolNames = toolSession.xdev?.builtInNames ?? new Set(toolRegistry.keys());
|
|
2608
|
+
// Capture the native built-in implementations before extension re-registration replaces registry
|
|
2609
|
+
// entries and before the ExtensionToolWrapper pass below, so `ctx.invokeTool` reaches the
|
|
2610
|
+
// unwrapped native execute (inheriting the caller's already-granted approval, not re-gating).
|
|
2611
|
+
for (const [name, tool] of toolRegistry) {
|
|
2612
|
+
nativeToolsByName.set(name, tool);
|
|
2613
|
+
}
|
|
2614
|
+
if (!restrictToolNames && !toolRegistry.has("goal") && settings.get("goal.enabled")) {
|
|
2615
|
+
const goalTool = await logger.time("createTools:goal:session", HIDDEN_TOOLS.goal, toolSession);
|
|
2616
|
+
if (goalTool) {
|
|
2617
|
+
const wrapped = wrapToolWithMetaNotice(goalTool);
|
|
2618
|
+
toolRegistry.set(goalTool.name, wrapped);
|
|
2619
|
+
builtInRegistryToolNames.add(goalTool.name);
|
|
2620
|
+
nativeToolsByName.set(goalTool.name, wrapped);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
for (const tool of wrappedExtensionTools) {
|
|
2624
|
+
toolRegistry.set(tool.name, tool);
|
|
2625
|
+
builtInRegistryToolNames.delete(tool.name);
|
|
2626
|
+
}
|
|
2627
|
+
// Expose the native built-ins to same-tool `ctx.invokeTool` on re-registered tools. Set after
|
|
2628
|
+
// the override loop so the map holds the natives, not the extension replacements. The context
|
|
2629
|
+
// factory is the loop's own tool context, so a delegated native call sees ordinary session state.
|
|
2630
|
+
extensionRunner.setNativeToolResolver(name => {
|
|
2631
|
+
const tool = nativeToolsByName.get(name);
|
|
2632
|
+
return tool ? { tool, makeContext: () => toolContextStore.getContext() } : undefined;
|
|
2633
|
+
});
|
|
2634
|
+
if (deferMCPDiscoveryForUI && mcpManager) {
|
|
2635
|
+
for (const name of collectPendingMCPToolNames(options.toolNames)) {
|
|
2636
|
+
if (!toolRegistry.has(name)) {
|
|
2637
|
+
toolRegistry.set(name, createPendingMCPTool(name));
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// Wrap every tool with `ExtensionToolWrapper` so the per-tool approval gate runs on every
|
|
2643
|
+
// call site, regardless of whether any user extensions are loaded. See the runner-construction
|
|
2644
|
+
// comment above for the safety invariant this enforces.
|
|
2645
|
+
for (const tool of toolRegistry.values()) {
|
|
2646
|
+
toolRegistry.set(tool.name, new ExtensionToolWrapper(tool, extensionRunner));
|
|
2647
|
+
}
|
|
2648
|
+
// Cursor's own client owns file edits, so `edit` is not advertised to the
|
|
2649
|
+
// model (commit 8ba0498eb: full-file `write` is used instead). The exec
|
|
2650
|
+
// bridge is a different consumer: the server sends native `pi_edit`
|
|
2651
|
+
// frames regardless of the advertised catalog, and answering them needs
|
|
2652
|
+
// a real tool.
|
|
2653
|
+
//
|
|
2654
|
+
// It must be a `replace`-mode instance. `PiEditExecArgs` carries
|
|
2655
|
+
// `old_string`/`new_string` replacements, which is exactly `replace`'s schema and
|
|
2656
|
+
// nothing else's — under the default `hashline` mode the frame's args do
|
|
2657
|
+
// not match the tool's parameters at all. The registry instance follows
|
|
2658
|
+
// the session's configured mode, so the bridge builds its own.
|
|
2659
|
+
//
|
|
2660
|
+
// The grant is captured HERE, before the Cursor branch below deletes
|
|
2661
|
+
// `edit` from the registry, and independently of the session's provider:
|
|
2662
|
+
// a session that starts on another provider can switch to Cursor later,
|
|
2663
|
+
// and the roster is built once, at session creation. Reading the registry
|
|
2664
|
+
// at frame time would see the switched-to state, not the grant.
|
|
2665
|
+
const editWasGranted = toolRegistry.has("edit");
|
|
2666
|
+
// Built on first use rather than eagerly: a session that never reaches
|
|
2667
|
+
// Cursor never constructs it.
|
|
2668
|
+
let cursorBridgeEditTool: AgentTool | undefined;
|
|
2669
|
+
const getCursorBridgeEditTool = (): AgentTool | undefined => {
|
|
2670
|
+
// Only when the session actually granted `edit`. `createTools` omits
|
|
2671
|
+
// it entirely for a restricted tool set, and the bridge answers native
|
|
2672
|
+
// frames that arrive regardless of the advertised catalog — so
|
|
2673
|
+
// building one unconditionally would hand a read-only agent a
|
|
2674
|
+
// mutating tool it was denied (the issue #5680 escalation).
|
|
2675
|
+
if (!editWasGranted) return undefined;
|
|
2676
|
+
cursorBridgeEditTool ??= createBridgeEditTool(toolSession, extensionRunner);
|
|
2677
|
+
return cursorBridgeEditTool;
|
|
2678
|
+
};
|
|
2679
|
+
// Whether this session granted a file-writing tool. Same capture-early
|
|
2680
|
+
// reasoning, plus `write` may be auto-registered further down as an xdev
|
|
2681
|
+
// transport. The exec bridge answers native `delete` and
|
|
2682
|
+
// resource-download frames that mutate files without running a registry
|
|
2683
|
+
// tool, so it needs the grant as the session actually made it.
|
|
2684
|
+
const cursorCanMutateFiles = editWasGranted || toolRegistry.has("write");
|
|
2685
|
+
if (model?.provider === "cursor") {
|
|
2686
|
+
toolRegistry.delete("edit");
|
|
2687
|
+
builtInRegistryToolNames.delete("edit");
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
let writeRegistration: Promise<boolean> | undefined;
|
|
2691
|
+
const ensureWriteRegistered = (): Promise<boolean> => {
|
|
2692
|
+
if (toolRegistry.has("write")) return Promise.resolve(builtInRegistryToolNames.has("write"));
|
|
2693
|
+
writeRegistration ??= (async () => {
|
|
2694
|
+
const writeTool = await logger.time("createTools:write:session", BUILTIN_TOOLS.write, toolSession);
|
|
2695
|
+
if (!writeTool || toolRegistry.has("write")) return builtInRegistryToolNames.has("write");
|
|
2696
|
+
const nativeWrite = wrapToolWithMetaNotice(writeTool);
|
|
2697
|
+
toolRegistry.set(writeTool.name, new ExtensionToolWrapper(nativeWrite, extensionRunner) as Tool);
|
|
2698
|
+
builtInRegistryToolNames.add(writeTool.name);
|
|
2699
|
+
nativeToolsByName.set(writeTool.name, nativeWrite);
|
|
2700
|
+
return true;
|
|
2701
|
+
})().finally(() => {
|
|
2702
|
+
writeRegistration = undefined;
|
|
2703
|
+
});
|
|
2704
|
+
return writeRegistration;
|
|
2705
|
+
};
|
|
2706
|
+
|
|
2707
|
+
// Existing staged/device paths need write registered before active-set assembly.
|
|
2708
|
+
// Deferred MCP also registers it now, but refresh activates it only after a server connects.
|
|
2709
|
+
// xd:// mounts never register write: xdev state only exists when the session
|
|
2710
|
+
// already granted a write tool (see createTools), so mounting rides that grant.
|
|
2711
|
+
const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
|
|
2712
|
+
const planModeAvailable = settings.get("plan.enabled");
|
|
2713
|
+
if (!restrictToolNames && (hasDeferrableTools || planModeAvailable || deferMCPDiscoveryForUI)) {
|
|
2714
|
+
await ensureWriteRegistered();
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
2718
|
+
// Cursor and the agent loop may call a mounted device by its top-level
|
|
2719
|
+
// name. Resolve that name from the canonical map and apply the same
|
|
2720
|
+
// execution-only ACP decorator used by `write xd://<tool>`; docs and
|
|
2721
|
+
// renderer lookup continue to use the undecorated canonical instance.
|
|
2722
|
+
const resolveDeviceTool = (name: string): AgentTool | undefined => {
|
|
2723
|
+
const state = toolSession.xdev;
|
|
2724
|
+
if (!state) return undefined;
|
|
2725
|
+
return resolveMountedXdevExecutable(state, name);
|
|
2726
|
+
};
|
|
2727
|
+
// Cursor's resource frames ask what THIS client's servers advertise; only
|
|
2728
|
+
// live connections have any. Built once: the advisor bridges answer from
|
|
2729
|
+
// the same connections the primary does.
|
|
2730
|
+
const cursorMcpResources: CursorMcpResourceAdapter | undefined = mcpManager && {
|
|
2731
|
+
serverNames: () => mcpManager.getConnectedServers(),
|
|
2732
|
+
getServerResources: async name => {
|
|
2733
|
+
// The manager registers a server's tools before its background
|
|
2734
|
+
// resource load finishes, so a frame arriving in that window
|
|
2735
|
+
// would read an empty cache and report "advertises nothing".
|
|
2736
|
+
await mcpManager.ensureServerResources(name);
|
|
2737
|
+
return mcpManager.getServerResources(name);
|
|
2738
|
+
},
|
|
2739
|
+
readServerResource: (name, uri) => mcpManager.readServerResource(name, uri),
|
|
2740
|
+
};
|
|
2741
|
+
const cursorExecHandlers = new CursorExecHandlers({
|
|
2742
|
+
cwd,
|
|
2743
|
+
// The session's cwd moves (`/cd`, resume, branch restore) while this
|
|
2744
|
+
// bridge is built once at startup. Path-confining frames — the native
|
|
2745
|
+
// `delete` and a `download_path` resource read — resolve against
|
|
2746
|
+
// whichever of the two they are given, so without the live resolver the
|
|
2747
|
+
// primary would write into the workspace the session has left while
|
|
2748
|
+
// reporting success for the path the server asked about. The advisor
|
|
2749
|
+
// bridge already passes one.
|
|
2750
|
+
getCwd: () => sessionManager.getCwd(),
|
|
2751
|
+
tools: toolRegistry,
|
|
2752
|
+
getExecutableTool: resolveDeviceTool,
|
|
2753
|
+
// `pi_edit` needs the `replace`-mode instance specifically, and the
|
|
2754
|
+
// registry may still hold the session's own `edit` (any mode) when
|
|
2755
|
+
// this session did not start on Cursor.
|
|
2756
|
+
getEditReplaceTool: getCursorBridgeEditTool,
|
|
2757
|
+
getToolContext: () => toolContextStore.getContext(),
|
|
2758
|
+
mcpResources: cursorMcpResources,
|
|
2759
|
+
emitEvent: event => cursorEventEmitter?.(event),
|
|
2760
|
+
getTodoPhases: () => session.getTodoPhases(),
|
|
2761
|
+
setTodoPhases: phases => session.setTodoPhases(phases),
|
|
2762
|
+
persistTodoPhases: phases => sessionManager.appendCustomEntry(USER_TODO_EDIT_CUSTOM_TYPE, { phases }),
|
|
2763
|
+
// `pi_grep` carries its own context width and match cap, which the
|
|
2764
|
+
// shared grep instance fixed at construction cannot express. Gated on
|
|
2765
|
+
// the grant: the factory builds a fresh tool and `executeTool` prefers
|
|
2766
|
+
// it over the registry, so installing it unconditionally would let a
|
|
2767
|
+
// session without `grep` search anyway.
|
|
2768
|
+
createGrepTool: toolRegistry.has("grep") ? createBridgeGrepFactory(toolSession, extensionRunner) : undefined,
|
|
2769
|
+
// The native `delete` and resource-download frames mutate files
|
|
2770
|
+
// without running a registry tool, so this grant is the only thing
|
|
2771
|
+
// standing between a restricted session and a workspace write.
|
|
2772
|
+
allowDirectFileMutation: cursorCanMutateFiles,
|
|
2773
|
+
});
|
|
2774
|
+
|
|
2775
|
+
// Resolve the inline-descriptors setting against the session-start model.
|
|
2776
|
+
// `auto` enforces the per-model policy (inline for Gemini, off otherwise);
|
|
2777
|
+
// like the rest of the prune machinery this is fixed for the session, so a
|
|
2778
|
+
// mid-session model switch keeps the start-time decision.
|
|
2779
|
+
const inlineToolDescriptors = shouldInlineToolDescriptors(settings.get("inlineToolDescriptors"), model?.id);
|
|
2780
|
+
const eagerTasks = settings.get("task.eager") !== "default";
|
|
2781
|
+
const eagerTasksAlways = settings.get("task.eager") === "always";
|
|
2782
|
+
const intentField = $flag("PI_INTENT_TRACING", settings.get("tools.intentTracing")) ? INTENT_FIELD : undefined;
|
|
2783
|
+
const includeWorkspaceTree = settings.get("includeWorkspaceTree") ?? false;
|
|
2784
|
+
const rebuildSystemPrompt = async (
|
|
2785
|
+
toolNames: string[],
|
|
2786
|
+
tools: Map<string, AgentTool>,
|
|
2787
|
+
): Promise<BuildSystemPromptResult> => {
|
|
2788
|
+
toolContextStore.setToolNames(toolNames);
|
|
2789
|
+
const promptCwd = sessionManager.getCwd();
|
|
2790
|
+
const activeRepoContext = hasSession
|
|
2791
|
+
? await logger.time("resolveActiveRepoContext", resolveRepoContext, promptCwd)
|
|
2792
|
+
: initialActiveRepoContext;
|
|
2793
|
+
if (hasSession && options.contextFiles === undefined) {
|
|
2794
|
+
contextFiles = await logger.time("discoverContextFiles", discoverContextFiles, promptCwd, agentDir, [
|
|
2795
|
+
...(settings.get("disabledExtensions") ?? []),
|
|
2796
|
+
]);
|
|
2797
|
+
toolSession.contextFiles = contextFiles;
|
|
2798
|
+
session.setAdvisorContextPrompt(formatAdvisorContextPrompt(contextFiles));
|
|
2799
|
+
}
|
|
2800
|
+
const memoryBackend = restrictToolNames ? undefined : await resolveMemoryBackend(settings);
|
|
2801
|
+
const memoryInstructions = memoryBackend
|
|
2802
|
+
? await memoryBackend.buildDeveloperInstructions(agentDir, settings, session)
|
|
2803
|
+
: undefined;
|
|
2804
|
+
|
|
2805
|
+
// Build combined append prompt: memory instructions + auto-learn guidance
|
|
2806
|
+
// + mounted MCP route guidance + optional MCP server instructions. For UI
|
|
2807
|
+
// sessions MCP discovery is deferred, so the initial registry and
|
|
2808
|
+
// `getServerInstructions()` are empty until the background connect
|
|
2809
|
+
// completes; the rebuild that `refreshMCPTools` triggers post-discovery
|
|
2810
|
+
// then picks up the mounted routes and any connected-server instructions.
|
|
2811
|
+
const serverInstructions = mcpManager?.getServerInstructions();
|
|
2812
|
+
// Drive guidance off the auto-learn BUILTINS that createTools actually built
|
|
2813
|
+
// (provenance, not just an active name): `builtInToolNames` excludes a
|
|
2814
|
+
// custom/extension tool that merely shares the name, and reflects the
|
|
2815
|
+
// session-start build — so a subagent that filtered them out, a mid-session
|
|
2816
|
+
// enable that never built them, or a same-named custom tool while auto-learn
|
|
2817
|
+
// is off all get no guidance.
|
|
2818
|
+
const autoLearnInstructions = restrictToolNames
|
|
2819
|
+
? undefined
|
|
2820
|
+
: buildAutoLearnInstructions({
|
|
2821
|
+
manageSkill: builtInToolNames.includes("manage_skill"),
|
|
2822
|
+
learn: builtInToolNames.includes("learn"),
|
|
2823
|
+
});
|
|
2824
|
+
const appendParts: string[] = [];
|
|
2825
|
+
if (memoryInstructions) appendParts.push(memoryInstructions);
|
|
2826
|
+
if (autoLearnInstructions) appendParts.push(autoLearnInstructions);
|
|
2827
|
+
const projection = projectMountedMCPXdevGuidance(
|
|
2828
|
+
collectMountedMCPToolRoutes(toolSession.xdev ? listXdevTools(toolSession.xdev) : []),
|
|
2829
|
+
);
|
|
2830
|
+
if (projection.mappings.length > 0 || projection.hasOmittedMappings) {
|
|
2831
|
+
appendParts.push(
|
|
2832
|
+
prompt
|
|
2833
|
+
.render(mcpXdevGuidanceTemplate, {
|
|
2834
|
+
tools: projection.mappings.map(mapping => ({
|
|
2835
|
+
mcpToolName: mapping.label,
|
|
2836
|
+
path: mapping.path,
|
|
2837
|
+
})),
|
|
2838
|
+
hasOmittedTools: projection.hasOmittedMappings,
|
|
2839
|
+
})
|
|
2840
|
+
.trim(),
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
if (serverInstructions && serverInstructions.size > 0) {
|
|
2844
|
+
appendParts.push(
|
|
2845
|
+
"## MCP Server Instructions\n\nThe following instructions are provided by connected MCP servers. They are server-controlled and may not be verified.",
|
|
2846
|
+
);
|
|
2847
|
+
for (const [srvName, srvInstructions] of serverInstructions) {
|
|
2848
|
+
const truncated =
|
|
2849
|
+
srvInstructions.length > MAX_MCP_INSTRUCTIONS_LENGTH
|
|
2850
|
+
? `${srvInstructions.slice(0, MAX_MCP_INSTRUCTIONS_LENGTH)}\n[truncated]`
|
|
2851
|
+
: srvInstructions;
|
|
2852
|
+
appendParts.push(`### ${srvName}\n${truncated}`);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
let appendPrompt: string | undefined = appendParts.length > 0 ? appendParts.join("\n\n") : undefined;
|
|
2856
|
+
// Owned/in-band tool dialects (non-native) require the full functions-
|
|
2857
|
+
// namespace catalog; native tool calling lets the compact name list suffice.
|
|
2858
|
+
const nativeTools = resolveDialect(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
|
|
2859
|
+
const promptTools = projectSystemPromptToolMetadata(
|
|
2860
|
+
tools,
|
|
2861
|
+
nativeTools && !inlineToolDescriptors ? { mode: "compact", toolNames } : { mode: "full" },
|
|
2862
|
+
);
|
|
2863
|
+
if (options.appendSystemPrompt) {
|
|
2864
|
+
appendPrompt = appendPrompt
|
|
2865
|
+
? `${appendPrompt}\n\n${options.appendSystemPrompt}`
|
|
2866
|
+
: options.appendSystemPrompt;
|
|
2867
|
+
}
|
|
2868
|
+
const defaultPrompt = await buildSystemPromptInternal({
|
|
2869
|
+
cwd: promptCwd,
|
|
2870
|
+
additionalWorkspaceRoots: sessionManager.getAdditionalDirectories(),
|
|
2871
|
+
xdevTools: toolSession.xdev ? xdevEntries(toolSession.xdev) : [],
|
|
2872
|
+
xdevDocs: toolSession.xdev
|
|
2873
|
+
? xdevDocsAll(toolSession.xdev, settings.get("tools.xdevDocs"), settings.get("tools.xdevInlineDevices"))
|
|
2874
|
+
: "",
|
|
2875
|
+
resolvedCustomPrompt: options.customSystemPrompt,
|
|
2876
|
+
skills: session?.skills ?? skills,
|
|
2877
|
+
contextFiles,
|
|
2878
|
+
tools: promptTools,
|
|
2879
|
+
toolNames,
|
|
2880
|
+
rules: rulebookRules,
|
|
2881
|
+
alwaysApplyRules,
|
|
2882
|
+
resolvedAppendSystemPrompt: appendPrompt,
|
|
2883
|
+
skillsSettings: settings.getGroup("skills"),
|
|
2884
|
+
inlineToolDescriptors,
|
|
2885
|
+
nativeTools,
|
|
2886
|
+
intentField,
|
|
2887
|
+
eagerTasks,
|
|
2888
|
+
eagerTasksAlways,
|
|
2889
|
+
taskBatch: settings.get("task.batch"),
|
|
2890
|
+
taskMaxConcurrency: settings.get("task.maxConcurrency"),
|
|
2891
|
+
scoutAvailable: isScoutSpawnable(
|
|
2892
|
+
settings.get("task.disabledAgents") as string[] | undefined,
|
|
2893
|
+
options.spawns ?? "*",
|
|
2894
|
+
),
|
|
2895
|
+
taskIrcEnabled: !restrictToolNames && isIrcEnabled(settings, options.taskDepth ?? 0),
|
|
2896
|
+
autoQaEnabled: !restrictToolNames && isAutoQaEnabled(settings),
|
|
2897
|
+
secretsEnabled,
|
|
2898
|
+
workspaceTree: workspaceTreePromise,
|
|
2899
|
+
includeWorkspaceTree,
|
|
2900
|
+
memoryRootEnabled: memoryBackend?.id === "local",
|
|
2901
|
+
securityEnabled: settings.get("security.enabled"),
|
|
2902
|
+
model: getActiveModelString(),
|
|
2903
|
+
includeModelInPrompt: settings.get("includeModelInPrompt"),
|
|
2904
|
+
personality: agentKind === "sub" ? "none" : settings.get("personality"),
|
|
2905
|
+
renderMermaid: settings.get("tui.renderMermaid"),
|
|
2906
|
+
activeRepoContext,
|
|
2907
|
+
});
|
|
2908
|
+
|
|
2909
|
+
if (options.systemPrompt === undefined) {
|
|
2910
|
+
return defaultPrompt;
|
|
2911
|
+
}
|
|
2912
|
+
const customPrompt =
|
|
2913
|
+
typeof options.systemPrompt === "function"
|
|
2914
|
+
? options.systemPrompt(defaultPrompt.systemPrompt)
|
|
2915
|
+
: options.systemPrompt;
|
|
2916
|
+
return {
|
|
2917
|
+
systemPrompt: typeof customPrompt === "string" ? [customPrompt] : customPrompt,
|
|
2918
|
+
};
|
|
2919
|
+
};
|
|
2920
|
+
|
|
2921
|
+
const toolNamesFromRegistry = Array.from(toolRegistry.keys());
|
|
2922
|
+
const explicitlyRequestedToolNames = options.toolNames ? normalizeToolNames(options.toolNames) : undefined;
|
|
2923
|
+
// When `requireYieldTool` is set, the subagent's prompts and idle-reminders demand a
|
|
2924
|
+
// `yield` call to terminate. The tool registry already includes `yield` (see
|
|
2925
|
+
// `createTools`), but an explicit `toolNames` list would otherwise drop it from the
|
|
2926
|
+
// active set — leaving the model unable to satisfy the contract. Mirror the same
|
|
2927
|
+
// invariant `parseAgentFields` enforces on frontmatter `tools`.
|
|
2928
|
+
if (
|
|
2929
|
+
options.requireYieldTool === true &&
|
|
2930
|
+
explicitlyRequestedToolNames &&
|
|
2931
|
+
!explicitlyRequestedToolNames.includes("yield")
|
|
2932
|
+
) {
|
|
2933
|
+
explicitlyRequestedToolNames.push("yield");
|
|
2934
|
+
}
|
|
2935
|
+
// Auto-learn builtins are force-included into the registry by `createTools`
|
|
2936
|
+
// for enabled top-level sessions (tools/index.ts), but — like `yield` above —
|
|
2937
|
+
// an explicit `toolNames` list would otherwise drop them from the ACTIVE set,
|
|
2938
|
+
// leaving the nudge/guidance pointing at tools the model cannot call. Activate
|
|
2939
|
+
// exactly the builtins createTools built (`builtInToolNames` — provenance, so a
|
|
2940
|
+
// same-named custom/extension tool is never force-activated when auto-learn is
|
|
2941
|
+
// off) to keep guidance, controller, and the active set consistent.
|
|
2942
|
+
if (!restrictToolNames && explicitlyRequestedToolNames) {
|
|
2943
|
+
for (const name of ["manage_skill", "learn"]) {
|
|
2944
|
+
if (builtInToolNames.includes(name) && !explicitlyRequestedToolNames.includes(name)) {
|
|
2945
|
+
explicitlyRequestedToolNames.push(name);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
// Checkpoint and rewind are a pair: `createTools` auto-includes the sister
|
|
2950
|
+
// tool in the registry, but an explicit `toolNames` list would otherwise
|
|
2951
|
+
// drop it from the ACTIVE set — leaving the agent able to checkpoint but
|
|
2952
|
+
// unable to rewind (or vice versa). Mirror the pairing here. Unlike the
|
|
2953
|
+
// manage_skill/learn mirror above, this is a safety pairing — it applies
|
|
2954
|
+
// to restricted sessions too.
|
|
2955
|
+
if (explicitlyRequestedToolNames) {
|
|
2956
|
+
if (builtInToolNames.includes("checkpoint") && !explicitlyRequestedToolNames.includes("rewind")) {
|
|
2957
|
+
explicitlyRequestedToolNames.push("rewind");
|
|
2958
|
+
} else if (builtInToolNames.includes("rewind") && !explicitlyRequestedToolNames.includes("checkpoint")) {
|
|
2959
|
+
explicitlyRequestedToolNames.push("checkpoint");
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
const requestedToolNames = explicitlyRequestedToolNames ?? toolNamesFromRegistry;
|
|
2963
|
+
const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
|
|
2964
|
+
const defaultInactiveToolNames = new Set(
|
|
2965
|
+
registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
|
|
2966
|
+
);
|
|
2967
|
+
const requestedActiveToolNames = normalizedRequested.filter(name => name !== "goal");
|
|
2968
|
+
const explicitlyRequestedToolNameSet = explicitlyRequestedToolNames
|
|
2969
|
+
? new Set(explicitlyRequestedToolNames)
|
|
2970
|
+
: undefined;
|
|
2971
|
+
const xdevReadAvailable =
|
|
2972
|
+
builtInRegistryToolNames.has("read") &&
|
|
2973
|
+
(explicitlyRequestedToolNameSet === undefined || explicitlyRequestedToolNameSet.has("read"));
|
|
2974
|
+
const xdevWriteAvailable =
|
|
2975
|
+
builtInRegistryToolNames.has("write") &&
|
|
2976
|
+
(explicitlyRequestedToolNameSet === undefined || explicitlyRequestedToolNameSet.has("write"));
|
|
2977
|
+
const initialRequestedActiveToolNames = options.toolNames
|
|
2978
|
+
? requestedActiveToolNames
|
|
2979
|
+
: requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
|
|
2980
|
+
let initialToolNames = [...initialRequestedActiveToolNames];
|
|
2981
|
+
|
|
2982
|
+
// Custom tools and extension-registered tools are always included regardless of toolNames filter.
|
|
2983
|
+
// Restricted callers own the list, so never widen it with registered tools.
|
|
2984
|
+
const alwaysInclude: string[] = restrictToolNames
|
|
2985
|
+
? []
|
|
2986
|
+
: [
|
|
2987
|
+
...sdkCustomTools.map(t => (isCustomTool(t) ? t.name : t.name)),
|
|
2988
|
+
...registeredTools.filter(t => !t.definition.defaultInactive).map(t => t.definition.name),
|
|
2989
|
+
];
|
|
2990
|
+
for (const name of alwaysInclude) {
|
|
2991
|
+
if (toolRegistry.has(name) && !initialToolNames.includes(name)) {
|
|
2992
|
+
initialToolNames.push(name);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
// Pre-register in the global agent registry BEFORE building the system prompt,
|
|
2997
|
+
// so that subagents launched in the same parallel batch can see each other in
|
|
2998
|
+
// their initial `# IRC Peers` block (rendered inside `rebuildSystemPrompt`).
|
|
2999
|
+
// The session reference is attached after construction below.
|
|
3000
|
+
const registrationInput = {
|
|
3001
|
+
id: resolvedAgentId,
|
|
3002
|
+
displayName: resolvedAgentDisplayName,
|
|
3003
|
+
kind: agentKind,
|
|
3004
|
+
parentId: options.parentAgentId,
|
|
3005
|
+
session: null,
|
|
3006
|
+
sessionFile: sessionManager.getSessionFile() ?? null,
|
|
3007
|
+
status: "running" as const,
|
|
3008
|
+
};
|
|
3009
|
+
registeredAgentRef =
|
|
3010
|
+
options.expectedAgentRef === undefined
|
|
3011
|
+
? agentRegistry.register(registrationInput)
|
|
3012
|
+
: agentRegistry.registerIfAvailable(registrationInput, options.expectedAgentRef);
|
|
3013
|
+
if (!registeredAgentRef) {
|
|
3014
|
+
throw new Error(`Agent "${resolvedAgentId}" is already owned by another session generation.`);
|
|
3015
|
+
}
|
|
3016
|
+
// A reused parked ref remains parked until the new AgentSession is fully
|
|
3017
|
+
// constructed and attached. Startup failure therefore leaves it revivable.
|
|
3018
|
+
hasRegistered = options.expectedAgentRef === undefined || options.expectedAgentRef === null;
|
|
3019
|
+
|
|
3020
|
+
// Partition the initial enabled set for the xd:// transport. Tool instances
|
|
3021
|
+
// remain in the canonical map; only presentation names move between layers.
|
|
3022
|
+
// Mounting requires both transport halves in the granted set (`read xd://`
|
|
3023
|
+
// discovers, `write xd://<tool>` executes); a session without either keeps
|
|
3024
|
+
// every tool top-level instead of auto-granting the missing transport.
|
|
3025
|
+
if (toolSession.xdev) {
|
|
3026
|
+
const topLevelToolNames: string[] = [];
|
|
3027
|
+
const mountedNames: string[] = [];
|
|
3028
|
+
for (const name of initialToolNames) {
|
|
3029
|
+
const tool = toolRegistry.get(name);
|
|
3030
|
+
const explicitlyRequested = explicitlyRequestedToolNameSet?.has(name) === true;
|
|
3031
|
+
if (tool && xdevReadAvailable && xdevWriteAvailable && !explicitlyRequested && isMountableUnderXdev(tool))
|
|
3032
|
+
mountedNames.push(name);
|
|
3033
|
+
else topLevelToolNames.push(name);
|
|
3034
|
+
}
|
|
3035
|
+
toolSession.xdev.mountedNames.clear();
|
|
3036
|
+
for (const name of mountedNames) toolSession.xdev.mountedNames.add(name);
|
|
3037
|
+
initialToolNames = topLevelToolNames;
|
|
3038
|
+
if (mountedNames.length > 0 && !initialToolNames.includes("write")) initialToolNames.push("write");
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
setActiveToolNames(initialToolNames);
|
|
3042
|
+
const { systemPrompt } = await logger.time(
|
|
3043
|
+
"buildSystemPrompt",
|
|
3044
|
+
rebuildSystemPrompt,
|
|
3045
|
+
initialToolNames,
|
|
3046
|
+
toolRegistry,
|
|
3047
|
+
);
|
|
3048
|
+
|
|
3049
|
+
const promptTemplates = await promptTemplatesPromise;
|
|
3050
|
+
toolSession.promptTemplates = promptTemplates;
|
|
3051
|
+
|
|
3052
|
+
const slashCommands = await slashCommandsPromise;
|
|
3053
|
+
|
|
3054
|
+
// Keep image blocks off the wire when they'd be rejected: either the user
|
|
3055
|
+
// disabled images (`images.blockImages`) or the active model has no vision
|
|
3056
|
+
// support. The latter covers switching from a vision model to a text-only
|
|
3057
|
+
// one mid-session — historical image blocks would otherwise be replayed to
|
|
3058
|
+
// a provider that 400s on them (#5400). Read both dynamically so a `/model`
|
|
3059
|
+
// switch or setting change takes effect on the next turn.
|
|
3060
|
+
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
|
|
3061
|
+
const converted = convertToLlm(messages);
|
|
3062
|
+
if (settings.get("images.blockImages")) {
|
|
3063
|
+
return replaceLlmImagesWithText(converted, "Image reading is disabled.");
|
|
3064
|
+
}
|
|
3065
|
+
const activeModel = agent?.state.model ?? model;
|
|
3066
|
+
if (activeModel && !activeModel.input.includes("image")) {
|
|
3067
|
+
return replaceLlmImagesWithText(
|
|
3068
|
+
converted,
|
|
3069
|
+
"[image omitted: the active model does not support image input]",
|
|
3070
|
+
);
|
|
3071
|
+
}
|
|
3072
|
+
return converted;
|
|
3073
|
+
};
|
|
3074
|
+
|
|
3075
|
+
// Final convertToLlm: live provider replay drops API-level refusal errors,
|
|
3076
|
+
// then applies secret obfuscation to the remaining outbound context.
|
|
3077
|
+
const convertToLlmFinal = (messages: AgentMessage[]): Message[] => {
|
|
3078
|
+
const converted = filterProviderReplayMessages(convertToLlmWithBlockImages(messages));
|
|
3079
|
+
if (!obfuscator?.hasSecrets()) return converted;
|
|
3080
|
+
return obfuscateMessages(obfuscator, converted);
|
|
3081
|
+
};
|
|
3082
|
+
|
|
3083
|
+
const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal) => {
|
|
3084
|
+
const withContext = await extensionRunner.emitContext(messages);
|
|
3085
|
+
return wrapSteeringForModel(withContext);
|
|
3086
|
+
};
|
|
3087
|
+
// Per-request provider-context transforms. Obfuscate FIRST so secrets are
|
|
3088
|
+
// redacted from text before snapcompact rasterizes it into PNG frames, then
|
|
3089
|
+
// clamp images to the active provider budget before the request is sent.
|
|
3090
|
+
const snapcompactSystemPromptMode = settings.get("snapcompact.systemPrompt");
|
|
3091
|
+
const snapcompactInline =
|
|
3092
|
+
snapcompactSystemPromptMode !== "none" || settings.get("snapcompact.toolResults")
|
|
3093
|
+
? new SnapcompactInlineTransformer(
|
|
3094
|
+
{
|
|
3095
|
+
renderSystemPrompt: snapcompactSystemPromptMode,
|
|
3096
|
+
renderToolResults: settings.get("snapcompact.toolResults"),
|
|
3097
|
+
shape: settings.get("snapcompact.shape"),
|
|
3098
|
+
},
|
|
3099
|
+
// Journal the tokens each imaged tool result keeps off the wire
|
|
3100
|
+
// (frames never reach session.jsonl, so this is their only trace).
|
|
3101
|
+
createSnapcompactSavingsRecorder(() => sessionManager.getSessionFile() ?? null),
|
|
3102
|
+
)
|
|
3103
|
+
: undefined;
|
|
3104
|
+
const transformProviderContext = async (context: Context, transformModel: Model): Promise<Context> => {
|
|
3105
|
+
let transformed = obfuscator ? obfuscateProviderContext(obfuscator, context) : context;
|
|
3106
|
+
if (snapcompactInline) transformed = await snapcompactInline.transform(transformed, transformModel);
|
|
3107
|
+
return clampProviderContextImages(transformed, transformModel);
|
|
3108
|
+
};
|
|
3109
|
+
const onPayload = async (payload: unknown, model?: Model) => {
|
|
3110
|
+
return await extensionRunner.emitBeforeProviderRequest(payload, model);
|
|
3111
|
+
};
|
|
3112
|
+
const onResponse: SimpleStreamOptions["onResponse"] = async (response, model) => {
|
|
3113
|
+
await extensionRunner.emitAfterProviderResponse(response, model);
|
|
3114
|
+
};
|
|
3115
|
+
|
|
3116
|
+
const setToolUIContext = (uiContext: ExtensionUIContext, hasUI: boolean) => {
|
|
3117
|
+
toolContextStore.setUIContext(uiContext, hasUI);
|
|
3118
|
+
};
|
|
3119
|
+
|
|
3120
|
+
const initialTools = initialToolNames
|
|
3121
|
+
.map(name => toolRegistry.get(name))
|
|
3122
|
+
.filter((tool): tool is AgentTool => tool !== undefined);
|
|
3123
|
+
const autoLearnCaptureTools = initialTools.filter(tool => tool.name === "manage_skill" || tool.name === "learn");
|
|
3124
|
+
|
|
3125
|
+
const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
|
|
3126
|
+
const preferOpenAICodexWebsockets =
|
|
3127
|
+
openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
|
|
3128
|
+
const configuredServiceTierByFamily = hasServiceTierEntry
|
|
3129
|
+
? (existingSession.serviceTier ?? {})
|
|
3130
|
+
: buildServiceTierByFamily(
|
|
3131
|
+
settings.get("tier.openai"),
|
|
3132
|
+
settings.get("tier.anthropic"),
|
|
3133
|
+
settings.get("tier.google"),
|
|
3134
|
+
);
|
|
3135
|
+
const initialServiceTierByFamily = { ...configuredServiceTierByFamily };
|
|
3136
|
+
if (options.openAIServiceTier === null) {
|
|
3137
|
+
delete initialServiceTierByFamily.openai;
|
|
3138
|
+
} else if (options.openAIServiceTier !== undefined) {
|
|
3139
|
+
initialServiceTierByFamily.openai = options.openAIServiceTier;
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
// One-shot launch-latency marker: fired the first time the loop dispatches
|
|
3143
|
+
// a chat request to the provider transport. See onFirstChatDispatch.
|
|
3144
|
+
let notifyFirstChatDispatch = options.onFirstChatDispatch;
|
|
3145
|
+
// Shared, settings-aware stream wrapper used by the main agent, advisor,
|
|
3146
|
+
// and side-channel requests (`/btw`, `/omfg`, IRC auto-replies, handoff).
|
|
3147
|
+
// Keeps OpenRouter sticky-routing variants, antigravity endpoint routing,
|
|
3148
|
+
// in-flight caps, and the loop guard consistent across every provider call
|
|
3149
|
+
// the session drives. Wrapped in a per-provider concurrency limiter so
|
|
3150
|
+
// each LLM HTTP request — not the whole subagent lifecycle — holds the
|
|
3151
|
+
// slot, preventing the nested-spawn deadlock from issue #3749.
|
|
3152
|
+
const settingsAwareStreamFn = wrapStreamFnWithProviderConcurrency(
|
|
3153
|
+
settings,
|
|
3154
|
+
createSettingsAwareStreamFn(settings),
|
|
3155
|
+
);
|
|
3156
|
+
const transformToolCallArguments = (args: Record<string, unknown>): Record<string, unknown> => {
|
|
3157
|
+
let result = args;
|
|
3158
|
+
const maxTimeout = settings.get("tools.maxTimeout");
|
|
3159
|
+
if (maxTimeout > 0 && typeof result.timeout === "number") {
|
|
3160
|
+
result = { ...result, timeout: Math.min(result.timeout, maxTimeout) };
|
|
3161
|
+
}
|
|
3162
|
+
if (obfuscator?.hasSecrets()) {
|
|
3163
|
+
result = deobfuscateToolArguments(obfuscator, result);
|
|
3164
|
+
}
|
|
3165
|
+
return result;
|
|
3166
|
+
};
|
|
3167
|
+
const kimiApiFormatSetting = settings.get("providers.kimiApiFormat");
|
|
3168
|
+
const kimiApiFormat = kimiApiFormatSetting === "auto" ? undefined : kimiApiFormatSetting;
|
|
3169
|
+
agent = new Agent({
|
|
3170
|
+
initialState: {
|
|
3171
|
+
systemPrompt,
|
|
3172
|
+
model,
|
|
3173
|
+
thinkingLevel: toReasoningEffort(effectiveThinkingLevel),
|
|
3174
|
+
disableReasoning: shouldDisableReasoning(effectiveThinkingLevel),
|
|
3175
|
+
tools: initialTools,
|
|
3176
|
+
},
|
|
3177
|
+
cwd,
|
|
3178
|
+
// Live cwd: `/move` updates SessionManager (and process cwd) without
|
|
3179
|
+
// reconstructing the Agent, so a static cwd would strand GitLab Duo Agent
|
|
3180
|
+
// namespace/project discovery on the original repo's git remote. Re-read it
|
|
3181
|
+
// per turn from the SessionManager.
|
|
3182
|
+
cwdResolver: () => sessionManager.getCwd(),
|
|
3183
|
+
convertToLlm: convertToLlmFinal,
|
|
3184
|
+
onPayload,
|
|
3185
|
+
onResponse,
|
|
3186
|
+
sessionId: providerSessionId,
|
|
3187
|
+
promptCacheKey: providerPromptCacheKey,
|
|
3188
|
+
deadline: options.deadline,
|
|
3189
|
+
transformContext,
|
|
3190
|
+
transformProviderContext,
|
|
3191
|
+
steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
|
|
3192
|
+
followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
|
|
3193
|
+
interruptMode: settings.get("interruptMode") ?? "immediate",
|
|
3194
|
+
thinkingBudgets: settings.getGroup("thinkingBudgets"),
|
|
3195
|
+
temperature: settings.get("temperature") >= 0 ? settings.get("temperature") : undefined,
|
|
3196
|
+
topP: settings.get("topP") >= 0 ? settings.get("topP") : undefined,
|
|
3197
|
+
topK: settings.get("topK") >= 0 ? settings.get("topK") : undefined,
|
|
3198
|
+
minP: settings.get("minP") >= 0 ? settings.get("minP") : undefined,
|
|
3199
|
+
presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
|
|
3200
|
+
repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
|
|
3201
|
+
hideThinkingSummary: settings.get("omitThinking"),
|
|
3202
|
+
kimiApiFormat,
|
|
3203
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
3204
|
+
getToolContext: tc => toolContextStore.getContext(tc),
|
|
3205
|
+
getApiKey: options.getApiKey ?? (requestModel => modelRegistry.resolver(requestModel, agent.sessionId)),
|
|
3206
|
+
streamFn: (streamModel, context, streamOptions) => {
|
|
3207
|
+
if (notifyFirstChatDispatch) {
|
|
3208
|
+
const cb = notifyFirstChatDispatch;
|
|
3209
|
+
notifyFirstChatDispatch = undefined;
|
|
3210
|
+
try {
|
|
3211
|
+
cb();
|
|
3212
|
+
} catch (err) {
|
|
3213
|
+
logger.warn("onFirstChatDispatch hook threw", {
|
|
3214
|
+
error: err instanceof Error ? err.message : String(err),
|
|
3215
|
+
});
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
return settingsAwareStreamFn(streamModel, context, streamOptions);
|
|
3219
|
+
},
|
|
3220
|
+
cursorExecHandlers,
|
|
3221
|
+
getCursorTools: () => (toolSession.xdev ? listXdevTools(toolSession.xdev) : []),
|
|
3222
|
+
transformToolCallArguments,
|
|
3223
|
+
resolveFallbackTool: resolveDeviceTool,
|
|
3224
|
+
intentTracing: !!intentField,
|
|
3225
|
+
pruneToolDescriptions: inlineToolDescriptors,
|
|
3226
|
+
dialect: resolveDialect(settings.get("tools.format"), model),
|
|
3227
|
+
abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
|
|
3228
|
+
getToolChoice: () => session?.nextToolChoiceDirective(),
|
|
3229
|
+
onToolChoiceUnavailable: () => session?.toolChoiceQueue.reject("unavailable"),
|
|
3230
|
+
telemetry: options.telemetry,
|
|
3231
|
+
appendOnlyContext: model
|
|
3232
|
+
? shouldEnableAppendOnlyContext(settings.get("provider.appendOnlyContext"), model)
|
|
3233
|
+
? new AppendOnlyContextManager()
|
|
3234
|
+
: undefined
|
|
3235
|
+
: undefined,
|
|
3236
|
+
});
|
|
3237
|
+
|
|
3238
|
+
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
3239
|
+
|
|
3240
|
+
// Restore messages if session has existing data
|
|
3241
|
+
if (hasExistingSession) {
|
|
3242
|
+
agent.replaceMessages(existingSession.messages);
|
|
3243
|
+
if (options.openAIServiceTier !== undefined) {
|
|
3244
|
+
sessionManager.appendServiceTierChange(
|
|
3245
|
+
Object.keys(initialServiceTierByFamily).length > 0 ? initialServiceTierByFamily : null,
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
} else {
|
|
3249
|
+
// Save initial model, thinking level, and service tier for new sessions so they can be restored on resume.
|
|
3250
|
+
if (model) {
|
|
3251
|
+
sessionManager.appendModelChange(`${model.provider}/${model.id}`);
|
|
3252
|
+
}
|
|
3253
|
+
if (!autoThinking) {
|
|
3254
|
+
// Do not write the `auto` selector before the first turn resolves; auto
|
|
3255
|
+
// classification persists its concrete effort once a real user turn runs.
|
|
3256
|
+
sessionManager.appendThinkingLevelChange(effectiveThinkingLevel);
|
|
3257
|
+
}
|
|
3258
|
+
if (options.openAIServiceTier !== undefined || Object.keys(initialServiceTierByFamily).length > 0) {
|
|
3259
|
+
sessionManager.appendServiceTierChange(
|
|
3260
|
+
Object.keys(initialServiceTierByFamily).length > 0 ? initialServiceTierByFamily : null,
|
|
3261
|
+
);
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
// Full toolset for the advisor, built unconditionally so it can be toggled at
|
|
3266
|
+
// runtime. Bound to a DISTINCT ToolSession (its own `-advisor` session id +
|
|
3267
|
+
// agent id) so the advisor's tool state — snapshot, seen-lines, conflict, and
|
|
3268
|
+
// summary caches, all keyed on session identity — stays isolated from the
|
|
3269
|
+
// primary, while edit/bash/write stay fully functional: the advisor is a full
|
|
3270
|
+
// agent and its config's `tools` selects which of these it actually gets
|
|
3271
|
+
// (defaulting to read/grep/glob).
|
|
3272
|
+
const advisorToolSession: ToolSession = {
|
|
3273
|
+
...toolSession,
|
|
3274
|
+
get cwd() {
|
|
3275
|
+
return sessionManager.getCwd();
|
|
3276
|
+
},
|
|
3277
|
+
hasEditTool: true,
|
|
3278
|
+
requireYieldTool: false,
|
|
3279
|
+
getSessionId: () => {
|
|
3280
|
+
const id = sessionManager.getSessionId?.();
|
|
3281
|
+
return id ? `${id}-advisor` : null;
|
|
3282
|
+
},
|
|
3283
|
+
queueLaunchCompletion: notification =>
|
|
3284
|
+
session?.queueLaunchCompletion(notification) ??
|
|
3285
|
+
Promise.reject(new Error("Session unavailable for launch completion delivery")),
|
|
3286
|
+
getAgentId: () => "advisor",
|
|
3287
|
+
// The primary's availability signals are wrong for advisors: their tool
|
|
3288
|
+
// slate is filtered separately at runtime (default read/grep/glob, no
|
|
3289
|
+
// write transport), so xd:// devices are unreachable and read must never
|
|
3290
|
+
// advertise inspect_image — images are inlined, and the provider
|
|
3291
|
+
// boundary handles text-only advisor models.
|
|
3292
|
+
xdev: undefined,
|
|
3293
|
+
isToolActive: name => name !== "inspect_image" && toolSession.isToolActive?.(name) === true,
|
|
3294
|
+
};
|
|
3295
|
+
const advisorToolBuilds: Array<Tool | null | Promise<Tool | null>> = [];
|
|
3296
|
+
for (const name in BUILTIN_TOOLS) {
|
|
3297
|
+
advisorToolBuilds.push(BUILTIN_TOOLS[name as keyof typeof BUILTIN_TOOLS](advisorToolSession));
|
|
3298
|
+
}
|
|
3299
|
+
const built = await Promise.all(advisorToolBuilds);
|
|
3300
|
+
// Wrapped like every registry tool: `ExtensionToolWrapper` is where the
|
|
3301
|
+
// approval mode, per-tool `tools.approval.<tool>` policies and
|
|
3302
|
+
// `autoApprove` are enforced. The advisor's loop and its Cursor exec
|
|
3303
|
+
// bridge both run these instances directly, so a raw one would execute a
|
|
3304
|
+
// `bash`/`write` the user configured as `ask` or `deny`. Meta-notice
|
|
3305
|
+
// first, matching the registry's wrap order.
|
|
3306
|
+
const advisorTools: Tool[] = built
|
|
3307
|
+
.filter((tool): tool is Tool => tool != null)
|
|
3308
|
+
.map(tool => new ExtensionToolWrapper(wrapToolWithMetaNotice(tool), extensionRunner) as Tool);
|
|
3309
|
+
|
|
3310
|
+
const advisorWatchdogPrompts = [...watchdogFiles];
|
|
3311
|
+
if (initialActiveRepoContext) {
|
|
3312
|
+
advisorWatchdogPrompts.push(formatActiveRepoWatchdogPrompt(initialActiveRepoContext));
|
|
3313
|
+
}
|
|
3314
|
+
const advisorWatchdogPrompt = advisorWatchdogPrompts.length > 0 ? advisorWatchdogPrompts.join("\n\n") : undefined;
|
|
3315
|
+
// Hand the advisor the same project context files (AGENTS.md, etc.) the
|
|
3316
|
+
// primary agent gets in its system prompt, so the read-only reviewer judges
|
|
3317
|
+
// against the user's standing project rules instead of advising blind.
|
|
3318
|
+
const advisorContextPrompt = formatAdvisorContextPrompt(contextFiles);
|
|
3319
|
+
// Owned only when this session created the manager; subagents receive a
|
|
3320
|
+
// parent's manager via `options.mcpManager` and MUST NOT disconnect it.
|
|
3321
|
+
const ownedMcpManager = options.mcpManager ? undefined : mcpManager;
|
|
3322
|
+
// A resumed session already has advisor turns on disk; without this the status
|
|
3323
|
+
// line would restart its `(adv)` total at zero for the rest of the session.
|
|
3324
|
+
const initialAdvisorCosts = await loadAdvisorTranscriptCosts(sessionManager.getSessionFile());
|
|
3325
|
+
session = new AgentSession({
|
|
3326
|
+
advisorWatchdogPrompt,
|
|
3327
|
+
advisorContextPrompt,
|
|
3328
|
+
advisorSharedInstructions: discoveredAdvisors.sharedInstructions,
|
|
3329
|
+
advisorConfigs: discoveredAdvisors.advisors,
|
|
3330
|
+
agent,
|
|
3331
|
+
pruneToolDescriptions: inlineToolDescriptors,
|
|
3332
|
+
thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
|
|
3333
|
+
thinkingLevelCeiling: options.thinkingLevelCeiling,
|
|
3334
|
+
initialRetryFallback,
|
|
3335
|
+
prewalk: options.prewalk,
|
|
3336
|
+
planYolo: options.planYolo,
|
|
3337
|
+
serviceTierByFamily: initialServiceTierByFamily,
|
|
3338
|
+
sessionManager,
|
|
3339
|
+
initialAdvisorCosts,
|
|
3340
|
+
settings,
|
|
3341
|
+
autoApprove: options.autoApprove,
|
|
3342
|
+
scoutAllowedBySpawnPolicy: isScoutSpawnable(undefined, options.spawns ?? "*"),
|
|
3343
|
+
evalKernelOwnerId,
|
|
3344
|
+
// Defined only for top-level sessions (creation is gated above).
|
|
3345
|
+
// AgentSession uses this to decide whether it may dispose the global
|
|
3346
|
+
// AsyncJobManager on teardown; subagents inherit the parent's and
|
|
3347
|
+
// **MUST NOT** tear it down.
|
|
3348
|
+
ownedAsyncJobManager: asyncJobManager,
|
|
3349
|
+
asyncJobManager: scopedAsyncJobManager,
|
|
3350
|
+
scopedModels: options.scopedModels,
|
|
3351
|
+
promptTemplates,
|
|
3352
|
+
slashCommands,
|
|
3353
|
+
extensionRunner,
|
|
3354
|
+
customCommands: customCommandsResult.commands,
|
|
3355
|
+
skills,
|
|
3356
|
+
skillWarnings,
|
|
3357
|
+
skillsReloadable: options.skills === undefined,
|
|
3358
|
+
skillsSettings: settings.getGroup("skills"),
|
|
3359
|
+
modelRegistry,
|
|
3360
|
+
toolRegistry,
|
|
3361
|
+
memoryAgentDir: agentDir,
|
|
3362
|
+
memoryTaskDepth: taskDepth,
|
|
3363
|
+
createMemoryTools: restrictToolNames
|
|
3364
|
+
? undefined
|
|
3365
|
+
: async () => {
|
|
3366
|
+
const tools = await Promise.all(
|
|
3367
|
+
MEMORY_BACKEND_TOOL_NAMES.map(name => BUILTIN_TOOLS[name](toolSession)),
|
|
3368
|
+
);
|
|
3369
|
+
return tools.filter((tool): tool is AgentTool => tool !== null);
|
|
3370
|
+
},
|
|
3371
|
+
createComputerTool: restrictToolNames
|
|
3372
|
+
? undefined
|
|
3373
|
+
: async () => (await BUILTIN_TOOLS.computer(toolSession)) ?? null,
|
|
3374
|
+
createInspectImageTool: restrictToolNames
|
|
3375
|
+
? undefined
|
|
3376
|
+
: async () => (await BUILTIN_TOOLS.inspect_image(toolSession)) ?? null,
|
|
3377
|
+
createVibeTools:
|
|
3378
|
+
(options.taskDepth ?? 0) === 0 && !options.parentTaskPrefix
|
|
3379
|
+
? () => createVibeTools(toolSession)
|
|
3380
|
+
: undefined,
|
|
3381
|
+
builtInToolNames: builtInRegistryToolNames,
|
|
3382
|
+
transformContext,
|
|
3383
|
+
transformProviderContext,
|
|
3384
|
+
onPayload,
|
|
3385
|
+
onResponse,
|
|
3386
|
+
sideStreamFn: settingsAwareStreamFn,
|
|
3387
|
+
advisorStreamFn: settingsAwareStreamFn,
|
|
3388
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
3389
|
+
convertToLlm: convertToLlmFinal,
|
|
3390
|
+
rebuildSystemPrompt,
|
|
3391
|
+
getXdevToolEntries: () => (toolSession.xdev ? xdevEntries(toolSession.xdev) : []),
|
|
3392
|
+
xdev: toolSession.xdev,
|
|
3393
|
+
presentationPinnedToolNames: explicitlyRequestedToolNameSet,
|
|
3394
|
+
setActiveToolNames,
|
|
3395
|
+
ensureWriteRegistered,
|
|
3396
|
+
getMcpServerInstructions: mcpManager
|
|
3397
|
+
? () => {
|
|
3398
|
+
const raw = mcpManager.getServerInstructions();
|
|
3399
|
+
if (!raw || raw.size === 0) return raw;
|
|
3400
|
+
const out = new Map<string, string>();
|
|
3401
|
+
for (const [name, text] of raw) {
|
|
3402
|
+
out.set(
|
|
3403
|
+
name,
|
|
3404
|
+
text.length > MAX_MCP_INSTRUCTIONS_LENGTH ? text.slice(0, MAX_MCP_INSTRUCTIONS_LENGTH) : text,
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
return out;
|
|
3408
|
+
}
|
|
3409
|
+
: undefined,
|
|
3410
|
+
disconnectOwnedMcpManager: ownedMcpManager ? () => ownedMcpManager.disconnectAll() : undefined,
|
|
3411
|
+
ttsrManager,
|
|
3412
|
+
obfuscator,
|
|
3413
|
+
agentId: resolvedAgentId,
|
|
3414
|
+
agentKind,
|
|
3415
|
+
providerSessionId: options.providerSessionId,
|
|
3416
|
+
providerPromptCacheKeySource,
|
|
3417
|
+
parentEvalSessionId: options.parentEvalSessionId,
|
|
3418
|
+
advisorTools,
|
|
3419
|
+
// Same per-call `grep` seam the primary bridge gets, built against the
|
|
3420
|
+
// advisor's own tool session so a `pi_grep` frame's context width and
|
|
3421
|
+
// match cap are honored there too.
|
|
3422
|
+
advisorCreateGrepTool: createBridgeGrepFactory(advisorToolSession, extensionRunner),
|
|
3423
|
+
// Same `replace`-mode requirement as the primary bridge; the advisor
|
|
3424
|
+
// path gates it on the advisor's own `edit` grant.
|
|
3425
|
+
advisorCreateEditTool: () => createBridgeEditTool(advisorToolSession, extensionRunner),
|
|
3426
|
+
// The advisor's bridge tools are wrapped for approval, but the wrapper
|
|
3427
|
+
// reads the mode and per-tool policies only from the execute-time
|
|
3428
|
+
// context — the primary bridge passes the same store.
|
|
3429
|
+
advisorGetToolContext: () => toolContextStore.getContext(),
|
|
3430
|
+
// Same live connections the primary bridge reads; an advisor's
|
|
3431
|
+
// resource frame would otherwise report every server as empty.
|
|
3432
|
+
advisorMcpResources: cursorMcpResources,
|
|
3433
|
+
titleSystemPrompt: options.titleSystemPrompt,
|
|
3434
|
+
});
|
|
3435
|
+
hasSession = true;
|
|
3436
|
+
session.yieldQueue.register<McpNotificationEntry>("mcp-notification", {
|
|
3437
|
+
build: buildMcpNotificationBatchMessage,
|
|
3438
|
+
});
|
|
3439
|
+
session.yieldQueue.register<DeferredDiagnosticsEntry>(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, {
|
|
3440
|
+
isStale: entry => entry.isStale(),
|
|
3441
|
+
build: buildLateDiagnosticsBatchMessage,
|
|
3442
|
+
});
|
|
3443
|
+
|
|
3444
|
+
// Attach the live session to the pre-registered ref so peers can route IRC
|
|
3445
|
+
// messages here. Refresh sessionFile in case it was unavailable at pre-register
|
|
3446
|
+
// time. The dispose wrapper below unregisters on teardown (unless parked).
|
|
3447
|
+
if (
|
|
3448
|
+
!registeredAgentRef ||
|
|
3449
|
+
!agentRegistry.attachSession(
|
|
3450
|
+
resolvedAgentId,
|
|
3451
|
+
session,
|
|
3452
|
+
sessionManager.getSessionFile() ?? null,
|
|
3453
|
+
registeredAgentRef,
|
|
3454
|
+
) ||
|
|
3455
|
+
!agentRegistry.setStatus(resolvedAgentId, "running", registeredAgentRef)
|
|
3456
|
+
) {
|
|
3457
|
+
throw new Error(`Agent "${resolvedAgentId}" was replaced during session initialization.`);
|
|
3458
|
+
}
|
|
3459
|
+
hasRegistered = true;
|
|
3460
|
+
// MCP notification bridge cleanup — assigned when the bridge is wired below,
|
|
3461
|
+
// invoked from the dispose wrapper AND registered as a postmortem so both
|
|
3462
|
+
// explicit-dispose (SDK embedders that reuse the process across sessions) and
|
|
3463
|
+
// process-exit paths tear the listener down. Nulled after use so the closure
|
|
3464
|
+
// graph (`extensionRunner`, `session`) can be GC'd instead of retained by the
|
|
3465
|
+
// process-global postmortem list.
|
|
3466
|
+
let unsubscribeMcpNotifications: (() => void) | undefined;
|
|
3467
|
+
let unregisterMcpPostmortem: (() => void) | undefined;
|
|
3468
|
+
|
|
3469
|
+
{
|
|
3470
|
+
const originalDispose = session.dispose.bind(session);
|
|
3471
|
+
session.dispose = async () => {
|
|
3472
|
+
try {
|
|
3473
|
+
// Reject new session work (eval starts) the moment disposal
|
|
3474
|
+
// begins — the lifecycle await below opens an async gap before
|
|
3475
|
+
// AgentSession.dispose() would otherwise set its guards.
|
|
3476
|
+
session.beginDispose();
|
|
3477
|
+
if (agentKind === "main") {
|
|
3478
|
+
// Top-level teardown owns the global agent lifecycle: park timers,
|
|
3479
|
+
// adopted subagent sessions, revivers. Tear it down while shared
|
|
3480
|
+
// resources (kernels, MCP, LSP) are still live. Subagent disposal
|
|
3481
|
+
// must NOT touch the global lifecycle.
|
|
3482
|
+
const vibeRegistry = VibeSessionRegistry.global();
|
|
3483
|
+
const vibeParentSession = {
|
|
3484
|
+
getAgentId: () => resolvedAgentId,
|
|
3485
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
3486
|
+
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
3487
|
+
sessionManager,
|
|
3488
|
+
asyncJobManager: scopedAsyncJobManager,
|
|
3489
|
+
settings,
|
|
3490
|
+
getActiveModelString,
|
|
3491
|
+
};
|
|
3492
|
+
await vibeRegistry.suspendScope(vibeRegistry.ownerScope(vibeParentSession), scopedAsyncJobManager);
|
|
3493
|
+
await AgentLifecycleManager.global().dispose();
|
|
3494
|
+
}
|
|
3495
|
+
await originalDispose();
|
|
3496
|
+
} finally {
|
|
3497
|
+
unregisterUnlessParked();
|
|
3498
|
+
unsubscribeCredentialDisabled?.();
|
|
3499
|
+
unsubscribeMcpNotifications?.();
|
|
3500
|
+
unregisterMcpPostmortem?.();
|
|
3501
|
+
for (const callback of disposeCallbacks) callback();
|
|
3502
|
+
disposeCallbacks.clear();
|
|
3503
|
+
// Drop refs so the process-global postmortem list doesn't retain
|
|
3504
|
+
// the bridge closure past explicit dispose.
|
|
3505
|
+
unsubscribeMcpNotifications = undefined;
|
|
3506
|
+
unregisterMcpPostmortem = undefined;
|
|
3507
|
+
}
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
if (model?.api === "openai-codex-responses") {
|
|
3512
|
+
// `.api` equality doesn't narrow the generic; the guard makes this cast sound.
|
|
3513
|
+
const codexModel = model as Model<"openai-codex-responses">;
|
|
3514
|
+
const codexTransport = getOpenAICodexTransportDetails(codexModel, {
|
|
3515
|
+
sessionId: providerSessionId,
|
|
3516
|
+
baseUrl: codexModel.baseUrl,
|
|
3517
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
3518
|
+
providerSessionState: session.providerSessionState,
|
|
3519
|
+
});
|
|
3520
|
+
if (codexTransport.websocketPreferred) {
|
|
3521
|
+
void (async () => {
|
|
3522
|
+
try {
|
|
3523
|
+
const codexPrewarmApiKey = options.getApiKey
|
|
3524
|
+
? // `getApiKey` returns a value-or-promise union; unwrap the promise,
|
|
3525
|
+
// then resolve the result if it is itself an ApiKeyResolver.
|
|
3526
|
+
await resolveApiKeyOnce(await options.getApiKey(codexModel))
|
|
3527
|
+
: await modelRegistry.getApiKey(codexModel, providerSessionId);
|
|
3528
|
+
if (!codexPrewarmApiKey) return;
|
|
3529
|
+
await logger.time("prewarmOpenAICodexResponses", prewarmOpenAICodexResponses, codexModel, {
|
|
3530
|
+
apiKey: codexPrewarmApiKey,
|
|
3531
|
+
sessionId: providerSessionId,
|
|
3532
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
3533
|
+
providerSessionState: session.providerSessionState,
|
|
3534
|
+
});
|
|
3535
|
+
} catch (error) {
|
|
3536
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3537
|
+
logger.debug("Codex websocket prewarm failed", {
|
|
3538
|
+
error: errorMessage,
|
|
3539
|
+
provider: codexModel.provider,
|
|
3540
|
+
model: codexModel.id,
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
})();
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
// Broker-shared language servers: one server per project, multiplexed
|
|
3548
|
+
// across omp instances by the LSP mux daemon. Session-level because the
|
|
3549
|
+
// flag lives in module state consulted on every client cold-start.
|
|
3550
|
+
setSharedLspEnabled(enableLsp && settings.get("lsp.shared"));
|
|
3551
|
+
|
|
3552
|
+
// Start LSP warmup in the background so startup does not block on language server initialization.
|
|
3553
|
+
// With `lsp.lazy` (the default) the warmup is skipped: recognized servers are still discovered and
|
|
3554
|
+
// surfaced in the UI as "available", but cold-start on first use — the lsp tool or an edit/write
|
|
3555
|
+
// touching a matching file type — through `getOrCreateClient`.
|
|
3556
|
+
// Print/script invocations (`hasUI=false`) skip it regardless: they don't render the warmup status
|
|
3557
|
+
// indicator AND typically finish before LSP servers would have stabilized — warming them just spends
|
|
3558
|
+
// CPU parsing big `initialize` responses concurrently with the LLM stream consumer, jittering
|
|
3559
|
+
// perceived latency.
|
|
3560
|
+
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
3561
|
+
if (enableLsp && options.hasUI && settings.get("lsp.lazy")) {
|
|
3562
|
+
lspServers = discoverStartupLspServers(cwd, "available");
|
|
3563
|
+
} else if (enableLsp && options.hasUI) {
|
|
3564
|
+
lspServers = discoverStartupLspServers(cwd);
|
|
3565
|
+
if (lspServers.length > 0) {
|
|
3566
|
+
void (async () => {
|
|
3567
|
+
try {
|
|
3568
|
+
const result = await logger.time("warmupLspServers", warmupLspServers, cwd);
|
|
3569
|
+
const serversByName = new Map(result.servers.map(server => [server.name, server] as const));
|
|
3570
|
+
for (const server of lspServers ?? []) {
|
|
3571
|
+
const next = serversByName.get(server.name);
|
|
3572
|
+
if (!next) continue;
|
|
3573
|
+
server.status = next.status;
|
|
3574
|
+
server.fileTypes = next.fileTypes;
|
|
3575
|
+
server.error = next.error;
|
|
3576
|
+
}
|
|
3577
|
+
const event: LspStartupEvent = {
|
|
3578
|
+
type: "completed",
|
|
3579
|
+
servers: result.servers,
|
|
3580
|
+
};
|
|
3581
|
+
if (!startupQuiet) eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
3582
|
+
} catch (error) {
|
|
3583
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3584
|
+
logger.warn("LSP server warmup failed", { cwd, error: errorMessage });
|
|
3585
|
+
for (const server of lspServers ?? []) {
|
|
3586
|
+
server.status = "error";
|
|
3587
|
+
server.error = errorMessage;
|
|
3588
|
+
}
|
|
3589
|
+
const event: LspStartupEvent = {
|
|
3590
|
+
type: "failed",
|
|
3591
|
+
error: errorMessage,
|
|
3592
|
+
};
|
|
3593
|
+
if (!startupQuiet) eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
3594
|
+
}
|
|
3595
|
+
})();
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
const startMemoryBackend = async () => {
|
|
3600
|
+
const memoryBackend = await resolveMemoryBackend(settings);
|
|
3601
|
+
await memoryBackend.start({
|
|
3602
|
+
session,
|
|
3603
|
+
settings,
|
|
3604
|
+
modelRegistry,
|
|
3605
|
+
agentDir,
|
|
3606
|
+
taskDepth,
|
|
3607
|
+
parentHindsightSessionState: options.parentHindsightSessionState,
|
|
3608
|
+
parentMnemopiSessionState: options.parentMnemopiSessionState,
|
|
3609
|
+
});
|
|
3610
|
+
};
|
|
3611
|
+
|
|
3612
|
+
const runAutoLearnCapture = createAutoLearnCaptureRunner({
|
|
3613
|
+
sourceAgent: agent,
|
|
3614
|
+
captureTools: autoLearnCaptureTools,
|
|
3615
|
+
onPayload,
|
|
3616
|
+
onResponse,
|
|
3617
|
+
createAgent: captureOptions => {
|
|
3618
|
+
const captureModel = captureOptions.initialState?.model;
|
|
3619
|
+
const captureSessionId = captureOptions.sessionId;
|
|
3620
|
+
if (!captureModel || !captureSessionId) throw new Error("Auto-learn capture identity is incomplete");
|
|
3621
|
+
return new Agent({
|
|
3622
|
+
...captureOptions,
|
|
3623
|
+
cwd: sessionManager.getCwd(),
|
|
3624
|
+
cwdResolver: () => sessionManager.getCwd(),
|
|
3625
|
+
convertToLlm: convertToLlmFinal,
|
|
3626
|
+
transformContext: async messages => wrapSteeringForModel(messages),
|
|
3627
|
+
transformProviderContext: async (context, transformModel) => {
|
|
3628
|
+
const transformed = obfuscator ? obfuscateProviderContext(obfuscator, context) : context;
|
|
3629
|
+
return clampProviderContextImages(transformed, transformModel);
|
|
3630
|
+
},
|
|
3631
|
+
thinkingBudgets: agent.thinkingBudgets,
|
|
3632
|
+
temperature: agent.temperature,
|
|
3633
|
+
topP: agent.topP,
|
|
3634
|
+
topK: agent.topK,
|
|
3635
|
+
minP: agent.minP,
|
|
3636
|
+
presencePenalty: agent.presencePenalty,
|
|
3637
|
+
repetitionPenalty: agent.repetitionPenalty,
|
|
3638
|
+
serviceTierResolver: agent.serviceTierResolver,
|
|
3639
|
+
hideThinkingSummary: agent.hideThinkingSummary,
|
|
3640
|
+
maxRetryDelayMs: agent.maxRetryDelayMs,
|
|
3641
|
+
kimiApiFormat,
|
|
3642
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
3643
|
+
getToolContext: toolCall => toolContextStore.getContext(toolCall),
|
|
3644
|
+
streamFn: settingsAwareStreamFn,
|
|
3645
|
+
transformToolCallArguments,
|
|
3646
|
+
resolveFallbackTool: resolveDeviceTool,
|
|
3647
|
+
intentTracing: !!intentField,
|
|
3648
|
+
pruneToolDescriptions: inlineToolDescriptors,
|
|
3649
|
+
dialect: resolveDialect(settings.get("tools.format"), captureModel),
|
|
3650
|
+
abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
|
|
3651
|
+
appendOnlyContext: shouldEnableAppendOnlyContext(
|
|
3652
|
+
settings.get("provider.appendOnlyContext"),
|
|
3653
|
+
captureModel,
|
|
3654
|
+
)
|
|
3655
|
+
? new AppendOnlyContextManager()
|
|
3656
|
+
: undefined,
|
|
3657
|
+
});
|
|
3658
|
+
},
|
|
3659
|
+
});
|
|
3660
|
+
|
|
3661
|
+
// Auto-learn can immediately trigger a private capture after the first real
|
|
3662
|
+
// stop. When a memory backend is selected, install that backend's
|
|
3663
|
+
// per-session state first so the capture turn's `learn` tool observes the
|
|
3664
|
+
// same initialized state as normal memory tools. Other sessions keep memory
|
|
3665
|
+
// startup in the background to preserve the existing startup profile.
|
|
3666
|
+
//
|
|
3667
|
+
// Gated on `autolearn.enabled` to match the tools: `createTools` builds the
|
|
3668
|
+
// `learn`/`manage_skill` registry ONCE at session start and no settings
|
|
3669
|
+
// change rebuilds it, so installing the controller while disabled would let a
|
|
3670
|
+
// mid-session enable fire a nudge pointing at tools the session never built.
|
|
3671
|
+
// Activation is therefore a session-start decision for BOTH the controller
|
|
3672
|
+
// and the tools; the fire-time re-check in `#onAgentEnd` still handles a
|
|
3673
|
+
// mid-session DISABLE. The subscription lives for the session's lifetime; the
|
|
3674
|
+
// reference is intentionally discarded (the listener retains it).
|
|
3675
|
+
if (!restrictToolNames) {
|
|
3676
|
+
if (settings.get("autolearn.enabled") && taskDepth === 0) {
|
|
3677
|
+
await logger.time("startMemoryStartupTask", startMemoryBackend);
|
|
3678
|
+
new AutoLearnController({
|
|
3679
|
+
session,
|
|
3680
|
+
settings,
|
|
3681
|
+
capture: content => session.runAutolearnCapture(signal => runAutoLearnCapture(content, signal)),
|
|
3682
|
+
});
|
|
3683
|
+
} else {
|
|
3684
|
+
void logger.time("startMemoryStartupTask", startMemoryBackend);
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// MCP manager wiring has two ownership models:
|
|
3689
|
+
// * Single-slot callbacks (tools/prompts/resources changed) — exactly one
|
|
3690
|
+
// owner per manager. When reusing a parent's manager (subagent path,
|
|
3691
|
+
// see task/executor.ts), the parent already owns these slots so we
|
|
3692
|
+
// MUST NOT overwrite them. Guarded by `!options.mcpManager`.
|
|
3693
|
+
// * Notification listener — multi-listener by design. Every session with
|
|
3694
|
+
// an MCP manager (fresh OR reused) needs its own bridge to its own
|
|
3695
|
+
// `extensionRunner` so extensions loaded in that session receive frames.
|
|
3696
|
+
// Guarded only by `mcpManager` (see the second `if` below).
|
|
3697
|
+
if (mcpManager && !options.mcpManager) {
|
|
3698
|
+
mcpManager.setOnToolsChanged(async tools => {
|
|
3699
|
+
try {
|
|
3700
|
+
await session.refreshMCPTools(tools);
|
|
3701
|
+
} catch (error) {
|
|
3702
|
+
logger.warn("MCP tool refresh failed", {
|
|
3703
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3704
|
+
});
|
|
3705
|
+
}
|
|
3706
|
+
});
|
|
3707
|
+
// Wire prompt refresh → rebuild MCP prompt slash commands
|
|
3708
|
+
mcpManager.setOnPromptsChanged(serverName => {
|
|
3709
|
+
const promptCommands = buildMCPPromptCommands(mcpManager);
|
|
3710
|
+
session.setMCPPromptCommands(promptCommands);
|
|
3711
|
+
logger.debug("MCP prompt commands refreshed", { path: `mcp:${serverName}` });
|
|
3712
|
+
});
|
|
3713
|
+
const notificationDebounceTimers = new Map<string, Timer>();
|
|
3714
|
+
const clearDebounceTimers = () => {
|
|
3715
|
+
for (const timer of notificationDebounceTimers.values()) clearTimeout(timer);
|
|
3716
|
+
notificationDebounceTimers.clear();
|
|
3717
|
+
};
|
|
3718
|
+
postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
|
|
3719
|
+
mcpManager.setOnResourcesChanged((serverName, uri) => {
|
|
3720
|
+
logger.debug("MCP resources changed", { path: `mcp:${serverName}`, uri });
|
|
3721
|
+
if (!settings.get("mcp.notifications")) return;
|
|
3722
|
+
const debounceMs = settings.get("mcp.notificationDebounceMs");
|
|
3723
|
+
const key = `${serverName}:${uri}`;
|
|
3724
|
+
const existing = notificationDebounceTimers.get(key);
|
|
3725
|
+
if (existing) clearTimeout(existing);
|
|
3726
|
+
notificationDebounceTimers.set(
|
|
3727
|
+
key,
|
|
3728
|
+
setTimeout(() => {
|
|
3729
|
+
notificationDebounceTimers.delete(key);
|
|
3730
|
+
// Re-check: user may have disabled notifications during the debounce window
|
|
3731
|
+
if (!settings.get("mcp.notifications")) return;
|
|
3732
|
+
session.yieldQueue.enqueue<McpNotificationEntry>("mcp-notification", { serverName, uri });
|
|
3733
|
+
}, debounceMs),
|
|
3734
|
+
);
|
|
3735
|
+
});
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
if (mcpManager) {
|
|
3739
|
+
// Bridge server-initiated notifications to this session's extension
|
|
3740
|
+
// handlers. Multi-listener registration: fresh-manager and reused-manager
|
|
3741
|
+
// sessions both install their own listener here, so a subagent's
|
|
3742
|
+
// extensions get frames even though the parent owns the single-slot
|
|
3743
|
+
// tool/prompt/resource callbacks above. MCPManager fires known
|
|
3744
|
+
// list/update refreshes internally, then invokes all registered
|
|
3745
|
+
// listeners with (server, method, params) for every frame (including
|
|
3746
|
+
// server-custom methods). Two-layer buffering protects the startup
|
|
3747
|
+
// race: MCPManager buffers frames received before the first
|
|
3748
|
+
// `addNotificationListener` subscriber (drains here); ExtensionRunner
|
|
3749
|
+
// buffers frames received before `initialize()` and drains them on
|
|
3750
|
+
// init. Both drop-oldest under pressure at cap 100.
|
|
3751
|
+
unsubscribeMcpNotifications = mcpManager.addNotificationListener((server, method, params) => {
|
|
3752
|
+
void extensionRunner.emitMcpNotification({ server, method, params });
|
|
3753
|
+
});
|
|
3754
|
+
// postmortem.register returns a cancel function; capture it so explicit
|
|
3755
|
+
// session.dispose can remove this from the global list (see finally above).
|
|
3756
|
+
unregisterMcpPostmortem = postmortem.register("mcp-notification-listener-cleanup", () =>
|
|
3757
|
+
unsubscribeMcpNotifications?.(),
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3761
|
+
startDeferredMCPDiscovery?.(session);
|
|
3762
|
+
|
|
3763
|
+
return {
|
|
3764
|
+
session,
|
|
3765
|
+
extensionsResult,
|
|
3766
|
+
setToolUIContext,
|
|
3767
|
+
mcpManager,
|
|
3768
|
+
modelFallbackMessage,
|
|
3769
|
+
lspServers,
|
|
3770
|
+
eventBus,
|
|
3771
|
+
};
|
|
3772
|
+
} catch (error) {
|
|
3773
|
+
// Release the subscription if the throw happened after install but before the
|
|
3774
|
+
// dispose-wrap took ownership. Idempotent with dispose() — Set.delete is a no-op
|
|
3775
|
+
// for already-removed listeners.
|
|
3776
|
+
unsubscribeCredentialDisabled?.();
|
|
3777
|
+
try {
|
|
3778
|
+
if (hasSession) {
|
|
3779
|
+
await session.dispose();
|
|
3780
|
+
if (hasRegistered) unregisterUnlessParked();
|
|
3781
|
+
} else {
|
|
3782
|
+
if (hasRegistered) unregisterUnlessParked();
|
|
3783
|
+
if (asyncJobManager) {
|
|
3784
|
+
if (AsyncJobManager.instance() === asyncJobManager) {
|
|
3785
|
+
AsyncJobManager.setInstance(undefined);
|
|
3786
|
+
}
|
|
3787
|
+
await asyncJobManager.dispose({ timeoutMs: 3_000 });
|
|
3788
|
+
}
|
|
3789
|
+
await releaseComputerSessionsForOwner(evalKernelOwnerId);
|
|
3790
|
+
await disposeKernelSessionsByOwner(evalKernelOwnerId);
|
|
3791
|
+
await disposeRubyKernelSessionsByOwner(evalKernelOwnerId);
|
|
3792
|
+
await disposeJuliaKernelSessionsByOwner(evalKernelOwnerId);
|
|
3793
|
+
await disposeVmContextsByOwner(evalKernelOwnerId);
|
|
3794
|
+
if (ownsAuthStorage) authStorage.close();
|
|
3795
|
+
}
|
|
3796
|
+
} catch (cleanupError) {
|
|
3797
|
+
logger.warn("Failed to clean up createAgentSession resources after startup error", {
|
|
3798
|
+
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
throw error;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3805
|
+
/**
|
|
3806
|
+
* Best-effort preconnect to the model's API host. Bun's `fetch.preconnect`
|
|
3807
|
+
* primes DNS + TCP + TLS + H2 so the first real request reuses the warm
|
|
3808
|
+
* connection. Errors are swallowed: preconnect is an optimization, never a
|
|
3809
|
+
* hard dependency.
|
|
3810
|
+
*/
|
|
3811
|
+
function preconnectModelHost(baseUrl: string | undefined): void {
|
|
3812
|
+
if (!baseUrl) return;
|
|
3813
|
+
const preconnect = (globalThis.fetch as typeof fetch & { preconnect?: (url: string) => void }).preconnect;
|
|
3814
|
+
if (typeof preconnect !== "function") return;
|
|
3815
|
+
try {
|
|
3816
|
+
preconnect(baseUrl);
|
|
3817
|
+
} catch {
|
|
3818
|
+
// Best effort.
|
|
3819
|
+
}
|
|
3820
|
+
}
|