@kr78/pi-coding-agent 17.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14861 -0
- package/README.md +35 -0
- package/dist/CHANGELOG-chtdxh1z.md +14861 -0
- package/dist/cli.js +20483 -0
- package/dist/template-c2hyaytt.js +1653 -0
- package/dist/template-f8wx9vfn.css +1355 -0
- package/dist/template-qat058wr.html +55 -0
- package/dist/tool-views.generated-jdfmzwmn.js +35 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +104 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/extensions/README.md +142 -0
- package/examples/extensions/api-demo.ts +78 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +31 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +549 -0
- package/examples/extensions/reload-runtime.ts +38 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +17 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +149 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +118 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +46 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +82 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +41 -0
- package/examples/sdk/08-slash-commands.ts +46 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/12-redis-sessions.ts +54 -0
- package/examples/sdk/13-sql-sessions.ts +61 -0
- package/examples/sdk/README.md +169 -0
- package/package.json +587 -0
- package/scripts/bench-guard.ts +71 -0
- package/scripts/bench-title-models.ts +332 -0
- package/scripts/build-binary.ts +117 -0
- package/scripts/bundle-dist.ts +123 -0
- package/scripts/compile-binary.ts +69 -0
- package/scripts/embed-mupdf-wasm.ts +67 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-aria-snapshot.ts +134 -0
- package/scripts/generate-docs-index.ts +58 -0
- package/scripts/generate-share-viewer.ts +34 -0
- package/scripts/legacy-pi-virtual-module.ts +205 -0
- package/scripts/measure-prompt-tokens.ts +63 -0
- package/scripts/omp +51 -0
- package/scripts/omp.ts +19 -0
- package/scripts/security-compare.ts +40 -0
- package/src/advisor/advise-tool.ts +234 -0
- package/src/advisor/config.ts +341 -0
- package/src/advisor/emission-guard.ts +172 -0
- package/src/advisor/index.ts +6 -0
- package/src/advisor/runtime.ts +1412 -0
- package/src/advisor/transcript-recorder.ts +215 -0
- package/src/advisor/watchdog.ts +135 -0
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +847 -0
- package/src/auto-thinking/classifier.ts +220 -0
- package/src/autolearn/controller.ts +152 -0
- package/src/autolearn/managed-skills.ts +255 -0
- package/src/autoresearch/command-resume.md +14 -0
- package/src/autoresearch/dashboard.ts +436 -0
- package/src/autoresearch/git.ts +331 -0
- package/src/autoresearch/helpers.ts +218 -0
- package/src/autoresearch/index.ts +541 -0
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +103 -0
- package/src/autoresearch/resume-message.md +10 -0
- package/src/autoresearch/state.ts +273 -0
- package/src/autoresearch/storage.ts +700 -0
- package/src/autoresearch/tools/init-experiment.ts +268 -0
- package/src/autoresearch/tools/log-experiment.ts +520 -0
- package/src/autoresearch/tools/run-experiment.ts +407 -0
- package/src/autoresearch/tools/update-notes.ts +109 -0
- package/src/autoresearch/types.ts +168 -0
- package/src/capability/context-file.ts +44 -0
- package/src/capability/extension-module.ts +34 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +117 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +467 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +115 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule-buckets.ts +66 -0
- package/src/capability/rule.ts +298 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +69 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +187 -0
- package/src/cleanse/agent.ts +226 -0
- package/src/cleanse/balance.ts +79 -0
- package/src/cleanse/checkers.ts +996 -0
- package/src/cleanse/index.ts +190 -0
- package/src/cleanse/loop.ts +51 -0
- package/src/cleanse/parsers.ts +726 -0
- package/src/cleanse/progress.ts +50 -0
- package/src/cleanse/prompts/assignment.md +47 -0
- package/src/cleanse/types.ts +72 -0
- package/src/cli/agents-cli.ts +138 -0
- package/src/cli/args.ts +368 -0
- package/src/cli/auth-broker-cli.ts +940 -0
- package/src/cli/auth-gateway-cli.ts +674 -0
- package/src/cli/bench-cli.ts +990 -0
- package/src/cli/browser-relay-cli.ts +119 -0
- package/src/cli/classify-install-target.ts +76 -0
- package/src/cli/claude-trace-cli.ts +795 -0
- package/src/cli/command-help.ts +107 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/completion-gen.ts +550 -0
- package/src/cli/config-cli.ts +459 -0
- package/src/cli/dry-balance-cli.ts +864 -0
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/file-processor.ts +132 -0
- package/src/cli/flag-tables.ts +367 -0
- package/src/cli/gallery-cli.ts +272 -0
- package/src/cli/gallery-fixtures/agentic.ts +420 -0
- package/src/cli/gallery-fixtures/codeintel.ts +187 -0
- package/src/cli/gallery-fixtures/edit.ts +254 -0
- package/src/cli/gallery-fixtures/fs.ts +245 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +46 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +177 -0
- package/src/cli/gallery-fixtures/search.ts +135 -0
- package/src/cli/gallery-fixtures/shell.ts +241 -0
- package/src/cli/gallery-fixtures/types.ts +57 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli/gc-cli.ts +1566 -0
- package/src/cli/grep-cli.ts +161 -0
- package/src/cli/grievances-cli.ts +256 -0
- package/src/cli/help-extra.ts +89 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/models-cli.ts +385 -0
- package/src/cli/plugin-cli.ts +996 -0
- package/src/cli/profile-alias.ts +369 -0
- package/src/cli/profile-bootstrap.ts +233 -0
- package/src/cli/read-cli.ts +99 -0
- package/src/cli/session-picker.ts +110 -0
- package/src/cli/setup-cli.ts +312 -0
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli/shell-cli.ts +176 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/startup-cwd.ts +58 -0
- package/src/cli/stats-cli.ts +229 -0
- package/src/cli/thinking-levels.ts +7 -0
- package/src/cli/tiny-models-cli.ts +153 -0
- package/src/cli/ttsr-cli.ts +1013 -0
- package/src/cli/update-cli.ts +1184 -0
- package/src/cli/usage-cli.ts +1084 -0
- package/src/cli/usage-error.ts +7 -0
- package/src/cli/web-search-cli.ts +144 -0
- package/src/cli/worktree-cli.ts +311 -0
- package/src/cli-commands.ts +308 -0
- package/src/cli.ts +428 -0
- package/src/collab/crypto.ts +63 -0
- package/src/collab/display-name.ts +13 -0
- package/src/collab/guest.ts +764 -0
- package/src/collab/host.ts +692 -0
- package/src/collab/protocol.ts +296 -0
- package/src/collab/relay-client.ts +282 -0
- package/src/collab/replication-shrink.ts +111 -0
- package/src/commands/acp.ts +35 -0
- package/src/commands/agents.ts +58 -0
- package/src/commands/auth-broker.ts +100 -0
- package/src/commands/auth-gateway.ts +70 -0
- package/src/commands/bench.ts +64 -0
- package/src/commands/browser-relay.ts +53 -0
- package/src/commands/cleanse.ts +45 -0
- package/src/commands/commit.ts +47 -0
- package/src/commands/complete.ts +67 -0
- package/src/commands/completions.ts +61 -0
- package/src/commands/config.ts +52 -0
- package/src/commands/dry-balance.ts +43 -0
- package/src/commands/gallery.ts +61 -0
- package/src/commands/gc.ts +47 -0
- package/src/commands/grep.ts +49 -0
- package/src/commands/grievances.ts +52 -0
- package/src/commands/install.ts +107 -0
- package/src/commands/join.ts +40 -0
- package/src/commands/launch-help.ts +116 -0
- package/src/commands/launch.ts +34 -0
- package/src/commands/models.ts +61 -0
- package/src/commands/plugin.ts +79 -0
- package/src/commands/read.ts +39 -0
- package/src/commands/say.ts +146 -0
- package/src/commands/setup.ts +68 -0
- package/src/commands/share.ts +71 -0
- package/src/commands/shell.ts +30 -0
- package/src/commands/ssh.ts +61 -0
- package/src/commands/stats.ts +30 -0
- package/src/commands/tiny-models.ts +36 -0
- package/src/commands/token.ts +164 -0
- package/src/commands/ttsr.ts +125 -0
- package/src/commands/update.ts +34 -0
- package/src/commands/usage.ts +55 -0
- package/src/commands/web-search.ts +43 -0
- package/src/commands/worktree.ts +63 -0
- package/src/commit/agentic/agent.ts +322 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +383 -0
- package/src/commit/agentic/lock-files.ts +107 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +60 -0
- package/src/commit/agentic/tools/analyze-file.ts +148 -0
- package/src/commit/agentic/tools/git-file-diff.ts +191 -0
- package/src/commit/agentic/tools/git-hunk.ts +52 -0
- package/src/commit/agentic/tools/git-overview.ts +62 -0
- package/src/commit/agentic/tools/index.ts +54 -0
- package/src/commit/agentic/tools/propose-changelog.ts +147 -0
- package/src/commit/agentic/tools/propose-commit.ts +109 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/schemas.ts +11 -0
- package/src/commit/agentic/tools/split-commit.ts +241 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +183 -0
- package/src/commit/analysis/conventional.ts +64 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +107 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +40 -0
- package/src/commit/changelog/generate.ts +101 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +85 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +69 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +49 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +95 -0
- package/src/commit/pipeline.ts +244 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/shared-llm.ts +70 -0
- package/src/commit/types.ts +118 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/commit/utils.ts +58 -0
- package/src/config/api-key-resolver.ts +81 -0
- package/src/config/append-only-context-mode.ts +76 -0
- package/src/config/config-file.ts +347 -0
- package/src/config/inline-tool-descriptors-mode.ts +26 -0
- package/src/config/keybindings.ts +703 -0
- package/src/config/mcp-schema.json +252 -0
- package/src/config/model-discovery.ts +1077 -0
- package/src/config/model-registry.ts +2822 -0
- package/src/config/model-resolver.ts +2115 -0
- package/src/config/model-roles.ts +113 -0
- package/src/config/models-config-schema-bundle.ts +315 -0
- package/src/config/models-config-schema.ts +14 -0
- package/src/config/models-config.ts +130 -0
- package/src/config/prompt-templates.ts +205 -0
- package/src/config/provider-globals.ts +25 -0
- package/src/config/resolve-config-value.ts +95 -0
- package/src/config/service-tier.ts +146 -0
- package/src/config/settings-schema.ts +5887 -0
- package/src/config/settings.ts +2394 -0
- package/src/config.ts +242 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +960 -0
- package/src/dap/client.ts +1043 -0
- package/src/dap/config.ts +480 -0
- package/src/dap/defaults.json +212 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1841 -0
- package/src/dap/types.ts +611 -0
- package/src/debug/index.ts +584 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +966 -0
- package/src/debug/profiler.ts +168 -0
- package/src/debug/protocol-probe.ts +267 -0
- package/src/debug/raw-sse-buffer.ts +421 -0
- package/src/debug/raw-sse.ts +312 -0
- package/src/debug/remote-debugger.ts +151 -0
- package/src/debug/report-bundle.ts +411 -0
- package/src/debug/system-info.ts +111 -0
- package/src/debug/terminal-info.ts +124 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +300 -0
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-defaults.ts +39 -0
- package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
- package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
- package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
- package/src/discovery/builtin-rules/go-ioutil.md +37 -0
- package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
- package/src/discovery/builtin-rules/go-new-expr.md +44 -0
- package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
- package/src/discovery/builtin-rules/go-range-int.md +45 -0
- package/src/discovery/builtin-rules/index.ts +74 -0
- package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
- package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
- package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
- package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
- package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
- package/src/discovery/builtin-rules/rs-result-type.md +20 -0
- package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
- package/src/discovery/builtin-rules/ts-import-type.md +43 -0
- package/src/discovery/builtin-rules/ts-no-any.md +66 -0
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
- package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
- package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
- package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/builtin-rules/ts-set-map.md +28 -0
- package/src/discovery/builtin.ts +945 -0
- package/src/discovery/claude-plugins.ts +640 -0
- package/src/discovery/claude.ts +591 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +553 -0
- package/src/discovery/contained-path.ts +78 -0
- package/src/discovery/cursor.ts +223 -0
- package/src/discovery/gemini.ts +386 -0
- package/src/discovery/github.ts +337 -0
- package/src/discovery/helpers.ts +1183 -0
- package/src/discovery/index.ts +82 -0
- package/src/discovery/mcp-json.ts +182 -0
- package/src/discovery/omp-extension-roots.ts +272 -0
- package/src/discovery/omp-plugins.ts +409 -0
- package/src/discovery/opencode.ts +441 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +77 -0
- package/src/discovery/vscode.ts +106 -0
- package/src/discovery/windsurf.ts +149 -0
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +1008 -0
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/file-snapshot-store.ts +148 -0
- package/src/edit/hashline/block-resolver.ts +33 -0
- package/src/edit/hashline/diff.ts +402 -0
- package/src/edit/hashline/execute.ts +313 -0
- package/src/edit/hashline/filesystem.ts +247 -0
- package/src/edit/hashline/index.ts +5 -0
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/hashline/params.ts +12 -0
- package/src/edit/index.ts +699 -0
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +53 -0
- package/src/edit/modes/patch.ts +1958 -0
- package/src/edit/modes/replace.ts +1191 -0
- package/src/edit/normalize.ts +345 -0
- package/src/edit/notebook.ts +242 -0
- package/src/edit/read-file.ts +25 -0
- package/src/edit/renderer.ts +998 -0
- package/src/edit/snapshot-details.ts +77 -0
- package/src/edit/streaming.ts +710 -0
- package/src/eval/agent-bridge.ts +224 -0
- package/src/eval/backend-helpers.ts +48 -0
- package/src/eval/backend.ts +71 -0
- package/src/eval/bridge-timeout.ts +65 -0
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/completion-bridge.ts +212 -0
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/executor-base.ts +562 -0
- package/src/eval/idle-timeout.ts +91 -0
- package/src/eval/index.ts +6 -0
- package/src/eval/jl/executor.ts +551 -0
- package/src/eval/jl/index.ts +54 -0
- package/src/eval/jl/kernel.ts +236 -0
- package/src/eval/jl/prelude.jl +736 -0
- package/src/eval/jl/prelude.ts +3 -0
- package/src/eval/jl/runner.jl +666 -0
- package/src/eval/jl/runtime.ts +118 -0
- package/src/eval/js/context-manager.ts +872 -0
- package/src/eval/js/executor.ts +178 -0
- package/src/eval/js/index.ts +41 -0
- package/src/eval/js/process-entry.ts +31 -0
- package/src/eval/js/shared/helpers.ts +170 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/local-module-loader.ts +364 -0
- package/src/eval/js/shared/prelude.ts +2 -0
- package/src/eval/js/shared/prelude.txt +294 -0
- package/src/eval/js/shared/rewrite-imports.ts +550 -0
- package/src/eval/js/shared/runtime.ts +590 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +163 -0
- package/src/eval/js/worker-core.ts +380 -0
- package/src/eval/js/worker-entry.ts +37 -0
- package/src/eval/js/worker-protocol.ts +47 -0
- package/src/eval/kernel-base.ts +569 -0
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +643 -0
- package/src/eval/py/index.ts +57 -0
- package/src/eval/py/kernel.ts +235 -0
- package/src/eval/py/prelude.py +673 -0
- package/src/eval/py/prelude.ts +3 -0
- package/src/eval/py/runner.py +1425 -0
- package/src/eval/py/runtime.ts +276 -0
- package/src/eval/py/spawn-options.ts +134 -0
- package/src/eval/py/tool-bridge.ts +201 -0
- package/src/eval/rb/executor.ts +511 -0
- package/src/eval/rb/index.ts +54 -0
- package/src/eval/rb/kernel.ts +231 -0
- package/src/eval/rb/prelude.rb +551 -0
- package/src/eval/rb/prelude.ts +3 -0
- package/src/eval/rb/runner.rb +581 -0
- package/src/eval/rb/runtime.ts +132 -0
- package/src/eval/runtime-env.ts +104 -0
- package/src/eval/session-id.ts +8 -0
- package/src/eval/types.ts +48 -0
- package/src/exa/index.ts +2 -0
- package/src/exa/mcp-client.ts +370 -0
- package/src/exa/types.ts +69 -0
- package/src/exec/bash-executor.ts +627 -0
- package/src/exec/direnv.ts +145 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/non-interactive-env.ts +118 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +320 -0
- package/src/export/html/share-loader.js +102 -0
- package/src/export/html/template.css +1355 -0
- package/src/export/html/template.html +55 -0
- package/src/export/html/template.js +1653 -0
- package/src/export/html/tool-views.generated.js +35 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/html/web-palette.ts +126 -0
- package/src/export/share.ts +686 -0
- package/src/export/ttsr.ts +590 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
- package/src/extensibility/custom-commands/index.ts +2 -0
- package/src/extensibility/custom-commands/loader.ts +242 -0
- package/src/extensibility/custom-commands/types.ts +119 -0
- package/src/extensibility/custom-tools/index.ts +7 -0
- package/src/extensibility/custom-tools/loader.ts +301 -0
- package/src/extensibility/custom-tools/types.ts +287 -0
- package/src/extensibility/custom-tools/wrapper.ts +50 -0
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/get-commands-handler.ts +78 -0
- package/src/extensibility/extensions/index.ts +16 -0
- package/src/extensibility/extensions/load-errors.ts +13 -0
- package/src/extensibility/extensions/loader.ts +663 -0
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/model-api.ts +39 -0
- package/src/extensibility/extensions/runner.ts +1396 -0
- package/src/extensibility/extensions/types.ts +1603 -0
- package/src/extensibility/extensions/wrapper.ts +395 -0
- package/src/extensibility/hooks/index.ts +6 -0
- package/src/extensibility/hooks/loader.ts +243 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +124 -0
- package/src/extensibility/hooks/types.ts +612 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
- package/src/extensibility/legacy-typebox.ts +101 -0
- package/src/extensibility/plugins/bun-git-cache.ts +91 -0
- package/src/extensibility/plugins/doctor.ts +65 -0
- package/src/extensibility/plugins/git-url.ts +367 -0
- package/src/extensibility/plugins/index.ts +9 -0
- package/src/extensibility/plugins/installer.ts +201 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
- package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
- package/src/extensibility/plugins/loader.ts +535 -0
- package/src/extensibility/plugins/manager.ts +1142 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +927 -0
- package/src/extensibility/plugins/marketplace/registry.ts +192 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +192 -0
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/extensibility/plugins/parser.ts +107 -0
- package/src/extensibility/plugins/runtime-config.ts +9 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/session-handler-types.ts +21 -0
- package/src/extensibility/shared-events.ts +401 -0
- package/src/extensibility/skills.ts +531 -0
- package/src/extensibility/slash-commands.ts +131 -0
- package/src/extensibility/tool-event-input.ts +80 -0
- package/src/extensibility/tool-proxy.ts +28 -0
- package/src/extensibility/utils.ts +192 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +521 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +251 -0
- package/src/hindsight/backend.ts +354 -0
- package/src/hindsight/bank.ts +156 -0
- package/src/hindsight/client.ts +680 -0
- package/src/hindsight/config.ts +193 -0
- package/src/hindsight/content.ts +266 -0
- package/src/hindsight/index.ts +8 -0
- package/src/hindsight/mental-models.ts +429 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +530 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/index.ts +66 -0
- package/src/internal-urls/agent-protocol.ts +180 -0
- package/src/internal-urls/artifact-protocol.ts +151 -0
- package/src/internal-urls/docs-index.ts +102 -0
- package/src/internal-urls/filesystem-resource.ts +34 -0
- package/src/internal-urls/history-protocol.ts +198 -0
- package/src/internal-urls/index.ts +28 -0
- package/src/internal-urls/issue-pr-protocol.ts +594 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/local-protocol.ts +471 -0
- package/src/internal-urls/mcp-protocol.ts +168 -0
- package/src/internal-urls/memory-protocol.ts +380 -0
- package/src/internal-urls/omp-protocol.ts +94 -0
- package/src/internal-urls/parse.ts +103 -0
- package/src/internal-urls/registry-helpers.ts +132 -0
- package/src/internal-urls/router.ts +153 -0
- package/src/internal-urls/rule-protocol.ts +45 -0
- package/src/internal-urls/security-protocol.ts +261 -0
- package/src/internal-urls/skill-protocol.ts +131 -0
- package/src/internal-urls/ssh-protocol.ts +368 -0
- package/src/internal-urls/types.ts +196 -0
- package/src/internal-urls/vault-protocol.ts +940 -0
- package/src/internal-urls/xd-protocol.ts +46 -0
- package/src/irc/bus.ts +380 -0
- package/src/jsonrpc/message-framing.ts +142 -0
- package/src/launch/broker.ts +1368 -0
- package/src/launch/client.ts +521 -0
- package/src/launch/ensure.ts +73 -0
- package/src/launch/paths.ts +14 -0
- package/src/launch/presence.ts +82 -0
- package/src/launch/protocol.ts +448 -0
- package/src/launch/spawn-options.ts +17 -0
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/launch/terminal-output.ts +46 -0
- package/src/lib/xai-http.ts +150 -0
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +517 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +422 -0
- package/src/live/visualizer.ts +221 -0
- package/src/live/voices.ts +18 -0
- package/src/lsp/client.ts +1465 -0
- package/src/lsp/clients/biome-client.ts +263 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +85 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +549 -0
- package/src/lsp/defaults.json +499 -0
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/edits.ts +288 -0
- package/src/lsp/format-options.ts +119 -0
- package/src/lsp/index.ts +2821 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/mux/daemon.ts +348 -0
- package/src/lsp/mux/protocol.ts +96 -0
- package/src/lsp/mux/server.ts +797 -0
- package/src/lsp/render.ts +668 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +479 -0
- package/src/lsp/utils.ts +747 -0
- package/src/main.ts +1799 -0
- package/src/markit/NOTICE +32 -0
- package/src/markit/converters/docx.ts +56 -0
- package/src/markit/converters/epub.ts +136 -0
- package/src/markit/converters/pdf/columns.ts +103 -0
- package/src/markit/converters/pdf/extract.ts +598 -0
- package/src/markit/converters/pdf/grid.ts +780 -0
- package/src/markit/converters/pdf/headers.ts +106 -0
- package/src/markit/converters/pdf/index.ts +146 -0
- package/src/markit/converters/pdf/render.ts +501 -0
- package/src/markit/converters/pdf/types.ts +84 -0
- package/src/markit/converters/pptx.ts +325 -0
- package/src/markit/converters/xlsx.ts +173 -0
- package/src/markit/index.ts +2 -0
- package/src/markit/registry.ts +59 -0
- package/src/markit/types.ts +35 -0
- package/src/mcp/client.ts +511 -0
- package/src/mcp/config-writer.ts +377 -0
- package/src/mcp/config.ts +385 -0
- package/src/mcp/index.ts +29 -0
- package/src/mcp/json-rpc.ts +122 -0
- package/src/mcp/loader.ts +125 -0
- package/src/mcp/manager.ts +1524 -0
- package/src/mcp/oauth-credentials.ts +104 -0
- package/src/mcp/oauth-discovery.ts +587 -0
- package/src/mcp/oauth-flow.ts +830 -0
- package/src/mcp/render.ts +214 -0
- package/src/mcp/request-id.ts +24 -0
- package/src/mcp/smithery-auth.ts +108 -0
- package/src/mcp/smithery-connect.ts +154 -0
- package/src/mcp/smithery-registry.ts +500 -0
- package/src/mcp/startup-events.ts +116 -0
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/tool-bridge.ts +691 -0
- package/src/mcp/tool-cache.ts +117 -0
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +508 -0
- package/src/mcp/transports/index.ts +7 -0
- package/src/mcp/transports/sse.ts +372 -0
- package/src/mcp/transports/stdio.ts +905 -0
- package/src/mcp/types.ts +467 -0
- package/src/memories/index.ts +1434 -0
- package/src/memories/storage.ts +578 -0
- package/src/memory-backend/index.ts +19 -0
- package/src/memory-backend/local-backend.ts +47 -0
- package/src/memory-backend/messages.ts +19 -0
- package/src/memory-backend/off-backend.ts +25 -0
- package/src/memory-backend/resolve.ts +25 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/memory-backend/types.ts +166 -0
- package/src/mnemopi/backend.ts +629 -0
- package/src/mnemopi/config.ts +267 -0
- package/src/mnemopi/embed-client.ts +293 -0
- package/src/mnemopi/embed-protocol.ts +35 -0
- package/src/mnemopi/embed-worker.ts +114 -0
- package/src/mnemopi/index.ts +3 -0
- package/src/mnemopi/state.ts +940 -0
- package/src/modes/acp/acp-agent.ts +2595 -0
- package/src/modes/acp/acp-client-bridge.ts +154 -0
- package/src/modes/acp/acp-event-mapper.ts +1084 -0
- package/src/modes/acp/acp-mode.ts +48 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/advisor-config.ts +635 -0
- package/src/modes/components/advisor-message.ts +109 -0
- package/src/modes/components/agent-dashboard.ts +1252 -0
- package/src/modes/components/agent-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +1126 -0
- package/src/modes/components/agent-transcript-viewer.ts +647 -0
- package/src/modes/components/ask-dialog.ts +1017 -0
- package/src/modes/components/assistant-message.ts +978 -0
- package/src/modes/components/background-tan-message.ts +36 -0
- package/src/modes/components/bash-execution.ts +233 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/btw-panel.ts +161 -0
- package/src/modes/components/cache-invalidation-marker.ts +110 -0
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/chat-transcript-builder.ts +518 -0
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/collab-prompt-message.ts +32 -0
- package/src/modes/components/compaction-summary-message.ts +221 -0
- package/src/modes/components/copy-selector.ts +218 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.ts +1036 -0
- package/src/modes/components/custom-message.ts +70 -0
- package/src/modes/components/diff.ts +254 -0
- package/src/modes/components/dynamic-border.ts +37 -0
- package/src/modes/components/error-banner.ts +33 -0
- package/src/modes/components/eval-execution.ts +169 -0
- package/src/modes/components/execution-shared.ts +101 -0
- package/src/modes/components/extensions/extension-dashboard.ts +492 -0
- package/src/modes/components/extensions/extension-list.ts +507 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +326 -0
- package/src/modes/components/extensions/state-manager.ts +648 -0
- package/src/modes/components/extensions/types.ts +186 -0
- package/src/modes/components/footer.ts +274 -0
- package/src/modes/components/history-search.ts +268 -0
- package/src/modes/components/hook-editor.ts +213 -0
- package/src/modes/components/hook-input.ts +87 -0
- package/src/modes/components/hook-message.ts +67 -0
- package/src/modes/components/hook-selector.ts +691 -0
- package/src/modes/components/index.ts +42 -0
- package/src/modes/components/keybinding-hints.ts +56 -0
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/login-dialog.ts +197 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1413 -0
- package/src/modes/components/message-frame.ts +98 -0
- package/src/modes/components/model-browser.ts +888 -0
- package/src/modes/components/model-hub.ts +2014 -0
- package/src/modes/components/model-picker.ts +237 -0
- package/src/modes/components/move-overlay.ts +293 -0
- package/src/modes/components/oauth-selector.ts +474 -0
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/overlay-box.ts +109 -0
- package/src/modes/components/pause-screen.ts +208 -0
- package/src/modes/components/plan-review-overlay.ts +1226 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/plugin-selector.ts +100 -0
- package/src/modes/components/plugin-settings.ts +745 -0
- package/src/modes/components/queue-mode-selector.ts +61 -0
- package/src/modes/components/read-tool-group.ts +856 -0
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/segment-track.ts +89 -0
- package/src/modes/components/select-list-mouse-routing.ts +35 -0
- package/src/modes/components/selector-helpers.ts +129 -0
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/session-selector.ts +1035 -0
- package/src/modes/components/settings-defs.ts +267 -0
- package/src/modes/components/settings-selector.ts +1445 -0
- package/src/modes/components/show-images-selector.ts +50 -0
- package/src/modes/components/skill-message.ts +110 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/status-line/component.ts +1843 -0
- package/src/modes/components/status-line/context-thresholds.ts +86 -0
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/index.ts +5 -0
- package/src/modes/components/status-line/presets.ts +106 -0
- package/src/modes/components/status-line/segments.ts +710 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/types.ts +159 -0
- package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
- package/src/modes/components/theme-selector.ts +68 -0
- package/src/modes/components/thinking-selector.ts +57 -0
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +26 -0
- package/src/modes/components/todo-reminder.ts +43 -0
- package/src/modes/components/tool-execution.ts +1467 -0
- package/src/modes/components/transcript-container.ts +527 -0
- package/src/modes/components/tree-selector.ts +1006 -0
- package/src/modes/components/ttsr-notification.ts +123 -0
- package/src/modes/components/usage-row.ts +52 -0
- package/src/modes/components/user-message-selector.ts +227 -0
- package/src/modes/components/user-message.ts +167 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +578 -0
- package/src/modes/controllers/btw-controller.ts +246 -0
- package/src/modes/controllers/command-controller-shared.ts +109 -0
- package/src/modes/controllers/command-controller.ts +2024 -0
- package/src/modes/controllers/event-controller.ts +2173 -0
- package/src/modes/controllers/extension-ui-controller.ts +1243 -0
- package/src/modes/controllers/input-controller.ts +2037 -0
- package/src/modes/controllers/live-command-controller.ts +261 -0
- package/src/modes/controllers/mcp-command-controller.ts +2552 -0
- package/src/modes/controllers/omfg-controller.ts +287 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +2058 -0
- package/src/modes/controllers/session-focus-controller.ts +117 -0
- package/src/modes/controllers/ssh-command-controller.ts +385 -0
- package/src/modes/controllers/streaming-reveal.ts +399 -0
- package/src/modes/controllers/tan-command-controller.ts +243 -0
- package/src/modes/controllers/todo-command-controller.ts +487 -0
- package/src/modes/controllers/tool-args-reveal.ts +591 -0
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/github-ref-autocomplete.ts +75 -0
- package/src/modes/gradient-highlight.ts +99 -0
- package/src/modes/image-references.ts +137 -0
- package/src/modes/index.ts +10 -0
- package/src/modes/interactive-mode.ts +5055 -0
- package/src/modes/internal-url-autocomplete.ts +158 -0
- package/src/modes/loop-limit.ts +192 -0
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/magic-keywords.ts +42 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/oauth-manual-input.ts +69 -0
- package/src/modes/orchestrate.ts +43 -0
- package/src/modes/print-mode.ts +282 -0
- package/src/modes/prompt-action-autocomplete.ts +322 -0
- package/src/modes/queue-input.ts +132 -0
- package/src/modes/rpc/host-tools.ts +204 -0
- package/src/modes/rpc/host-uris.ts +241 -0
- package/src/modes/rpc/rpc-client.ts +1213 -0
- package/src/modes/rpc/rpc-frame.ts +316 -0
- package/src/modes/rpc/rpc-input.ts +38 -0
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +1519 -0
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +544 -0
- package/src/modes/running-subagent-badge.ts +13 -0
- package/src/modes/runtime-init.ts +144 -0
- package/src/modes/session-observer-registry.ts +223 -0
- package/src/modes/session-teardown.ts +82 -0
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +103 -0
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
- package/src/modes/setup-wizard/scenes/model.ts +132 -0
- package/src/modes/setup-wizard/scenes/outro.ts +35 -0
- package/src/modes/setup-wizard/scenes/providers.ts +105 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
- package/src/modes/setup-wizard/scenes/splash.ts +201 -0
- package/src/modes/setup-wizard/scenes/theme.ts +330 -0
- package/src/modes/setup-wizard/scenes/types.ts +65 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
- package/src/modes/setup-wizard/startup-splash.ts +107 -0
- package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
- package/src/modes/shared.ts +49 -0
- package/src/modes/skill-command.ts +91 -0
- package/src/modes/theme/dark.json +95 -0
- package/src/modes/theme/defaults/alabaster.json +93 -0
- package/src/modes/theme/defaults/amethyst.json +96 -0
- package/src/modes/theme/defaults/anthracite.json +93 -0
- package/src/modes/theme/defaults/basalt.json +91 -0
- package/src/modes/theme/defaults/birch.json +95 -0
- package/src/modes/theme/defaults/dark-abyss.json +91 -0
- package/src/modes/theme/defaults/dark-arctic.json +104 -0
- package/src/modes/theme/defaults/dark-aurora.json +95 -0
- package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
- package/src/modes/theme/defaults/dark-cavern.json +91 -0
- package/src/modes/theme/defaults/dark-copper.json +95 -0
- package/src/modes/theme/defaults/dark-cosmos.json +90 -0
- package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/modes/theme/defaults/dark-dracula.json +98 -0
- package/src/modes/theme/defaults/dark-eclipse.json +91 -0
- package/src/modes/theme/defaults/dark-ember.json +95 -0
- package/src/modes/theme/defaults/dark-equinox.json +90 -0
- package/src/modes/theme/defaults/dark-forest.json +96 -0
- package/src/modes/theme/defaults/dark-github.json +105 -0
- package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
- package/src/modes/theme/defaults/dark-lavender.json +95 -0
- package/src/modes/theme/defaults/dark-lunar.json +89 -0
- package/src/modes/theme/defaults/dark-midnight.json +95 -0
- package/src/modes/theme/defaults/dark-monochrome.json +94 -0
- package/src/modes/theme/defaults/dark-monokai.json +98 -0
- package/src/modes/theme/defaults/dark-nebula.json +90 -0
- package/src/modes/theme/defaults/dark-nord.json +97 -0
- package/src/modes/theme/defaults/dark-ocean.json +101 -0
- package/src/modes/theme/defaults/dark-one.json +100 -0
- package/src/modes/theme/defaults/dark-poimandres.json +143 -0
- package/src/modes/theme/defaults/dark-rainforest.json +91 -0
- package/src/modes/theme/defaults/dark-reef.json +91 -0
- package/src/modes/theme/defaults/dark-retro.json +92 -0
- package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
- package/src/modes/theme/defaults/dark-sakura.json +95 -0
- package/src/modes/theme/defaults/dark-slate.json +95 -0
- package/src/modes/theme/defaults/dark-solarized.json +97 -0
- package/src/modes/theme/defaults/dark-solstice.json +90 -0
- package/src/modes/theme/defaults/dark-starfall.json +91 -0
- package/src/modes/theme/defaults/dark-sunset.json +99 -0
- package/src/modes/theme/defaults/dark-swamp.json +90 -0
- package/src/modes/theme/defaults/dark-synthwave.json +103 -0
- package/src/modes/theme/defaults/dark-taiga.json +91 -0
- package/src/modes/theme/defaults/dark-terminal.json +95 -0
- package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/modes/theme/defaults/dark-tundra.json +91 -0
- package/src/modes/theme/defaults/dark-twilight.json +91 -0
- package/src/modes/theme/defaults/dark-volcanic.json +91 -0
- package/src/modes/theme/defaults/graphite.json +92 -0
- package/src/modes/theme/defaults/index.ts +199 -0
- package/src/modes/theme/defaults/light-arctic.json +107 -0
- package/src/modes/theme/defaults/light-aurora-day.json +91 -0
- package/src/modes/theme/defaults/light-canyon.json +91 -0
- package/src/modes/theme/defaults/light-catppuccin.json +106 -0
- package/src/modes/theme/defaults/light-cirrus.json +90 -0
- package/src/modes/theme/defaults/light-coral.json +95 -0
- package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
- package/src/modes/theme/defaults/light-dawn.json +90 -0
- package/src/modes/theme/defaults/light-dunes.json +91 -0
- package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
- package/src/modes/theme/defaults/light-forest.json +100 -0
- package/src/modes/theme/defaults/light-frost.json +95 -0
- package/src/modes/theme/defaults/light-github.json +115 -0
- package/src/modes/theme/defaults/light-glacier.json +91 -0
- package/src/modes/theme/defaults/light-gruvbox.json +108 -0
- package/src/modes/theme/defaults/light-haze.json +90 -0
- package/src/modes/theme/defaults/light-honeycomb.json +95 -0
- package/src/modes/theme/defaults/light-lagoon.json +91 -0
- package/src/modes/theme/defaults/light-lavender.json +95 -0
- package/src/modes/theme/defaults/light-meadow.json +91 -0
- package/src/modes/theme/defaults/light-mint.json +95 -0
- package/src/modes/theme/defaults/light-monochrome.json +101 -0
- package/src/modes/theme/defaults/light-ocean.json +99 -0
- package/src/modes/theme/defaults/light-one.json +99 -0
- package/src/modes/theme/defaults/light-opal.json +91 -0
- package/src/modes/theme/defaults/light-orchard.json +91 -0
- package/src/modes/theme/defaults/light-paper.json +95 -0
- package/src/modes/theme/defaults/light-poimandres.json +143 -0
- package/src/modes/theme/defaults/light-prism.json +90 -0
- package/src/modes/theme/defaults/light-retro.json +98 -0
- package/src/modes/theme/defaults/light-sand.json +95 -0
- package/src/modes/theme/defaults/light-savanna.json +91 -0
- package/src/modes/theme/defaults/light-solarized.json +102 -0
- package/src/modes/theme/defaults/light-soleil.json +90 -0
- package/src/modes/theme/defaults/light-sunset.json +99 -0
- package/src/modes/theme/defaults/light-synthwave.json +98 -0
- package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
- package/src/modes/theme/defaults/light-wetland.json +91 -0
- package/src/modes/theme/defaults/light-zenith.json +89 -0
- package/src/modes/theme/defaults/limestone.json +94 -0
- package/src/modes/theme/defaults/mahogany.json +97 -0
- package/src/modes/theme/defaults/marble.json +93 -0
- package/src/modes/theme/defaults/obsidian.json +91 -0
- package/src/modes/theme/defaults/onyx.json +91 -0
- package/src/modes/theme/defaults/pearl.json +93 -0
- package/src/modes/theme/defaults/porcelain.json +91 -0
- package/src/modes/theme/defaults/quartz.json +96 -0
- package/src/modes/theme/defaults/sandstone.json +95 -0
- package/src/modes/theme/defaults/titanium.json +90 -0
- package/src/modes/theme/light.json +93 -0
- package/src/modes/theme/mermaid-cache.ts +92 -0
- package/src/modes/theme/shimmer.ts +305 -0
- package/src/modes/theme/theme-schema.json +463 -0
- package/src/modes/theme/theme.ts +3171 -0
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +494 -0
- package/src/modes/ultrathink.ts +42 -0
- package/src/modes/utils/context-usage.ts +518 -0
- package/src/modes/utils/copy-targets.ts +378 -0
- package/src/modes/utils/hotkeys-markdown.ts +65 -0
- package/src/modes/utils/interactive-context-helpers.ts +31 -0
- package/src/modes/utils/keybinding-matchers.ts +86 -0
- package/src/modes/utils/tools-markdown.ts +31 -0
- package/src/modes/utils/transcript-render-helpers.ts +253 -0
- package/src/modes/utils/ui-helpers.ts +976 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/modes/workflow.ts +55 -0
- package/src/plan-mode/approved-plan.ts +194 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/plan-mode/plan-handoff.ts +37 -0
- package/src/plan-mode/plan-protection.ts +31 -0
- package/src/plan-mode/state.ts +6 -0
- package/src/priority.json +60 -0
- package/src/prompts/advisor/active-repo-watchdog.md +6 -0
- package/src/prompts/advisor/advise-tool.md +3 -0
- package/src/prompts/advisor/context-files.md +8 -0
- package/src/prompts/advisor/system.md +98 -0
- package/src/prompts/agents/designer.md +74 -0
- package/src/prompts/agents/frontmatter.md +12 -0
- package/src/prompts/agents/init.md +33 -0
- package/src/prompts/agents/librarian.md +119 -0
- package/src/prompts/agents/reviewer.md +139 -0
- package/src/prompts/agents/scout.md +58 -0
- package/src/prompts/agents/security-reviewer.md +75 -0
- package/src/prompts/agents/task.md +17 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/bench.md +6 -0
- package/src/prompts/ci-green-request.md +36 -0
- package/src/prompts/dry-balance-bench.md +8 -0
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/goals/goal-mode-context.md +4 -0
- package/src/prompts/goals/goal-todo-context.md +12 -0
- package/src/prompts/goals/guided-goal-interview.md +43 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/consolidation_system.md +4 -0
- package/src/prompts/memories/read-path.md +17 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-custom-request.md +21 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +68 -0
- package/src/prompts/security/scan-coordinator.md +7 -0
- package/src/prompts/security/scan-request.md +21 -0
- package/src/prompts/security/validate-request.md +8 -0
- package/src/prompts/session/launch-completion.md +1 -0
- package/src/prompts/skills/autoload.md +8 -0
- package/src/prompts/skills/user-invocation.md +11 -0
- package/src/prompts/steering/parent-irc.md +5 -0
- package/src/prompts/steering/user-interjection.md +6 -0
- package/src/prompts/system/active-repo-context.md +4 -0
- package/src/prompts/system/agent-creation-architect.md +50 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/auto-continue.md +1 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +14 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/commit-message-system.md +14 -0
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/custom-system-prompt.md +64 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +18 -0
- package/src/prompts/system/empty-stop-retry.md +4 -0
- package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/irc-autoreply.md +6 -0
- package/src/prompts/system/irc-incoming.md +9 -0
- package/src/prompts/system/manual-continue.md +7 -0
- package/src/prompts/system/mcp-xdev-guidance.md +11 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/system/mid-run-todo-nudge.md +3 -0
- package/src/prompts/system/omfg-user.md +50 -0
- package/src/prompts/system/orchestrate-notice.md +40 -0
- package/src/prompts/system/personalities/default.md +18 -0
- package/src/prompts/system/personalities/friendly.md +17 -0
- package/src/prompts/system/personalities/pragmatic.md +15 -0
- package/src/prompts/system/plan-mode-active.md +125 -0
- package/src/prompts/system/plan-mode-approved.md +22 -0
- package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
- package/src/prompts/system/plan-mode-reference.md +10 -0
- package/src/prompts/system/plan-mode-subagent.md +33 -0
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
- package/src/prompts/system/plan-yolo-handoff.md +5 -0
- package/src/prompts/system/prewalk-checklist.md +7 -0
- package/src/prompts/system/prewalk-continue.md +1 -0
- package/src/prompts/system/prewalk-plan.md +13 -0
- package/src/prompts/system/project-prompt.md +61 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/resolve-device-reminder.md +3 -0
- package/src/prompts/system/rewind-report.md +6 -0
- package/src/prompts/system/side-channel-no-tools.md +3 -0
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-system-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
- package/src/prompts/system/speech-rewrite.md +15 -0
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/subagent-system-prompt.md +73 -0
- package/src/prompts/system/subagent-user-prompt.md +3 -0
- package/src/prompts/system/subagent-yield-reminder.md +23 -0
- package/src/prompts/system/system-prompt.md +263 -0
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/system/task-label.md +23 -0
- package/src/prompts/system/thinking-loop-redirect.md +10 -0
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system.md +16 -0
- package/src/prompts/system/tool-call-loop-redirect.md +8 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/system/ultrathink-notice.md +3 -0
- package/src/prompts/system/unexpected-stop-classifier.md +17 -0
- package/src/prompts/system/unexpected-stop-retry.md +4 -0
- package/src/prompts/system/vibe-mode-active.md +26 -0
- package/src/prompts/system/web-search.md +25 -0
- package/src/prompts/system/workflow-notice.md +112 -0
- package/src/prompts/system/xdev-mount-notice.md +20 -0
- package/src/prompts/tools/apply-patch.md +65 -0
- package/src/prompts/tools/ask.md +22 -0
- package/src/prompts/tools/ast-edit.md +11 -0
- package/src/prompts/tools/ast-grep.md +19 -0
- package/src/prompts/tools/async-result.md +8 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/browser.md +29 -0
- package/src/prompts/tools/checkpoint.md +15 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/debug.md +3 -0
- package/src/prompts/tools/eval.md +45 -0
- package/src/prompts/tools/github.md +22 -0
- package/src/prompts/tools/glob.md +16 -0
- package/src/prompts/tools/goal.md +11 -0
- package/src/prompts/tools/grep.md +13 -0
- package/src/prompts/tools/hub.md +34 -0
- package/src/prompts/tools/image-attachment-describe-system.md +8 -0
- package/src/prompts/tools/image-attachment-describe.md +10 -0
- package/src/prompts/tools/image-gen.md +7 -0
- package/src/prompts/tools/inspect-image-system.md +20 -0
- package/src/prompts/tools/inspect-image.md +22 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
- package/src/prompts/tools/lsp.md +19 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/memory-edit.md +12 -0
- package/src/prompts/tools/patch.md +57 -0
- package/src/prompts/tools/read.md +27 -0
- package/src/prompts/tools/recall.md +7 -0
- package/src/prompts/tools/reflect.md +5 -0
- package/src/prompts/tools/replace.md +30 -0
- package/src/prompts/tools/retain.md +6 -0
- package/src/prompts/tools/rewind.md +14 -0
- package/src/prompts/tools/security-publish.md +1 -0
- package/src/prompts/tools/security-scan.md +1 -0
- package/src/prompts/tools/task-async-contract.md +1 -0
- package/src/prompts/tools/task-summary.md +20 -0
- package/src/prompts/tools/task.md +83 -0
- package/src/prompts/tools/todo.md +42 -0
- package/src/prompts/tools/vibe-kill.md +3 -0
- package/src/prompts/tools/vibe-list.md +3 -0
- package/src/prompts/tools/vibe-send.md +9 -0
- package/src/prompts/tools/vibe-spawn.md +10 -0
- package/src/prompts/tools/vibe-turn-result.md +19 -0
- package/src/prompts/tools/vibe-wait.md +8 -0
- package/src/prompts/tools/web-search.md +8 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/registry/agent-lifecycle.ts +505 -0
- package/src/registry/agent-registry.ts +287 -0
- package/src/registry/persisted-agents.ts +423 -0
- package/src/sdk.ts +3820 -0
- package/src/secrets/index.ts +375 -0
- package/src/secrets/obfuscator.ts +2629 -0
- package/src/secrets/regex.ts +21 -0
- package/src/security/auth.ts +98 -0
- package/src/security/cloud.ts +686 -0
- package/src/security/comparison.ts +255 -0
- package/src/security/contracts/ids.ts +111 -0
- package/src/security/contracts/index.ts +4 -0
- package/src/security/contracts/schemas.ts +219 -0
- package/src/security/contracts/types.ts +254 -0
- package/src/security/contracts/validation.ts +69 -0
- package/src/security/coordinator.ts +708 -0
- package/src/security/importers/codex-security.ts +387 -0
- package/src/security/importers/index.ts +2 -0
- package/src/security/importers/sarif.ts +357 -0
- package/src/security/index.ts +13 -0
- package/src/security/preflight.ts +405 -0
- package/src/security/provenance.ts +106 -0
- package/src/security/publication.ts +326 -0
- package/src/security/remediation.ts +93 -0
- package/src/security/resource-output.ts +50 -0
- package/src/security/sarif.ts +78 -0
- package/src/security/store.ts +430 -0
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-events.ts +67 -0
- package/src/session/agent-session-types.ts +407 -0
- package/src/session/agent-session.ts +9000 -0
- package/src/session/agent-storage.ts +807 -0
- package/src/session/artifacts.ts +154 -0
- package/src/session/async-job-delivery.ts +82 -0
- package/src/session/auth-broker-config.ts +92 -0
- package/src/session/auth-storage.ts +25 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.ts +295 -0
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/client-bridge.ts +85 -0
- package/src/session/codex-auto-reset.ts +673 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/compact-modes.ts +105 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/eval-runner.ts +219 -0
- package/src/session/exit-diagnostics.ts +310 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/history-storage.ts +329 -0
- package/src/session/indexed-session-storage.ts +553 -0
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/launch-completion.ts +37 -0
- package/src/session/messages.ts +1304 -0
- package/src/session/model-controls.ts +757 -0
- package/src/session/prewalk.ts +279 -0
- package/src/session/provider-image-budget.ts +86 -0
- package/src/session/queued-messages.ts +99 -0
- package/src/session/redis-session-storage.ts +257 -0
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1896 -0
- package/src/session/session-context.ts +584 -0
- package/src/session/session-dump-format.ts +216 -0
- package/src/session/session-entries.ts +306 -0
- package/src/session/session-handoff.ts +325 -0
- package/src/session/session-history-format.ts +459 -0
- package/src/session/session-listing.ts +715 -0
- package/src/session/session-loader.ts +362 -0
- package/src/session/session-maintenance.ts +3115 -0
- package/src/session/session-manager.ts +2747 -0
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-metadata.ts +53 -0
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +280 -0
- package/src/session/session-persistence.ts +293 -0
- package/src/session/session-provider-boundary.ts +306 -0
- package/src/session/session-stats.ts +349 -0
- package/src/session/session-storage.ts +774 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/session-tools.ts +1385 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/settings-stream-fn.ts +79 -0
- package/src/session/shake-types.ts +43 -0
- package/src/session/snapcompact-inline.ts +545 -0
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/sql-session-storage.ts +374 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +1459 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/tool-choice-queue.ts +305 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-persistence.ts +142 -0
- package/src/session/turn-recovery.ts +2005 -0
- package/src/session/unexpected-stop-classifier.ts +141 -0
- package/src/session/yield-queue.ts +288 -0
- package/src/slash-commands/acp-builtins.ts +70 -0
- package/src/slash-commands/available-commands.ts +105 -0
- package/src/slash-commands/builtin-registry.ts +3149 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
- package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
- package/src/slash-commands/helpers/context-report.ts +66 -0
- package/src/slash-commands/helpers/format.ts +46 -0
- package/src/slash-commands/helpers/logout.ts +108 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +533 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/reset-usage.ts +68 -0
- package/src/slash-commands/helpers/security.ts +451 -0
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/ssh.ts +196 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
- package/src/slash-commands/helpers/todo.ts +285 -0
- package/src/slash-commands/helpers/usage-report.ts +198 -0
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/slash-commands/types.ts +139 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +667 -0
- package/src/ssh/file-transfer.ts +214 -0
- package/src/ssh/sshfs-mount.ts +163 -0
- package/src/ssh/utils.ts +51 -0
- package/src/startup-splash.ts +19 -0
- package/src/stt/asr-client.ts +401 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +603 -0
- package/src/stt/downloader.ts +142 -0
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +6 -0
- package/src/stt/models.ts +150 -0
- package/src/stt/sherpa-runtime.ts +71 -0
- package/src/stt/stt-controller.ts +320 -0
- package/src/stt/submit-trigger.ts +74 -0
- package/src/subprocess/worker-client.ts +463 -0
- package/src/subprocess/worker-runtime.ts +494 -0
- package/src/system-prompt.ts +921 -0
- package/src/task/agents.ts +170 -0
- package/src/task/commands.ts +132 -0
- package/src/task/discovery.ts +145 -0
- package/src/task/executor.ts +3431 -0
- package/src/task/index.ts +1515 -0
- package/src/task/isolation-ownership.ts +106 -0
- package/src/task/isolation-runner.ts +444 -0
- package/src/task/label.ts +40 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/omp-command.ts +26 -0
- package/src/task/output-manager.ts +115 -0
- package/src/task/parallel.ts +221 -0
- package/src/task/persisted-revive.ts +173 -0
- package/src/task/prewalk.ts +6 -0
- package/src/task/prompt-policy.ts +8 -0
- package/src/task/provider-concurrency.ts +100 -0
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/render.ts +1830 -0
- package/src/task/renderer.ts +14 -0
- package/src/task/repair-args.ts +118 -0
- package/src/task/spawn-policy.ts +72 -0
- package/src/task/structured-subagent.ts +676 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/types.ts +555 -0
- package/src/task/worktree.ts +967 -0
- package/src/task/yield-assembly.ts +198 -0
- package/src/telemetry-export.ts +504 -0
- package/src/thinking.ts +377 -0
- package/src/tiny/device.ts +111 -0
- package/src/tiny/dtype.ts +101 -0
- package/src/tiny/message-preproc.ts +155 -0
- package/src/tiny/models.ts +268 -0
- package/src/tiny/text.ts +290 -0
- package/src/tiny/title-client.ts +460 -0
- package/src/tiny/title-protocol.ts +56 -0
- package/src/tiny/worker.ts +354 -0
- package/src/tools/acp-bridge.ts +125 -0
- package/src/tools/approval.ts +245 -0
- package/src/tools/ask.ts +1459 -0
- package/src/tools/ast-edit.ts +718 -0
- package/src/tools/ast-grep.ts +526 -0
- package/src/tools/auto-generated-guard.ts +335 -0
- package/src/tools/bash-interactive.ts +435 -0
- package/src/tools/bash-interceptor.ts +148 -0
- package/src/tools/bash-pty-selection.ts +14 -0
- package/src/tools/bash-skill-urls.ts +350 -0
- package/src/tools/bash.ts +1826 -0
- package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
- package/src/tools/browser/aria/aria-snapshot.ts +131 -0
- package/src/tools/browser/attach.ts +219 -0
- package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
- package/src/tools/browser/cmux/rpc.ts +200 -0
- package/src/tools/browser/cmux/socket-client.ts +445 -0
- package/src/tools/browser/launch.ts +939 -0
- package/src/tools/browser/readable.ts +112 -0
- package/src/tools/browser/registry.ts +447 -0
- package/src/tools/browser/relay/bridge.ts +945 -0
- package/src/tools/browser/relay/daemon.ts +117 -0
- package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
- package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
- package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
- package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
- package/src/tools/browser/relay/kind.ts +41 -0
- package/src/tools/browser/relay/protocol.ts +54 -0
- package/src/tools/browser/relay/server.ts +141 -0
- package/src/tools/browser/render.ts +229 -0
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/shared-daemon.ts +139 -0
- package/src/tools/browser/tab-protocol.ts +123 -0
- package/src/tools/browser/tab-supervisor.ts +1077 -0
- package/src/tools/browser/tab-worker-entry.ts +29 -0
- package/src/tools/browser/tab-worker.ts +1982 -0
- package/src/tools/browser.ts +487 -0
- package/src/tools/builtin-names.ts +67 -0
- package/src/tools/checkpoint.ts +137 -0
- package/src/tools/computer/exposure.ts +14 -0
- package/src/tools/computer/protocol.ts +69 -0
- package/src/tools/computer/supervisor.ts +409 -0
- package/src/tools/computer/worker-entry.ts +39 -0
- package/src/tools/computer/worker.ts +745 -0
- package/src/tools/computer-renderer.ts +147 -0
- package/src/tools/computer.ts +222 -0
- package/src/tools/conflict-detect.ts +815 -0
- package/src/tools/context.ts +49 -0
- package/src/tools/debug.ts +1121 -0
- package/src/tools/default-renderer.ts +154 -0
- package/src/tools/essential-tools.ts +46 -0
- package/src/tools/eval-backends.ts +34 -0
- package/src/tools/eval-format/index.ts +24 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/julia.ts +446 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval-format/ruby.ts +380 -0
- package/src/tools/eval-render.ts +784 -0
- package/src/tools/eval.ts +774 -0
- package/src/tools/fetch.ts +1889 -0
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gh-cache-invalidation.ts +175 -0
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +484 -0
- package/src/tools/gh.ts +3958 -0
- package/src/tools/github-cache.ts +663 -0
- package/src/tools/glob.ts +691 -0
- package/src/tools/grep.ts +1920 -0
- package/src/tools/grouped-file-output.ts +210 -0
- package/src/tools/hub/index.ts +579 -0
- package/src/tools/hub/jobs.ts +714 -0
- package/src/tools/hub/launch.ts +690 -0
- package/src/tools/hub/messaging.ts +735 -0
- package/src/tools/hub/types.ts +117 -0
- package/src/tools/image-gen.ts +1689 -0
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +736 -0
- package/src/tools/inspect-image-renderer.ts +133 -0
- package/src/tools/inspect-image.ts +316 -0
- package/src/tools/json-tree.ts +260 -0
- package/src/tools/jtd-to-json-schema.ts +219 -0
- package/src/tools/jtd-to-typescript.ts +136 -0
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/learn.ts +141 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/manage-skill.ts +102 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/memory-edit.ts +61 -0
- package/src/tools/memory-recall.ts +102 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +211 -0
- package/src/tools/memory-retain.ts +89 -0
- package/src/tools/output-meta.ts +860 -0
- package/src/tools/output-schema-validator.ts +307 -0
- package/src/tools/path-utils.ts +1489 -0
- package/src/tools/plan-mode-guard.ts +155 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
- package/src/tools/read.ts +3762 -0
- package/src/tools/render-utils.ts +926 -0
- package/src/tools/renderers.ts +133 -0
- package/src/tools/report-tool-issue.ts +568 -0
- package/src/tools/resolve.ts +423 -0
- package/src/tools/review.ts +103 -0
- package/src/tools/run-scope.ts +417 -0
- package/src/tools/security-scan.ts +287 -0
- package/src/tools/shell-tokenize.ts +213 -0
- package/src/tools/sqlite-reader.ts +884 -0
- package/src/tools/terminal-output.ts +141 -0
- package/src/tools/todo.ts +1226 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +102 -0
- package/src/tools/tool-timeouts.ts +38 -0
- package/src/tools/tts.ts +266 -0
- package/src/tools/vibe.ts +608 -0
- package/src/tools/write.ts +1646 -0
- package/src/tools/xdev.ts +560 -0
- package/src/tools/yield.ts +486 -0
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +10 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/speakable.ts +392 -0
- package/src/tts/speech-enhancer.ts +206 -0
- package/src/tts/streaming-player.ts +120 -0
- package/src/tts/tts-client.ts +475 -0
- package/src/tts/tts-protocol.ts +69 -0
- package/src/tts/tts-worker.ts +434 -0
- package/src/tts/vocalizer.ts +419 -0
- package/src/tts/wav.ts +58 -0
- package/src/tui/code-cell.ts +268 -0
- package/src/tui/file-list.ts +55 -0
- package/src/tui/hyperlink.ts +178 -0
- package/src/tui/index.ts +13 -0
- package/src/tui/output-block.ts +268 -0
- package/src/tui/status-line.ts +54 -0
- package/src/tui/tree-list.ts +172 -0
- package/src/tui/types.ts +15 -0
- package/src/tui/utils.ts +103 -0
- package/src/tui/width-aware-text.ts +58 -0
- package/src/utils/active-repo-context.ts +143 -0
- package/src/utils/block-context.ts +312 -0
- package/src/utils/changelog.ts +373 -0
- package/src/utils/clipboard.ts +361 -0
- package/src/utils/command-args.ts +74 -0
- package/src/utils/commit-message-generator.ts +148 -0
- package/src/utils/cpuprofile.ts +235 -0
- package/src/utils/edit-mode.ts +61 -0
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +80 -0
- package/src/utils/fetch-timeout.ts +10 -0
- package/src/utils/file-display-mode.ts +44 -0
- package/src/utils/file-mentions.ts +293 -0
- package/src/utils/git.ts +2472 -0
- package/src/utils/image-loading.ts +236 -0
- package/src/utils/image-resize.ts +420 -0
- package/src/utils/image-vision-fallback.ts +196 -0
- package/src/utils/inspect-image-mode.ts +39 -0
- package/src/utils/ipc.ts +38 -0
- package/src/utils/jj.ts +422 -0
- package/src/utils/lang-from-path.ts +251 -0
- package/src/utils/late-cleanup.ts +17 -0
- package/src/utils/local-date.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/markit-cache.ts +166 -0
- package/src/utils/markit.ts +223 -0
- package/src/utils/mupdf-wasm-embed.ts +12 -0
- package/src/utils/open.ts +126 -0
- package/src/utils/profile-tree.ts +111 -0
- package/src/utils/prompt-path.ts +3 -0
- package/src/utils/qrcode.ts +535 -0
- package/src/utils/sample-profile.ts +437 -0
- package/src/utils/session-color.ts +142 -0
- package/src/utils/shell-snapshot-fn-env.sh +63 -0
- package/src/utils/shell-snapshot.ts +326 -0
- package/src/utils/sixel.ts +69 -0
- package/src/utils/thinking-display.ts +163 -0
- package/src/utils/title-generator.ts +597 -0
- package/src/utils/token-rate.ts +72 -0
- package/src/utils/tool-choice.ts +50 -0
- package/src/utils/tools-manager.ts +411 -0
- package/src/utils/turndown.ts +82 -0
- package/src/utils/zip.ts +1106 -0
- package/src/vibe/runtime.ts +1684 -0
- package/src/vibe/state.ts +4 -0
- package/src/web/kagi.ts +305 -0
- package/src/web/parallel.ts +354 -0
- package/src/web/scrapers/artifacthub.ts +207 -0
- package/src/web/scrapers/arxiv.ts +83 -0
- package/src/web/scrapers/aur.ts +162 -0
- package/src/web/scrapers/biorxiv.ts +133 -0
- package/src/web/scrapers/bluesky.ts +262 -0
- package/src/web/scrapers/brew.ts +172 -0
- package/src/web/scrapers/cheatsh.ts +68 -0
- package/src/web/scrapers/chocolatey.ts +196 -0
- package/src/web/scrapers/choosealicense.ts +95 -0
- package/src/web/scrapers/cisa-kev.ts +87 -0
- package/src/web/scrapers/clojars.ts +154 -0
- package/src/web/scrapers/coingecko.ts +177 -0
- package/src/web/scrapers/crates-io.ts +97 -0
- package/src/web/scrapers/crossref.ts +136 -0
- package/src/web/scrapers/devto.ts +147 -0
- package/src/web/scrapers/discogs.ts +306 -0
- package/src/web/scrapers/discourse.ts +197 -0
- package/src/web/scrapers/dockerhub.ts +138 -0
- package/src/web/scrapers/docs-rs.ts +663 -0
- package/src/web/scrapers/fdroid.ts +134 -0
- package/src/web/scrapers/firefox-addons.ts +191 -0
- package/src/web/scrapers/flathub.ts +223 -0
- package/src/web/scrapers/github-gist.ts +58 -0
- package/src/web/scrapers/github.ts +800 -0
- package/src/web/scrapers/gitlab.ts +401 -0
- package/src/web/scrapers/go-pkg.ts +266 -0
- package/src/web/scrapers/hackage.ts +129 -0
- package/src/web/scrapers/hackernews.ts +189 -0
- package/src/web/scrapers/hex.ts +105 -0
- package/src/web/scrapers/huggingface.ts +321 -0
- package/src/web/scrapers/iacr.ts +89 -0
- package/src/web/scrapers/index.ts +252 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
- package/src/web/scrapers/lemmy.ts +203 -0
- package/src/web/scrapers/lobsters.ts +175 -0
- package/src/web/scrapers/mastodon.ts +292 -0
- package/src/web/scrapers/maven.ts +138 -0
- package/src/web/scrapers/mdn.ts +173 -0
- package/src/web/scrapers/metacpan.ts +222 -0
- package/src/web/scrapers/musicbrainz.ts +250 -0
- package/src/web/scrapers/npm.ts +98 -0
- package/src/web/scrapers/nuget.ts +183 -0
- package/src/web/scrapers/nvd.ts +222 -0
- package/src/web/scrapers/ollama.ts +239 -0
- package/src/web/scrapers/open-vsx.ts +106 -0
- package/src/web/scrapers/opencorporates.ts +292 -0
- package/src/web/scrapers/openlibrary.ts +336 -0
- package/src/web/scrapers/orcid.ts +286 -0
- package/src/web/scrapers/osv.ts +176 -0
- package/src/web/scrapers/packagist.ts +160 -0
- package/src/web/scrapers/pub-dev.ts +143 -0
- package/src/web/scrapers/pubmed.ts +211 -0
- package/src/web/scrapers/pypi.ts +112 -0
- package/src/web/scrapers/rawg.ts +110 -0
- package/src/web/scrapers/readthedocs.ts +121 -0
- package/src/web/scrapers/reddit.ts +95 -0
- package/src/web/scrapers/repology.ts +251 -0
- package/src/web/scrapers/rfc.ts +201 -0
- package/src/web/scrapers/rubygems.ts +103 -0
- package/src/web/scrapers/searchcode.ts +189 -0
- package/src/web/scrapers/sec-edgar.ts +261 -0
- package/src/web/scrapers/semantic-scholar.ts +171 -0
- package/src/web/scrapers/snapcraft.ts +187 -0
- package/src/web/scrapers/sourcegraph.ts +336 -0
- package/src/web/scrapers/spdx.ts +108 -0
- package/src/web/scrapers/spotify.ts +198 -0
- package/src/web/scrapers/stackoverflow.ts +120 -0
- package/src/web/scrapers/terraform.ts +277 -0
- package/src/web/scrapers/tldr.ts +47 -0
- package/src/web/scrapers/twitter.ts +94 -0
- package/src/web/scrapers/types.ts +354 -0
- package/src/web/scrapers/utils.ts +109 -0
- package/src/web/scrapers/vimeo.ts +133 -0
- package/src/web/scrapers/vscode-marketplace.ts +187 -0
- package/src/web/scrapers/w3c.ts +156 -0
- package/src/web/scrapers/wikidata.ts +344 -0
- package/src/web/scrapers/wikipedia.ts +84 -0
- package/src/web/scrapers/youtube.ts +325 -0
- package/src/web/search/index.ts +393 -0
- package/src/web/search/provider.ts +272 -0
- package/src/web/search/providers/anthropic.ts +406 -0
- package/src/web/search/providers/base.ts +112 -0
- package/src/web/search/providers/brave.ts +181 -0
- package/src/web/search/providers/browser-headers.ts +82 -0
- package/src/web/search/providers/browser-page.ts +125 -0
- package/src/web/search/providers/codex.ts +788 -0
- package/src/web/search/providers/duckduckgo.ts +382 -0
- package/src/web/search/providers/ecosia.ts +183 -0
- package/src/web/search/providers/exa.ts +513 -0
- package/src/web/search/providers/firecrawl.ts +213 -0
- package/src/web/search/providers/gemini.ts +634 -0
- package/src/web/search/providers/google.ts +195 -0
- package/src/web/search/providers/jina.ts +144 -0
- package/src/web/search/providers/kagi.ts +98 -0
- package/src/web/search/providers/kimi.ts +221 -0
- package/src/web/search/providers/mojeek.ts +220 -0
- package/src/web/search/providers/parallel.ts +186 -0
- package/src/web/search/providers/perplexity-auth.ts +142 -0
- package/src/web/search/providers/perplexity.ts +997 -0
- package/src/web/search/providers/public.ts +199 -0
- package/src/web/search/providers/searxng.ts +467 -0
- package/src/web/search/providers/startpage.ts +225 -0
- package/src/web/search/providers/synthetic.ts +126 -0
- package/src/web/search/providers/tavily.ts +244 -0
- package/src/web/search/providers/tinyfish.ts +166 -0
- package/src/web/search/providers/utils.ts +130 -0
- package/src/web/search/providers/xai.ts +372 -0
- package/src/web/search/providers/zai.ts +452 -0
- package/src/web/search/query.ts +850 -0
- package/src/web/search/render.ts +262 -0
- package/src/web/search/types.ts +518 -0
- package/src/web/search/utils.ts +17 -0
- package/src/workspace-tree.ts +326 -0
|
@@ -0,0 +1,2595 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
4
|
+
import type { AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
5
|
+
import { getBlobsDir, isEnoent, logger, type postmortem, VERSION } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import {
|
|
7
|
+
type Agent,
|
|
8
|
+
type AgentSideConnection,
|
|
9
|
+
type AuthenticateRequest,
|
|
10
|
+
type AuthenticateResponse,
|
|
11
|
+
type AuthMethod,
|
|
12
|
+
type AvailableCommand,
|
|
13
|
+
type ClientCapabilities,
|
|
14
|
+
type CloseSessionRequest,
|
|
15
|
+
type CloseSessionResponse,
|
|
16
|
+
type CreateElicitationResponse,
|
|
17
|
+
type ElicitationContentValue,
|
|
18
|
+
type ElicitationPropertySchema,
|
|
19
|
+
type ForkSessionRequest,
|
|
20
|
+
type ForkSessionResponse,
|
|
21
|
+
type InitializeRequest,
|
|
22
|
+
type InitializeResponse,
|
|
23
|
+
type ListSessionsRequest,
|
|
24
|
+
type ListSessionsResponse,
|
|
25
|
+
type LoadSessionRequest,
|
|
26
|
+
type LoadSessionResponse,
|
|
27
|
+
type McpServer,
|
|
28
|
+
type NewSessionRequest,
|
|
29
|
+
type NewSessionResponse,
|
|
30
|
+
PROTOCOL_VERSION,
|
|
31
|
+
type PromptRequest,
|
|
32
|
+
type PromptResponse,
|
|
33
|
+
type ResumeSessionRequest,
|
|
34
|
+
type ResumeSessionResponse,
|
|
35
|
+
type SessionConfigOption,
|
|
36
|
+
type SessionInfo,
|
|
37
|
+
type SessionModeState,
|
|
38
|
+
type SessionNotification,
|
|
39
|
+
type SessionUpdate,
|
|
40
|
+
type SetSessionConfigOptionRequest,
|
|
41
|
+
type SetSessionConfigOptionResponse,
|
|
42
|
+
type SetSessionModeRequest,
|
|
43
|
+
type SetSessionModeResponse,
|
|
44
|
+
type Usage,
|
|
45
|
+
} from "@oh-my-pi/pi-utils/acp";
|
|
46
|
+
import { disableProvider, enableProvider, reset as resetCapabilities } from "../../capability";
|
|
47
|
+
import { Settings } from "../../config/settings";
|
|
48
|
+
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
49
|
+
import {
|
|
50
|
+
type ExtensionUIContext,
|
|
51
|
+
type ExtensionUIDialogOptions,
|
|
52
|
+
getExtensionUISelectOptionLabel,
|
|
53
|
+
} from "../../extensibility/extensions";
|
|
54
|
+
import { runExtensionCompact } from "../../extensibility/extensions/compact-handler";
|
|
55
|
+
import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
|
|
56
|
+
import { buildSkillPromptMessage, parseSkillInvocation } from "../../extensibility/skills";
|
|
57
|
+
import { loadSlashCommands } from "../../extensibility/slash-commands";
|
|
58
|
+
import { resolveLocalUrlToPath } from "../../internal-urls";
|
|
59
|
+
import { MCPManager } from "../../mcp/manager";
|
|
60
|
+
import type { MCPServerConfig } from "../../mcp/types";
|
|
61
|
+
import { loadAllExtensions } from "../../modes/components/extensions/state-manager";
|
|
62
|
+
import { theme } from "../../modes/theme/theme";
|
|
63
|
+
import { normalizePlanTitle, type PlanApprovalDetails, resolveApprovedPlan } from "../../plan-mode/approved-plan";
|
|
64
|
+
import type { AgentSession, AgentSessionEvent } from "../../session/agent-session";
|
|
65
|
+
import { BlobStore, resolveImageDataSync } from "../../session/blob-store";
|
|
66
|
+
import { isSilentAbort, SKILL_PROMPT_MESSAGE_TYPE, USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
67
|
+
import type { UsageStatistics } from "../../session/session-entries";
|
|
68
|
+
import type { SessionInfo as StoredSessionInfo } from "../../session/session-listing";
|
|
69
|
+
import { SessionManager } from "../../session/session-manager";
|
|
70
|
+
import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
|
|
71
|
+
import { buildAvailableSlashCommands, toAcpAvailableCommands } from "../../slash-commands/available-commands";
|
|
72
|
+
import { DEFAULT_STT_MODEL_KEY, STT_MODEL_OPTIONS } from "../../stt/models";
|
|
73
|
+
import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
|
|
74
|
+
import { normalizeLocalScheme } from "../../tools/path-utils";
|
|
75
|
+
import { ToolError } from "../../tools/tool-errors";
|
|
76
|
+
import {
|
|
77
|
+
DEFAULT_TTS_LOCAL_MODEL_KEY,
|
|
78
|
+
DEFAULT_TTS_VOICE,
|
|
79
|
+
TTS_LOCAL_MODELS,
|
|
80
|
+
TTS_LOCAL_VOICE_OPTIONS,
|
|
81
|
+
} from "../../tts/models";
|
|
82
|
+
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
83
|
+
import { createAcpClientBridge } from "./acp-client-bridge";
|
|
84
|
+
import {
|
|
85
|
+
extractAssistantMessageText,
|
|
86
|
+
mapAgentSessionEventToAcpSessionUpdates,
|
|
87
|
+
normalizeReplayToolArguments,
|
|
88
|
+
} from "./acp-event-mapper";
|
|
89
|
+
import { ACP_TERMINAL_AUTH_FLAG } from "./terminal-auth";
|
|
90
|
+
|
|
91
|
+
const ACP_DEFAULT_MODE_ID = "default";
|
|
92
|
+
const ACP_PLAN_MODE_ID = "plan";
|
|
93
|
+
const DEFAULT_PLAN_FILE_URL = "local://PLAN.md";
|
|
94
|
+
const APPROVE_OPTION = "Approve and execute";
|
|
95
|
+
const REFINE_OPTION = "Refine plan";
|
|
96
|
+
const MODE_CONFIG_ID = "mode";
|
|
97
|
+
const MODEL_CONFIG_ID = "model";
|
|
98
|
+
const THINKING_CONFIG_ID = "thinking";
|
|
99
|
+
const THINKING_OFF = "off";
|
|
100
|
+
const SESSION_PAGE_SIZE = 50;
|
|
101
|
+
const SPEECH_MODELS_LIST_METHOD = "speech.models.list";
|
|
102
|
+
/**
|
|
103
|
+
* Delay between `session/new` (or `session/load` / `session/resume` /
|
|
104
|
+
* `unstable_session/fork`) returning and the agent firing the first
|
|
105
|
+
* notifications against the new session id. Mitigates Zed's
|
|
106
|
+
* `Received session notification for unknown session` race — see
|
|
107
|
+
* `#scheduleBootstrapUpdates`. Exported so the ACP test harness can
|
|
108
|
+
* wait past this guard without hard-coding the literal.
|
|
109
|
+
*/
|
|
110
|
+
export const ACP_BOOTSTRAP_RACE_GUARD_MS = 50;
|
|
111
|
+
const ACP_CANCEL_CLEANUP_TIMEOUT_MS = 5_000;
|
|
112
|
+
const ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS = 250;
|
|
113
|
+
const ACP_ASYNC_DELIVERY_DRAIN_MAX_PASSES = 3;
|
|
114
|
+
|
|
115
|
+
type AgentImageContent = {
|
|
116
|
+
type: "image";
|
|
117
|
+
data: string;
|
|
118
|
+
mimeType: string;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
type PromptQueueState = {
|
|
122
|
+
promise: Promise<void>;
|
|
123
|
+
release: (() => void) | undefined;
|
|
124
|
+
};
|
|
125
|
+
type PromptLifecycleError = Error & { readonly code: "ACP_SESSION_CLOSED" };
|
|
126
|
+
|
|
127
|
+
type PromptTurnState = {
|
|
128
|
+
cancelRequested: boolean;
|
|
129
|
+
settled: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Delivery of streamed assistant `error` chunks this turn (the mapper
|
|
132
|
+
* surfaces them as `agent_message_chunk`s). Resolves `true` once at least
|
|
133
|
+
* one error chunk reached the client — the `agent_end` error fallback in
|
|
134
|
+
* {@link AcpAgent##flushUnreportedTurnError} awaits it and stays silent on
|
|
135
|
+
* success, so a fallback racing an in-flight delivery can neither duplicate
|
|
136
|
+
* the error nor drop it when delivery fails.
|
|
137
|
+
*/
|
|
138
|
+
errorTextDelivery: Promise<boolean> | undefined;
|
|
139
|
+
/**
|
|
140
|
+
* `abort()` is in-flight (or its bounded-timeout race). `undefined` while the turn is
|
|
141
|
+
* running normally and after cleanup completes. The turn occupies `record.promptTurn`
|
|
142
|
+
* for as long as either `!settled` or `cleanup` is set — that combined window is the
|
|
143
|
+
* "turn in flight" predicate (`isPromptTurnInFlight`) every consumer gates on.
|
|
144
|
+
*/
|
|
145
|
+
cleanup: Promise<void> | undefined;
|
|
146
|
+
usageBaseline: UsageStatistics;
|
|
147
|
+
unsubscribe: (() => void) | undefined;
|
|
148
|
+
resolve: (value: PromptResponse) => void;
|
|
149
|
+
reject: (reason?: unknown) => void;
|
|
150
|
+
promise: Promise<PromptResponse>;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A turn is "in flight" from the moment `prompt()` reserves the slot until `settled` is
|
|
155
|
+
* true AND any cancel cleanup has completed. Fork/queue/event gating all depend on this
|
|
156
|
+
* combined window — a settled-but-still-aborting turn is not safe to fork from, queue
|
|
157
|
+
* onto, or forward late events for.
|
|
158
|
+
*/
|
|
159
|
+
function isPromptTurnInFlight(turn: PromptTurnState | undefined): turn is PromptTurnState {
|
|
160
|
+
return turn !== undefined && (!turn.settled || turn.cleanup !== undefined);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type ManagedSessionRecord = {
|
|
164
|
+
session: AgentSession;
|
|
165
|
+
mcpManager: MCPManager | undefined;
|
|
166
|
+
// Ordered queue of MCP tool refreshes for this record. Rebuilt per
|
|
167
|
+
// `#configureMcpServers` call; drained on reconfigure so a stale in-flight
|
|
168
|
+
// refresh can never land after a newer configuration's tools.
|
|
169
|
+
mcpRefreshChain: Promise<void> | undefined;
|
|
170
|
+
promptTurn: PromptTurnState | undefined;
|
|
171
|
+
promptQueue: PromptQueueState;
|
|
172
|
+
liveMessageId: string | undefined;
|
|
173
|
+
liveMessageProgress: { textEmitted: boolean; thoughtEmitted: boolean } | undefined;
|
|
174
|
+
toolArgsById: Map<string, unknown>;
|
|
175
|
+
extensionsConfigured: boolean;
|
|
176
|
+
// Installed inside `#scheduleBootstrapUpdates` (post-race-guard); released
|
|
177
|
+
// in `#disposeSessionRecord`. Lives independent of any prompt turn.
|
|
178
|
+
lifetimeUnsubscribe: (() => void) | undefined;
|
|
179
|
+
closedError: PromptLifecycleError | undefined;
|
|
180
|
+
promptEventHandlers: Set<Promise<void>>;
|
|
181
|
+
extensionUserMessageTasks: Set<Promise<void>>;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
type ReplayableMessage = {
|
|
185
|
+
role: string;
|
|
186
|
+
content?: unknown;
|
|
187
|
+
errorMessage?: string;
|
|
188
|
+
toolCallId?: string;
|
|
189
|
+
toolName?: string;
|
|
190
|
+
details?: unknown;
|
|
191
|
+
isError?: boolean;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
type ReplayableToolItem = {
|
|
195
|
+
type?: unknown;
|
|
196
|
+
id?: unknown;
|
|
197
|
+
name?: unknown;
|
|
198
|
+
arguments?: unknown;
|
|
199
|
+
input?: unknown;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
type MCPConfigMap = {
|
|
203
|
+
[name: string]: MCPServerConfig;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
type MCPSource = {
|
|
207
|
+
provider: string;
|
|
208
|
+
providerName: string;
|
|
209
|
+
path: string;
|
|
210
|
+
level: "project";
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
type MCPSourceMap = {
|
|
214
|
+
[name: string]: MCPSource;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
type CreateAcpSession = (cwd: string) => Promise<AgentSession>;
|
|
218
|
+
|
|
219
|
+
type AcpSpeechOption = {
|
|
220
|
+
value: string;
|
|
221
|
+
label: string;
|
|
222
|
+
description?: string;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
type AcpSpeechVoiceOption = {
|
|
226
|
+
value: string;
|
|
227
|
+
label: string;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
type AcpSpeechTtsModelOption = AcpSpeechOption & {
|
|
231
|
+
voices: AcpSpeechVoiceOption[];
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
function buildAcpSpeechModelsCatalog(): Record<string, unknown> {
|
|
235
|
+
const voices = TTS_LOCAL_VOICE_OPTIONS.map(({ value, label }) => ({ value, label }));
|
|
236
|
+
return {
|
|
237
|
+
settings: {
|
|
238
|
+
speechToTextModel: "stt.modelName",
|
|
239
|
+
textToSpeechModel: "tts.localModel",
|
|
240
|
+
textToSpeechVoice: "tts.localVoice",
|
|
241
|
+
speechVoice: "speech.voice",
|
|
242
|
+
},
|
|
243
|
+
defaults: {
|
|
244
|
+
speechToTextModel: DEFAULT_STT_MODEL_KEY,
|
|
245
|
+
textToSpeechModel: DEFAULT_TTS_LOCAL_MODEL_KEY,
|
|
246
|
+
voice: DEFAULT_TTS_VOICE,
|
|
247
|
+
},
|
|
248
|
+
speechToText: {
|
|
249
|
+
setting: "stt.modelName",
|
|
250
|
+
defaultValue: DEFAULT_STT_MODEL_KEY,
|
|
251
|
+
models: STT_MODEL_OPTIONS.map(({ value, label, description }) => ({ value, label, description })),
|
|
252
|
+
},
|
|
253
|
+
textToSpeech: {
|
|
254
|
+
modelSetting: "tts.localModel",
|
|
255
|
+
voiceSetting: "tts.localVoice",
|
|
256
|
+
speechVoiceSetting: "speech.voice",
|
|
257
|
+
defaultModel: DEFAULT_TTS_LOCAL_MODEL_KEY,
|
|
258
|
+
defaultVoice: DEFAULT_TTS_VOICE,
|
|
259
|
+
models: TTS_LOCAL_MODELS.map(
|
|
260
|
+
({ key, label, description, voices: modelVoices }): AcpSpeechTtsModelOption => ({
|
|
261
|
+
value: key,
|
|
262
|
+
label,
|
|
263
|
+
description,
|
|
264
|
+
voices: modelVoices.map(({ id, label: voiceLabel }) => ({ value: id, label: voiceLabel })),
|
|
265
|
+
}),
|
|
266
|
+
),
|
|
267
|
+
voices,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Bridge a single ExtensionUIContext call to the ACP `unstable_createElicitation`
|
|
274
|
+
* surface. Skills/extensions ask for one value at a time (a chosen option, a
|
|
275
|
+
* confirmation, a piece of text), so every elicitation here uses a one-property
|
|
276
|
+
* `value` schema; the caller narrows the resulting `ElicitationContentValue`
|
|
277
|
+
* back to its concrete primitive type.
|
|
278
|
+
*
|
|
279
|
+
* `dialogOptions.signal` short-circuits the elicitation if it is already
|
|
280
|
+
* aborted and races the in-flight request against the abort event. The SDK
|
|
281
|
+
* exposes no `cancel_elicitation` surface for form-mode elicitations
|
|
282
|
+
* (`unstable_completeElicitation` is URL-mode only), so the ACP request itself
|
|
283
|
+
* keeps running on the client side until the user dismisses it — but
|
|
284
|
+
* resolving the local promise unblocks the caller (matches the RPC mode
|
|
285
|
+
* pattern in `requestRpcEditor`). The abort listener is removed once the
|
|
286
|
+
* elicitation settles so that callers which reuse the same signal across many
|
|
287
|
+
* elicitations (e.g. `ask` multi-select loops) don't accumulate listeners and
|
|
288
|
+
* trip Node's `MaxListeners` warning.
|
|
289
|
+
*
|
|
290
|
+
* `dialogOptions.timeout` mirrors `RpcExtensionUIContext.#createDialogPromise`:
|
|
291
|
+
* when the timer fires before the client responds, `onTimeout` is invoked and
|
|
292
|
+
* the caller's promise resolves to the stub fallback. Late SDK responses that
|
|
293
|
+
* arrive after abort/timeout — both rejections and successful `accept`s —
|
|
294
|
+
* are dropped silently (no `logger.warn`) to keep operator logs clean.
|
|
295
|
+
*/
|
|
296
|
+
async function elicitFromAcpClient(
|
|
297
|
+
connection: AgentSideConnection,
|
|
298
|
+
sessionId: string,
|
|
299
|
+
method: "select" | "confirm" | "input" | "editor",
|
|
300
|
+
message: string,
|
|
301
|
+
property: ElicitationPropertySchema,
|
|
302
|
+
dialogOptions: ExtensionUIDialogOptions | undefined,
|
|
303
|
+
): Promise<ElicitationContentValue | undefined> {
|
|
304
|
+
const signal = dialogOptions?.signal;
|
|
305
|
+
if (signal?.aborted) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
const { promise, resolve } = Promise.withResolvers<CreateElicitationResponse | undefined>();
|
|
309
|
+
let settled = false;
|
|
310
|
+
let timeoutId: NodeJS.Timeout | undefined;
|
|
311
|
+
const finish = (value: CreateElicitationResponse | undefined) => {
|
|
312
|
+
if (settled) return;
|
|
313
|
+
settled = true;
|
|
314
|
+
if (timeoutId !== undefined) clearTimeout(timeoutId);
|
|
315
|
+
signal?.removeEventListener("abort", onAbort);
|
|
316
|
+
resolve(value);
|
|
317
|
+
};
|
|
318
|
+
const onAbort = () => finish(undefined);
|
|
319
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
320
|
+
if (dialogOptions?.timeout !== undefined) {
|
|
321
|
+
timeoutId = setTimeout(() => {
|
|
322
|
+
if (settled) return;
|
|
323
|
+
try {
|
|
324
|
+
dialogOptions.onTimeout?.();
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// A throwing `onTimeout` must not leave the elicitation promise
|
|
327
|
+
// pending — settle it via `finish` below regardless.
|
|
328
|
+
logger.warn("ACP elicitation onTimeout threw", { sessionId, method, error });
|
|
329
|
+
}
|
|
330
|
+
finish(undefined);
|
|
331
|
+
}, dialogOptions.timeout);
|
|
332
|
+
// A long pending timeout alone shouldn't keep the event loop alive when
|
|
333
|
+
// the rest of the agent has shut down — matches `job-manager.ts` /
|
|
334
|
+
// `executor.ts` timer hygiene. Connection + session lifetimes keep the
|
|
335
|
+
// loop alive on the happy path.
|
|
336
|
+
timeoutId.unref();
|
|
337
|
+
}
|
|
338
|
+
connection
|
|
339
|
+
.unstable_createElicitation({
|
|
340
|
+
mode: "form",
|
|
341
|
+
sessionId,
|
|
342
|
+
message,
|
|
343
|
+
requestedSchema: {
|
|
344
|
+
type: "object",
|
|
345
|
+
properties: { value: property },
|
|
346
|
+
required: ["value"],
|
|
347
|
+
},
|
|
348
|
+
})
|
|
349
|
+
.then(finish, error => {
|
|
350
|
+
// Caller may already have moved on via abort/timeout; suppress noise.
|
|
351
|
+
if (settled) return;
|
|
352
|
+
logger.warn("ACP elicitation failed", { sessionId, method, error });
|
|
353
|
+
finish(undefined);
|
|
354
|
+
});
|
|
355
|
+
const response = await promise;
|
|
356
|
+
if (!isAcceptedElicitation(response) || !response.content) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
return response.content.value;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Narrows a `CreateElicitationResponse` to the accepted-with-content branch; the SDK's `action: string` catch-all arm otherwise defeats literal narrowing on `action !== "accept"`. */
|
|
363
|
+
function isAcceptedElicitation(
|
|
364
|
+
response: CreateElicitationResponse | undefined,
|
|
365
|
+
): response is Extract<CreateElicitationResponse, { action: "accept" }> {
|
|
366
|
+
return response?.action === "accept";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Build an {@link ExtensionUIContext} that translates skill/extension UI
|
|
371
|
+
* requests into ACP elicitations against `connection` for the session
|
|
372
|
+
* returned by `getSessionId()`. The id is read lazily at each elicitation
|
|
373
|
+
* because `AgentSession.sessionId` is a getter over `sessionManager` state
|
|
374
|
+
* that mutates when an extension command calls `ctx.newSession` /
|
|
375
|
+
* `ctx.switchSession` — snapshotting it once at factory time would route
|
|
376
|
+
* later elicitations to the pre-switch id. Live reads keep the bridge
|
|
377
|
+
* symmetric with every other `sessionUpdate` call in this file
|
|
378
|
+
* (`record.session.sessionId` is always evaluated at emit time).
|
|
379
|
+
*
|
|
380
|
+
* The non-elicitation surface (custom components, theming, terminal
|
|
381
|
+
* input) remains stubbed — ACP clients render those themselves or not
|
|
382
|
+
* at all. Capability gating respects the client's `initialize`
|
|
383
|
+
* advertisement.
|
|
384
|
+
*/
|
|
385
|
+
export function createAcpExtensionUiContext(
|
|
386
|
+
connection: AgentSideConnection,
|
|
387
|
+
getSessionId: () => string,
|
|
388
|
+
clientCapabilities: ClientCapabilities | undefined,
|
|
389
|
+
): ExtensionUIContext {
|
|
390
|
+
const supportsForm = clientCapabilities?.elicitation?.form != null;
|
|
391
|
+
return {
|
|
392
|
+
select: async (title, options, dialogOptions) => {
|
|
393
|
+
if (!supportsForm) return undefined;
|
|
394
|
+
const value = await elicitFromAcpClient(
|
|
395
|
+
connection,
|
|
396
|
+
getSessionId(),
|
|
397
|
+
"select",
|
|
398
|
+
title,
|
|
399
|
+
{ type: "string", enum: options.map(getExtensionUISelectOptionLabel) },
|
|
400
|
+
dialogOptions,
|
|
401
|
+
);
|
|
402
|
+
return typeof value === "string" ? value : undefined;
|
|
403
|
+
},
|
|
404
|
+
confirm: async (title, message, dialogOptions) => {
|
|
405
|
+
if (!supportsForm) return false;
|
|
406
|
+
const value = await elicitFromAcpClient(
|
|
407
|
+
connection,
|
|
408
|
+
getSessionId(),
|
|
409
|
+
"confirm",
|
|
410
|
+
message.trim().length > 0 ? `${title}\n\n${message}` : title,
|
|
411
|
+
{ type: "boolean" },
|
|
412
|
+
dialogOptions,
|
|
413
|
+
);
|
|
414
|
+
return typeof value === "boolean" ? value : false;
|
|
415
|
+
},
|
|
416
|
+
input: async (title, placeholder, dialogOptions) => {
|
|
417
|
+
if (!supportsForm) return undefined;
|
|
418
|
+
const value = await elicitFromAcpClient(
|
|
419
|
+
connection,
|
|
420
|
+
getSessionId(),
|
|
421
|
+
"input",
|
|
422
|
+
title,
|
|
423
|
+
// ACP's `StringPropertySchema` has no `placeholder` field, so we
|
|
424
|
+
// surface the placeholder text as `description` — the closest
|
|
425
|
+
// semantic field a client can render alongside the input.
|
|
426
|
+
// Empty / whitespace-only placeholders are treated as absent.
|
|
427
|
+
{ type: "string", ...(placeholder?.trim() ? { description: placeholder } : {}) },
|
|
428
|
+
dialogOptions,
|
|
429
|
+
);
|
|
430
|
+
return typeof value === "string" ? value : undefined;
|
|
431
|
+
},
|
|
432
|
+
notify: (message, type) => {
|
|
433
|
+
logger.debug("ACP extension notification", { message, type });
|
|
434
|
+
},
|
|
435
|
+
onTerminalInput: () => () => {},
|
|
436
|
+
setStatus: () => {},
|
|
437
|
+
setWorkingMessage: () => {},
|
|
438
|
+
setWidget: () => {},
|
|
439
|
+
setFooter: () => {},
|
|
440
|
+
setHeader: () => {},
|
|
441
|
+
setTitle: () => {},
|
|
442
|
+
custom: async () => undefined as never,
|
|
443
|
+
pasteToEditor: () => {},
|
|
444
|
+
setEditorText: () => {},
|
|
445
|
+
getEditorText: () => "",
|
|
446
|
+
editor: async (title, prefill, dialogOptions) => {
|
|
447
|
+
if (!supportsForm) return undefined;
|
|
448
|
+
const value = await elicitFromAcpClient(
|
|
449
|
+
connection,
|
|
450
|
+
getSessionId(),
|
|
451
|
+
"editor",
|
|
452
|
+
title,
|
|
453
|
+
{ type: "string", ...(prefill ? { default: prefill } : {}) },
|
|
454
|
+
dialogOptions,
|
|
455
|
+
);
|
|
456
|
+
return typeof value === "string" ? value : undefined;
|
|
457
|
+
},
|
|
458
|
+
addAutocompleteProvider: () => {},
|
|
459
|
+
setEditorComponent: () => {},
|
|
460
|
+
get theme() {
|
|
461
|
+
return theme;
|
|
462
|
+
},
|
|
463
|
+
getAllThemes: async () => [],
|
|
464
|
+
getTheme: async () => undefined,
|
|
465
|
+
setTheme: async () => ({ success: false, error: "Theme changes are unavailable in ACP mode" }),
|
|
466
|
+
getToolsExpanded: () => false,
|
|
467
|
+
setToolsExpanded: () => {},
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export class AcpAgent implements Agent {
|
|
472
|
+
#connection: AgentSideConnection;
|
|
473
|
+
#initialSession: AgentSession | undefined;
|
|
474
|
+
#createSession: CreateAcpSession;
|
|
475
|
+
#sessions = new Map<string, ManagedSessionRecord>();
|
|
476
|
+
#disposePromise: Promise<void> | undefined;
|
|
477
|
+
#cleanupRegistered = false;
|
|
478
|
+
#clientCapabilities: ClientCapabilities | undefined;
|
|
479
|
+
#cancelCleanupTimeoutMs = ACP_CANCEL_CLEANUP_TIMEOUT_MS;
|
|
480
|
+
#blobs = new BlobStore(getBlobsDir());
|
|
481
|
+
|
|
482
|
+
constructor(connection: AgentSideConnection, createSession: CreateAcpSession, initialSession?: AgentSession) {
|
|
483
|
+
this.#connection = connection;
|
|
484
|
+
this.#initialSession = initialSession;
|
|
485
|
+
this.#createSession = createSession;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
setCancelCleanupTimeoutForTesting(timeoutMs: number): void {
|
|
489
|
+
this.#cancelCleanupTimeoutMs = Math.max(1, timeoutMs);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async initialize(params: InitializeRequest): Promise<InitializeResponse> {
|
|
493
|
+
this.#registerConnectionCleanup();
|
|
494
|
+
this.#clientCapabilities = params.clientCapabilities;
|
|
495
|
+
const authMethods: AuthMethod[] = [
|
|
496
|
+
{
|
|
497
|
+
id: "agent",
|
|
498
|
+
name: "Use existing local credentials",
|
|
499
|
+
description: "Authenticate via the provider keys/OAuth state already configured under ~/.omp.",
|
|
500
|
+
},
|
|
501
|
+
];
|
|
502
|
+
if (params.clientCapabilities?.auth?.terminal === true) {
|
|
503
|
+
authMethods.push({
|
|
504
|
+
type: "terminal",
|
|
505
|
+
id: "terminal",
|
|
506
|
+
name: "Set up Oh My Pi in terminal",
|
|
507
|
+
description: "Launch the omp TUI to add provider keys and select models.",
|
|
508
|
+
args: [ACP_TERMINAL_AUTH_FLAG],
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
513
|
+
agentInfo: {
|
|
514
|
+
name: "oh-my-pi",
|
|
515
|
+
title: "Oh My Pi",
|
|
516
|
+
version: VERSION,
|
|
517
|
+
},
|
|
518
|
+
authMethods,
|
|
519
|
+
agentCapabilities: {
|
|
520
|
+
loadSession: true,
|
|
521
|
+
mcpCapabilities: {
|
|
522
|
+
http: true,
|
|
523
|
+
sse: true,
|
|
524
|
+
},
|
|
525
|
+
promptCapabilities: {
|
|
526
|
+
embeddedContext: true,
|
|
527
|
+
image: true,
|
|
528
|
+
},
|
|
529
|
+
sessionCapabilities: {
|
|
530
|
+
list: {},
|
|
531
|
+
fork: {},
|
|
532
|
+
resume: {},
|
|
533
|
+
close: {},
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async authenticate(params: AuthenticateRequest): Promise<AuthenticateResponse> {
|
|
540
|
+
// ACP spec: `methodId` must be one of the methods advertised by `initialize`.
|
|
541
|
+
// Reject anything else so malformed clients fail fast rather than appearing
|
|
542
|
+
// authenticated and surfacing a downstream model failure later.
|
|
543
|
+
const supportsTerminalAuth = this.#clientCapabilities?.auth?.terminal === true;
|
|
544
|
+
const validMethods = supportsTerminalAuth ? ["agent", "terminal"] : ["agent"];
|
|
545
|
+
if (!validMethods.includes(params.methodId)) {
|
|
546
|
+
throw new Error(`Unknown ACP auth method: ${params.methodId}`);
|
|
547
|
+
}
|
|
548
|
+
return {};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
|
552
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
553
|
+
const record = await this.#createNewSessionRecord(params.cwd, params.mcpServers);
|
|
554
|
+
const response: NewSessionResponse = {
|
|
555
|
+
sessionId: record.session.sessionId,
|
|
556
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
557
|
+
modes: this.#buildModeState(record.session),
|
|
558
|
+
};
|
|
559
|
+
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
560
|
+
return response;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
|
564
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
565
|
+
const record = await this.#loadManagedSession(params.sessionId, params.cwd, params.mcpServers);
|
|
566
|
+
await this.#replaySessionHistory(record);
|
|
567
|
+
const response: LoadSessionResponse = {
|
|
568
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
569
|
+
modes: this.#buildModeState(record.session),
|
|
570
|
+
};
|
|
571
|
+
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
572
|
+
return response;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
|
|
576
|
+
if (params.cwd) {
|
|
577
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
578
|
+
}
|
|
579
|
+
for (const record of this.#sessions.values()) {
|
|
580
|
+
await record.session.sessionManager.flush();
|
|
581
|
+
}
|
|
582
|
+
const sessions = await this.#listStoredSessions(params.cwd ?? undefined);
|
|
583
|
+
const offset = this.#parseCursor(params.cursor ?? undefined);
|
|
584
|
+
const paged = sessions.slice(offset, offset + SESSION_PAGE_SIZE);
|
|
585
|
+
const nextOffset = offset + paged.length;
|
|
586
|
+
return {
|
|
587
|
+
sessions: paged.map(session => this.#toSessionInfo(session)),
|
|
588
|
+
nextCursor: nextOffset < sessions.length ? String(nextOffset) : undefined,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
|
|
593
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
594
|
+
const record = await this.#resumeManagedSession(params.sessionId, params.cwd, params.mcpServers ?? []);
|
|
595
|
+
const response: ResumeSessionResponse = {
|
|
596
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
597
|
+
modes: this.#buildModeState(record.session),
|
|
598
|
+
};
|
|
599
|
+
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
600
|
+
return response;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse> {
|
|
604
|
+
this.#assertAbsoluteCwd(params.cwd);
|
|
605
|
+
const record = await this.#forkManagedSession(params);
|
|
606
|
+
const response: ForkSessionResponse = {
|
|
607
|
+
sessionId: record.session.sessionId,
|
|
608
|
+
configOptions: this.#buildConfigOptions(record.session),
|
|
609
|
+
modes: this.#buildModeState(record.session),
|
|
610
|
+
};
|
|
611
|
+
this.#scheduleBootstrapUpdates(record.session.sessionId);
|
|
612
|
+
return response;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
|
|
616
|
+
const record = this.#sessions.get(params.sessionId);
|
|
617
|
+
if (!record) {
|
|
618
|
+
return {};
|
|
619
|
+
}
|
|
620
|
+
await this.#closeManagedSession(params.sessionId, record);
|
|
621
|
+
return {};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse> {
|
|
625
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
626
|
+
this.#applyModeChange(record.session, params.modeId);
|
|
627
|
+
await this.#connection.sessionUpdate({
|
|
628
|
+
sessionId: record.session.sessionId,
|
|
629
|
+
update: this.#buildCurrentModeUpdate(record.session),
|
|
630
|
+
});
|
|
631
|
+
await this.#pushConfigOptionUpdate(record);
|
|
632
|
+
return {};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
async setSessionConfigOption(params: SetSessionConfigOptionRequest): Promise<SetSessionConfigOptionResponse> {
|
|
636
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
637
|
+
if (typeof params.value === "boolean") {
|
|
638
|
+
throw new Error(`Unsupported boolean ACP config option: ${params.configId}`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
switch (params.configId) {
|
|
642
|
+
case MODE_CONFIG_ID:
|
|
643
|
+
this.#applyModeChange(record.session, params.value);
|
|
644
|
+
break;
|
|
645
|
+
case MODEL_CONFIG_ID:
|
|
646
|
+
await this.#setModelById(record.session, params.value);
|
|
647
|
+
break;
|
|
648
|
+
case THINKING_CONFIG_ID:
|
|
649
|
+
this.#setThinkingLevelById(record.session, params.value);
|
|
650
|
+
break;
|
|
651
|
+
default:
|
|
652
|
+
throw new Error(`Unknown ACP config option: ${params.configId}`);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// When mode is changed via the generic config-option API, mirror the
|
|
656
|
+
// `current_mode_update` notification that `setSessionMode` emits so
|
|
657
|
+
// ACP clients tracking session-mode state see a consistent transition.
|
|
658
|
+
if (params.configId === MODE_CONFIG_ID) {
|
|
659
|
+
await this.#connection.sessionUpdate({
|
|
660
|
+
sessionId: record.session.sessionId,
|
|
661
|
+
update: this.#buildCurrentModeUpdate(record.session),
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// For `model`/`thinking`, `#setModelById`/`#setThinkingLevelById` change
|
|
666
|
+
// the session model/thinking level through AgentSession, which now emits
|
|
667
|
+
// a lifetime event (`model_changed`/`thinking_level_changed`) that
|
|
668
|
+
// `#handleLifetimeEvent` turns into a push once the subscription is
|
|
669
|
+
// installed. Only push here when that subscription is not yet
|
|
670
|
+
// installed, so pre-bootstrap callers still see the change without a
|
|
671
|
+
// post-bootstrap duplicate.
|
|
672
|
+
const handledBySubscription =
|
|
673
|
+
(params.configId === THINKING_CONFIG_ID || params.configId === MODEL_CONFIG_ID) &&
|
|
674
|
+
record.lifetimeUnsubscribe !== undefined;
|
|
675
|
+
if (!handledBySubscription) {
|
|
676
|
+
await this.#pushConfigOptionUpdate(record);
|
|
677
|
+
}
|
|
678
|
+
return { configOptions: this.#buildConfigOptions(record.session) };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
async prompt(params: PromptRequest): Promise<PromptResponse> {
|
|
682
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
683
|
+
const activeTurn = record.promptTurn;
|
|
684
|
+
if (activeTurn && !activeTurn.settled && record.session.isStreaming) {
|
|
685
|
+
// New prompt arrived while the previous turn is still in-flight (e.g. the
|
|
686
|
+
// client sent a message immediately after pressing stop, before or without
|
|
687
|
+
// a preceding session/cancel notification). Implicitly cancel the running
|
|
688
|
+
// turn so the new prompt can queue behind the abort cleanup — identical to
|
|
689
|
+
// what cancel() does when called explicitly. #beginCancelCleanup is
|
|
690
|
+
// idempotent, so a concurrent session/cancel notification is harmless.
|
|
691
|
+
// Mirror cancel()'s timeout handling: if abort() hangs past the cleanup
|
|
692
|
+
// timeout, close the managed session instead of leaving it registered
|
|
693
|
+
// with a still-streaming AgentSession. The queued prompt below observes
|
|
694
|
+
// the same cleanup rejection and fails accordingly.
|
|
695
|
+
this.#beginCancelCleanup(record, activeTurn).catch(async (error: unknown) => {
|
|
696
|
+
logger.warn("ACP cancel cleanup timed out; closing session", {
|
|
697
|
+
sessionId: record.session.sessionId,
|
|
698
|
+
error,
|
|
699
|
+
});
|
|
700
|
+
await this.#closeManagedSession(params.sessionId, record);
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
return await this.#queuePrompt(record, async () => {
|
|
704
|
+
const previousTurn = record.promptTurn;
|
|
705
|
+
if (previousTurn) {
|
|
706
|
+
// Wait for any prompt that's still settling or whose cancel cleanup is
|
|
707
|
+
// still in flight. We deliberately swallow the prompt rejection (the
|
|
708
|
+
// owning caller already received it) but let cleanup rejections
|
|
709
|
+
// propagate — a timed-out cancel must fail this queued prompt instead
|
|
710
|
+
// of letting it run on a session that is about to be closed.
|
|
711
|
+
await previousTurn.promise.catch(() => undefined);
|
|
712
|
+
await previousTurn.cleanup;
|
|
713
|
+
}
|
|
714
|
+
this.#throwIfRecordClosed(record);
|
|
715
|
+
|
|
716
|
+
const converted = this.#convertPromptBlocks(params.prompt);
|
|
717
|
+
const pendingPrompt = Promise.withResolvers<PromptResponse>();
|
|
718
|
+
record.promptTurn = {
|
|
719
|
+
cancelRequested: false,
|
|
720
|
+
settled: false,
|
|
721
|
+
errorTextDelivery: undefined,
|
|
722
|
+
cleanup: undefined,
|
|
723
|
+
usageBaseline: this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
|
|
724
|
+
unsubscribe: undefined,
|
|
725
|
+
resolve: pendingPrompt.resolve,
|
|
726
|
+
reject: pendingPrompt.reject,
|
|
727
|
+
promise: pendingPrompt.promise,
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
record.promptTurn.unsubscribe = record.session.subscribe(event => {
|
|
731
|
+
this.#trackPromptEvent(record, event);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
this.#runPromptOrCommand(record, converted.text, converted.images).catch((error: unknown) => {
|
|
735
|
+
this.#finishPrompt(record, undefined, error);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
return await pendingPrompt.promise;
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
async #queuePrompt(record: ManagedSessionRecord, run: () => Promise<PromptResponse>): Promise<PromptResponse> {
|
|
743
|
+
const nextQueue = Promise.withResolvers<void>();
|
|
744
|
+
const releaseQueue = nextQueue.resolve;
|
|
745
|
+
const previousQueue = record.promptQueue;
|
|
746
|
+
record.promptQueue = {
|
|
747
|
+
promise: nextQueue.promise,
|
|
748
|
+
release: releaseQueue,
|
|
749
|
+
};
|
|
750
|
+
await previousQueue.promise;
|
|
751
|
+
this.#throwIfRecordClosed(record);
|
|
752
|
+
try {
|
|
753
|
+
return await run();
|
|
754
|
+
} finally {
|
|
755
|
+
releaseQueue();
|
|
756
|
+
if (record.promptQueue.release === releaseQueue) {
|
|
757
|
+
record.promptQueue.release = undefined;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
#throwIfRecordClosed(record: ManagedSessionRecord): void {
|
|
763
|
+
if (record.closedError) {
|
|
764
|
+
throw record.closedError;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
#createPromptLifecycleError(message: string): PromptLifecycleError {
|
|
769
|
+
return Object.assign(new Error(message), { code: "ACP_SESSION_CLOSED" as const });
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
#trackPromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): void {
|
|
773
|
+
const handling = this.#handlePromptEvent(record, event).catch((error: unknown) => {
|
|
774
|
+
logger.warn("ACP prompt event handler failed", { error });
|
|
775
|
+
});
|
|
776
|
+
record.promptEventHandlers.add(handling);
|
|
777
|
+
void handling.finally(() => {
|
|
778
|
+
record.promptEventHandlers.delete(handling);
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
async #waitForPromptEventHandlers(record: ManagedSessionRecord): Promise<void> {
|
|
783
|
+
while (record.promptEventHandlers.size > 0) {
|
|
784
|
+
await Promise.allSettled(Array.from(record.promptEventHandlers));
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
#trackExtensionUserMessage(record: ManagedSessionRecord, task: Promise<void>): void {
|
|
789
|
+
const tracked = task.catch((error: unknown) => {
|
|
790
|
+
logger.warn("ACP extension sendUserMessage failed", { error });
|
|
791
|
+
});
|
|
792
|
+
record.extensionUserMessageTasks.add(tracked);
|
|
793
|
+
void tracked.finally(() => {
|
|
794
|
+
record.extensionUserMessageTasks.delete(tracked);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
async #waitForExtensionUserMessages(
|
|
799
|
+
record: ManagedSessionRecord,
|
|
800
|
+
baseline: ReadonlySet<Promise<void>>,
|
|
801
|
+
): Promise<void> {
|
|
802
|
+
while (true) {
|
|
803
|
+
const pending = Array.from(record.extensionUserMessageTasks).filter(task => !baseline.has(task));
|
|
804
|
+
if (pending.length === 0) {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
await Promise.allSettled(pending);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async #runPromptOrCommand(record: ManagedSessionRecord, text: string, images: AgentImageContent[]): Promise<void> {
|
|
812
|
+
const skillResult = await this.#tryRunSkillCommand(record, text);
|
|
813
|
+
if (skillResult) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const builtinResult = await executeAcpBuiltinSlashCommand(text, {
|
|
818
|
+
session: record.session,
|
|
819
|
+
sessionManager: record.session.sessionManager,
|
|
820
|
+
settings: record.session.settings,
|
|
821
|
+
cwd: record.session.sessionManager.getCwd(),
|
|
822
|
+
output: output => this.#emitCommandOutput(record, output),
|
|
823
|
+
refreshCommands: () => this.#emitAvailableCommandsUpdate(record),
|
|
824
|
+
reloadPlugins: () => this.#reloadPluginState(record),
|
|
825
|
+
notifyTitleChanged: async () => {
|
|
826
|
+
await this.#connection.sessionUpdate({
|
|
827
|
+
sessionId: record.session.sessionId,
|
|
828
|
+
update: {
|
|
829
|
+
sessionUpdate: "session_info_update",
|
|
830
|
+
title: record.session.sessionName,
|
|
831
|
+
updatedAt: new Date().toISOString(),
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
},
|
|
835
|
+
notifyConfigChanged: async () => {
|
|
836
|
+
await this.#pushConfigOptionUpdate(record);
|
|
837
|
+
},
|
|
838
|
+
});
|
|
839
|
+
if (builtinResult !== false) {
|
|
840
|
+
if ("prompt" in builtinResult) {
|
|
841
|
+
await record.session.prompt(builtinResult.prompt, { images });
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const promptTurn = record.promptTurn;
|
|
845
|
+
this.#finishPrompt(record, {
|
|
846
|
+
stopReason: "end_turn",
|
|
847
|
+
usage: this.#buildTurnUsage(
|
|
848
|
+
promptTurn?.usageBaseline ??
|
|
849
|
+
this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
|
|
850
|
+
record.session.sessionManager.getUsageStatistics(),
|
|
851
|
+
),
|
|
852
|
+
});
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const extensionPromptBaseline = new Set(record.extensionUserMessageTasks);
|
|
857
|
+
const agentInvoked = await record.session.prompt(text, { images });
|
|
858
|
+
// Extension and custom-TS commands are handled locally inside session.prompt().
|
|
859
|
+
// An ACP extension command can still call pi.sendUserMessage(), which starts
|
|
860
|
+
// an async nested prompt through the extension runtime. Keep the ACP turn
|
|
861
|
+
// subscribed until those scheduled prompts and their event handlers drain;
|
|
862
|
+
// only then is `false` proof that the slash command was purely local.
|
|
863
|
+
if (!agentInvoked) {
|
|
864
|
+
await this.#waitForExtensionUserMessages(record, extensionPromptBaseline);
|
|
865
|
+
await this.#waitForPromptEventHandlers(record);
|
|
866
|
+
this.#finishPrompt(record, { stopReason: "end_turn" });
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async #tryRunSkillCommand(record: ManagedSessionRecord, text: string): Promise<boolean> {
|
|
871
|
+
if (!record.session.skillsSettings?.enableSkillCommands) {
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
const parsed = parseSkillInvocation(text);
|
|
875
|
+
if (!parsed) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
const skill = record.session.skills.find(candidate => candidate.name === parsed.name);
|
|
879
|
+
if (!skill) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
const built = await buildSkillPromptMessage(skill, parsed.args, "user");
|
|
883
|
+
await record.session.promptCustomMessage(
|
|
884
|
+
{
|
|
885
|
+
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
886
|
+
content: built.message,
|
|
887
|
+
display: true,
|
|
888
|
+
details: built.details,
|
|
889
|
+
attribution: "user",
|
|
890
|
+
},
|
|
891
|
+
{ streamingBehavior: "steer" },
|
|
892
|
+
);
|
|
893
|
+
return true;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
async cancel(params: { sessionId: string }): Promise<void> {
|
|
897
|
+
const record = this.#getSessionRecord(params.sessionId);
|
|
898
|
+
const promptTurn = record.promptTurn;
|
|
899
|
+
if (!promptTurn || promptTurn.settled) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const cleanup = this.#beginCancelCleanup(record, promptTurn);
|
|
903
|
+
try {
|
|
904
|
+
await cleanup;
|
|
905
|
+
} catch (error: unknown) {
|
|
906
|
+
logger.warn("ACP cancel cleanup timed out; closing session", { sessionId: record.session.sessionId, error });
|
|
907
|
+
await this.#closeManagedSession(record.session.sessionId, record);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Transition a still-running turn into cancellation: mark intent, drop the live-event
|
|
913
|
+
* subscription, start the bounded `abort()` race, and resolve the ACP prompt response
|
|
914
|
+
* with `stopReason: "cancelled"` so the client sees acceptance immediately. The
|
|
915
|
+
* returned promise is the cleanup barrier — it resolves when `abort()` completes and
|
|
916
|
+
* rejects when the timeout fires. Idempotent: a second call returns the same barrier.
|
|
917
|
+
*/
|
|
918
|
+
#beginCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
|
|
919
|
+
if (promptTurn.cleanup) {
|
|
920
|
+
return promptTurn.cleanup;
|
|
921
|
+
}
|
|
922
|
+
promptTurn.cancelRequested = true;
|
|
923
|
+
promptTurn.unsubscribe?.();
|
|
924
|
+
const cleanup = this.#runCancelCleanup(record, promptTurn);
|
|
925
|
+
promptTurn.cleanup = cleanup;
|
|
926
|
+
this.#finishPrompt(record, {
|
|
927
|
+
stopReason: "cancelled",
|
|
928
|
+
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
929
|
+
});
|
|
930
|
+
return cleanup;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
async #runCancelCleanup(record: ManagedSessionRecord, promptTurn: PromptTurnState): Promise<void> {
|
|
934
|
+
let timer: NodeJS.Timeout | undefined;
|
|
935
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
936
|
+
timer = setTimeout(() => reject(new Error("ACP cancel cleanup timed out")), this.#cancelCleanupTimeoutMs);
|
|
937
|
+
});
|
|
938
|
+
try {
|
|
939
|
+
await Promise.race([record.session.abort({ reason: USER_INTERRUPT_LABEL }), timeout]);
|
|
940
|
+
} finally {
|
|
941
|
+
if (timer) clearTimeout(timer);
|
|
942
|
+
// Order matters: clear `cleanup` before evicting the slot so the slot-eviction
|
|
943
|
+
// branch matches what `#finishPrompt` saw if it ran first.
|
|
944
|
+
promptTurn.cleanup = undefined;
|
|
945
|
+
if (promptTurn.settled && record.promptTurn === promptTurn) {
|
|
946
|
+
record.promptTurn = undefined;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
async extMethod(method: string, params: { [key: string]: unknown }): Promise<{ [key: string]: unknown }> {
|
|
952
|
+
switch (method) {
|
|
953
|
+
case SPEECH_MODELS_LIST_METHOD:
|
|
954
|
+
return buildAcpSpeechModelsCatalog();
|
|
955
|
+
case "_omp/sessions/listAll": {
|
|
956
|
+
const limit = typeof params.limit === "number" ? Math.max(1, Math.min(5000, params.limit as number)) : 1000;
|
|
957
|
+
const sessions = await SessionManager.listAll();
|
|
958
|
+
const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
|
|
959
|
+
return {
|
|
960
|
+
sessions: sorted.map(s => this.#toSessionInfo(s)),
|
|
961
|
+
total: sessions.length,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
case "_omp/projects/list": {
|
|
965
|
+
const sessions = await SessionManager.listAll();
|
|
966
|
+
const buckets = new Map<
|
|
967
|
+
string,
|
|
968
|
+
{ cwd: string; sessionCount: number; lastActivityAt: number; lastTitle: string }
|
|
969
|
+
>();
|
|
970
|
+
for (const s of sessions) {
|
|
971
|
+
if (!s.cwd) continue;
|
|
972
|
+
const ts = s.modified.getTime();
|
|
973
|
+
const existing = buckets.get(s.cwd);
|
|
974
|
+
if (existing) {
|
|
975
|
+
existing.sessionCount += 1;
|
|
976
|
+
if (ts > existing.lastActivityAt) {
|
|
977
|
+
existing.lastActivityAt = ts;
|
|
978
|
+
existing.lastTitle = s.title ?? "";
|
|
979
|
+
}
|
|
980
|
+
} else {
|
|
981
|
+
buckets.set(s.cwd, {
|
|
982
|
+
cwd: s.cwd,
|
|
983
|
+
sessionCount: 1,
|
|
984
|
+
lastActivityAt: ts,
|
|
985
|
+
lastTitle: s.title ?? "",
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
const projects = Array.from(buckets.values()).sort((a, b) => b.lastActivityAt - a.lastActivityAt);
|
|
990
|
+
return { projects, totalSessions: sessions.length };
|
|
991
|
+
}
|
|
992
|
+
case "_omp/chats/byCwd": {
|
|
993
|
+
const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
|
|
994
|
+
if (!cwd) throw new Error("cwd required");
|
|
995
|
+
const limit = typeof params.limit === "number" ? Math.max(1, Math.min(500, params.limit as number)) : 100;
|
|
996
|
+
const sessions = await SessionManager.list(cwd);
|
|
997
|
+
const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
|
|
998
|
+
return { sessions: sorted.map(s => this.#toSessionInfo(s)) };
|
|
999
|
+
}
|
|
1000
|
+
case "_omp/usage": {
|
|
1001
|
+
const [firstRecord] = this.#sessions.values();
|
|
1002
|
+
const target = firstRecord?.session ?? this.#initialSession;
|
|
1003
|
+
if (!target) {
|
|
1004
|
+
return { reports: [] };
|
|
1005
|
+
}
|
|
1006
|
+
const reports = await target.fetchUsageReports();
|
|
1007
|
+
return { reports: reports ?? [] };
|
|
1008
|
+
}
|
|
1009
|
+
case "_omp/extensions": {
|
|
1010
|
+
const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
|
|
1011
|
+
const sm = await Settings.init();
|
|
1012
|
+
const disabledIds = (sm.get("disabledExtensions") as string[] | undefined) ?? [];
|
|
1013
|
+
const extensions = await loadAllExtensions(cwd, disabledIds);
|
|
1014
|
+
return { extensions: extensions as unknown as Array<{ [key: string]: unknown }> };
|
|
1015
|
+
}
|
|
1016
|
+
case "_omp/extensions/toggle": {
|
|
1017
|
+
const providerId = params.providerId;
|
|
1018
|
+
if (typeof providerId !== "string") throw new Error("providerId required");
|
|
1019
|
+
if (params.enabled === false) {
|
|
1020
|
+
disableProvider(providerId);
|
|
1021
|
+
return { enabled: false };
|
|
1022
|
+
}
|
|
1023
|
+
enableProvider(providerId);
|
|
1024
|
+
return { enabled: true };
|
|
1025
|
+
}
|
|
1026
|
+
default:
|
|
1027
|
+
throw new Error(`Unknown ACP ext method: ${method}`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
async extNotification(_method: string, _params: { [key: string]: unknown }): Promise<void> {}
|
|
1032
|
+
|
|
1033
|
+
get signal(): AbortSignal {
|
|
1034
|
+
return this.#connection.signal;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
get closed(): Promise<void> {
|
|
1038
|
+
return this.#connection.closed;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
#registerConnectionCleanup(): void {
|
|
1042
|
+
if (this.#cleanupRegistered) {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
this.#cleanupRegistered = true;
|
|
1046
|
+
this.#connection.signal.addEventListener(
|
|
1047
|
+
"abort",
|
|
1048
|
+
() => {
|
|
1049
|
+
void this.dispose();
|
|
1050
|
+
},
|
|
1051
|
+
{ once: true },
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
async #createNewSessionRecord(cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
|
|
1056
|
+
const session = await this.#createSession(path.resolve(cwd));
|
|
1057
|
+
try {
|
|
1058
|
+
await session.sessionManager.ensureOnDisk();
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
await this.#disposeStandaloneSession(session);
|
|
1061
|
+
throw error;
|
|
1062
|
+
}
|
|
1063
|
+
return await this.#registerPreparedSession(session, mcpServers);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
async #loadManagedSession(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
|
|
1067
|
+
const existing = this.#sessions.get(sessionId);
|
|
1068
|
+
if (existing) {
|
|
1069
|
+
this.#assertMatchingCwd(existing.session, cwd);
|
|
1070
|
+
await this.#configureMcpServers(existing, mcpServers);
|
|
1071
|
+
return existing;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const storedSession = await this.#findStoredSession(sessionId, cwd);
|
|
1075
|
+
if (!storedSession) {
|
|
1076
|
+
throw new Error(`ACP session not found: ${sessionId}`);
|
|
1077
|
+
}
|
|
1078
|
+
return await this.#openStoredSession(storedSession.path, cwd, mcpServers, sessionId);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
async #resumeManagedSession(sessionId: string, cwd: string, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
|
|
1082
|
+
const existing = this.#sessions.get(sessionId);
|
|
1083
|
+
if (existing) {
|
|
1084
|
+
this.#assertMatchingCwd(existing.session, cwd);
|
|
1085
|
+
await this.#configureMcpServers(existing, mcpServers);
|
|
1086
|
+
return existing;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
const storedSession = await this.#findStoredSession(sessionId, cwd);
|
|
1090
|
+
if (!storedSession) {
|
|
1091
|
+
throw new Error(`ACP session not found: ${sessionId}`);
|
|
1092
|
+
}
|
|
1093
|
+
return await this.#openStoredSession(storedSession.path, cwd, mcpServers, sessionId);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async #forkManagedSession(params: ForkSessionRequest): Promise<ManagedSessionRecord> {
|
|
1097
|
+
const sourcePath = await this.#resolveForkSourceSessionPath(params.sessionId);
|
|
1098
|
+
const session = await this.#createSession(path.resolve(params.cwd));
|
|
1099
|
+
try {
|
|
1100
|
+
const success = await session.switchSession(sourcePath);
|
|
1101
|
+
if (!success) {
|
|
1102
|
+
throw new Error(`ACP session fork was cancelled: ${params.sessionId}`);
|
|
1103
|
+
}
|
|
1104
|
+
const forked = await session.fork();
|
|
1105
|
+
if (!forked) {
|
|
1106
|
+
throw new Error(`ACP session fork failed: ${params.sessionId}`);
|
|
1107
|
+
}
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
await this.#disposeStandaloneSession(session);
|
|
1110
|
+
throw error;
|
|
1111
|
+
}
|
|
1112
|
+
return await this.#registerPreparedSession(session, params.mcpServers ?? []);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
async #openStoredSession(
|
|
1116
|
+
sessionPath: string,
|
|
1117
|
+
cwd: string,
|
|
1118
|
+
mcpServers: McpServer[],
|
|
1119
|
+
sessionId: string,
|
|
1120
|
+
): Promise<ManagedSessionRecord> {
|
|
1121
|
+
const session = await this.#createSession(path.resolve(cwd));
|
|
1122
|
+
try {
|
|
1123
|
+
const success = await session.switchSession(sessionPath);
|
|
1124
|
+
if (!success) {
|
|
1125
|
+
throw new Error(`ACP session load was cancelled: ${sessionId}`);
|
|
1126
|
+
}
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
await this.#disposeStandaloneSession(session);
|
|
1129
|
+
throw error;
|
|
1130
|
+
}
|
|
1131
|
+
return await this.#registerPreparedSession(session, mcpServers);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
async #registerPreparedSession(session: AgentSession, mcpServers: McpServer[]): Promise<ManagedSessionRecord> {
|
|
1135
|
+
const record = this.#createManagedSessionRecord(session);
|
|
1136
|
+
session.setClientBridge(createAcpClientBridge(this.#connection, session.sessionId, this.#clientCapabilities));
|
|
1137
|
+
// `record.lifetimeUnsubscribe` is installed in `#scheduleBootstrapUpdates`
|
|
1138
|
+
// so it shares the bootstrap race guard — see that comment for why.
|
|
1139
|
+
try {
|
|
1140
|
+
await this.#configureExtensions(record);
|
|
1141
|
+
await this.#configureMcpServers(record, mcpServers);
|
|
1142
|
+
this.#sessions.set(session.sessionId, record);
|
|
1143
|
+
return record;
|
|
1144
|
+
} catch (error) {
|
|
1145
|
+
await this.#disposeSessionRecord(record);
|
|
1146
|
+
throw error;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
#createManagedSessionRecord(session: AgentSession): ManagedSessionRecord {
|
|
1151
|
+
return {
|
|
1152
|
+
session,
|
|
1153
|
+
mcpManager: undefined,
|
|
1154
|
+
mcpRefreshChain: undefined,
|
|
1155
|
+
promptTurn: undefined,
|
|
1156
|
+
promptQueue: { promise: Promise.resolve(), release: undefined },
|
|
1157
|
+
liveMessageId: undefined,
|
|
1158
|
+
liveMessageProgress: undefined,
|
|
1159
|
+
toolArgsById: new Map(),
|
|
1160
|
+
extensionsConfigured: false,
|
|
1161
|
+
closedError: undefined,
|
|
1162
|
+
promptEventHandlers: new Set(),
|
|
1163
|
+
extensionUserMessageTasks: new Set(),
|
|
1164
|
+
lifetimeUnsubscribe: undefined,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async #handleLifetimeEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
|
|
1169
|
+
if (event.type !== "thinking_level_changed" && event.type !== "model_changed") {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
try {
|
|
1173
|
+
await this.#pushConfigOptionUpdate(record);
|
|
1174
|
+
} catch (error) {
|
|
1175
|
+
logger.warn("Failed to push config_option_update after a lifetime event", {
|
|
1176
|
+
sessionId: record.session.sessionId,
|
|
1177
|
+
eventType: event.type,
|
|
1178
|
+
error,
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
#getSessionRecord(sessionId: string): ManagedSessionRecord {
|
|
1184
|
+
const record = this.#sessions.get(sessionId);
|
|
1185
|
+
if (!record) {
|
|
1186
|
+
throw new Error(`Unsupported ACP session: ${sessionId}`);
|
|
1187
|
+
}
|
|
1188
|
+
return record;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
#assertMatchingCwd(session: AgentSession, cwd: string): void {
|
|
1192
|
+
const expected = path.resolve(cwd);
|
|
1193
|
+
const actual = path.resolve(session.sessionManager.getCwd());
|
|
1194
|
+
if (actual !== expected) {
|
|
1195
|
+
throw new Error(`ACP session ${session.sessionId} is already loaded for ${actual}, not ${expected}`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
async #resolveForkSourceSessionPath(sessionId: string): Promise<string> {
|
|
1200
|
+
const loaded = this.#sessions.get(sessionId);
|
|
1201
|
+
if (loaded) {
|
|
1202
|
+
if (isPromptTurnInFlight(loaded.promptTurn)) {
|
|
1203
|
+
throw new Error(`ACP session fork is unavailable while a prompt is in progress: ${sessionId}`);
|
|
1204
|
+
}
|
|
1205
|
+
await loaded.session.sessionManager.flush();
|
|
1206
|
+
const sessionPath = loaded.session.sessionManager.getSessionFile();
|
|
1207
|
+
if (!sessionPath) {
|
|
1208
|
+
throw new Error(`ACP session cannot be forked before it is persisted: ${sessionId}`);
|
|
1209
|
+
}
|
|
1210
|
+
return sessionPath;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const storedSession = await this.#findStoredSessionById(sessionId);
|
|
1214
|
+
if (!storedSession) {
|
|
1215
|
+
throw new Error(`ACP session not found: ${sessionId}`);
|
|
1216
|
+
}
|
|
1217
|
+
return storedSession.path;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
async #handlePromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): Promise<void> {
|
|
1221
|
+
const promptTurn = record.promptTurn;
|
|
1222
|
+
if (!promptTurn || promptTurn.settled || promptTurn.cancelRequested) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (event.type === "tool_execution_start" || event.type === "tool_execution_update") {
|
|
1227
|
+
record.toolArgsById.set(event.toolCallId, event.args);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
this.#prepareLiveAssistantMessage(record, event);
|
|
1231
|
+
const imageDataCache = new Map<string, string>();
|
|
1232
|
+
const resolveImageDataForAcp = (data: string, mimeType: string | undefined): string => {
|
|
1233
|
+
const key = `${mimeType ?? ""}\u0000${data}`;
|
|
1234
|
+
const cached = imageDataCache.get(key);
|
|
1235
|
+
if (cached !== undefined) return cached;
|
|
1236
|
+
const resolved = resolveImageDataSync(this.#blobs, data);
|
|
1237
|
+
imageDataCache.set(key, resolved);
|
|
1238
|
+
return resolved;
|
|
1239
|
+
};
|
|
1240
|
+
const streamedAssistantError =
|
|
1241
|
+
event.type === "message_update" &&
|
|
1242
|
+
event.message.role === "assistant" &&
|
|
1243
|
+
event.assistantMessageEvent.type === "error";
|
|
1244
|
+
for (const notification of mapAgentSessionEventToAcpSessionUpdates(event, record.session.sessionId, {
|
|
1245
|
+
getMessageId: message => this.#getLiveMessageId(record, message),
|
|
1246
|
+
getMessageProgress: message => this.#getLiveMessageProgress(record, message),
|
|
1247
|
+
getToolArgs: toolCallId => record.toolArgsById.get(toolCallId),
|
|
1248
|
+
cwd: record.session.sessionManager.getCwd(),
|
|
1249
|
+
resolveImageData: resolveImageDataForAcp,
|
|
1250
|
+
})) {
|
|
1251
|
+
const delivery = this.#connection.sessionUpdate(notification);
|
|
1252
|
+
if (streamedAssistantError) {
|
|
1253
|
+
// Resolves true only once the error chunk actually reached the
|
|
1254
|
+
// client — a failed delivery keeps the agent_end fallback armed.
|
|
1255
|
+
const outcome = delivery.then(
|
|
1256
|
+
() => true,
|
|
1257
|
+
() => false,
|
|
1258
|
+
);
|
|
1259
|
+
const prior = promptTurn.errorTextDelivery;
|
|
1260
|
+
promptTurn.errorTextDelivery = prior ? Promise.all([prior, outcome]).then(([a, b]) => a || b) : outcome;
|
|
1261
|
+
}
|
|
1262
|
+
await delivery;
|
|
1263
|
+
}
|
|
1264
|
+
if (event.type === "tool_execution_end") {
|
|
1265
|
+
record.toolArgsById.delete(event.toolCallId);
|
|
1266
|
+
}
|
|
1267
|
+
this.#clearLiveAssistantMessageAfterEvent(record, event);
|
|
1268
|
+
|
|
1269
|
+
if (event.type === "agent_end") {
|
|
1270
|
+
await this.#flushMissedFinalAssistantText(record, event);
|
|
1271
|
+
await this.#flushUnreportedTurnError(record, event);
|
|
1272
|
+
await this.#emitEndOfTurnUpdates(record);
|
|
1273
|
+
await this.#waitForAcpPromptIdle(record);
|
|
1274
|
+
record.liveMessageId = undefined;
|
|
1275
|
+
record.liveMessageProgress = undefined;
|
|
1276
|
+
this.#finishPrompt(record, {
|
|
1277
|
+
stopReason: this.#resolveStopReason(event, promptTurn.cancelRequested),
|
|
1278
|
+
usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Deliver the final visible answer when the assistant `message_end` never
|
|
1285
|
+
* reached this prompt turn's subscription. Session event handlers are
|
|
1286
|
+
* fire-and-forget (`Agent#emit` does not await async listeners), and
|
|
1287
|
+
* `agent_end` is flushed through the session's `#endInFlight` path while the
|
|
1288
|
+
* assistant `message_end` fan-out can still be parked on extension delivery —
|
|
1289
|
+
* so `agent_end` can overtake `message_end`. Once the turn finishes,
|
|
1290
|
+
* `#finishPrompt` unsubscribes and the fallback text emission in
|
|
1291
|
+
* `mapAssistantMessageEnd` is lost for good: a client that only received
|
|
1292
|
+
* `agent_thought_chunk`s stays stuck on the thinking block (#4902). The live
|
|
1293
|
+
* message progress records whether visible text ever reached the client; if
|
|
1294
|
+
* it has not, emit the last assistant message's text before the prompt
|
|
1295
|
+
* resolves. A `message_end` that lands during the end-of-turn waits still
|
|
1296
|
+
* takes the normal mapper path and sees `textEmitted` already set, so the
|
|
1297
|
+
* answer is delivered exactly once.
|
|
1298
|
+
*/
|
|
1299
|
+
async #flushMissedFinalAssistantText(
|
|
1300
|
+
record: ManagedSessionRecord,
|
|
1301
|
+
event: Extract<AgentSessionEvent, { type: "agent_end" }>,
|
|
1302
|
+
): Promise<void> {
|
|
1303
|
+
const progress = record.liveMessageProgress;
|
|
1304
|
+
if (!progress || progress.textEmitted) {
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const lastAssistant = [...event.messages]
|
|
1308
|
+
.reverse()
|
|
1309
|
+
.find((message): message is AssistantMessage => message.role === "assistant");
|
|
1310
|
+
if (!lastAssistant) {
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
const text = extractAssistantMessageText(lastAssistant);
|
|
1314
|
+
if (text.length === 0) {
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
progress.textEmitted = true;
|
|
1318
|
+
await this.#connection.sessionUpdate({
|
|
1319
|
+
sessionId: record.session.sessionId,
|
|
1320
|
+
update: {
|
|
1321
|
+
sessionUpdate: "agent_message_chunk",
|
|
1322
|
+
content: { type: "text", text },
|
|
1323
|
+
messageId: record.liveMessageId,
|
|
1324
|
+
},
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/**
|
|
1329
|
+
* Surface a turn-fatal provider error that never reached the client. A
|
|
1330
|
+
* request that fails before streaming any assistant events — e.g. GitHub
|
|
1331
|
+
* Copilot's `HTTP 400 model_not_supported` after retries — emits only
|
|
1332
|
+
* `agent_end` with an empty assistant message carrying `errorMessage`
|
|
1333
|
+
* (`Agent#runLoop`'s catch), so no `message_update`/`message_end` ever maps
|
|
1334
|
+
* to a session update and the client sees the turn end silently. Errors
|
|
1335
|
+
* that did stream are tracked via {@link PromptTurnState.errorTextDelivery};
|
|
1336
|
+
* the fallback awaits that delivery and re-sends only when it failed.
|
|
1337
|
+
*/
|
|
1338
|
+
async #flushUnreportedTurnError(
|
|
1339
|
+
record: ManagedSessionRecord,
|
|
1340
|
+
event: Extract<AgentSessionEvent, { type: "agent_end" }>,
|
|
1341
|
+
): Promise<void> {
|
|
1342
|
+
const streamedDelivery = record.promptTurn?.errorTextDelivery;
|
|
1343
|
+
if (streamedDelivery && (await streamedDelivery)) {
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
const lastAssistant = [...event.messages]
|
|
1347
|
+
.reverse()
|
|
1348
|
+
.find((message): message is AssistantMessage => message.role === "assistant");
|
|
1349
|
+
if (lastAssistant?.stopReason !== "error") {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const errorMessage = lastAssistant.errorMessage;
|
|
1353
|
+
if (!errorMessage || isSilentAbort(lastAssistant)) {
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
await this.#connection.sessionUpdate({
|
|
1357
|
+
sessionId: record.session.sessionId,
|
|
1358
|
+
update: {
|
|
1359
|
+
sessionUpdate: "agent_message_chunk",
|
|
1360
|
+
content: { type: "text", text: errorMessage },
|
|
1361
|
+
messageId: record.liveMessageId ?? crypto.randomUUID(),
|
|
1362
|
+
},
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
async #waitForAcpPromptIdle(record: ManagedSessionRecord): Promise<void> {
|
|
1367
|
+
for (let pass = 0; pass < ACP_ASYNC_DELIVERY_DRAIN_MAX_PASSES; pass++) {
|
|
1368
|
+
await record.session.waitForIdle();
|
|
1369
|
+
const delivered = await record.session.drainAsyncJobDeliveriesForAcp({
|
|
1370
|
+
timeoutMs: ACP_ASYNC_DELIVERY_DRAIN_TIMEOUT_MS,
|
|
1371
|
+
});
|
|
1372
|
+
if (!delivered) {
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
await record.session.waitForIdle();
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
#prepareLiveAssistantMessage(record: ManagedSessionRecord, event: AgentSessionEvent): void {
|
|
1381
|
+
if (
|
|
1382
|
+
(event.type === "message_start" || event.type === "message_update" || event.type === "message_end") &&
|
|
1383
|
+
event.message.role === "assistant" &&
|
|
1384
|
+
(event.type === "message_start" || !record.liveMessageId || !record.liveMessageProgress)
|
|
1385
|
+
) {
|
|
1386
|
+
record.liveMessageId = crypto.randomUUID();
|
|
1387
|
+
record.liveMessageProgress = { textEmitted: false, thoughtEmitted: false };
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* Reset live-message tracking once the assistant `message_end` is handled.
|
|
1393
|
+
* The `agent_end` reset happens inside the `agent_end` branch of
|
|
1394
|
+
* `#handlePromptEvent` — after `#flushMissedFinalAssistantText` — so a
|
|
1395
|
+
* `message_end` that arrives during the end-of-turn waits maps against the
|
|
1396
|
+
* real progress instead of resurrecting a fresh one (which would double-emit
|
|
1397
|
+
* the final answer).
|
|
1398
|
+
*/
|
|
1399
|
+
#clearLiveAssistantMessageAfterEvent(record: ManagedSessionRecord, event: AgentSessionEvent): void {
|
|
1400
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
1401
|
+
record.liveMessageId = undefined;
|
|
1402
|
+
record.liveMessageProgress = undefined;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
#getLiveMessageId(record: ManagedSessionRecord, message: unknown): string | undefined {
|
|
1407
|
+
if (typeof message !== "object" || message === null) {
|
|
1408
|
+
return undefined;
|
|
1409
|
+
}
|
|
1410
|
+
record.liveMessageId ??= crypto.randomUUID();
|
|
1411
|
+
return record.liveMessageId;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
#getLiveMessageProgress(
|
|
1415
|
+
record: ManagedSessionRecord,
|
|
1416
|
+
message: unknown,
|
|
1417
|
+
): { textEmitted: boolean; thoughtEmitted: boolean } | undefined {
|
|
1418
|
+
if (typeof message !== "object" || message === null) {
|
|
1419
|
+
return undefined;
|
|
1420
|
+
}
|
|
1421
|
+
record.liveMessageProgress ??= { textEmitted: false, thoughtEmitted: false };
|
|
1422
|
+
return record.liveMessageProgress;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
#finishPrompt(record: ManagedSessionRecord, response?: PromptResponse, error?: unknown): void {
|
|
1426
|
+
const promptTurn = record.promptTurn;
|
|
1427
|
+
if (!promptTurn || promptTurn.settled) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
promptTurn.settled = true;
|
|
1431
|
+
promptTurn.unsubscribe?.();
|
|
1432
|
+
// Keep the slot occupied until cancel cleanup finishes — `#runCancelCleanup`
|
|
1433
|
+
// evicts the slot in its finally block once both flags say it's safe.
|
|
1434
|
+
if (!promptTurn.cleanup && record.promptTurn === promptTurn) {
|
|
1435
|
+
record.promptTurn = undefined;
|
|
1436
|
+
}
|
|
1437
|
+
if (error !== undefined) {
|
|
1438
|
+
promptTurn.reject(error);
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
promptTurn.resolve(response ?? { stopReason: "end_turn" });
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
#resolveStopReason(
|
|
1445
|
+
event: Extract<AgentSessionEvent, { type: "agent_end" }>,
|
|
1446
|
+
cancelRequested: boolean,
|
|
1447
|
+
): PromptResponse["stopReason"] {
|
|
1448
|
+
if (cancelRequested) {
|
|
1449
|
+
return "cancelled";
|
|
1450
|
+
}
|
|
1451
|
+
const lastAssistant = [...event.messages]
|
|
1452
|
+
.reverse()
|
|
1453
|
+
.find((message): message is AssistantMessage => message.role === "assistant");
|
|
1454
|
+
const reason = lastAssistant?.stopReason;
|
|
1455
|
+
switch (reason) {
|
|
1456
|
+
case "aborted":
|
|
1457
|
+
return "cancelled";
|
|
1458
|
+
case "length":
|
|
1459
|
+
return "max_tokens";
|
|
1460
|
+
case "error": {
|
|
1461
|
+
const errorMessage = lastAssistant?.errorMessage ?? "";
|
|
1462
|
+
if (/content[_ ]?filter|refus(al|ed)/i.test(errorMessage)) {
|
|
1463
|
+
return "refusal";
|
|
1464
|
+
}
|
|
1465
|
+
return "end_turn";
|
|
1466
|
+
}
|
|
1467
|
+
default:
|
|
1468
|
+
return "end_turn";
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
async #emitCommandOutput(record: ManagedSessionRecord, text: string): Promise<void> {
|
|
1473
|
+
if (!text) {
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
await this.#connection.sessionUpdate({
|
|
1477
|
+
sessionId: record.session.sessionId,
|
|
1478
|
+
update: {
|
|
1479
|
+
sessionUpdate: "agent_message_chunk",
|
|
1480
|
+
content: { type: "text", text },
|
|
1481
|
+
messageId: crypto.randomUUID(),
|
|
1482
|
+
},
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
#assertAbsoluteCwd(cwd: string): void {
|
|
1487
|
+
if (!path.isAbsolute(cwd)) {
|
|
1488
|
+
throw new Error(`ACP cwd must be absolute: ${cwd}`);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
#convertPromptBlocks(blocks: PromptRequest["prompt"]): { text: string; images: AgentImageContent[] } {
|
|
1493
|
+
const textParts: string[] = [];
|
|
1494
|
+
const images: AgentImageContent[] = [];
|
|
1495
|
+
for (const block of blocks) {
|
|
1496
|
+
switch (block.type) {
|
|
1497
|
+
case "text":
|
|
1498
|
+
textParts.push(block.text);
|
|
1499
|
+
break;
|
|
1500
|
+
case "image":
|
|
1501
|
+
images.push({ type: "image", data: block.data, mimeType: block.mimeType });
|
|
1502
|
+
break;
|
|
1503
|
+
case "resource":
|
|
1504
|
+
if ("text" in block.resource) {
|
|
1505
|
+
textParts.push(block.resource.text);
|
|
1506
|
+
} else if (typeof block.resource.mimeType === "string" && block.resource.mimeType.startsWith("image/")) {
|
|
1507
|
+
// `embeddedContext: true` covers both text and blob resources, but
|
|
1508
|
+
// blobs aren't directly consumable by the LLM. Route image blobs
|
|
1509
|
+
// to the images array so the user's intent survives; everything
|
|
1510
|
+
// else falls back to the URI placeholder below.
|
|
1511
|
+
images.push({ type: "image", data: block.resource.blob, mimeType: block.resource.mimeType });
|
|
1512
|
+
} else {
|
|
1513
|
+
textParts.push(`[embedded resource: ${block.resource.uri}]`);
|
|
1514
|
+
}
|
|
1515
|
+
break;
|
|
1516
|
+
case "resource_link":
|
|
1517
|
+
textParts.push(block.title ?? block.name ?? block.uri);
|
|
1518
|
+
break;
|
|
1519
|
+
case "audio":
|
|
1520
|
+
textParts.push("[audio omitted]");
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
return {
|
|
1525
|
+
text: textParts.join("\n\n").trim(),
|
|
1526
|
+
images,
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
async #pushConfigOptionUpdate(record: ManagedSessionRecord): Promise<void> {
|
|
1531
|
+
await this.#pushConfigOptionUpdateForSession(record.session);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
async #pushConfigOptionUpdateForSession(session: AgentSession): Promise<void> {
|
|
1535
|
+
await this.#connection.sessionUpdate({
|
|
1536
|
+
sessionId: session.sessionId,
|
|
1537
|
+
update: {
|
|
1538
|
+
sessionUpdate: "config_option_update",
|
|
1539
|
+
configOptions: this.#buildConfigOptions(session),
|
|
1540
|
+
},
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
#buildConfigOptions(session: AgentSession): SessionConfigOption[] {
|
|
1545
|
+
const currentModeId = this.#getCurrentModeId(session);
|
|
1546
|
+
const modeOptions = this.#getAvailableModes(session).map(mode => ({
|
|
1547
|
+
value: mode.id,
|
|
1548
|
+
name: mode.name,
|
|
1549
|
+
description: mode.description,
|
|
1550
|
+
}));
|
|
1551
|
+
const configOptions: SessionConfigOption[] = [
|
|
1552
|
+
{
|
|
1553
|
+
id: MODE_CONFIG_ID,
|
|
1554
|
+
name: "Mode",
|
|
1555
|
+
category: "mode",
|
|
1556
|
+
type: "select",
|
|
1557
|
+
currentValue: currentModeId,
|
|
1558
|
+
options: modeOptions,
|
|
1559
|
+
},
|
|
1560
|
+
];
|
|
1561
|
+
|
|
1562
|
+
const models = session.getAvailableModels();
|
|
1563
|
+
const currentModel = session.model;
|
|
1564
|
+
if (models.length > 0) {
|
|
1565
|
+
configOptions.push({
|
|
1566
|
+
id: MODEL_CONFIG_ID,
|
|
1567
|
+
name: "Model",
|
|
1568
|
+
category: "model",
|
|
1569
|
+
type: "select",
|
|
1570
|
+
currentValue: currentModel ? this.#toModelId(currentModel) : this.#toModelId(models[0]),
|
|
1571
|
+
options: models.map(model => ({
|
|
1572
|
+
value: this.#toModelId(model),
|
|
1573
|
+
name: model.name,
|
|
1574
|
+
description: `${model.provider}/${model.id}`,
|
|
1575
|
+
})),
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
configOptions.push({
|
|
1580
|
+
id: THINKING_CONFIG_ID,
|
|
1581
|
+
name: "Thinking",
|
|
1582
|
+
category: "thought_level",
|
|
1583
|
+
type: "select",
|
|
1584
|
+
currentValue: this.#toThinkingConfigValue(
|
|
1585
|
+
session.model?.reasoning ? this.#getConfiguredThinkingLevel(session) : undefined,
|
|
1586
|
+
),
|
|
1587
|
+
options: this.#buildThinkingOptions(session),
|
|
1588
|
+
});
|
|
1589
|
+
return configOptions;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
#buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
|
|
1593
|
+
return [
|
|
1594
|
+
{ value: THINKING_OFF, name: "Off" },
|
|
1595
|
+
{ value: AUTO_THINKING, name: "Auto", description: "Auto-detect per prompt" },
|
|
1596
|
+
...session.getAvailableThinkingLevels().map(level => ({
|
|
1597
|
+
value: level,
|
|
1598
|
+
name: level,
|
|
1599
|
+
})),
|
|
1600
|
+
];
|
|
1601
|
+
}
|
|
1602
|
+
#getConfiguredThinkingLevel(session: AgentSession): string | undefined {
|
|
1603
|
+
const configuredThinkingLevel = (session as { configuredThinkingLevel?: () => string | undefined })
|
|
1604
|
+
.configuredThinkingLevel;
|
|
1605
|
+
return typeof configuredThinkingLevel === "function"
|
|
1606
|
+
? configuredThinkingLevel.call(session)
|
|
1607
|
+
: session.thinkingLevel;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
#toThinkingConfigValue(value: string | undefined): string {
|
|
1611
|
+
return value && value !== "inherit" ? value : THINKING_OFF;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
async #setModelById(session: AgentSession, modelId: string): Promise<void> {
|
|
1615
|
+
const model = session.getAvailableModels().find(candidate => this.#toModelId(candidate) === modelId);
|
|
1616
|
+
if (!model) {
|
|
1617
|
+
throw new Error(`Unknown ACP model: ${modelId}`);
|
|
1618
|
+
}
|
|
1619
|
+
await session.setModel(model);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
#setThinkingLevelById(session: AgentSession, value: string): void {
|
|
1623
|
+
const thinkingLevel = parseConfiguredThinkingLevel(value);
|
|
1624
|
+
if (!thinkingLevel) {
|
|
1625
|
+
throw new Error(`Unknown ACP thinking level: ${value}`);
|
|
1626
|
+
}
|
|
1627
|
+
session.setThinkingLevel(thinkingLevel);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
#toModelId(model: Model): string {
|
|
1631
|
+
return `${model.provider}/${model.id}`;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
#getAvailableModes(session: AgentSession): Array<{ id: string; name: string; description: string }> {
|
|
1635
|
+
const modes = [{ id: ACP_DEFAULT_MODE_ID, name: "Default", description: "Standard ACP headless mode" }];
|
|
1636
|
+
if (session.settings.get("plan.enabled")) {
|
|
1637
|
+
modes.push({
|
|
1638
|
+
id: ACP_PLAN_MODE_ID,
|
|
1639
|
+
name: "Plan",
|
|
1640
|
+
description: "Read-only planning mode that drafts a plan to a markdown file before any code changes",
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
void session;
|
|
1644
|
+
return modes;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
#getCurrentModeId(session: AgentSession): string {
|
|
1648
|
+
return session.getPlanModeState()?.enabled ? ACP_PLAN_MODE_ID : ACP_DEFAULT_MODE_ID;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
#applyModeChange(session: AgentSession, modeId: string): void {
|
|
1652
|
+
const availableModes = this.#getAvailableModes(session);
|
|
1653
|
+
if (!availableModes.some(mode => mode.id === modeId)) {
|
|
1654
|
+
throw new Error(`Unsupported ACP mode: ${modeId}`);
|
|
1655
|
+
}
|
|
1656
|
+
if (modeId === ACP_PLAN_MODE_ID) {
|
|
1657
|
+
const previous = session.getPlanModeState();
|
|
1658
|
+
session.setPlanModeState({
|
|
1659
|
+
enabled: true,
|
|
1660
|
+
planFilePath: previous?.planFilePath ?? DEFAULT_PLAN_FILE_URL,
|
|
1661
|
+
workflow: previous?.workflow ?? "parallel",
|
|
1662
|
+
reentry: previous !== undefined,
|
|
1663
|
+
});
|
|
1664
|
+
// Mirror `InteractiveMode.#enterPlanMode`: register the plan-proposal
|
|
1665
|
+
// handler that consumes `xd://propose` writes from plan mode. Without
|
|
1666
|
+
// this, proposal dispatch falls through and plan mode has no approval
|
|
1667
|
+
// path (issue #1869).
|
|
1668
|
+
session.setPlanProposalHandler?.(title => this.#handleAcpPlanProposal(session, title));
|
|
1669
|
+
} else {
|
|
1670
|
+
session.setPlanProposalHandler?.(null);
|
|
1671
|
+
session.setPlanModeState(undefined);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/**
|
|
1676
|
+
* Plan-proposal handler installed while ACP plan mode is active. The agent
|
|
1677
|
+
* submits the finalized plan by writing its `<slug>`/title to
|
|
1678
|
+
* `xd://propose`; this handler validates the plan file, normalizes the
|
|
1679
|
+
* title, asks the ACP client to confirm (via `unstable_createElicitation`
|
|
1680
|
+
* when supported), and on approval keeps the chosen plan path, exits plan
|
|
1681
|
+
* mode, and notifies the client so the agent regains full tools.
|
|
1682
|
+
*
|
|
1683
|
+
* Mirrors `InteractiveMode.#handlePlanProposal` for the parts the agent sees
|
|
1684
|
+
* (same `PlanApprovalDetails` shape). Clients without form-mode elicitation
|
|
1685
|
+
* get an auto-approve so plan mode is never stranded — the agent always has
|
|
1686
|
+
* a way out.
|
|
1687
|
+
*/
|
|
1688
|
+
async #handleAcpPlanProposal(session: AgentSession, title: string): Promise<AgentToolResult<unknown>> {
|
|
1689
|
+
const state = session.getPlanModeState();
|
|
1690
|
+
if (!state?.enabled) {
|
|
1691
|
+
throw new ToolError("Plan mode is not active.");
|
|
1692
|
+
}
|
|
1693
|
+
const {
|
|
1694
|
+
planFilePath,
|
|
1695
|
+
planContent,
|
|
1696
|
+
title: resolvedTitle,
|
|
1697
|
+
} = await resolveApprovedPlan({
|
|
1698
|
+
suppliedTitle: title,
|
|
1699
|
+
statePlanFilePath: state.planFilePath,
|
|
1700
|
+
readPlan: url => this.#readAcpPlanFile(session, url),
|
|
1701
|
+
listPlanFiles: () => this.#listAcpLocalPlanFiles(session),
|
|
1702
|
+
});
|
|
1703
|
+
const approved = await this.#requestAcpPlanApprovalChoice(session.sessionId, resolvedTitle, planContent);
|
|
1704
|
+
const details: PlanApprovalDetails = {
|
|
1705
|
+
planFilePath,
|
|
1706
|
+
title: resolvedTitle,
|
|
1707
|
+
planExists: true,
|
|
1708
|
+
};
|
|
1709
|
+
if (!approved) {
|
|
1710
|
+
// Rejection keeps plan mode active for another planning turn. Promote the
|
|
1711
|
+
// reviewed path into plan-mode state so the next `#buildPlanModeMessage()`
|
|
1712
|
+
// targets the plan just reviewed, not the stale state path.
|
|
1713
|
+
if (state.planFilePath !== planFilePath) {
|
|
1714
|
+
session.setPlanModeState({ ...state, planFilePath });
|
|
1715
|
+
}
|
|
1716
|
+
const normalizedTitle = normalizePlanTitle(resolvedTitle).title;
|
|
1717
|
+
return {
|
|
1718
|
+
content: [
|
|
1719
|
+
{
|
|
1720
|
+
type: "text" as const,
|
|
1721
|
+
text: `Plan refinement requested. Update the plan file, then write ${normalizedTitle} to xd://propose again when ready.`,
|
|
1722
|
+
},
|
|
1723
|
+
],
|
|
1724
|
+
details,
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
// Approved. Set the plan reference so the next turn injects the plan
|
|
1728
|
+
// content as context (the file keeps its agent-chosen name — no rename),
|
|
1729
|
+
// then exit plan mode so the agent regains full tools.
|
|
1730
|
+
session.setPlanReferencePath(planFilePath);
|
|
1731
|
+
session.setPlanProposalHandler?.(null);
|
|
1732
|
+
session.setPlanModeState(undefined);
|
|
1733
|
+
try {
|
|
1734
|
+
await this.#connection.sessionUpdate({
|
|
1735
|
+
sessionId: session.sessionId,
|
|
1736
|
+
update: this.#buildCurrentModeUpdate(session),
|
|
1737
|
+
});
|
|
1738
|
+
await this.#pushConfigOptionUpdateForSession(session);
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
logger.warn("Failed to emit mode updates after plan approval", {
|
|
1741
|
+
sessionId: session.sessionId,
|
|
1742
|
+
error,
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
return {
|
|
1746
|
+
content: [
|
|
1747
|
+
{
|
|
1748
|
+
type: "text" as const,
|
|
1749
|
+
text: `Plan approved at ${planFilePath}. Plan mode exited; proceed with the implementation.`,
|
|
1750
|
+
},
|
|
1751
|
+
],
|
|
1752
|
+
details,
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
#resolveAcpPlanFilePath(session: AgentSession, planFilePath: string): string {
|
|
1757
|
+
if (planFilePath.startsWith("local:")) {
|
|
1758
|
+
const normalized = normalizeLocalScheme(planFilePath);
|
|
1759
|
+
return resolveLocalUrlToPath(normalized, {
|
|
1760
|
+
getArtifactsDir: () => session.sessionManager.getArtifactsDir(),
|
|
1761
|
+
getSessionId: () => session.sessionManager.getSessionId(),
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
return path.resolve(session.sessionManager.getCwd(), planFilePath);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
async #readAcpPlanFile(session: AgentSession, planFilePath: string): Promise<string | null> {
|
|
1768
|
+
const resolvedPath = this.#resolveAcpPlanFilePath(session, planFilePath);
|
|
1769
|
+
try {
|
|
1770
|
+
return await Bun.file(resolvedPath).text();
|
|
1771
|
+
} catch (error) {
|
|
1772
|
+
if (isEnoent(error)) {
|
|
1773
|
+
return null;
|
|
1774
|
+
}
|
|
1775
|
+
throw error;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
/** `local://` URLs of plan files in the session-local root, newest first —
|
|
1780
|
+
* the `resolveApprovedPlan` fallback for a dropped `extra.title`. */
|
|
1781
|
+
async #listAcpLocalPlanFiles(session: AgentSession): Promise<string[]> {
|
|
1782
|
+
const localRoot = this.#resolveAcpPlanFilePath(session, "local://");
|
|
1783
|
+
try {
|
|
1784
|
+
const entries = await fs.readdir(localRoot, { withFileTypes: true });
|
|
1785
|
+
const plans = await Promise.all(
|
|
1786
|
+
entries
|
|
1787
|
+
.filter(entry => entry.isFile() && /plan\.md$/i.test(entry.name))
|
|
1788
|
+
.map(async entry => {
|
|
1789
|
+
const stat = await fs.stat(path.join(localRoot, entry.name)).catch(() => null);
|
|
1790
|
+
return { url: `local://${entry.name}`, mtime: stat?.mtimeMs ?? 0 };
|
|
1791
|
+
}),
|
|
1792
|
+
);
|
|
1793
|
+
return plans.sort((a, b) => b.mtime - a.mtime).map(plan => plan.url);
|
|
1794
|
+
} catch {
|
|
1795
|
+
return [];
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
/**
|
|
1800
|
+
* Ask the ACP client to confirm plan approval. Returns `true` only on an
|
|
1801
|
+
* explicit `APPROVE_OPTION` selection. Refine, dismissal (`undefined`), or
|
|
1802
|
+
* any unrecognized value falls through to refine semantics — the caller
|
|
1803
|
+
* keeps plan mode active and surfaces guidance text to the agent. Clients
|
|
1804
|
+
* without `elicitation.form` support auto-approve because there is no
|
|
1805
|
+
* confirmation surface available; without that, plan mode would strand
|
|
1806
|
+
* the agent (the bug this method exists to fix).
|
|
1807
|
+
*/
|
|
1808
|
+
async #requestAcpPlanApprovalChoice(sessionId: string, title: string, planContent: string): Promise<boolean> {
|
|
1809
|
+
const supportsForm = this.#clientCapabilities?.elicitation?.form != null;
|
|
1810
|
+
if (!supportsForm) return true;
|
|
1811
|
+
// Include a short preview of the plan so the user has context in the
|
|
1812
|
+
// dialog. Keep the body bounded — Zed renders elicitation messages
|
|
1813
|
+
// inline and a multi-thousand-line plan blows out the dialog.
|
|
1814
|
+
const previewLines = planContent.split("\n").slice(0, 12).join("\n");
|
|
1815
|
+
const ellipsis = planContent.split("\n").length > 12 ? "\n…" : "";
|
|
1816
|
+
const message = `Approve plan "${title}" and start implementation?\n\n${previewLines}${ellipsis}`;
|
|
1817
|
+
const value = await elicitFromAcpClient(
|
|
1818
|
+
this.#connection,
|
|
1819
|
+
sessionId,
|
|
1820
|
+
"select",
|
|
1821
|
+
message,
|
|
1822
|
+
{ type: "string", enum: [APPROVE_OPTION, REFINE_OPTION] },
|
|
1823
|
+
undefined,
|
|
1824
|
+
);
|
|
1825
|
+
// Approve ONLY on the explicit approve selection. Dismissal, cancel,
|
|
1826
|
+
// timeout, or any other non-approve response falls through to refine
|
|
1827
|
+
// semantics so closing the dialog can never grant write access.
|
|
1828
|
+
return value === APPROVE_OPTION;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
#buildModeState(session: AgentSession): SessionModeState {
|
|
1832
|
+
return {
|
|
1833
|
+
availableModes: this.#getAvailableModes(session),
|
|
1834
|
+
currentModeId: this.#getCurrentModeId(session),
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
#buildCurrentModeUpdate(session: AgentSession): SessionUpdate {
|
|
1839
|
+
return {
|
|
1840
|
+
sessionUpdate: "current_mode_update",
|
|
1841
|
+
currentModeId: this.#getCurrentModeId(session),
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
async #buildAvailableCommands(session: AgentSession): Promise<AvailableCommand[]> {
|
|
1846
|
+
return toAcpAvailableCommands(await buildAvailableSlashCommands(session));
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
#toSessionInfo(session: StoredSessionInfo): SessionInfo {
|
|
1850
|
+
return {
|
|
1851
|
+
sessionId: session.id,
|
|
1852
|
+
cwd: session.cwd,
|
|
1853
|
+
title: session.title,
|
|
1854
|
+
updatedAt: session.modified.toISOString(),
|
|
1855
|
+
_meta: {
|
|
1856
|
+
messageCount: session.messageCount,
|
|
1857
|
+
size: session.size,
|
|
1858
|
+
},
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
#scheduleBootstrapUpdates(sessionId: string): void {
|
|
1863
|
+
// Defer first notifications until the response has reached the client.
|
|
1864
|
+
// Zed's agent-client-protocol reader dispatches responses and
|
|
1865
|
+
// notifications to different async tasks; sending the first
|
|
1866
|
+
// `available_commands_update` from `setTimeout(0)` reliably loses the
|
|
1867
|
+
// race against the response handler and Zed logs `Received session
|
|
1868
|
+
// notification for unknown session` then drops the update — leaving
|
|
1869
|
+
// the slash-command palette empty (#1015 follow-up; see
|
|
1870
|
+
// zed-industries/zed#55965 for the same race biting other ACP agents).
|
|
1871
|
+
// `ACP_BOOTSTRAP_RACE_GUARD_MS` is invisible to the operator and large
|
|
1872
|
+
// enough that the response future has scheduled before our timer fires
|
|
1873
|
+
// on stdio-only transports.
|
|
1874
|
+
//
|
|
1875
|
+
// The session-lifetime subscription is installed inside the same timer
|
|
1876
|
+
// so it shares this guard — without it, an extension's `session_start`
|
|
1877
|
+
// handler (or any async work it schedules) calling `setThinkingLevel`
|
|
1878
|
+
// would push a `config_option_update` for a session id the client
|
|
1879
|
+
// hasn't been told about yet. The pre-bootstrap thinking level is
|
|
1880
|
+
// reported in the response's `configOptions`, so deferring the
|
|
1881
|
+
// notification loses no state.
|
|
1882
|
+
setTimeout(() => {
|
|
1883
|
+
if (this.#connection.signal.aborted) {
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
const record = this.#sessions.get(sessionId);
|
|
1887
|
+
if (!record) {
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
if (!record.lifetimeUnsubscribe) {
|
|
1891
|
+
record.lifetimeUnsubscribe = record.session.subscribe(event => {
|
|
1892
|
+
void this.#handleLifetimeEvent(record, event);
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
void this.#emitBootstrapUpdates(sessionId, record);
|
|
1896
|
+
}, ACP_BOOTSTRAP_RACE_GUARD_MS);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
async #emitBootstrapUpdates(sessionId: string, record: ManagedSessionRecord): Promise<void> {
|
|
1900
|
+
if (this.#sessions.get(sessionId) !== record) {
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
await this.#connection.sessionUpdate({
|
|
1904
|
+
sessionId,
|
|
1905
|
+
update: {
|
|
1906
|
+
sessionUpdate: "available_commands_update",
|
|
1907
|
+
availableCommands: await this.#buildAvailableCommands(record.session),
|
|
1908
|
+
},
|
|
1909
|
+
});
|
|
1910
|
+
await this.#connection.sessionUpdate({
|
|
1911
|
+
sessionId,
|
|
1912
|
+
update: {
|
|
1913
|
+
sessionUpdate: "session_info_update",
|
|
1914
|
+
title: record.session.sessionName,
|
|
1915
|
+
updatedAt: record.session.sessionManager.getHeader()?.timestamp,
|
|
1916
|
+
},
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
async #emitAvailableCommandsUpdate(record: ManagedSessionRecord): Promise<void> {
|
|
1921
|
+
await this.#connection.sessionUpdate({
|
|
1922
|
+
sessionId: record.session.sessionId,
|
|
1923
|
+
update: {
|
|
1924
|
+
sessionUpdate: "available_commands_update",
|
|
1925
|
+
availableCommands: await this.#buildAvailableCommands(record.session),
|
|
1926
|
+
},
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* Reload plugin/registry state for an ACP session. Mirrors the interactive
|
|
1932
|
+
* `/reload-plugins` and `/move` flows: invalidates the plugin-roots cache,
|
|
1933
|
+
* resets the capability cache, refreshes the session's slash-command state,
|
|
1934
|
+
* then re-advertises commands so the client sees newly installed/disabled
|
|
1935
|
+
* plugins.
|
|
1936
|
+
*/
|
|
1937
|
+
async #reloadPluginState(record: ManagedSessionRecord): Promise<void> {
|
|
1938
|
+
const cwd = record.session.sessionManager.getCwd();
|
|
1939
|
+
const projectPath = await resolveActiveProjectRegistryPath(cwd);
|
|
1940
|
+
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
1941
|
+
resetCapabilities();
|
|
1942
|
+
await record.session.refreshSkills();
|
|
1943
|
+
const fileCommands = await loadSlashCommands({ cwd });
|
|
1944
|
+
record.session.setSlashCommands(fileCommands);
|
|
1945
|
+
await this.#emitAvailableCommandsUpdate(record);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
async #emitEndOfTurnUpdates(record: ManagedSessionRecord): Promise<void> {
|
|
1949
|
+
const sessionId = record.session.sessionId;
|
|
1950
|
+
|
|
1951
|
+
const contextUsage = record.session.getContextUsage();
|
|
1952
|
+
if (contextUsage) {
|
|
1953
|
+
const usageStats = record.session.sessionManager.getUsageStatistics();
|
|
1954
|
+
await this.#connection.sessionUpdate({
|
|
1955
|
+
sessionId,
|
|
1956
|
+
update: {
|
|
1957
|
+
sessionUpdate: "usage_update",
|
|
1958
|
+
size: contextUsage.contextWindow,
|
|
1959
|
+
used: contextUsage.tokens ?? 0,
|
|
1960
|
+
cost: usageStats.cost > 0 ? { amount: usageStats.cost, currency: "USD" } : undefined,
|
|
1961
|
+
},
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
await this.#connection.sessionUpdate({
|
|
1966
|
+
sessionId,
|
|
1967
|
+
update: {
|
|
1968
|
+
sessionUpdate: "session_info_update",
|
|
1969
|
+
title: record.session.sessionName,
|
|
1970
|
+
updatedAt: new Date().toISOString(),
|
|
1971
|
+
},
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
#cloneUsageStatistics(usage: UsageStatistics): UsageStatistics {
|
|
1976
|
+
return {
|
|
1977
|
+
input: usage.input,
|
|
1978
|
+
output: usage.output,
|
|
1979
|
+
cacheRead: usage.cacheRead,
|
|
1980
|
+
cacheWrite: usage.cacheWrite,
|
|
1981
|
+
totalTokens: usage.totalTokens,
|
|
1982
|
+
orchestrationInput: usage.orchestrationInput,
|
|
1983
|
+
orchestrationOutput: usage.orchestrationOutput,
|
|
1984
|
+
orchestrationCacheRead: usage.orchestrationCacheRead,
|
|
1985
|
+
premiumRequests: usage.premiumRequests,
|
|
1986
|
+
cost: usage.cost,
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
#buildTurnUsage(previous: UsageStatistics, current: UsageStatistics): Usage | undefined {
|
|
1991
|
+
const inputTokens = Math.max(0, current.input - previous.input);
|
|
1992
|
+
const outputTokens = Math.max(0, current.output - previous.output);
|
|
1993
|
+
const cachedReadTokens = Math.max(0, current.cacheRead - previous.cacheRead);
|
|
1994
|
+
const cachedWriteTokens = Math.max(0, current.cacheWrite - previous.cacheWrite);
|
|
1995
|
+
const totalTokens = Math.max(0, current.totalTokens - previous.totalTokens);
|
|
1996
|
+
|
|
1997
|
+
if (totalTokens === 0) {
|
|
1998
|
+
return undefined;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
const usage: Usage = {
|
|
2002
|
+
inputTokens,
|
|
2003
|
+
outputTokens,
|
|
2004
|
+
totalTokens,
|
|
2005
|
+
};
|
|
2006
|
+
if (cachedReadTokens > 0) {
|
|
2007
|
+
usage.cachedReadTokens = cachedReadTokens;
|
|
2008
|
+
}
|
|
2009
|
+
if (cachedWriteTokens > 0) {
|
|
2010
|
+
usage.cachedWriteTokens = cachedWriteTokens;
|
|
2011
|
+
}
|
|
2012
|
+
return usage;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
async #listStoredSessions(cwd?: string): Promise<StoredSessionInfo[]> {
|
|
2016
|
+
const sessions = cwd ? await SessionManager.list(cwd) : await SessionManager.listAll();
|
|
2017
|
+
return sessions.sort((left, right) => right.modified.getTime() - left.modified.getTime());
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
async #findStoredSession(sessionId: string, cwd: string): Promise<StoredSessionInfo | undefined> {
|
|
2021
|
+
const sessions = await this.#listStoredSessions(cwd);
|
|
2022
|
+
return sessions.find(session => session.id === sessionId);
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
async #findStoredSessionById(sessionId: string): Promise<StoredSessionInfo | undefined> {
|
|
2026
|
+
const sessions = await this.#listStoredSessions();
|
|
2027
|
+
return sessions.find(session => session.id === sessionId);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
#parseCursor(cursor: string | undefined): number {
|
|
2031
|
+
if (!cursor) {
|
|
2032
|
+
return 0;
|
|
2033
|
+
}
|
|
2034
|
+
const parsed = Number.parseInt(cursor, 10);
|
|
2035
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
2036
|
+
throw new Error(`Invalid ACP session cursor: ${cursor}`);
|
|
2037
|
+
}
|
|
2038
|
+
return parsed;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
async #replaySessionHistory(record: ManagedSessionRecord): Promise<void> {
|
|
2042
|
+
const cwd = record.session.sessionManager.getCwd();
|
|
2043
|
+
const replayedToolCallIds = new Set<string>();
|
|
2044
|
+
const replayedToolCallArgs = new Map<string, unknown>();
|
|
2045
|
+
for (const message of record.session.sessionManager.buildSessionContext().messages as ReplayableMessage[]) {
|
|
2046
|
+
for (const notification of this.#messageToReplayNotifications(
|
|
2047
|
+
record.session.sessionId,
|
|
2048
|
+
message,
|
|
2049
|
+
cwd,
|
|
2050
|
+
replayedToolCallIds,
|
|
2051
|
+
replayedToolCallArgs,
|
|
2052
|
+
)) {
|
|
2053
|
+
await this.#connection.sessionUpdate(notification);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
#messageToReplayNotifications(
|
|
2059
|
+
sessionId: string,
|
|
2060
|
+
message: ReplayableMessage,
|
|
2061
|
+
cwd: string,
|
|
2062
|
+
replayedToolCallIds: Set<string>,
|
|
2063
|
+
replayedToolCallArgs: Map<string, unknown>,
|
|
2064
|
+
): SessionNotification[] {
|
|
2065
|
+
if (message.role === "assistant") {
|
|
2066
|
+
return this.#replayAssistantMessage(sessionId, message, cwd, replayedToolCallIds, replayedToolCallArgs);
|
|
2067
|
+
}
|
|
2068
|
+
if (
|
|
2069
|
+
message.role === "user" ||
|
|
2070
|
+
message.role === "developer" ||
|
|
2071
|
+
message.role === "custom" ||
|
|
2072
|
+
message.role === "hookMessage"
|
|
2073
|
+
) {
|
|
2074
|
+
return this.#wrapReplayContent(
|
|
2075
|
+
sessionId,
|
|
2076
|
+
this.#extractReplayContent(message.content, undefined),
|
|
2077
|
+
"user_message_chunk",
|
|
2078
|
+
crypto.randomUUID(),
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
if (
|
|
2082
|
+
message.role === "toolResult" &&
|
|
2083
|
+
typeof message.toolCallId === "string" &&
|
|
2084
|
+
typeof message.toolName === "string"
|
|
2085
|
+
) {
|
|
2086
|
+
return this.#replayToolResult(
|
|
2087
|
+
sessionId,
|
|
2088
|
+
cwd,
|
|
2089
|
+
{
|
|
2090
|
+
...message,
|
|
2091
|
+
toolCallId: message.toolCallId,
|
|
2092
|
+
toolName: message.toolName,
|
|
2093
|
+
},
|
|
2094
|
+
{
|
|
2095
|
+
includeStart: !replayedToolCallIds.has(message.toolCallId),
|
|
2096
|
+
toolArgs: replayedToolCallArgs.get(message.toolCallId),
|
|
2097
|
+
},
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
if (
|
|
2101
|
+
message.role === "bashExecution" ||
|
|
2102
|
+
message.role === "pythonExecution" ||
|
|
2103
|
+
message.role === "compactionSummary"
|
|
2104
|
+
) {
|
|
2105
|
+
return this.#wrapReplayContent(
|
|
2106
|
+
sessionId,
|
|
2107
|
+
this.#extractReplayContent(message.content, undefined),
|
|
2108
|
+
"user_message_chunk",
|
|
2109
|
+
crypto.randomUUID(),
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
return [];
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
#replayAssistantMessage(
|
|
2116
|
+
sessionId: string,
|
|
2117
|
+
message: ReplayableMessage,
|
|
2118
|
+
cwd: string,
|
|
2119
|
+
replayedToolCallIds: Set<string>,
|
|
2120
|
+
replayedToolCallArgs: Map<string, unknown>,
|
|
2121
|
+
): SessionNotification[] {
|
|
2122
|
+
const notifications: SessionNotification[] = [];
|
|
2123
|
+
const messageId = crypto.randomUUID();
|
|
2124
|
+
if (Array.isArray(message.content)) {
|
|
2125
|
+
for (const item of message.content) {
|
|
2126
|
+
if (typeof item !== "object" || item === null || !("type" in item)) {
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
|
|
2130
|
+
notifications.push({
|
|
2131
|
+
sessionId,
|
|
2132
|
+
update: {
|
|
2133
|
+
sessionUpdate: "agent_message_chunk",
|
|
2134
|
+
content: { type: "text", text: item.text },
|
|
2135
|
+
messageId,
|
|
2136
|
+
},
|
|
2137
|
+
});
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
if (
|
|
2141
|
+
item.type === "image" &&
|
|
2142
|
+
"data" in item &&
|
|
2143
|
+
typeof item.data === "string" &&
|
|
2144
|
+
"mimeType" in item &&
|
|
2145
|
+
typeof item.mimeType === "string"
|
|
2146
|
+
) {
|
|
2147
|
+
notifications.push({
|
|
2148
|
+
sessionId,
|
|
2149
|
+
update: {
|
|
2150
|
+
sessionUpdate: "agent_message_chunk",
|
|
2151
|
+
content: { type: "image", data: item.data, mimeType: item.mimeType },
|
|
2152
|
+
messageId,
|
|
2153
|
+
},
|
|
2154
|
+
});
|
|
2155
|
+
continue;
|
|
2156
|
+
}
|
|
2157
|
+
if (item.type === "thinking" && "thinking" in item && typeof item.thinking === "string") {
|
|
2158
|
+
const thinking = canonicalizeMessage(item.thinking);
|
|
2159
|
+
if (thinking.length === 0) continue;
|
|
2160
|
+
notifications.push({
|
|
2161
|
+
sessionId,
|
|
2162
|
+
update: {
|
|
2163
|
+
sessionUpdate: "agent_thought_chunk",
|
|
2164
|
+
content: { type: "text", text: thinking },
|
|
2165
|
+
messageId,
|
|
2166
|
+
},
|
|
2167
|
+
});
|
|
2168
|
+
continue;
|
|
2169
|
+
}
|
|
2170
|
+
const toolItem = item as ReplayableToolItem;
|
|
2171
|
+
if (
|
|
2172
|
+
(toolItem.type === "toolCall" || toolItem.type === "tool_use") &&
|
|
2173
|
+
typeof toolItem.id === "string" &&
|
|
2174
|
+
typeof toolItem.name === "string"
|
|
2175
|
+
) {
|
|
2176
|
+
const args = this.#buildReplayAssistantToolArgs(toolItem);
|
|
2177
|
+
notifications.push(
|
|
2178
|
+
...mapAgentSessionEventToAcpSessionUpdates(
|
|
2179
|
+
{
|
|
2180
|
+
type: "tool_execution_start",
|
|
2181
|
+
toolCallId: toolItem.id,
|
|
2182
|
+
toolName: toolItem.name,
|
|
2183
|
+
args,
|
|
2184
|
+
},
|
|
2185
|
+
sessionId,
|
|
2186
|
+
{ cwd },
|
|
2187
|
+
),
|
|
2188
|
+
);
|
|
2189
|
+
replayedToolCallIds.add(toolItem.id);
|
|
2190
|
+
replayedToolCallArgs.set(toolItem.id, args);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
if (notifications.length === 0 && message.errorMessage && !isSilentAbort(message)) {
|
|
2195
|
+
notifications.push({
|
|
2196
|
+
sessionId,
|
|
2197
|
+
update: {
|
|
2198
|
+
sessionUpdate: "agent_message_chunk",
|
|
2199
|
+
content: { type: "text", text: message.errorMessage },
|
|
2200
|
+
messageId,
|
|
2201
|
+
},
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
return notifications;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
#buildReplayAssistantToolArgs(item: ReplayableToolItem): unknown {
|
|
2208
|
+
if ("arguments" in item) {
|
|
2209
|
+
return normalizeReplayToolArguments(item.arguments).args;
|
|
2210
|
+
}
|
|
2211
|
+
if (item.type === "tool_use" && "input" in item) {
|
|
2212
|
+
return item.input;
|
|
2213
|
+
}
|
|
2214
|
+
return {};
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
#replayToolResult(
|
|
2218
|
+
sessionId: string,
|
|
2219
|
+
cwd: string,
|
|
2220
|
+
message: Required<Pick<ReplayableMessage, "toolCallId" | "toolName">> & ReplayableMessage,
|
|
2221
|
+
options: { includeStart?: boolean; toolArgs?: unknown } = {},
|
|
2222
|
+
): SessionNotification[] {
|
|
2223
|
+
const args = this.#buildReplayToolArgs(message.details);
|
|
2224
|
+
const startEvent: AgentSessionEvent = {
|
|
2225
|
+
type: "tool_execution_start",
|
|
2226
|
+
toolCallId: message.toolCallId,
|
|
2227
|
+
toolName: message.toolName,
|
|
2228
|
+
args,
|
|
2229
|
+
};
|
|
2230
|
+
const endEvent: AgentSessionEvent = {
|
|
2231
|
+
type: "tool_execution_end",
|
|
2232
|
+
toolCallId: message.toolCallId,
|
|
2233
|
+
toolName: message.toolName,
|
|
2234
|
+
isError: message.isError === true,
|
|
2235
|
+
result: {
|
|
2236
|
+
content: message.content,
|
|
2237
|
+
details: message.details,
|
|
2238
|
+
errorMessage: message.errorMessage,
|
|
2239
|
+
},
|
|
2240
|
+
};
|
|
2241
|
+
const notifications = mapAgentSessionEventToAcpSessionUpdates(endEvent, sessionId, {
|
|
2242
|
+
cwd,
|
|
2243
|
+
getToolArgs: toolCallId => (toolCallId === message.toolCallId ? options.toolArgs : undefined),
|
|
2244
|
+
resolveImageData: (data, _mimeType) => resolveImageDataSync(this.#blobs, data),
|
|
2245
|
+
});
|
|
2246
|
+
if (options.includeStart === false) {
|
|
2247
|
+
return notifications;
|
|
2248
|
+
}
|
|
2249
|
+
return [...mapAgentSessionEventToAcpSessionUpdates(startEvent, sessionId, { cwd }), ...notifications];
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
#buildReplayToolArgs(details: unknown): { path?: string } {
|
|
2253
|
+
if (typeof details !== "object" || details === null || !("path" in details)) {
|
|
2254
|
+
return {};
|
|
2255
|
+
}
|
|
2256
|
+
const value = (details as { path?: unknown }).path;
|
|
2257
|
+
return typeof value === "string" && value.length > 0 ? { path: value } : {};
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
#wrapReplayContent(
|
|
2261
|
+
sessionId: string,
|
|
2262
|
+
content: PromptRequest["prompt"],
|
|
2263
|
+
kind: "agent_message_chunk" | "user_message_chunk",
|
|
2264
|
+
messageId: string,
|
|
2265
|
+
): SessionNotification[] {
|
|
2266
|
+
return content.map(block => ({
|
|
2267
|
+
sessionId,
|
|
2268
|
+
update: {
|
|
2269
|
+
sessionUpdate: kind,
|
|
2270
|
+
content: block,
|
|
2271
|
+
messageId,
|
|
2272
|
+
},
|
|
2273
|
+
}));
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
#extractReplayContent(content: unknown, errorMessage: string | undefined): PromptRequest["prompt"] {
|
|
2277
|
+
const replay: PromptRequest["prompt"] = [];
|
|
2278
|
+
if (Array.isArray(content)) {
|
|
2279
|
+
for (const item of content) {
|
|
2280
|
+
if (typeof item !== "object" || item === null || !("type" in item)) {
|
|
2281
|
+
continue;
|
|
2282
|
+
}
|
|
2283
|
+
if (item.type === "text" && "text" in item && typeof item.text === "string" && item.text.length > 0) {
|
|
2284
|
+
replay.push({ type: "text", text: item.text });
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2287
|
+
if (
|
|
2288
|
+
item.type === "image" &&
|
|
2289
|
+
"data" in item &&
|
|
2290
|
+
"mimeType" in item &&
|
|
2291
|
+
typeof item.data === "string" &&
|
|
2292
|
+
typeof item.mimeType === "string"
|
|
2293
|
+
) {
|
|
2294
|
+
replay.push({ type: "image", data: item.data, mimeType: item.mimeType });
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
if (replay.length === 0 && errorMessage) {
|
|
2299
|
+
replay.push({ type: "text", text: errorMessage });
|
|
2300
|
+
}
|
|
2301
|
+
return replay;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
async #configureExtensions(record: ManagedSessionRecord): Promise<void> {
|
|
2305
|
+
if (record.extensionsConfigured) {
|
|
2306
|
+
return;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
const uiContext = createAcpExtensionUiContext(
|
|
2310
|
+
this.#connection,
|
|
2311
|
+
() => record.session.sessionId,
|
|
2312
|
+
this.#clientCapabilities,
|
|
2313
|
+
);
|
|
2314
|
+
if (this.#clientCapabilities?.elicitation?.form != null) {
|
|
2315
|
+
record.session.setUsageFallbackConfirmer((confirmation, signal) => {
|
|
2316
|
+
const reserve =
|
|
2317
|
+
confirmation.remainingPercent === undefined
|
|
2318
|
+
? "inside the configured reserve margin"
|
|
2319
|
+
: `${confirmation.remainingPercent.toFixed(1)}% remaining`;
|
|
2320
|
+
return uiContext.confirm(
|
|
2321
|
+
"Coding-plan reserve reached",
|
|
2322
|
+
`${confirmation.from} has ${reserve}. Switch to ${confirmation.to}? Choose No to keep using the current plan.`,
|
|
2323
|
+
{ signal },
|
|
2324
|
+
);
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
const extensionRunner = record.session.extensionRunner;
|
|
2329
|
+
if (!extensionRunner) {
|
|
2330
|
+
record.extensionsConfigured = true;
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
extensionRunner.initialize(
|
|
2335
|
+
{
|
|
2336
|
+
sendMessage: (message, options) => {
|
|
2337
|
+
record.session.sendCustomMessage(message, options).catch((error: unknown) => {
|
|
2338
|
+
logger.warn("ACP extension sendMessage failed", { error });
|
|
2339
|
+
});
|
|
2340
|
+
},
|
|
2341
|
+
sendUserMessage: (content, options) => {
|
|
2342
|
+
this.#trackExtensionUserMessage(record, record.session.sendUserMessage(content, options));
|
|
2343
|
+
},
|
|
2344
|
+
appendEntry: (customType, data) => {
|
|
2345
|
+
record.session.sessionManager.appendCustomEntry(customType, data);
|
|
2346
|
+
},
|
|
2347
|
+
setLabel: (targetId, label) => {
|
|
2348
|
+
record.session.sessionManager.appendLabelChange(targetId, label);
|
|
2349
|
+
},
|
|
2350
|
+
getActiveTools: () => record.session.getEnabledToolNames(),
|
|
2351
|
+
getAllTools: () => record.session.getAllToolInfos(),
|
|
2352
|
+
setActiveTools: toolNames => record.session.setActiveToolsByName(toolNames),
|
|
2353
|
+
getCommands: () => getSessionSlashCommands(record.session),
|
|
2354
|
+
setModel: async model => {
|
|
2355
|
+
const apiKey = await record.session.modelRegistry.getApiKey(model);
|
|
2356
|
+
if (!apiKey) {
|
|
2357
|
+
return false;
|
|
2358
|
+
}
|
|
2359
|
+
await record.session.setModel(model);
|
|
2360
|
+
return true;
|
|
2361
|
+
},
|
|
2362
|
+
getThinkingLevel: () => record.session.thinkingLevel,
|
|
2363
|
+
setThinkingLevel: level => record.session.setThinkingLevel(level),
|
|
2364
|
+
getServiceTiers: () => record.session.serviceTierByFamily,
|
|
2365
|
+
setServiceTier: (family, tier) => record.session.setServiceTierFamily(family, tier),
|
|
2366
|
+
getSessionName: () => record.session.sessionManager.getSessionName(),
|
|
2367
|
+
setSessionName: async name => {
|
|
2368
|
+
await record.session.sessionManager.setSessionName(name, "user");
|
|
2369
|
+
},
|
|
2370
|
+
},
|
|
2371
|
+
{
|
|
2372
|
+
getModel: () => record.session.model,
|
|
2373
|
+
isIdle: () => !record.session.isStreaming,
|
|
2374
|
+
abort: () => {
|
|
2375
|
+
void record.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
2376
|
+
},
|
|
2377
|
+
hasPendingMessages: () => record.session.queuedMessageCount > 0,
|
|
2378
|
+
shutdown: () => {},
|
|
2379
|
+
getContextUsage: () => record.session.getContextUsage(),
|
|
2380
|
+
getSystemPrompt: () => record.session.systemPrompt,
|
|
2381
|
+
compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
|
|
2382
|
+
},
|
|
2383
|
+
{
|
|
2384
|
+
getContextUsage: () => record.session.getContextUsage(),
|
|
2385
|
+
waitForIdle: () => record.session.agent.waitForIdle(),
|
|
2386
|
+
newSession: async options => {
|
|
2387
|
+
const success = await record.session.newSession({ parentSession: options?.parentSession });
|
|
2388
|
+
if (success && options?.setup) {
|
|
2389
|
+
await options.setup(record.session.sessionManager);
|
|
2390
|
+
}
|
|
2391
|
+
return { cancelled: !success };
|
|
2392
|
+
},
|
|
2393
|
+
branch: async entryId => {
|
|
2394
|
+
const result = await record.session.branch(entryId);
|
|
2395
|
+
return { cancelled: result.cancelled };
|
|
2396
|
+
},
|
|
2397
|
+
navigateTree: async (targetId, options) => {
|
|
2398
|
+
const result = await record.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
2399
|
+
return { cancelled: result.cancelled };
|
|
2400
|
+
},
|
|
2401
|
+
switchSession: async sessionPath => {
|
|
2402
|
+
const success = await record.session.switchSession(sessionPath);
|
|
2403
|
+
return { cancelled: !success };
|
|
2404
|
+
},
|
|
2405
|
+
reload: async () => {
|
|
2406
|
+
await record.session.reload();
|
|
2407
|
+
},
|
|
2408
|
+
compact: instructionsOrOptions => runExtensionCompact(record.session, instructionsOrOptions),
|
|
2409
|
+
},
|
|
2410
|
+
uiContext,
|
|
2411
|
+
);
|
|
2412
|
+
await extensionRunner.emit({ type: "session_start" });
|
|
2413
|
+
record.extensionsConfigured = true;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
async #configureMcpServers(record: ManagedSessionRecord, servers: McpServer[]): Promise<void> {
|
|
2417
|
+
if (record.mcpManager) {
|
|
2418
|
+
await record.mcpManager.disconnectAll();
|
|
2419
|
+
}
|
|
2420
|
+
// Drain any in-flight refresh queued by a previous configuration: a refresh
|
|
2421
|
+
// that already passed its manager guard could otherwise finish applying a
|
|
2422
|
+
// stale tool set after this reconfiguration installs the new one.
|
|
2423
|
+
await record.mcpRefreshChain;
|
|
2424
|
+
record.mcpRefreshChain = undefined;
|
|
2425
|
+
if (servers.length === 0) {
|
|
2426
|
+
record.mcpManager = undefined;
|
|
2427
|
+
await record.session.refreshMCPTools([]);
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
const manager = new MCPManager(record.session.sessionManager.getCwd());
|
|
2432
|
+
// MCP servers connect and reconnect independently, so `onToolsChanged` can fire
|
|
2433
|
+
// several times back to back. Each firing is chained onto `record.mcpRefreshChain`
|
|
2434
|
+
// so refreshes apply in order, and each one re-reads `manager.getTools()` at the
|
|
2435
|
+
// time it actually runs rather than the snapshot from when it was queued — so a
|
|
2436
|
+
// refresh can never apply a stale, smaller tool set after a newer one already landed.
|
|
2437
|
+
// The returned promise propagates failures (the initial awaited refresh below must
|
|
2438
|
+
// fail session setup, as the pre-queue code did); the stored chain swallows them
|
|
2439
|
+
// after logging so background firings only warn and the chain never rejects.
|
|
2440
|
+
const enqueueMcpToolsRefresh = (): Promise<void> => {
|
|
2441
|
+
const run = (record.mcpRefreshChain ?? Promise.resolve()).then(async () => {
|
|
2442
|
+
if (record.mcpManager !== manager) return;
|
|
2443
|
+
await record.session.refreshMCPTools(manager.getTools());
|
|
2444
|
+
});
|
|
2445
|
+
record.mcpRefreshChain = run.catch(error => {
|
|
2446
|
+
logger.warn("ACP MCP tool refresh failed", {
|
|
2447
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2448
|
+
});
|
|
2449
|
+
});
|
|
2450
|
+
return run;
|
|
2451
|
+
};
|
|
2452
|
+
manager.setOnToolsChanged(() => {
|
|
2453
|
+
// Failures are logged once via the stored chain's catch above.
|
|
2454
|
+
enqueueMcpToolsRefresh().catch(() => {});
|
|
2455
|
+
});
|
|
2456
|
+
const configs: MCPConfigMap = {};
|
|
2457
|
+
const sources: MCPSourceMap = {};
|
|
2458
|
+
for (const server of servers) {
|
|
2459
|
+
configs[server.name] = this.#toMcpConfig(server);
|
|
2460
|
+
sources[server.name] = {
|
|
2461
|
+
provider: "acp",
|
|
2462
|
+
providerName: "ACP Client",
|
|
2463
|
+
path: `acp://${server.name}`,
|
|
2464
|
+
level: "project",
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
const result = await manager.connectServers(configs, sources);
|
|
2469
|
+
if (result.errors.size > 0) {
|
|
2470
|
+
throw new Error(
|
|
2471
|
+
Array.from(result.errors.entries())
|
|
2472
|
+
.map(([name, message]) => `${name}: ${message}`)
|
|
2473
|
+
.join("; "),
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
record.mcpManager = manager;
|
|
2478
|
+
await enqueueMcpToolsRefresh();
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
#toMcpConfig(server: McpServer): MCPServerConfig {
|
|
2482
|
+
if ("command" in server) {
|
|
2483
|
+
return {
|
|
2484
|
+
type: "stdio",
|
|
2485
|
+
command: server.command,
|
|
2486
|
+
args: server.args,
|
|
2487
|
+
env: this.#toNameValueMap(server.env),
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
if (server.type === "http") {
|
|
2491
|
+
return {
|
|
2492
|
+
type: "http",
|
|
2493
|
+
url: server.url,
|
|
2494
|
+
headers: this.#toNameValueMap(server.headers),
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
if (server.type === "sse") {
|
|
2498
|
+
return {
|
|
2499
|
+
type: "sse",
|
|
2500
|
+
url: server.url,
|
|
2501
|
+
headers: this.#toNameValueMap(server.headers),
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
// The experimental ACP-channel transport (`type: "acp"`) is not advertised in
|
|
2505
|
+
// `mcpCapabilities`, so a spec-compliant client never sends it; reject defensively.
|
|
2506
|
+
throw new Error(`Unsupported MCP server transport: ${server.type}`);
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
#toNameValueMap(values: Array<{ name: string; value: string }>): { [name: string]: string } {
|
|
2510
|
+
const mapped: { [name: string]: string } = {};
|
|
2511
|
+
for (const value of values) {
|
|
2512
|
+
mapped[value.name] = value.value;
|
|
2513
|
+
}
|
|
2514
|
+
return mapped;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
async #closeManagedSession(sessionId: string, record: ManagedSessionRecord): Promise<void> {
|
|
2518
|
+
record.closedError ??= this.#createPromptLifecycleError("ACP session closed before queued prompt could run");
|
|
2519
|
+
this.#sessions.delete(sessionId);
|
|
2520
|
+
await this.#cancelPromptForClose(record);
|
|
2521
|
+
await this.#disposeSessionRecord(record);
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
async #cancelPromptForClose(record: ManagedSessionRecord): Promise<void> {
|
|
2525
|
+
const promptTurn = record.promptTurn;
|
|
2526
|
+
if (!isPromptTurnInFlight(promptTurn)) {
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
const cleanup = promptTurn.cleanup ?? this.#beginCancelCleanup(record, promptTurn);
|
|
2530
|
+
try {
|
|
2531
|
+
await cleanup;
|
|
2532
|
+
} catch (error) {
|
|
2533
|
+
logger.warn("Failed to abort ACP prompt during session close", { error });
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
async #disposeSessionRecord(record: ManagedSessionRecord, reason?: postmortem.Reason): Promise<void> {
|
|
2538
|
+
record.lifetimeUnsubscribe?.();
|
|
2539
|
+
if (record.mcpManager) {
|
|
2540
|
+
try {
|
|
2541
|
+
await record.mcpManager.disconnectAll();
|
|
2542
|
+
} catch (error) {
|
|
2543
|
+
logger.warn("Failed to disconnect ACP MCP servers", { error });
|
|
2544
|
+
}
|
|
2545
|
+
record.mcpManager = undefined;
|
|
2546
|
+
}
|
|
2547
|
+
try {
|
|
2548
|
+
await record.session.dispose({ reason });
|
|
2549
|
+
} catch (error) {
|
|
2550
|
+
logger.warn("Failed to dispose ACP session", { error });
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
async #disposeStandaloneSession(session: AgentSession, reason?: postmortem.Reason): Promise<void> {
|
|
2555
|
+
try {
|
|
2556
|
+
await session.dispose({ reason });
|
|
2557
|
+
} catch (error) {
|
|
2558
|
+
logger.warn("Failed to dispose ACP session", { error });
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
/** Dispose every session owned by this ACP connection and await persisted teardown. */
|
|
2563
|
+
async dispose(reason?: postmortem.Reason): Promise<void> {
|
|
2564
|
+
if (this.#disposePromise) {
|
|
2565
|
+
await this.#disposePromise;
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
this.#disposePromise = (async () => {
|
|
2570
|
+
const records = Array.from(this.#sessions.entries());
|
|
2571
|
+
this.#sessions.clear();
|
|
2572
|
+
await Promise.all(
|
|
2573
|
+
records.map(async ([sessionId, record]) => {
|
|
2574
|
+
try {
|
|
2575
|
+
record.closedError ??= this.#createPromptLifecycleError(
|
|
2576
|
+
"ACP agent disposed before queued prompt could run",
|
|
2577
|
+
);
|
|
2578
|
+
await this.#cancelPromptForClose(record);
|
|
2579
|
+
await this.#disposeSessionRecord(record, reason);
|
|
2580
|
+
} catch (error) {
|
|
2581
|
+
logger.warn("Failed to clean up ACP session", { sessionId, error });
|
|
2582
|
+
}
|
|
2583
|
+
}),
|
|
2584
|
+
);
|
|
2585
|
+
|
|
2586
|
+
const initialSession = this.#initialSession;
|
|
2587
|
+
this.#initialSession = undefined;
|
|
2588
|
+
if (initialSession) {
|
|
2589
|
+
await this.#disposeStandaloneSession(initialSession, reason);
|
|
2590
|
+
}
|
|
2591
|
+
})();
|
|
2592
|
+
|
|
2593
|
+
await this.#disposePromise;
|
|
2594
|
+
}
|
|
2595
|
+
}
|