@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,2173 @@
|
|
|
1
|
+
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
3
|
+
import { getStreamingPartialJson } from "@oh-my-pi/pi-ai/utils/block-symbols";
|
|
4
|
+
import { type Component, Loader, TERMINAL } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { logger, prompt, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
7
|
+
import { extractTextContent } from "../../commit/utils";
|
|
8
|
+
import { settings } from "../../config/settings";
|
|
9
|
+
import { getEditClipboard } from "../../edit/edit-clipboard";
|
|
10
|
+
import { getFileSnapshotStore } from "../../edit/file-snapshot-store";
|
|
11
|
+
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
12
|
+
import { detectCacheInvalidation } from "../../modes/components/cache-invalidation-marker";
|
|
13
|
+
import {
|
|
14
|
+
groupedReadUsageCallIds,
|
|
15
|
+
ReadToolGroupComponent,
|
|
16
|
+
readArgsCollapseIntoGroup,
|
|
17
|
+
readArgsHaveTarget,
|
|
18
|
+
} from "../../modes/components/read-tool-group";
|
|
19
|
+
import { TodoReminderComponent } from "../../modes/components/todo-reminder";
|
|
20
|
+
import { ToolExecutionComponent, type ToolExecutionHandle } from "../../modes/components/tool-execution";
|
|
21
|
+
import { TtsrNotificationComponent } from "../../modes/components/ttsr-notification";
|
|
22
|
+
import { createUsageRowBlock } from "../../modes/components/usage-row";
|
|
23
|
+
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
24
|
+
import type { InteractiveModeContext, TodoPhase } from "../../modes/types";
|
|
25
|
+
import idleRecapPrompt from "../../prompts/system/recap-user.md" with { type: "text" };
|
|
26
|
+
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
27
|
+
import { isSilentAbort, readQueueChipText, resolveAbortLabel } from "../../session/messages";
|
|
28
|
+
import { type ApprovalMode, resolveApproval } from "../../tools/approval";
|
|
29
|
+
import { previewLine, TRUNCATE_LENGTHS } from "../../tools/render-utils";
|
|
30
|
+
import { PROPOSE_DEVICE_NAME, writeDeviceDispatch } from "../../tools/resolve";
|
|
31
|
+
import { nextActionableTask } from "../../tools/todo";
|
|
32
|
+
import { SpeechEnhancer } from "../../tts/speech-enhancer";
|
|
33
|
+
import { vocalizer } from "../../tts/vocalizer";
|
|
34
|
+
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
35
|
+
import { setTerminalTitleState } from "../../utils/title-generator";
|
|
36
|
+
import { interruptHint } from "../shared";
|
|
37
|
+
import { createAssistantMessageComponent } from "../utils/interactive-context-helpers";
|
|
38
|
+
import {
|
|
39
|
+
assistantHasVisibleContent,
|
|
40
|
+
assistantUsageIsBilled,
|
|
41
|
+
splitAssistantMessageToolTimeline,
|
|
42
|
+
} from "../utils/transcript-render-helpers";
|
|
43
|
+
import { isWarpCliAgentProtocolActive } from "../warp-events";
|
|
44
|
+
import { StreamingRevealController } from "./streaming-reveal";
|
|
45
|
+
import { streamingStringKeysForTool, ToolArgsRevealController } from "./tool-args-reveal";
|
|
46
|
+
|
|
47
|
+
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
48
|
+
|
|
49
|
+
const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
50
|
+
/**
|
|
51
|
+
* Concurrent IRC cards allowed in the transcript's live region. Cards land
|
|
52
|
+
* below a still-live block (a running task), where they cannot commit to
|
|
53
|
+
* native scrollback (commits are prefix-only) — every visible card inflates
|
|
54
|
+
* the live region and pushes the live block's uncommitted rows above the
|
|
55
|
+
* window top, where they are neither on screen nor in history. A swarm burst
|
|
56
|
+
* (several agents coordinating at once) must therefore stay bounded: the
|
|
57
|
+
* oldest live-region card retires as soon as a new one would exceed the cap.
|
|
58
|
+
*/
|
|
59
|
+
const MAX_LIVE_IRC_CARDS = 4;
|
|
60
|
+
const IDLE_RECAP_MIN_SECONDS = 1;
|
|
61
|
+
const IDLE_RECAP_MAX_SECONDS = 3600;
|
|
62
|
+
|
|
63
|
+
const RAW_PARTIAL_JSON_RENDERERS: Record<string, true> = { bash: true, edit: true, apply_patch: true };
|
|
64
|
+
|
|
65
|
+
function exposesRawPartialJson(toolName: string, rawInput: boolean, tool: unknown): boolean {
|
|
66
|
+
if (rawInput) return true;
|
|
67
|
+
if (RAW_PARTIAL_JSON_RENDERERS[toolName]) return true;
|
|
68
|
+
if (tool === null || typeof tool !== "object" || !("renderCall" in tool)) return false;
|
|
69
|
+
return typeof tool.renderCall === "function";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type AgentSessionEventHandlers = {
|
|
73
|
+
[E in AgentSessionEventKind]: (event: Extract<AgentSessionEvent, { type: E }>) => Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export class EventController {
|
|
77
|
+
#lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
78
|
+
// Count of visible assistant content blocks (rendered non-empty text/thinking)
|
|
79
|
+
// already seen in the current streaming message. A newly appearing one breaks
|
|
80
|
+
// the read run: the rendered reasoning/answer is a visual separator, so reads
|
|
81
|
+
// after it start a fresh group. Empty/absent thinking — common when a model
|
|
82
|
+
// emits one read per completion — does not break it, so a run of consecutive
|
|
83
|
+
// reads collapses into one group even across completion boundaries.
|
|
84
|
+
#lastVisibleBlockCount = 0;
|
|
85
|
+
#renderedCustomMessages = new Set<string>();
|
|
86
|
+
#lastIntent: string | undefined = undefined;
|
|
87
|
+
#backgroundTaskCallIds = new Set<string>();
|
|
88
|
+
/** Tool calls whose approval prompt drove the title into `attention`; cleared
|
|
89
|
+
* at their tool_execution_end so the title returns to `working`. */
|
|
90
|
+
#approvalAttentionToolCallIds = new Set<string>();
|
|
91
|
+
#readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
92
|
+
#readToolCallAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
93
|
+
#toolTimelineComponents = new Map<string, Component>();
|
|
94
|
+
// Stable identity for a streamed tool call while its assistant message is
|
|
95
|
+
// live: maps the tool-call block's position in the streaming message to the
|
|
96
|
+
// id last seen at that position. A streamed id can CHANGE across cumulative
|
|
97
|
+
// `message_update`s — some providers (GitHub Copilot's `call_id|id`
|
|
98
|
+
// transport, any stream that delivers name/args before the id) emit the
|
|
99
|
+
// block with an empty or partial id first and rewrite it in a later delta
|
|
100
|
+
// (openai-completions sets `id: toolCall.id || ""` then overwrites on the
|
|
101
|
+
// next chunk). Keyed only by id, the changed id reads as a brand-new call
|
|
102
|
+
// and a second card is created — the old-id card orphans as a pending
|
|
103
|
+
// preview while the new-id card takes the result (#6879). Reset per assistant
|
|
104
|
+
// message (indices are per-message).
|
|
105
|
+
#streamedToolCallIdByIndex = new Map<number, string>();
|
|
106
|
+
// A TTSR rewind retracts its uncommitted never-run cards at message_end;
|
|
107
|
+
// retain their ids until agent-loop's synthetic tool_execution_start/end for
|
|
108
|
+
// those calls arrive so the normal no-pending path cannot recreate the
|
|
109
|
+
// retracted card below the rewind's fresh blocks (#6879).
|
|
110
|
+
#retractedToolCallIds = new Set<string>();
|
|
111
|
+
// Cards settled by a synthetic aborted/error `tool_execution_end` (agent-loop
|
|
112
|
+
// emits one per never-run call on a terminal error/abort). They stay visible
|
|
113
|
+
// for a genuinely terminal failure, but if an auto-retry then supersedes the
|
|
114
|
+
// turn (`auto_retry_start`, decided AFTER message_end) they must be removed so
|
|
115
|
+
// the retry's fresh cards do not render the same call twice (Codex review on
|
|
116
|
+
// #6881). Keyed by call id; reset per turn.
|
|
117
|
+
#syntheticFailureCards = new Map<string, ToolExecutionHandle>();
|
|
118
|
+
// Completions that arrived before any component existed for their call id.
|
|
119
|
+
// Cursor's server-resolved tools (todo) emit `tool_execution_end` through a
|
|
120
|
+
// synchronous callback fired mid-parse, while the `toolcall_start` for the
|
|
121
|
+
// same call rides `AssistantMessageEventStream` and is delivered a microtask
|
|
122
|
+
// later. When the server packs start and completion into one HTTP/2 chunk
|
|
123
|
+
// the completion is handled FIRST — with no `pendingTools` entry to settle.
|
|
124
|
+
// Dropping it would strand the card the streamed block creates moments
|
|
125
|
+
// later, so the event is held here and replayed the moment that component
|
|
126
|
+
// materializes (`#handleMessageUpdate`). Keyed by call id; ids are unique
|
|
127
|
+
// per turn, and the map is cleared with the other transcript anchors.
|
|
128
|
+
#orphanedToolCompletions = new Map<string, Extract<AgentSessionEvent, { type: "tool_execution_end" }>>();
|
|
129
|
+
#postToolAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
130
|
+
#lastAssistantComponent: AssistantMessageComponent | undefined = undefined;
|
|
131
|
+
// Assistant component whose turn-ending error is currently mirrored in the
|
|
132
|
+
// pinned banner. Its inline `Error: …` line is suppressed while pinned and
|
|
133
|
+
// restored when the banner clears at the next `agent_start` (see
|
|
134
|
+
// #handleMessageEnd / #handleAgentStart).
|
|
135
|
+
#pinnedErrorComponent: AssistantMessageComponent | undefined = undefined;
|
|
136
|
+
#retrySupersededAssistantComponents = new Map<string, AssistantMessageComponent>();
|
|
137
|
+
#retrySupersededAssistantQueue: AssistantMessageComponent[] = [];
|
|
138
|
+
// Set when `auto_retry_start` fires and cleared by `auto_retry_end` (both
|
|
139
|
+
// outcomes) — true for exactly the window a retry is outstanding. Gates
|
|
140
|
+
// `sendErrorNotification`: the wire-level `agent_end` for a retryable
|
|
141
|
+
// failure is coalesced with every other attempt in the same saga while the
|
|
142
|
+
// prompt is in flight (see `AgentSession#emitSessionEvent`), so the single
|
|
143
|
+
// `agent_end` that survives to reach this controller can be either a
|
|
144
|
+
// mid-retry blip or the final settle — only the retry lifecycle events
|
|
145
|
+
// (never deferred) can tell them apart.
|
|
146
|
+
#retryPending = false;
|
|
147
|
+
#idleCompactionTimer?: NodeJS.Timeout;
|
|
148
|
+
#idleRecapTimer?: NodeJS.Timeout;
|
|
149
|
+
// In-flight ephemeral recap turn; aborted by #cancelIdleRecap when any
|
|
150
|
+
// activity (new turn, compaction, editor draft) supersedes the idle recap.
|
|
151
|
+
#idleRecapAbort?: AbortController;
|
|
152
|
+
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
153
|
+
// Insertion-ordered IRC cards not yet retired; values are the transcript
|
|
154
|
+
// components each card contributed (see #retireIrcCard for the guard).
|
|
155
|
+
#liveIrcCards = new Map<string, Component[]>();
|
|
156
|
+
// Most recent `hub` tool block whose result still had every watched job
|
|
157
|
+
// running. Kept un-finalized (live) so the next `hub` call displaces it —
|
|
158
|
+
// one persistent poll instead of a stack of "waiting on N jobs" frames —
|
|
159
|
+
// and sealed in place the moment anything else lands below it.
|
|
160
|
+
#displaceablePollComponent: ToolExecutionComponent | undefined = undefined;
|
|
161
|
+
// Most recent successful `todo` snapshot in the active turn. It stays live
|
|
162
|
+
// across intervening tool output so a later `todo` update can replace the
|
|
163
|
+
// old full list; the turn boundary seals the final snapshot as history.
|
|
164
|
+
#displaceableTodoComponent: ToolExecutionComponent | undefined = undefined;
|
|
165
|
+
// Most recent TTSR notification block. A new ttsr_triggered event merges its
|
|
166
|
+
// rules into this block while it is still the (live-region) transcript tail.
|
|
167
|
+
#lastTtsrNotification: TtsrNotificationComponent | undefined = undefined;
|
|
168
|
+
#streamingReveal: StreamingRevealController;
|
|
169
|
+
#toolArgsReveal: ToolArgsRevealController;
|
|
170
|
+
#prevHideThinking = false;
|
|
171
|
+
#handlers: AgentSessionEventHandlers;
|
|
172
|
+
#terminalProgressActive = false;
|
|
173
|
+
// Coalescing window for `message_update` events at the subscription boundary.
|
|
174
|
+
// `message_update` carries the CUMULATIVE assistant message (every update
|
|
175
|
+
// re-lists all content blocks), so when a burst of deltas arrives faster than
|
|
176
|
+
// this window only the latest snapshot needs to rebuild streaming state — the
|
|
177
|
+
// intermediate rebuilds are redundant work. The TUI already caps the paint
|
|
178
|
+
// rate via its own render cadence; this caps the per-token handler work that
|
|
179
|
+
// feeds it. Speech stays intact: `#vocalizeDelta` runs at ARRIVAL for every
|
|
180
|
+
// delta before the snapshot is coalesced away.
|
|
181
|
+
#pendingMessageUpdate: Extract<AgentSessionEvent, { type: "message_update" }> | undefined = undefined;
|
|
182
|
+
#messageUpdateTimer: NodeJS.Timeout | undefined = undefined;
|
|
183
|
+
/** Tail of the serialized dispatch chain; see #runSerialized. */
|
|
184
|
+
#dispatchTail: Promise<void> = Promise.resolve();
|
|
185
|
+
/** Whether a chained run is currently in flight (awaiting its own awaits). */
|
|
186
|
+
#dispatchInFlight = false;
|
|
187
|
+
// Deltas already fed to speech at arrival by the coalescer. `#handleMessageUpdate`
|
|
188
|
+
// also vocalizes so the direct `handleEvent` path (tests, session focus replay)
|
|
189
|
+
// keeps working — the WeakSet makes the coalesced path speak each delta exactly
|
|
190
|
+
// once instead of twice.
|
|
191
|
+
#vocalizedMessageUpdates = new WeakSet<object>();
|
|
192
|
+
static readonly #MESSAGE_UPDATE_COALESCE_MS = 33;
|
|
193
|
+
|
|
194
|
+
constructor(private ctx: InteractiveModeContext) {
|
|
195
|
+
// Enhanced speech (`speech.enhanced`) rewrites blocks through the
|
|
196
|
+
// tiny/smol role with this session's registry and credentials; the
|
|
197
|
+
// vocalizer falls back to mechanical cleanup when unset. Tolerates
|
|
198
|
+
// partial contexts (tests, minimal embeddings) by wiring null.
|
|
199
|
+
const session = ctx.session;
|
|
200
|
+
vocalizer.setEnhancer(
|
|
201
|
+
session?.modelRegistry && session.agent && session.settings
|
|
202
|
+
? new SpeechEnhancer({
|
|
203
|
+
settings: session.settings,
|
|
204
|
+
registry: session.modelRegistry,
|
|
205
|
+
sessionId: session.sessionId,
|
|
206
|
+
metadataResolver: provider => session.agent.metadataForProvider(provider),
|
|
207
|
+
})
|
|
208
|
+
: null,
|
|
209
|
+
);
|
|
210
|
+
this.#streamingReveal = new StreamingRevealController({
|
|
211
|
+
getSmoothStreaming: () => this.ctx.settings.get("display.smoothStreaming"),
|
|
212
|
+
getHideThinkingBlock: () => this.ctx.effectiveHideThinkingBlock,
|
|
213
|
+
getProseOnlyThinking: () => this.ctx.proseOnlyThinking,
|
|
214
|
+
requestRender: component => this.ctx.ui.requestComponentRender(component),
|
|
215
|
+
});
|
|
216
|
+
this.#toolArgsReveal = new ToolArgsRevealController({
|
|
217
|
+
getSmoothStreaming: () => this.ctx.settings.get("display.smoothStreaming"),
|
|
218
|
+
requestRender: component => this.ctx.ui.requestComponentRender(component),
|
|
219
|
+
});
|
|
220
|
+
this.#handlers = {
|
|
221
|
+
agent_start: e => this.#handleAgentStart(e),
|
|
222
|
+
agent_end: e => this.#handleAgentEnd(e),
|
|
223
|
+
turn_start: async () => {},
|
|
224
|
+
turn_end: async e => this.#handleTurnEnd(e),
|
|
225
|
+
message_start: e => this.#handleMessageStart(e),
|
|
226
|
+
message_update: e => this.#handleMessageUpdate(e),
|
|
227
|
+
message_end: e => this.#handleMessageEnd(e),
|
|
228
|
+
tool_execution_start: e => this.#handleToolExecutionStart(e),
|
|
229
|
+
tool_execution_update: e => this.#handleToolExecutionUpdate(e),
|
|
230
|
+
tool_execution_end: e => this.#handleToolExecutionEnd(e),
|
|
231
|
+
auto_compaction_start: e => this.#handleAutoCompactionStart(e),
|
|
232
|
+
auto_compaction_end: e => this.#handleAutoCompactionEnd(e),
|
|
233
|
+
auto_retry_start: e => this.#handleAutoRetryStart(e),
|
|
234
|
+
auto_retry_end: e => this.#handleAutoRetryEnd(e),
|
|
235
|
+
retry_fallback_applied: e => this.#handleRetryFallbackApplied(e),
|
|
236
|
+
retry_fallback_succeeded: e => this.#handleRetryFallbackSucceeded(e),
|
|
237
|
+
ttsr_triggered: e => this.#handleTtsrTriggered(e),
|
|
238
|
+
todo_reminder: e => this.#handleTodoReminder(e),
|
|
239
|
+
todo_auto_clear: e => this.#handleTodoAutoClear(e),
|
|
240
|
+
irc_message: e => this.#handleIrcMessage(e),
|
|
241
|
+
notice: e => this.#handleNotice(e),
|
|
242
|
+
model_changed: async () => {
|
|
243
|
+
this.ctx.statusLine.invalidate();
|
|
244
|
+
this.ctx.ui.requestRender();
|
|
245
|
+
},
|
|
246
|
+
thinking_level_changed: async () => {
|
|
247
|
+
this.ctx.statusLine.invalidate();
|
|
248
|
+
this.ctx.updateEditorBorderColor();
|
|
249
|
+
const hideThinking = this.ctx.effectiveHideThinkingBlock;
|
|
250
|
+
// Only do the expensive full resetDisplay when the effective
|
|
251
|
+
// visibility actually changed. Auto-classification (e.g. high→medium)
|
|
252
|
+
// emits thinking_level_changed without changing visibility — a full
|
|
253
|
+
// terminal replay for those would be disruptive.
|
|
254
|
+
if (hideThinking === this.#prevHideThinking) {
|
|
255
|
+
this.ctx.ui.requestRender();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
this.#prevHideThinking = hideThinking;
|
|
259
|
+
// Propagate visibility to existing rendered messages.
|
|
260
|
+
for (const child of this.ctx.chatContainer.children) {
|
|
261
|
+
if (child instanceof AssistantMessageComponent) {
|
|
262
|
+
child.setHideThinkingBlock(hideThinking);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
|
|
266
|
+
this.ctx.streamingComponent.setHideThinkingBlock(hideThinking);
|
|
267
|
+
this.#streamingReveal.resyncVisibility();
|
|
268
|
+
}
|
|
269
|
+
this.ctx.ui.resetDisplay();
|
|
270
|
+
},
|
|
271
|
+
goal_updated: async () => {},
|
|
272
|
+
} satisfies AgentSessionEventHandlers;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
dispose(): void {
|
|
276
|
+
if (this.#messageUpdateTimer) {
|
|
277
|
+
clearTimeout(this.#messageUpdateTimer);
|
|
278
|
+
this.#messageUpdateTimer = undefined;
|
|
279
|
+
}
|
|
280
|
+
this.#pendingMessageUpdate = undefined;
|
|
281
|
+
this.#streamingReveal.stop();
|
|
282
|
+
this.#toolArgsReveal.stop();
|
|
283
|
+
this.#cancelIdleCompaction();
|
|
284
|
+
this.#cancelIdleRecap();
|
|
285
|
+
this.#setTerminalProgress(false);
|
|
286
|
+
for (const timer of this.#ircExpiryTimers.values()) {
|
|
287
|
+
clearTimeout(timer);
|
|
288
|
+
}
|
|
289
|
+
this.#ircExpiryTimers.clear();
|
|
290
|
+
this.#liveIrcCards.clear();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#resetReadGroup(): void {
|
|
294
|
+
this.#lastReadGroup?.finalize();
|
|
295
|
+
this.#lastReadGroup = undefined;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#getReadGroup(): ReadToolGroupComponent {
|
|
299
|
+
if (!this.#lastReadGroup) {
|
|
300
|
+
const group = new ReadToolGroupComponent({
|
|
301
|
+
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
302
|
+
});
|
|
303
|
+
group.setExpanded(this.ctx.toolOutputExpanded);
|
|
304
|
+
group.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
305
|
+
this.ctx.chatContainer.addChild(group);
|
|
306
|
+
this.#lastReadGroup = group;
|
|
307
|
+
}
|
|
308
|
+
return this.#lastReadGroup;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#trackReadToolCall(toolCallId: string, args: unknown): void {
|
|
312
|
+
if (!toolCallId) return;
|
|
313
|
+
const normalizedArgs =
|
|
314
|
+
args && typeof args === "object" && !Array.isArray(args) ? (args as Record<string, unknown>) : {};
|
|
315
|
+
this.#readToolCallArgs.set(toolCallId, normalizedArgs);
|
|
316
|
+
const assistantComponent = this.ctx.streamingComponent ?? this.#lastAssistantComponent;
|
|
317
|
+
if (assistantComponent) {
|
|
318
|
+
this.#readToolCallAssistantComponents.set(toolCallId, assistantComponent);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#clearReadToolCall(toolCallId: string): void {
|
|
323
|
+
this.#readToolCallArgs.delete(toolCallId);
|
|
324
|
+
this.#readToolCallAssistantComponents.delete(toolCallId);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#retractToolCardEntry(toolCallId: string, component: ToolExecutionHandle): void {
|
|
328
|
+
component.seal();
|
|
329
|
+
let removeComponent = true;
|
|
330
|
+
if (component instanceof ReadToolGroupComponent) {
|
|
331
|
+
removeComponent = component.removeEntry(toolCallId);
|
|
332
|
+
if (component === this.#lastReadGroup) this.#resetReadGroup();
|
|
333
|
+
}
|
|
334
|
+
if (removeComponent) this.ctx.chatContainer.removeChild(component);
|
|
335
|
+
this.ctx.pendingTools.delete(toolCallId);
|
|
336
|
+
this.#toolTimelineComponents.delete(toolCallId);
|
|
337
|
+
this.#clearReadToolCall(toolCallId);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Re-key a live streamed tool card whose id changed mid-stream (see
|
|
342
|
+
* {@link #streamedToolCallIdByIndex}). Moves every id-keyed tracker from the
|
|
343
|
+
* old id to the new one so the next cumulative `message_update` reuses the
|
|
344
|
+
* existing card instead of creating a duplicate (#6879). The card component
|
|
345
|
+
* itself is id-agnostic (routing is via `pendingTools`), so only the maps and
|
|
346
|
+
* the shared read group's entry need re-keying.
|
|
347
|
+
*/
|
|
348
|
+
#migrateStreamedToolCallId(oldId: string, newId: string): void {
|
|
349
|
+
// `oldId` may be "" (the block streamed before its id): that empty key still
|
|
350
|
+
// owns a live card and must migrate. Skip only a no-op or an empty target.
|
|
351
|
+
if (oldId === newId || !newId) return;
|
|
352
|
+
const pending = this.ctx.pendingTools.get(oldId);
|
|
353
|
+
if (pending && !this.ctx.pendingTools.has(newId)) {
|
|
354
|
+
this.ctx.pendingTools.delete(oldId);
|
|
355
|
+
this.ctx.pendingTools.set(newId, pending);
|
|
356
|
+
}
|
|
357
|
+
const timeline = this.#toolTimelineComponents.get(oldId);
|
|
358
|
+
if (timeline && !this.#toolTimelineComponents.has(newId)) {
|
|
359
|
+
this.#toolTimelineComponents.delete(oldId);
|
|
360
|
+
this.#toolTimelineComponents.set(newId, timeline);
|
|
361
|
+
}
|
|
362
|
+
// The reveal controller is id-keyed; drop the stale target so the loop's
|
|
363
|
+
// setTarget/bind under the new id owns the paced reveal.
|
|
364
|
+
this.#toolArgsReveal.finish(oldId);
|
|
365
|
+
const readArgs = this.#readToolCallArgs.get(oldId);
|
|
366
|
+
if (readArgs !== undefined) {
|
|
367
|
+
this.#readToolCallArgs.delete(oldId);
|
|
368
|
+
this.#readToolCallArgs.set(newId, readArgs);
|
|
369
|
+
}
|
|
370
|
+
const readAssistant = this.#readToolCallAssistantComponents.get(oldId);
|
|
371
|
+
if (readAssistant !== undefined) {
|
|
372
|
+
this.#readToolCallAssistantComponents.delete(oldId);
|
|
373
|
+
this.#readToolCallAssistantComponents.set(newId, readAssistant);
|
|
374
|
+
}
|
|
375
|
+
// A collapsed read renders into a shared group keyed by id; rename its
|
|
376
|
+
// entry so the row isn't duplicated under the new id.
|
|
377
|
+
if (pending instanceof ReadToolGroupComponent) pending.renameEntry(oldId, newId);
|
|
378
|
+
// A server-resolved completion (Cursor/todo) can land under `newId` while
|
|
379
|
+
// the card was still keyed by `oldId`, so it was parked in
|
|
380
|
+
// `#orphanedToolCompletions` instead of settling. Now that the card owns
|
|
381
|
+
// `newId`, apply the held result — the normal creation path that consumes
|
|
382
|
+
// held completions is skipped on a re-key (Codex review on #6881).
|
|
383
|
+
if (pending) {
|
|
384
|
+
const orphan = this.#orphanedToolCompletions.get(newId);
|
|
385
|
+
if (orphan) {
|
|
386
|
+
this.#orphanedToolCompletions.delete(newId);
|
|
387
|
+
this.#settleHeldCompletion(pending, orphan);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
#inlineReadToolImages(
|
|
393
|
+
toolCallId: string,
|
|
394
|
+
result: { content: Array<{ type: string; data?: string; mimeType?: string }> },
|
|
395
|
+
): boolean {
|
|
396
|
+
const assistantComponent = this.#readToolCallAssistantComponents.get(toolCallId);
|
|
397
|
+
if (!assistantComponent) return false;
|
|
398
|
+
const images: ImageContent[] = result.content
|
|
399
|
+
.filter(
|
|
400
|
+
(content): content is ImageContent =>
|
|
401
|
+
content.type === "image" && typeof content.data === "string" && typeof content.mimeType === "string",
|
|
402
|
+
)
|
|
403
|
+
.map(content => ({ type: "image", data: content.data, mimeType: content.mimeType }));
|
|
404
|
+
if (images.length === 0) return false;
|
|
405
|
+
assistantComponent.setToolResultImages(toolCallId, images);
|
|
406
|
+
return settings.get("terminal.showImages");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
#insertAfterTranscriptComponent(anchor: Component | undefined, component: Component): boolean {
|
|
410
|
+
const children = this.ctx.chatContainer.children;
|
|
411
|
+
const anchorIndex = anchor ? children.indexOf(anchor) : -1;
|
|
412
|
+
if (anchorIndex < 0) return false;
|
|
413
|
+
if (children.slice(anchorIndex + 1).some(child => !this.ctx.chatContainer.isBlockUncommitted(child))) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
this.ctx.chatContainer.addChild(component);
|
|
417
|
+
children.splice(children.length - 1, 1);
|
|
418
|
+
children.splice(anchorIndex + 1, 0, component);
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
#upsertPostToolAssistantSegment(
|
|
423
|
+
toolCallId: string,
|
|
424
|
+
segment: AssistantMessage | undefined,
|
|
425
|
+
): AssistantMessageComponent | undefined {
|
|
426
|
+
if (!segment || !assistantHasVisibleContent(segment)) return undefined;
|
|
427
|
+
const existing = this.#postToolAssistantComponents.get(toolCallId);
|
|
428
|
+
if (existing) {
|
|
429
|
+
existing.updateContent(segment);
|
|
430
|
+
return existing;
|
|
431
|
+
}
|
|
432
|
+
const component = createAssistantMessageComponent(this.ctx);
|
|
433
|
+
component.updateContent(segment);
|
|
434
|
+
this.#postToolAssistantComponents.set(toolCallId, component);
|
|
435
|
+
if (!this.#insertAfterTranscriptComponent(this.#toolTimelineComponents.get(toolCallId), component)) {
|
|
436
|
+
this.ctx.chatContainer.addChild(component);
|
|
437
|
+
}
|
|
438
|
+
return component;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
#updateWorkingMessageFromIntent(intent: unknown): void {
|
|
442
|
+
if (this.ctx.session.isAborting) return;
|
|
443
|
+
// Streamed JSON can deliver non-string `i` (object, number, boolean) before
|
|
444
|
+
// schema validation; `?.` only guards null/undefined, so guard the type too.
|
|
445
|
+
if (typeof intent !== "string") return;
|
|
446
|
+
const trimmed = intent.trim();
|
|
447
|
+
if (!trimmed || trimmed === this.#lastIntent) return;
|
|
448
|
+
this.#lastIntent = trimmed;
|
|
449
|
+
this.ctx.setWorkingMessage(`${trimmed}${interruptHint()}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
subscribeToAgent(): void {
|
|
453
|
+
// Serialize non-update dispatch behind any in-flight handler run:
|
|
454
|
+
// AgentSession.#emit fires listeners fire-and-forget (it does not await
|
|
455
|
+
// listener promises), so without this a rapid stream tail
|
|
456
|
+
// (message_update → message_end → agent_end) could let a later callback
|
|
457
|
+
// overtake the coalesced flush's handler mid-await — agent_end removing
|
|
458
|
+
// `streamingComponent` before #handleMessageEnd finalizes and records
|
|
459
|
+
// the final message (issue #7443 follow-up). When the tail has settled,
|
|
460
|
+
// dispatch stays synchronous: the flush's streaming rebuild runs before
|
|
461
|
+
// the listener's first await, preserving the timing the coalescing
|
|
462
|
+
// tests assert on. `message_update` enqueue is itself synchronous and
|
|
463
|
+
// needs no serialization.
|
|
464
|
+
this.ctx.unsubscribe = this.ctx.session.subscribe(async (event: AgentSessionEvent) => {
|
|
465
|
+
// Coalesce the cumulative `message_update` deltas of a streaming turn
|
|
466
|
+
// into at most one handler run per window. `#handleMessageUpdate` is
|
|
467
|
+
// synchronous, so without this every token re-runs the whole
|
|
468
|
+
// streaming rebuild (splitAssistantMessageToolTimeline, reveal
|
|
469
|
+
// setTarget, per-block tool-call reconciliation) even though the TUI
|
|
470
|
+
// paints at most ~30fps — at 40-100 tps the handler work then
|
|
471
|
+
// dominates the CPU profile of an idle-looking streaming session
|
|
472
|
+
// (issue #7443). Only the latest snapshot is meaningful; non-update
|
|
473
|
+
// events flush the pending snapshot first so ordering is preserved.
|
|
474
|
+
if (event.type === "message_update") {
|
|
475
|
+
this.#enqueueMessageUpdate(event);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
await this.#runSerialized(async () => {
|
|
479
|
+
await this.#flushPendingMessageUpdate();
|
|
480
|
+
await this.handleEvent(event);
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Run `run` in the serialized dispatch chain: every run is its own link on
|
|
487
|
+
* the tail, so a burst of events queued behind an in-flight run start one
|
|
488
|
+
* after the other, never concurrently. This closes two races (issue #7443
|
|
489
|
+
* follow-up): a rapid stream tail (message_update → message_end →
|
|
490
|
+
* agent_end) cannot overtake the coalesced flush mid-await — agent_end
|
|
491
|
+
* removing `streamingComponent` before #handleMessageEnd finalizes and
|
|
492
|
+
* records the final message — and two+ events landing in the same window
|
|
493
|
+
* cannot all resume from one shared await and dispatch in parallel. When
|
|
494
|
+
* the chain is drained, `run` starts synchronously (no intermediate
|
|
495
|
+
* microtask), preserving the synchronous-flush timing the coalescing
|
|
496
|
+
* tests assert on. A rejection propagates to the caller (the session's
|
|
497
|
+
* fire-and-forget emit) and the next event starts a fresh chain link
|
|
498
|
+
* instead of being dropped.
|
|
499
|
+
*/
|
|
500
|
+
async #runSerialized(run: () => Promise<void>): Promise<void> {
|
|
501
|
+
if (this.#dispatchInFlight) {
|
|
502
|
+
// Queue behind the CURRENT tail: the next run starts only after
|
|
503
|
+
// the previous one settles. Each waiter gets its own link, so a
|
|
504
|
+
// burst cannot fan out from the same shared await.
|
|
505
|
+
const link = this.#dispatchTail.then(
|
|
506
|
+
() => run(),
|
|
507
|
+
() => run(),
|
|
508
|
+
);
|
|
509
|
+
this.#dispatchTail = link;
|
|
510
|
+
void link.then(
|
|
511
|
+
() => {
|
|
512
|
+
// Only the tail owner clears the flag: a later chained
|
|
513
|
+
// link clears it when it settles as the tail.
|
|
514
|
+
if (this.#dispatchTail === link) this.#dispatchInFlight = false;
|
|
515
|
+
},
|
|
516
|
+
() => {
|
|
517
|
+
if (this.#dispatchTail === link) this.#dispatchInFlight = false;
|
|
518
|
+
},
|
|
519
|
+
);
|
|
520
|
+
await link;
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
this.#dispatchInFlight = true;
|
|
524
|
+
const link = run();
|
|
525
|
+
this.#dispatchTail = link;
|
|
526
|
+
void link.then(
|
|
527
|
+
() => {
|
|
528
|
+
if (this.#dispatchTail === link) this.#dispatchInFlight = false;
|
|
529
|
+
},
|
|
530
|
+
() => {
|
|
531
|
+
if (this.#dispatchTail === link) this.#dispatchInFlight = false;
|
|
532
|
+
},
|
|
533
|
+
);
|
|
534
|
+
await link;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Queue a streaming `message_update` for the next coalesced handler run.
|
|
539
|
+
* Speech is per-delta, so the delta is vocalized at arrival before the
|
|
540
|
+
* snapshot is (possibly) superseded by a newer one.
|
|
541
|
+
*/
|
|
542
|
+
#enqueueMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): void {
|
|
543
|
+
// Speech is per-delta: every delta is spoken at arrival even when its
|
|
544
|
+
// cumulative snapshot is later superseded and never rebuilt.
|
|
545
|
+
this.#vocalizeDelta(event);
|
|
546
|
+
this.#vocalizedMessageUpdates.add(event);
|
|
547
|
+
this.#pendingMessageUpdate = event;
|
|
548
|
+
if (this.#messageUpdateTimer) return;
|
|
549
|
+
this.#messageUpdateTimer = setTimeout(() => {
|
|
550
|
+
this.#messageUpdateTimer = undefined;
|
|
551
|
+
// Mirror AgentSession.#emit: attach a catch so a streaming rebuild
|
|
552
|
+
// failure surfaces as a logged warning instead of a process-level
|
|
553
|
+
// unhandled rejection (the timer path has no listener to attach one).
|
|
554
|
+
// Runs inside the serialized dispatch chain so a message_end /
|
|
555
|
+
// agent_end landing mid-window cannot overtake this flush (issue
|
|
556
|
+
// #7443 follow-up).
|
|
557
|
+
void this.#runSerialized(async () => {
|
|
558
|
+
await this.#flushPendingMessageUpdate();
|
|
559
|
+
}).catch(err => {
|
|
560
|
+
logger.warn("Message update flush rejected", {
|
|
561
|
+
error: err instanceof Error ? err.message : String(err),
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
}, EventController.#MESSAGE_UPDATE_COALESCE_MS);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Run the coalesced `message_update` handler on the latest pending snapshot
|
|
569
|
+
* (dropping any superseded intermediates) and clear the queue. Safe to call
|
|
570
|
+
* more than once; no-ops when nothing is pending.
|
|
571
|
+
*/
|
|
572
|
+
async #flushPendingMessageUpdate(): Promise<void> {
|
|
573
|
+
if (this.#messageUpdateTimer) {
|
|
574
|
+
clearTimeout(this.#messageUpdateTimer);
|
|
575
|
+
this.#messageUpdateTimer = undefined;
|
|
576
|
+
}
|
|
577
|
+
const event = this.#pendingMessageUpdate;
|
|
578
|
+
if (!event) return;
|
|
579
|
+
this.#pendingMessageUpdate = undefined;
|
|
580
|
+
await this.handleEvent(event);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Clear every transcript-anchored/turn-scoped piece of state. Used by the
|
|
584
|
+
* session focus proxy when re-pointing the transcript at another session:
|
|
585
|
+
* components, timers, and stream-reveal state all reference the previous
|
|
586
|
+
* session's transcript and must not bleed into the new one.
|
|
587
|
+
*/
|
|
588
|
+
resetTranscriptAnchors(): void {
|
|
589
|
+
if (this.#messageUpdateTimer) {
|
|
590
|
+
clearTimeout(this.#messageUpdateTimer);
|
|
591
|
+
this.#messageUpdateTimer = undefined;
|
|
592
|
+
}
|
|
593
|
+
this.#pendingMessageUpdate = undefined;
|
|
594
|
+
this.#resetReadGroup();
|
|
595
|
+
this.#lastVisibleBlockCount = 0;
|
|
596
|
+
this.#renderedCustomMessages.clear();
|
|
597
|
+
this.#lastIntent = undefined;
|
|
598
|
+
this.#toolTimelineComponents.clear();
|
|
599
|
+
this.#streamedToolCallIdByIndex.clear();
|
|
600
|
+
this.#retractedToolCallIds.clear();
|
|
601
|
+
this.#syntheticFailureCards.clear();
|
|
602
|
+
this.#orphanedToolCompletions.clear();
|
|
603
|
+
this.#postToolAssistantComponents.clear();
|
|
604
|
+
this.#backgroundTaskCallIds.clear();
|
|
605
|
+
this.#approvalAttentionToolCallIds.clear();
|
|
606
|
+
this.#readToolCallArgs.clear();
|
|
607
|
+
this.#readToolCallAssistantComponents.clear();
|
|
608
|
+
this.#lastAssistantComponent = undefined;
|
|
609
|
+
this.#pinnedErrorComponent = undefined;
|
|
610
|
+
this.#retryPending = this.ctx.viewSession.isRetrying;
|
|
611
|
+
this.#cancelIdleCompaction();
|
|
612
|
+
this.#cancelIdleRecap();
|
|
613
|
+
for (const timer of this.#ircExpiryTimers.values()) {
|
|
614
|
+
clearTimeout(timer);
|
|
615
|
+
}
|
|
616
|
+
this.#ircExpiryTimers.clear();
|
|
617
|
+
this.#liveIrcCards.clear();
|
|
618
|
+
this.#displaceablePollComponent = undefined;
|
|
619
|
+
this.#displaceableTodoComponent = undefined;
|
|
620
|
+
this.#lastTtsrNotification = undefined;
|
|
621
|
+
this.#streamingReveal.stop();
|
|
622
|
+
this.#toolArgsReveal.stop();
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async handleEvent(event: AgentSessionEvent): Promise<void> {
|
|
626
|
+
if (!this.ctx.isInitialized) {
|
|
627
|
+
await this.ctx.init();
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Each handler explicitly requests a render (or leaves it out, when it
|
|
631
|
+
// changed nothing visible). A blanket pre-render fired on every event —
|
|
632
|
+
// including the ~hundreds of `message_update` deltas per streaming turn —
|
|
633
|
+
// doubled the paint rate: the pre-render's frame fires while the handler
|
|
634
|
+
// is awaiting, then the handler's own final requestRender schedules a
|
|
635
|
+
// second identical frame. Removing it lets the render cadence follow real
|
|
636
|
+
// state changes rather than event volume (issue #4353).
|
|
637
|
+
const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
|
|
638
|
+
await run(event);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
#setTerminalProgress(active: boolean): void {
|
|
642
|
+
if (active) {
|
|
643
|
+
if (this.#terminalProgressActive || this.ctx.settings?.get("terminal.showProgress") !== true) return;
|
|
644
|
+
this.ctx.ui.terminal.setProgress(true);
|
|
645
|
+
this.#terminalProgressActive = true;
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (!this.#terminalProgressActive) return;
|
|
649
|
+
this.ctx.ui.terminal.setProgress(false);
|
|
650
|
+
this.#terminalProgressActive = false;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
#trackRetrySupersededAssistantComponent(component: AssistantMessageComponent | undefined): void {
|
|
654
|
+
if (!component) return;
|
|
655
|
+
const persistenceKey = component.messagePersistenceKey();
|
|
656
|
+
if (persistenceKey) this.#retrySupersededAssistantComponents.set(persistenceKey, component);
|
|
657
|
+
if (!this.#retrySupersededAssistantQueue.includes(component)) {
|
|
658
|
+
this.#retrySupersededAssistantQueue.push(component);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
#takeRetrySupersededAssistantComponent(persistenceKey: string | undefined): AssistantMessageComponent | undefined {
|
|
663
|
+
if (persistenceKey) {
|
|
664
|
+
const component = this.#retrySupersededAssistantComponents.get(persistenceKey);
|
|
665
|
+
if (component) {
|
|
666
|
+
this.#retrySupersededAssistantComponents.delete(persistenceKey);
|
|
667
|
+
this.#retrySupersededAssistantQueue = this.#retrySupersededAssistantQueue.filter(
|
|
668
|
+
item => item !== component,
|
|
669
|
+
);
|
|
670
|
+
return component;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
while (this.#retrySupersededAssistantQueue.length > 0) {
|
|
674
|
+
const component = this.#retrySupersededAssistantQueue.shift();
|
|
675
|
+
if (!component) continue;
|
|
676
|
+
const key = component.messagePersistenceKey();
|
|
677
|
+
if (key && this.#retrySupersededAssistantComponents.get(key) !== component) continue;
|
|
678
|
+
if (key) this.#retrySupersededAssistantComponents.delete(key);
|
|
679
|
+
return component;
|
|
680
|
+
}
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
#clearRetrySupersededAssistantComponents(): void {
|
|
685
|
+
this.#retrySupersededAssistantComponents.clear();
|
|
686
|
+
this.#retrySupersededAssistantQueue = [];
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
690
|
+
this.#toolTimelineComponents.clear();
|
|
691
|
+
this.#streamedToolCallIdByIndex.clear();
|
|
692
|
+
this.#retractedToolCallIds.clear();
|
|
693
|
+
this.#syntheticFailureCards.clear();
|
|
694
|
+
this.#orphanedToolCompletions.clear();
|
|
695
|
+
this.#postToolAssistantComponents.clear();
|
|
696
|
+
this.#lastIntent = undefined;
|
|
697
|
+
this.#readToolCallArgs.clear();
|
|
698
|
+
this.#readToolCallAssistantComponents.clear();
|
|
699
|
+
this.#resetReadGroup();
|
|
700
|
+
this.#resolveDisplaceableTodo();
|
|
701
|
+
this.#lastAssistantComponent = undefined;
|
|
702
|
+
// Restore the previous turn's inline error in the transcript before dropping
|
|
703
|
+
// the banner, so the error stays in history once the banner is gone.
|
|
704
|
+
this.#pinnedErrorComponent?.setErrorPinned(false);
|
|
705
|
+
this.#pinnedErrorComponent = undefined;
|
|
706
|
+
this.ctx.clearPinnedError();
|
|
707
|
+
if (this.ctx.retryLoader) {
|
|
708
|
+
this.ctx.retryLoader.stop();
|
|
709
|
+
this.ctx.retryLoader = undefined;
|
|
710
|
+
this.ctx.statusContainer.disposeChildren();
|
|
711
|
+
}
|
|
712
|
+
this.#cancelIdleCompaction();
|
|
713
|
+
this.#cancelIdleRecap();
|
|
714
|
+
this.ctx.statusLine.markActivityStart();
|
|
715
|
+
this.#setTerminalProgress(true);
|
|
716
|
+
this.ctx.ensureLoadingAnimation();
|
|
717
|
+
setTerminalTitleState("working");
|
|
718
|
+
this.ctx.ui.requestRender();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async #handleMessageStart(event: Extract<AgentSessionEvent, { type: "message_start" }>): Promise<void> {
|
|
722
|
+
this.#ensureWorkingLoaderWhileStreaming();
|
|
723
|
+
if (event.message.role === "hookMessage" || event.message.role === "custom") {
|
|
724
|
+
const signature = `${event.message.role}:${event.message.customType}:${event.message.timestamp}`;
|
|
725
|
+
if (this.#renderedCustomMessages.has(signature)) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
this.#renderedCustomMessages.add(signature);
|
|
729
|
+
this.#resetReadGroup();
|
|
730
|
+
this.ctx.addMessageToChat(event.message);
|
|
731
|
+
// Queued custom-message chips are derived from the agent queue; refresh the
|
|
732
|
+
// pending bar when the queued custom is consumed so the chip disappears
|
|
733
|
+
// immediately.
|
|
734
|
+
if (event.message.role === "custom" && readQueueChipText(event.message.details)) {
|
|
735
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
736
|
+
}
|
|
737
|
+
this.ctx.ui.requestRender();
|
|
738
|
+
} else if (event.message.role === "user") {
|
|
739
|
+
vocalizer.clear();
|
|
740
|
+
const textContent = this.ctx.getUserMessageText(event.message);
|
|
741
|
+
const imageBlocks =
|
|
742
|
+
typeof event.message.content === "string"
|
|
743
|
+
? []
|
|
744
|
+
: event.message.content.filter(
|
|
745
|
+
(content): content is ImageContent =>
|
|
746
|
+
content.type === "image" &&
|
|
747
|
+
typeof content.data === "string" &&
|
|
748
|
+
typeof content.mimeType === "string",
|
|
749
|
+
);
|
|
750
|
+
const imageCount = imageBlocks.length;
|
|
751
|
+
const signature = `${textContent}\u0000${imageCount}`;
|
|
752
|
+
|
|
753
|
+
this.#resetReadGroup();
|
|
754
|
+
this.#resolveDisplaceablePoll();
|
|
755
|
+
this.#resolveDisplaceableTodo();
|
|
756
|
+
const wasOptimistic = this.ctx.optimisticUserMessageSignature === signature;
|
|
757
|
+
const matchedLocalSubmission = this.ctx.locallySubmittedUserSignatures.delete(signature);
|
|
758
|
+
const replacesOptimistic =
|
|
759
|
+
this.ctx.optimisticUserMessageSignature !== undefined && !wasOptimistic && !matchedLocalSubmission;
|
|
760
|
+
const wasLocallySubmitted = matchedLocalSubmission || wasOptimistic || replacesOptimistic;
|
|
761
|
+
if (wasOptimistic) {
|
|
762
|
+
this.ctx.clearOptimisticUserMessage();
|
|
763
|
+
} else if (replacesOptimistic) {
|
|
764
|
+
this.ctx.replaceOptimisticUserMessage(event.message);
|
|
765
|
+
} else {
|
|
766
|
+
// Append synchronously: #emit dispatches to this listener fire-and-forget
|
|
767
|
+
// (see AgentSession.#emit), so any await between the user message_start and
|
|
768
|
+
// addMessageToChat lets later events (assistant message_start, tool execution
|
|
769
|
+
// start/end) append their components first and scramble transcript order /
|
|
770
|
+
// live-region block boundaries. addMessageToChat materializes clickable image
|
|
771
|
+
// links via the synchronous putBlobSync fallback, so no await is needed here.
|
|
772
|
+
this.ctx.addMessageToChat(event.message);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Clear the editor only when the submission did not originate from a
|
|
776
|
+
// local submission (optimistic or queued-while-streaming). Both local
|
|
777
|
+
// paths already cleared the editor at submit time; clearing again here
|
|
778
|
+
// would race with the user typing the next prompt while the previous
|
|
779
|
+
// large redraw lands and erase their in-progress draft (#783).
|
|
780
|
+
if (!event.message.synthetic) {
|
|
781
|
+
if (!wasLocallySubmitted) {
|
|
782
|
+
this.ctx.editor.setText("");
|
|
783
|
+
}
|
|
784
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
785
|
+
}
|
|
786
|
+
this.ctx.ui.requestRender();
|
|
787
|
+
} else if (event.message.role === "fileMention") {
|
|
788
|
+
this.#resetReadGroup();
|
|
789
|
+
this.ctx.addMessageToChat(event.message);
|
|
790
|
+
this.ctx.ui.requestRender();
|
|
791
|
+
} else if (event.message.role === "assistant") {
|
|
792
|
+
this.#lastVisibleBlockCount = 0;
|
|
793
|
+
this.#streamedToolCallIdByIndex.clear();
|
|
794
|
+
this.ctx.streamingComponent = createAssistantMessageComponent(this.ctx);
|
|
795
|
+
this.ctx.streamingMessage = event.message;
|
|
796
|
+
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
797
|
+
this.#streamingReveal.begin(
|
|
798
|
+
this.ctx.streamingComponent,
|
|
799
|
+
splitAssistantMessageToolTimeline(this.ctx.streamingMessage).beforeTools,
|
|
800
|
+
);
|
|
801
|
+
this.ctx.ui.requestRender();
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async #handleIrcMessage(event: Extract<AgentSessionEvent, { type: "irc_message" }>): Promise<void> {
|
|
806
|
+
const signature = `${event.message.role}:${event.message.customType}:${event.message.timestamp}`;
|
|
807
|
+
if (this.#renderedCustomMessages.has(signature)) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
this.#renderedCustomMessages.add(signature);
|
|
811
|
+
this.#resetReadGroup();
|
|
812
|
+
const components = this.ctx.addMessageToChat(event.message);
|
|
813
|
+
this.#scheduleIrcExpiry(signature, components);
|
|
814
|
+
this.#enforceIrcCardCap(signature);
|
|
815
|
+
this.ctx.ui.requestRender();
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
#scheduleIrcExpiry(signature: string, components: Component[]): void {
|
|
819
|
+
if (components.length === 0 || this.#ircExpiryTimers.has(signature)) return;
|
|
820
|
+
const timer = setTimeout(() => {
|
|
821
|
+
this.#ircExpiryTimers.delete(signature);
|
|
822
|
+
this.#retireIrcCard(signature);
|
|
823
|
+
}, IRC_MESSAGE_VISIBLE_TTL_MS);
|
|
824
|
+
timer.unref?.();
|
|
825
|
+
this.#ircExpiryTimers.set(signature, timer);
|
|
826
|
+
this.#liveIrcCards.set(signature, components);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Remove an expired/evicted IRC card — but only while it still sits below a
|
|
831
|
+
* live block, where its rows cannot have entered native scrollback. Once
|
|
832
|
+
* everything above it has finalized, its rows may already be committed;
|
|
833
|
+
* removing them then is an interior deletion of the committed prefix, which
|
|
834
|
+
* the engine can only repair by recommitting every row below the gap —
|
|
835
|
+
* exactly the duplicated-block artifact this guard exists to prevent. Such
|
|
836
|
+
* a card simply stays: it is final history, and the window scrolls past it.
|
|
837
|
+
*/
|
|
838
|
+
#retireIrcCard(signature: string): void {
|
|
839
|
+
const components = this.#liveIrcCards.get(signature);
|
|
840
|
+
this.#liveIrcCards.delete(signature);
|
|
841
|
+
if (!components) return;
|
|
842
|
+
let removed = false;
|
|
843
|
+
for (const component of components) {
|
|
844
|
+
if (!this.ctx.chatContainer.isBlockUncommitted(component)) continue;
|
|
845
|
+
this.ctx.chatContainer.removeChild(component);
|
|
846
|
+
removed = true;
|
|
847
|
+
}
|
|
848
|
+
if (removed) this.ctx.ui.requestRender();
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/** Evict oldest live-region cards beyond {@link MAX_LIVE_IRC_CARDS}. */
|
|
852
|
+
#enforceIrcCardCap(latestSignature: string): void {
|
|
853
|
+
while (this.#liveIrcCards.size > MAX_LIVE_IRC_CARDS) {
|
|
854
|
+
const oldest = this.#liveIrcCards.keys().next().value;
|
|
855
|
+
if (oldest === undefined || oldest === latestSignature) return;
|
|
856
|
+
const timer = this.#ircExpiryTimers.get(oldest);
|
|
857
|
+
if (timer) {
|
|
858
|
+
clearTimeout(timer);
|
|
859
|
+
this.#ircExpiryTimers.delete(oldest);
|
|
860
|
+
}
|
|
861
|
+
this.#retireIrcCard(oldest);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Resolve the pending displaceable poll block before the next block lands.
|
|
867
|
+
* A follow-up `hub` call displaces it — the stale "waiting on N jobs" frame
|
|
868
|
+
* is removed so repeated polls read as one persistent poll — while anything
|
|
869
|
+
* else seals it in place as final history. Removal is gated on none of the
|
|
870
|
+
* block's rows having entered native scrollback: rows already on the tape
|
|
871
|
+
* are immutable visual history, so a scrolled-off poll seals instead of
|
|
872
|
+
* being retracted.
|
|
873
|
+
*/
|
|
874
|
+
#resolveDisplaceablePoll(nextToolName?: string): void {
|
|
875
|
+
const previous = this.#displaceablePollComponent;
|
|
876
|
+
if (!previous) return;
|
|
877
|
+
this.#displaceablePollComponent = undefined;
|
|
878
|
+
if (
|
|
879
|
+
nextToolName === "hub" &&
|
|
880
|
+
previous.isDisplaceableBlock() &&
|
|
881
|
+
this.ctx.chatContainer.isBlockUncommitted(previous)
|
|
882
|
+
) {
|
|
883
|
+
this.ctx.chatContainer.removeChild(previous);
|
|
884
|
+
}
|
|
885
|
+
// Sealing stops the waiting-poll spinner and freezes the block (for a
|
|
886
|
+
// just-removed component it only clears the animation timer).
|
|
887
|
+
previous.seal();
|
|
888
|
+
this.ctx.ui.requestRender();
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
#resolveDisplaceableTodo(nextToolName?: string): void {
|
|
892
|
+
const previous = this.#displaceableTodoComponent;
|
|
893
|
+
if (!previous) return;
|
|
894
|
+
if (!previous.isDisplaceableBlock()) {
|
|
895
|
+
this.#displaceableTodoComponent = undefined;
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
if (previous.canBeDisplacedBy(nextToolName)) {
|
|
899
|
+
this.#displaceableTodoComponent = undefined;
|
|
900
|
+
if (this.ctx.chatContainer.isBlockUncommitted(previous)) {
|
|
901
|
+
this.ctx.chatContainer.removeChild(previous);
|
|
902
|
+
}
|
|
903
|
+
previous.seal();
|
|
904
|
+
this.ctx.ui.requestRender();
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
if (nextToolName !== undefined) return;
|
|
908
|
+
this.#displaceableTodoComponent = undefined;
|
|
909
|
+
previous.seal();
|
|
910
|
+
this.ctx.ui.requestRender();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Adopt a rebuilt-tail todo snapshot as the controller's tracked live
|
|
915
|
+
* snapshot. Used by rebuild paths (settings/extensions overlay close, focus
|
|
916
|
+
* attach, /resume) to preserve displacement continuity when a turn is still
|
|
917
|
+
* active — without this, the next same-turn `todo` update would stack
|
|
918
|
+
* another panel because the controller's tracker was reset before rebuild.
|
|
919
|
+
* Drops the candidate when it is no longer a displaceable todo.
|
|
920
|
+
*/
|
|
921
|
+
inheritDisplaceableTodo(component: ToolExecutionComponent | null | undefined): void {
|
|
922
|
+
this.#displaceableTodoComponent = component?.canBeDisplacedBy("todo") ? component : undefined;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
async #handleNotice(event: Extract<AgentSessionEvent, { type: "notice" }>): Promise<void> {
|
|
926
|
+
const message = event.source ? `${event.source}: ${event.message}` : event.message;
|
|
927
|
+
if (event.level === "error") {
|
|
928
|
+
this.ctx.showError(message);
|
|
929
|
+
} else if (event.level === "warning") {
|
|
930
|
+
this.ctx.showWarning(message);
|
|
931
|
+
} else {
|
|
932
|
+
this.ctx.showStatus(message);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Speak streamed assistant output as a side effect of the turn. The mode
|
|
938
|
+
* decides which deltas feed the vocalizer (the vocalizer re-checks enabled):
|
|
939
|
+
* assistant|all speak text; all also speaks thinking; yield speaks nothing
|
|
940
|
+
* live (the final message is spoken at turn end).
|
|
941
|
+
*/
|
|
942
|
+
#vocalizeDelta(event: Extract<AgentSessionEvent, { type: "message_update" }>): void {
|
|
943
|
+
if (!settings.get("speech.enabled")) return;
|
|
944
|
+
const mode = settings.get("speech.mode");
|
|
945
|
+
const delta = event.assistantMessageEvent;
|
|
946
|
+
if (delta.type === "text_delta" && (mode === "assistant" || mode === "all")) {
|
|
947
|
+
vocalizer.pushDelta(delta.delta);
|
|
948
|
+
} else if (delta.type === "thinking_delta" && mode === "all") {
|
|
949
|
+
vocalizer.pushDelta(delta.delta);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* End-of-turn vocalization: yield mode speaks the final assistant message in
|
|
955
|
+
* one shot here (the only mode that is post-hoc); every other mode just makes
|
|
956
|
+
* sure the live buffer's trailing partial gets flushed.
|
|
957
|
+
*/
|
|
958
|
+
#handleTurnEnd(event: Extract<AgentSessionEvent, { type: "turn_end" }>): void {
|
|
959
|
+
if (!settings.get("speech.enabled")) return;
|
|
960
|
+
if (settings.get("speech.mode") !== "yield") {
|
|
961
|
+
vocalizer.flush();
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
if (event.message.role !== "assistant") return;
|
|
965
|
+
if (event.message.stopReason === "aborted") return; // interrupted: never speak the aborted partial
|
|
966
|
+
const text = extractTextContent(event.message);
|
|
967
|
+
if (text) vocalizer.speak(text);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
971
|
+
this.#ensureWorkingLoaderWhileStreaming();
|
|
972
|
+
if (!this.#vocalizedMessageUpdates.delete(event)) {
|
|
973
|
+
this.#vocalizeDelta(event);
|
|
974
|
+
}
|
|
975
|
+
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
976
|
+
const unlockedThinkingVisibility = this.ctx.noteDisplayableThinkingContent(event.message);
|
|
977
|
+
if (unlockedThinkingVisibility) {
|
|
978
|
+
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.effectiveHideThinkingBlock);
|
|
979
|
+
this.#streamingReveal.resyncVisibility();
|
|
980
|
+
}
|
|
981
|
+
this.ctx.streamingMessage = event.message;
|
|
982
|
+
const timeline = splitAssistantMessageToolTimeline(this.ctx.streamingMessage);
|
|
983
|
+
this.#streamingReveal.setTarget(timeline.beforeTools);
|
|
984
|
+
|
|
985
|
+
const visibleBlockCount = this.ctx.streamingMessage.content.filter(
|
|
986
|
+
content =>
|
|
987
|
+
(content.type === "text" && canonicalizeMessage(content.text)) ||
|
|
988
|
+
(content.type === "thinking" && canonicalizeMessage(content.thinking)),
|
|
989
|
+
).length;
|
|
990
|
+
if (visibleBlockCount > this.#lastVisibleBlockCount) {
|
|
991
|
+
this.#resetReadGroup();
|
|
992
|
+
this.#lastVisibleBlockCount = visibleBlockCount;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Content blocks stream sequentially: a toolCall block can only begin
|
|
996
|
+
// after every preceding thinking/text block has closed, and the
|
|
997
|
+
// reveal's setTarget above force-completes the visible text for
|
|
998
|
+
// toolCall messages. Finalize the assistant block now instead of at
|
|
999
|
+
// message_end so the transcript's commit-safe run can extend through
|
|
1000
|
+
// it into the streaming tool preview below — otherwise a long args
|
|
1001
|
+
// stream (a big write/edit/eval) sits below a still-live block and
|
|
1002
|
+
// can never reach native scrollback: the head of the preview is
|
|
1003
|
+
// neither committed nor on screen and the transcript reads as cut.
|
|
1004
|
+
if (this.ctx.streamingMessage.content.some(content => content.type === "toolCall")) {
|
|
1005
|
+
this.ctx.streamingComponent.markTranscriptBlockFinalized();
|
|
1006
|
+
}
|
|
1007
|
+
for (let contentIndex = 0; contentIndex < this.ctx.streamingMessage.content.length; contentIndex++) {
|
|
1008
|
+
const content = this.ctx.streamingMessage.content[contentIndex]!;
|
|
1009
|
+
if (content.type !== "toolCall") continue;
|
|
1010
|
+
// Re-key the live card when a provider rewrites this block's id
|
|
1011
|
+
// across deltas, so the changed id reuses the existing card
|
|
1012
|
+
// instead of spawning a duplicate (#6879).
|
|
1013
|
+
const priorId = this.#streamedToolCallIdByIndex.get(contentIndex);
|
|
1014
|
+
if (priorId !== undefined && priorId !== content.id) {
|
|
1015
|
+
this.#migrateStreamedToolCallId(priorId, content.id);
|
|
1016
|
+
}
|
|
1017
|
+
this.#streamedToolCallIdByIndex.set(contentIndex, content.id);
|
|
1018
|
+
if (content.name === "read") {
|
|
1019
|
+
if (!readArgsHaveTarget(content.arguments)) {
|
|
1020
|
+
// Args still streaming — defer until path is parseable so we can route to the
|
|
1021
|
+
// read group (files + xd:// devices) vs ToolExecutionComponent (other internal URLs).
|
|
1022
|
+
// Creating either component now would lock the read into the wrong shape.
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if (readArgsCollapseIntoGroup(content.arguments)) {
|
|
1026
|
+
if (!this.ctx.pendingTools.has(content.id)) this.#resolveDisplaceablePoll(content.name);
|
|
1027
|
+
this.#trackReadToolCall(content.id, content.arguments);
|
|
1028
|
+
const component = this.ctx.pendingTools.get(content.id);
|
|
1029
|
+
if (component) {
|
|
1030
|
+
component.updateArgs(content.arguments, content.id);
|
|
1031
|
+
} else {
|
|
1032
|
+
const group = this.#getReadGroup();
|
|
1033
|
+
group.updateArgs(content.arguments, content.id);
|
|
1034
|
+
this.ctx.pendingTools.set(content.id, group);
|
|
1035
|
+
this.#toolTimelineComponents.set(content.id, group);
|
|
1036
|
+
}
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
// Other internal-URL reads fall through to ToolExecutionComponent below.
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Preserve the raw partial JSON only for renderers that need to surface fields before the JSON object closes.
|
|
1043
|
+
// Bash uses this to show inline env assignments during streaming instead of popping them in at completion.
|
|
1044
|
+
// While the JSON is still open, ToolArgsRevealController paces the
|
|
1045
|
+
// reveal (write/edit/bash previews grow smoothly when a slow provider
|
|
1046
|
+
// delivers large batches); once it closes, the final args render
|
|
1047
|
+
// as-is — mirroring how assistant text snaps at message_end.
|
|
1048
|
+
let renderArgs: Record<string, unknown>;
|
|
1049
|
+
const partialJson = getStreamingPartialJson(content);
|
|
1050
|
+
const rawInput = content.customWireName !== undefined;
|
|
1051
|
+
const tool = this.ctx.viewSession.getToolByName(content.name);
|
|
1052
|
+
if (partialJson) {
|
|
1053
|
+
renderArgs = this.#toolArgsReveal.setTarget(content.id, partialJson, {
|
|
1054
|
+
rawInput,
|
|
1055
|
+
exposeRawPartialJson: exposesRawPartialJson(content.name, rawInput, tool),
|
|
1056
|
+
streamingStringKeys: streamingStringKeysForTool(content.name, rawInput),
|
|
1057
|
+
});
|
|
1058
|
+
} else {
|
|
1059
|
+
this.#toolArgsReveal.finish(content.id);
|
|
1060
|
+
renderArgs = content.arguments;
|
|
1061
|
+
}
|
|
1062
|
+
// `message_update` is cumulative — every update re-lists all blocks
|
|
1063
|
+
// of the streaming message — so creation must also be guarded by the
|
|
1064
|
+
// timeline map: `pendingTools` loses the id the moment a completion
|
|
1065
|
+
// settles the card, and for server-resolved (Cursor) tools that can
|
|
1066
|
+
// happen while the message is still streaming. Without the second
|
|
1067
|
+
// check the next cumulative update would recreate a card for a call
|
|
1068
|
+
// that already finished, permanently pending.
|
|
1069
|
+
if (!this.ctx.pendingTools.has(content.id) && !this.#toolTimelineComponents.has(content.id)) {
|
|
1070
|
+
this.#resolveDisplaceablePoll(content.name);
|
|
1071
|
+
this.#resetReadGroup();
|
|
1072
|
+
const component = new ToolExecutionComponent(
|
|
1073
|
+
content.name,
|
|
1074
|
+
renderArgs,
|
|
1075
|
+
{
|
|
1076
|
+
snapshots: getFileSnapshotStore(this.ctx.viewSession),
|
|
1077
|
+
clipboard: getEditClipboard(this.ctx.viewSession),
|
|
1078
|
+
showImages: settings.get("terminal.showImages"),
|
|
1079
|
+
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
1080
|
+
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
1081
|
+
},
|
|
1082
|
+
tool,
|
|
1083
|
+
this.ctx.ui,
|
|
1084
|
+
this.ctx.sessionManager.getCwd(),
|
|
1085
|
+
content.id,
|
|
1086
|
+
);
|
|
1087
|
+
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
1088
|
+
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
1089
|
+
this.ctx.chatContainer.addChild(component);
|
|
1090
|
+
this.ctx.pendingTools.set(content.id, component);
|
|
1091
|
+
this.#toolTimelineComponents.set(content.id, component);
|
|
1092
|
+
this.#toolArgsReveal.bind(content.id, component);
|
|
1093
|
+
// A held completion for this call means its `tool_execution_end`
|
|
1094
|
+
// outran this streamed block (see #orphanedToolCompletions).
|
|
1095
|
+
// Attach it now that the card exists so it settles immediately
|
|
1096
|
+
// instead of animating forever. Only the component is settled —
|
|
1097
|
+
// the handler's other side effects already ran on first arrival.
|
|
1098
|
+
const orphan = this.#orphanedToolCompletions.get(content.id);
|
|
1099
|
+
if (orphan) {
|
|
1100
|
+
this.#orphanedToolCompletions.delete(content.id);
|
|
1101
|
+
this.#settleHeldCompletion(component, orphan);
|
|
1102
|
+
}
|
|
1103
|
+
} else {
|
|
1104
|
+
const component = this.ctx.pendingTools.get(content.id);
|
|
1105
|
+
if (component) {
|
|
1106
|
+
component.updateArgs(renderArgs, content.id);
|
|
1107
|
+
this.#toolArgsReveal.bind(content.id, component);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
for (const [toolCallId, segment] of timeline.afterToolCalls) {
|
|
1112
|
+
this.#upsertPostToolAssistantSegment(toolCallId, segment);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Update working message with intent from streamed tool arguments
|
|
1116
|
+
for (const content of this.ctx.streamingMessage.content) {
|
|
1117
|
+
if (content.type !== "toolCall") continue;
|
|
1118
|
+
const args = content.arguments;
|
|
1119
|
+
if (!args || typeof args !== "object") continue;
|
|
1120
|
+
if (INTENT_FIELD in args) {
|
|
1121
|
+
this.#updateWorkingMessageFromIntent(args[INTENT_FIELD]);
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
const tool = this.ctx.viewSession.getToolByName(content.name);
|
|
1125
|
+
if (typeof tool?.intent !== "function") continue;
|
|
1126
|
+
try {
|
|
1127
|
+
const derived = tool.intent(args as never)?.trim();
|
|
1128
|
+
if (derived) {
|
|
1129
|
+
this.#updateWorkingMessageFromIntent(derived);
|
|
1130
|
+
}
|
|
1131
|
+
} catch {
|
|
1132
|
+
// intent function must never break the UI
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
this.ctx.ui.requestRender();
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
async #handleMessageEnd(event: Extract<AgentSessionEvent, { type: "message_end" }>): Promise<void> {
|
|
1141
|
+
if (event.message.role === "user") return;
|
|
1142
|
+
const unlockedThinkingVisibility =
|
|
1143
|
+
event.message.role === "assistant" && this.ctx.noteDisplayableThinkingContent(event.message);
|
|
1144
|
+
if (unlockedThinkingVisibility && this.ctx.streamingComponent) {
|
|
1145
|
+
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.effectiveHideThinkingBlock);
|
|
1146
|
+
this.#streamingReveal.resyncVisibility();
|
|
1147
|
+
}
|
|
1148
|
+
if (event.message.role === "assistant" && settings.get("speech.enabled")) {
|
|
1149
|
+
if (event.message.stopReason === "aborted") {
|
|
1150
|
+
// Esc / Ctrl+C / interrupt: stop speaking now and drop the trailing partial.
|
|
1151
|
+
vocalizer.clear();
|
|
1152
|
+
} else {
|
|
1153
|
+
const mode = settings.get("speech.mode");
|
|
1154
|
+
// Speak the last partial sentence of a completed message; yield mode
|
|
1155
|
+
// instead speaks the whole final message at turn end.
|
|
1156
|
+
if (mode === "assistant" || mode === "all") vocalizer.flush();
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
1160
|
+
this.ctx.streamingMessage = event.message;
|
|
1161
|
+
this.#streamingReveal.stop();
|
|
1162
|
+
this.#toolArgsReveal.flushAll();
|
|
1163
|
+
let errorMessage: string | undefined;
|
|
1164
|
+
const aborted = this.ctx.streamingMessage.stopReason === "aborted";
|
|
1165
|
+
const silentlyAborted = aborted && isSilentAbort(this.ctx.streamingMessage);
|
|
1166
|
+
const ttsrSilenced = aborted && this.ctx.viewSession.isTtsrAbortPending;
|
|
1167
|
+
if (aborted && !silentlyAborted && !ttsrSilenced) {
|
|
1168
|
+
// Resolve the operator-facing label: a user-interrupt (Esc) abort
|
|
1169
|
+
// carries USER_INTERRUPT_LABEL on errorMessage (threaded through the
|
|
1170
|
+
// AbortController), which is preserved verbatim; any other abort with
|
|
1171
|
+
// no threaded reason falls back to the retry-aware generic label.
|
|
1172
|
+
// AgentSession.#handleAgentEvent already stamped SILENT_ABORT_MARKER for
|
|
1173
|
+
// the plan-compact transition before this controller ran, so reaching
|
|
1174
|
+
// this branch implies the abort was NOT a silent internal transition.
|
|
1175
|
+
errorMessage = resolveAbortLabel(this.ctx.streamingMessage, this.ctx.viewSession.retryAttempt);
|
|
1176
|
+
this.ctx.streamingMessage.errorMessage = errorMessage;
|
|
1177
|
+
}
|
|
1178
|
+
const displayMessage: AssistantMessage =
|
|
1179
|
+
silentlyAborted || ttsrSilenced
|
|
1180
|
+
? {
|
|
1181
|
+
// Silence the streaming render by downgrading stopReason to "stop" for
|
|
1182
|
+
// display only — does NOT mutate the persisted message's stopReason
|
|
1183
|
+
// (the marker on errorMessage drives replay-side suppression).
|
|
1184
|
+
...this.ctx.streamingMessage,
|
|
1185
|
+
stopReason: "stop",
|
|
1186
|
+
}
|
|
1187
|
+
: this.ctx.streamingMessage;
|
|
1188
|
+
const displayTimeline = splitAssistantMessageToolTimeline(displayMessage);
|
|
1189
|
+
this.ctx.streamingComponent.updateContent(displayTimeline.beforeTools);
|
|
1190
|
+
|
|
1191
|
+
if (this.ctx.streamingMessage.stopReason !== "aborted" && this.ctx.streamingMessage.stopReason !== "error") {
|
|
1192
|
+
for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
|
|
1193
|
+
component.setArgsComplete(toolCallId);
|
|
1194
|
+
}
|
|
1195
|
+
} else {
|
|
1196
|
+
// The turn ended without running these calls. What happens next
|
|
1197
|
+
// decides whether their cards should vanish or stay:
|
|
1198
|
+
// • TTSR rewind — known NOW via `isTtsrAbortPending` — re-runs the
|
|
1199
|
+
// turn and re-streams fresh cards, so retract the uncommitted
|
|
1200
|
+
// never-run cards here and swallow the synthetic completions
|
|
1201
|
+
// agent-loop emits for them, else the call renders twice (#6879).
|
|
1202
|
+
// • A plain terminal error/abort, or an auto-retry (whose
|
|
1203
|
+
// supersession is only known later at `auto_retry_start`), must
|
|
1204
|
+
// NOT be retracted here: agent-loop emits a synthetic
|
|
1205
|
+
// `tool_execution_end` right after this that settles each card
|
|
1206
|
+
// into a visible aborted/error result. Leave them so the terminal
|
|
1207
|
+
// failure stays visible; `#handleAutoRetryStart` removes them only
|
|
1208
|
+
// if a retry actually supersedes them (Codex review on #6881).
|
|
1209
|
+
const supersededByRewind =
|
|
1210
|
+
this.ctx.streamingMessage.stopReason === "aborted" && this.ctx.viewSession.isTtsrAbortPending;
|
|
1211
|
+
if (supersededByRewind) {
|
|
1212
|
+
for (const [toolCallId, component] of Array.from(this.ctx.pendingTools.entries())) {
|
|
1213
|
+
if (this.#backgroundTaskCallIds.has(toolCallId)) continue;
|
|
1214
|
+
if (
|
|
1215
|
+
!(component instanceof ToolExecutionComponent) &&
|
|
1216
|
+
!(component instanceof ReadToolGroupComponent)
|
|
1217
|
+
) {
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
if (this.ctx.chatContainer.isBlockUncommitted(component)) {
|
|
1221
|
+
this.#retractToolCardEntry(toolCallId, component);
|
|
1222
|
+
this.#retractedToolCallIds.add(toolCallId);
|
|
1223
|
+
} else {
|
|
1224
|
+
component.seal();
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
// These calls will never run this attempt, so the tracked waiting
|
|
1229
|
+
// poll cannot be displaced anymore — freeze it in place.
|
|
1230
|
+
this.#resolveDisplaceablePoll();
|
|
1231
|
+
}
|
|
1232
|
+
// Surface a prompt-cache invalidation: if the previous turn cached a
|
|
1233
|
+
// meaningful prefix and this request read none of it back, flag the turn.
|
|
1234
|
+
const usage = event.message.usage;
|
|
1235
|
+
if (usage.cacheRead + usage.cacheWrite + usage.input > 0) {
|
|
1236
|
+
if (settings.get("display.cacheMissMarker")) {
|
|
1237
|
+
const invalidation = detectCacheInvalidation(this.ctx.lastAssistantUsage, usage);
|
|
1238
|
+
if (invalidation) this.ctx.streamingComponent.setCacheInvalidation(invalidation);
|
|
1239
|
+
}
|
|
1240
|
+
this.ctx.lastAssistantUsage = usage;
|
|
1241
|
+
}
|
|
1242
|
+
this.ctx.streamingComponent.markTranscriptBlockFinalized();
|
|
1243
|
+
let lastPostToolAssistantComponent: AssistantMessageComponent | undefined;
|
|
1244
|
+
for (const [toolCallId, segment] of displayTimeline.afterToolCalls) {
|
|
1245
|
+
const component = this.#upsertPostToolAssistantSegment(toolCallId, segment);
|
|
1246
|
+
component?.markTranscriptBlockFinalized();
|
|
1247
|
+
if (component) lastPostToolAssistantComponent = component;
|
|
1248
|
+
}
|
|
1249
|
+
this.#lastAssistantComponent = lastPostToolAssistantComponent ?? this.ctx.streamingComponent;
|
|
1250
|
+
if (settings.get("display.showTokenUsage") && assistantUsageIsBilled(event.message.usage)) {
|
|
1251
|
+
const readCallIds = groupedReadUsageCallIds(event.message);
|
|
1252
|
+
const usageAttached =
|
|
1253
|
+
readCallIds !== undefined &&
|
|
1254
|
+
(this.#lastReadGroup?.attachUsage(
|
|
1255
|
+
readCallIds,
|
|
1256
|
+
event.message.usage,
|
|
1257
|
+
event.message.duration,
|
|
1258
|
+
event.message.ttft,
|
|
1259
|
+
event.message.timestamp,
|
|
1260
|
+
) ??
|
|
1261
|
+
false);
|
|
1262
|
+
if (!usageAttached) {
|
|
1263
|
+
this.#resetReadGroup();
|
|
1264
|
+
this.ctx.chatContainer.addChild(
|
|
1265
|
+
createUsageRowBlock(
|
|
1266
|
+
event.message.usage,
|
|
1267
|
+
event.message.duration,
|
|
1268
|
+
event.message.ttft,
|
|
1269
|
+
event.message.timestamp,
|
|
1270
|
+
),
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (displayMessage === event.message) {
|
|
1275
|
+
this.ctx.transcriptMessageComponents.set(event.message, this.ctx.streamingComponent);
|
|
1276
|
+
}
|
|
1277
|
+
this.ctx.streamingComponent = undefined;
|
|
1278
|
+
this.ctx.streamingMessage = undefined;
|
|
1279
|
+
// Pin a turn-ending provider error (e.g. Anthropic content-filter block)
|
|
1280
|
+
// above the editor so it survives transcript scroll. Cleared at the next
|
|
1281
|
+
// turn's agent_start. Suppress the transcript's inline `Error: …` line for
|
|
1282
|
+
// the same message while pinned so the error isn't rendered twice.
|
|
1283
|
+
if (event.message.stopReason === "error" && event.message.errorMessage && !isSilentAbort(event.message)) {
|
|
1284
|
+
this.#lastAssistantComponent?.setErrorPinned(true);
|
|
1285
|
+
this.#pinnedErrorComponent = this.#lastAssistantComponent;
|
|
1286
|
+
this.ctx.showPinnedError(event.message.errorMessage);
|
|
1287
|
+
}
|
|
1288
|
+
this.ctx.statusLine.invalidate();
|
|
1289
|
+
this.ctx.ui.requestRender();
|
|
1290
|
+
}
|
|
1291
|
+
this.ctx.ui.requestRender();
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
async #handleToolExecutionStart(event: Extract<AgentSessionEvent, { type: "tool_execution_start" }>): Promise<void> {
|
|
1295
|
+
if (this.#retractedToolCallIds.has(event.toolCallId)) return;
|
|
1296
|
+
this.#ensureWorkingLoaderWhileStreaming();
|
|
1297
|
+
this.#updateWorkingMessageFromIntent(event.intent);
|
|
1298
|
+
if (event.toolName === "ask" || this.#toolWillPromptForApproval(event.toolName, event.args)) {
|
|
1299
|
+
this.#approvalAttentionToolCallIds.add(event.toolCallId);
|
|
1300
|
+
setTerminalTitleState("attention");
|
|
1301
|
+
}
|
|
1302
|
+
this.#resolveDisplaceablePoll(event.toolName);
|
|
1303
|
+
if (!this.ctx.pendingTools.has(event.toolCallId)) {
|
|
1304
|
+
if (event.toolName === "read" && readArgsCollapseIntoGroup(event.args)) {
|
|
1305
|
+
this.#trackReadToolCall(event.toolCallId, event.args);
|
|
1306
|
+
const component = this.ctx.pendingTools.get(event.toolCallId);
|
|
1307
|
+
if (component) {
|
|
1308
|
+
component.updateArgs(event.args, event.toolCallId);
|
|
1309
|
+
} else {
|
|
1310
|
+
const group = this.#getReadGroup();
|
|
1311
|
+
group.updateArgs(event.args, event.toolCallId);
|
|
1312
|
+
this.ctx.pendingTools.set(event.toolCallId, group);
|
|
1313
|
+
this.#toolTimelineComponents.set(event.toolCallId, group);
|
|
1314
|
+
}
|
|
1315
|
+
this.ctx.ui.requestRender();
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
this.#resetReadGroup();
|
|
1320
|
+
const tool = this.ctx.viewSession.getToolByName(event.toolName);
|
|
1321
|
+
const component = new ToolExecutionComponent(
|
|
1322
|
+
event.toolName,
|
|
1323
|
+
event.args,
|
|
1324
|
+
{
|
|
1325
|
+
snapshots: getFileSnapshotStore(this.ctx.viewSession),
|
|
1326
|
+
clipboard: getEditClipboard(this.ctx.viewSession),
|
|
1327
|
+
showImages: settings.get("terminal.showImages"),
|
|
1328
|
+
editFuzzyThreshold: settings.get("edit.fuzzyThreshold"),
|
|
1329
|
+
editAllowFuzzy: settings.get("edit.fuzzyMatch"),
|
|
1330
|
+
liveRegion: this.ctx.chatContainer,
|
|
1331
|
+
},
|
|
1332
|
+
tool,
|
|
1333
|
+
this.ctx.ui,
|
|
1334
|
+
this.ctx.sessionManager.getCwd(),
|
|
1335
|
+
event.toolCallId,
|
|
1336
|
+
);
|
|
1337
|
+
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
1338
|
+
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
1339
|
+
this.ctx.chatContainer.addChild(component);
|
|
1340
|
+
this.ctx.pendingTools.set(event.toolCallId, component);
|
|
1341
|
+
this.#toolTimelineComponents.set(event.toolCallId, component);
|
|
1342
|
+
this.ctx.ui.requestRender();
|
|
1343
|
+
} else {
|
|
1344
|
+
// The tool is about to run, so its arguments are final and validated.
|
|
1345
|
+
// A pending component created while args streamed (message_update) may
|
|
1346
|
+
// still show a mid-reveal prefix — or, when the closing full-args
|
|
1347
|
+
// `message_update` never lands (smooth-streaming off leaving the
|
|
1348
|
+
// throttled `arguments` stale, an owned-dialect projector, or a
|
|
1349
|
+
// superseded/aborted turn that still executes the call), a stale body
|
|
1350
|
+
// the result render then freezes at its `…` placeholder. Reconcile the
|
|
1351
|
+
// authoritative args here and drop any live reveal so a late tick can't
|
|
1352
|
+
// re-truncate them: tool_execution_start is the one event every
|
|
1353
|
+
// execution path emits with the full args immediately before the result.
|
|
1354
|
+
this.#toolArgsReveal.finish(event.toolCallId);
|
|
1355
|
+
const component = this.ctx.pendingTools.get(event.toolCallId);
|
|
1356
|
+
if (component && typeof component.updateArgs === "function") {
|
|
1357
|
+
component.updateArgs(event.args, event.toolCallId);
|
|
1358
|
+
if (typeof component.setArgsComplete === "function") {
|
|
1359
|
+
component.setArgsComplete(event.toolCallId);
|
|
1360
|
+
}
|
|
1361
|
+
this.ctx.ui.requestRender();
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Whether this tool call will block on an approval prompt before executing.
|
|
1368
|
+
* The extension wrapper waits on `uiContext.select(...)` after emitting
|
|
1369
|
+
* `tool_execution_start`, so an approval-mode / per-tool `prompt` policy is
|
|
1370
|
+
* user-blocking — the title should read `attention`, not `working`. Mirrors
|
|
1371
|
+
* the wrapper's `resolveApproval` inputs (approvalMode + tools.approval); uses
|
|
1372
|
+
* `resolveApproval` rather than `requiresApproval` so a `deny` policy does not
|
|
1373
|
+
* throw in the render path.
|
|
1374
|
+
*/
|
|
1375
|
+
#toolWillPromptForApproval(toolName: string, args: unknown): boolean {
|
|
1376
|
+
const tool = this.ctx.viewSession.getToolByName(toolName);
|
|
1377
|
+
if (!tool) return false;
|
|
1378
|
+
const mode = (settings.get("tools.approvalMode") ?? "yolo") as ApprovalMode;
|
|
1379
|
+
const userPolicies = (settings.get("tools.approval") ?? {}) as Record<string, unknown>;
|
|
1380
|
+
return resolveApproval(tool, args, mode, userPolicies).policy === "prompt";
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
async #handleToolExecutionUpdate(
|
|
1384
|
+
event: Extract<AgentSessionEvent, { type: "tool_execution_update" }>,
|
|
1385
|
+
): Promise<void> {
|
|
1386
|
+
this.#ensureWorkingLoaderWhileStreaming();
|
|
1387
|
+
const component = this.ctx.pendingTools.get(event.toolCallId);
|
|
1388
|
+
if (component) {
|
|
1389
|
+
const asyncState = (event.partialResult.details as { async?: { state?: string } } | undefined)?.async?.state;
|
|
1390
|
+
const isFinalAsyncState = asyncState === "completed" || asyncState === "failed";
|
|
1391
|
+
// A final async snapshot is terminal only for a parked background
|
|
1392
|
+
// block (the call already returned and was kept alive for its jobs).
|
|
1393
|
+
// While the call is still executing — a mixed blocking+async task
|
|
1394
|
+
// call whose jobs settle before its blocking subset — treat it as a
|
|
1395
|
+
// partial frame: `tool_execution_end` still owns the terminal result.
|
|
1396
|
+
const isTerminal = isFinalAsyncState && this.#backgroundTaskCallIds.has(event.toolCallId);
|
|
1397
|
+
component.updateResult(
|
|
1398
|
+
{ ...event.partialResult, isError: asyncState === "failed" },
|
|
1399
|
+
!isTerminal,
|
|
1400
|
+
event.toolCallId,
|
|
1401
|
+
);
|
|
1402
|
+
if (isTerminal) {
|
|
1403
|
+
this.ctx.pendingTools.delete(event.toolCallId);
|
|
1404
|
+
this.#backgroundTaskCallIds.delete(event.toolCallId);
|
|
1405
|
+
}
|
|
1406
|
+
this.ctx.ui.requestRender();
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Attach a held completion to the component that was created for it after
|
|
1412
|
+
* the fact (see {@link #orphanedToolCompletions}) and settle the card.
|
|
1413
|
+
*
|
|
1414
|
+
* Deliberately NOT a re-entry into `#handleToolExecutionEnd`: every
|
|
1415
|
+
* user-facing side effect of that handler — the todo panel refresh, the
|
|
1416
|
+
* failure warning, plan approval, the terminal-title transition — already
|
|
1417
|
+
* ran when the completion first arrived. Replaying the whole handler would
|
|
1418
|
+
* show the same warning twice and re-push an identical `setTodos`. Only the
|
|
1419
|
+
* component half was missed, because the component did not exist yet.
|
|
1420
|
+
*/
|
|
1421
|
+
#settleHeldCompletion(
|
|
1422
|
+
component: ToolExecutionHandle,
|
|
1423
|
+
event: Extract<AgentSessionEvent, { type: "tool_execution_end" }>,
|
|
1424
|
+
): void {
|
|
1425
|
+
component.updateResult({ ...event.result, isError: event.isError }, false, event.toolCallId);
|
|
1426
|
+
this.ctx.pendingTools.delete(event.toolCallId);
|
|
1427
|
+
if (
|
|
1428
|
+
component instanceof ToolExecutionComponent &&
|
|
1429
|
+
component.isDisplaceableBlock() &&
|
|
1430
|
+
event.toolName === "todo" &&
|
|
1431
|
+
component.canBeDisplacedBy("todo")
|
|
1432
|
+
) {
|
|
1433
|
+
// Mirrors the displacement bookkeeping in `#handleToolExecutionEnd`:
|
|
1434
|
+
// a successful snapshot supersedes the previous live panel.
|
|
1435
|
+
const previous = this.#displaceableTodoComponent;
|
|
1436
|
+
if (previous && previous !== component && previous.isDisplaceableBlock()) {
|
|
1437
|
+
this.#displaceableTodoComponent = undefined;
|
|
1438
|
+
if (this.ctx.chatContainer.isBlockUncommitted(previous)) {
|
|
1439
|
+
this.ctx.chatContainer.removeChild(previous);
|
|
1440
|
+
}
|
|
1441
|
+
previous.seal();
|
|
1442
|
+
}
|
|
1443
|
+
this.#displaceableTodoComponent = component;
|
|
1444
|
+
}
|
|
1445
|
+
this.ctx.ui.requestRender();
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
async #handleToolExecutionEnd(event: Extract<AgentSessionEvent, { type: "tool_execution_end" }>): Promise<void> {
|
|
1449
|
+
// `createAbortedToolResult` emits start/end after an error/aborted
|
|
1450
|
+
// assistant message. The matching card was deliberately retracted at
|
|
1451
|
+
// message_end; consume the completion instead of recreating/updating UI.
|
|
1452
|
+
if (this.#retractedToolCallIds.delete(event.toolCallId)) return;
|
|
1453
|
+
// A synthetic aborted/error completion (agent-loop's placeholder for a
|
|
1454
|
+
// never-run call on a terminal error/abort) settles the card in place so a
|
|
1455
|
+
// terminal failure stays visible. Remember it so `#handleAutoRetryStart`
|
|
1456
|
+
// can remove it if an auto-retry then supersedes the turn (Codex review on
|
|
1457
|
+
// #6881). Capture the live component now — the settle path below drops it
|
|
1458
|
+
// from `pendingTools` but leaves it in the transcript.
|
|
1459
|
+
const syntheticFailureDetails = event.result.details as { __synthetic?: boolean; source?: string } | undefined;
|
|
1460
|
+
const syntheticFailureCard =
|
|
1461
|
+
syntheticFailureDetails?.__synthetic === true &&
|
|
1462
|
+
(syntheticFailureDetails.source === "assistant_stop_error" ||
|
|
1463
|
+
syntheticFailureDetails.source === "assistant_stop_aborted")
|
|
1464
|
+
? this.ctx.pendingTools.get(event.toolCallId)
|
|
1465
|
+
: undefined;
|
|
1466
|
+
// A transient overlay (auto-compaction / auto-retry / handoff) that ran
|
|
1467
|
+
// between this tool's start and end could have detached the working
|
|
1468
|
+
// loader. `tool_execution_update` already reconciles this so the spinner
|
|
1469
|
+
// reappears mid-tool; mirror it here so subagent (`task`) completions —
|
|
1470
|
+
// which only fire `tool_execution_end`, never `_update` — do not leave
|
|
1471
|
+
// the UI looking idle while the session keeps streaming (#3857).
|
|
1472
|
+
this.#ensureWorkingLoaderWhileStreaming();
|
|
1473
|
+
// Return to `working` only when the LAST outstanding user-blocking prompt
|
|
1474
|
+
// resolves: with queued approval prompts (always-ask/write), the first tool
|
|
1475
|
+
// to finish must not clear the attention signal while another prompt still
|
|
1476
|
+
// waits. `ask` ids are in the set too (added at tool_execution_start), so
|
|
1477
|
+
// the delete also covers them without leaking ids until turn end.
|
|
1478
|
+
if (
|
|
1479
|
+
this.#approvalAttentionToolCallIds.delete(event.toolCallId) &&
|
|
1480
|
+
this.#approvalAttentionToolCallIds.size === 0
|
|
1481
|
+
) {
|
|
1482
|
+
setTerminalTitleState("working");
|
|
1483
|
+
}
|
|
1484
|
+
if (event.toolName === "read") {
|
|
1485
|
+
if (this.#inlineReadToolImages(event.toolCallId, event.result)) {
|
|
1486
|
+
const component = this.ctx.pendingTools.get(event.toolCallId);
|
|
1487
|
+
if (component) {
|
|
1488
|
+
component.updateResult({ ...event.result, isError: event.isError }, false, event.toolCallId);
|
|
1489
|
+
this.ctx.pendingTools.delete(event.toolCallId);
|
|
1490
|
+
}
|
|
1491
|
+
this.#clearReadToolCall(event.toolCallId);
|
|
1492
|
+
this.ctx.ui.requestRender();
|
|
1493
|
+
} else {
|
|
1494
|
+
let component = this.ctx.pendingTools.get(event.toolCallId);
|
|
1495
|
+
if (!component) {
|
|
1496
|
+
// A persisted result can win a mid-stream transcript rebuild
|
|
1497
|
+
// before this live completion handler runs. Rebuild removes the
|
|
1498
|
+
// pending handle and replay owns the completed card, but the
|
|
1499
|
+
// original timeline entry remains as proof that a card already
|
|
1500
|
+
// existed. Do not create a fallback read group beside replay
|
|
1501
|
+
// (#6879); the fallback is only for a completion that genuinely
|
|
1502
|
+
// outran every streamed card.
|
|
1503
|
+
if (this.#toolTimelineComponents.has(event.toolCallId)) {
|
|
1504
|
+
this.#clearReadToolCall(event.toolCallId);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const group = this.#getReadGroup();
|
|
1508
|
+
const args = this.#readToolCallArgs.get(event.toolCallId);
|
|
1509
|
+
if (args) {
|
|
1510
|
+
group.updateArgs(args, event.toolCallId);
|
|
1511
|
+
}
|
|
1512
|
+
component = group;
|
|
1513
|
+
this.ctx.pendingTools.set(event.toolCallId, group);
|
|
1514
|
+
}
|
|
1515
|
+
component.updateResult({ ...event.result, isError: event.isError }, false, event.toolCallId);
|
|
1516
|
+
this.ctx.pendingTools.delete(event.toolCallId);
|
|
1517
|
+
this.#clearReadToolCall(event.toolCallId);
|
|
1518
|
+
this.ctx.ui.requestRender();
|
|
1519
|
+
}
|
|
1520
|
+
} else {
|
|
1521
|
+
const component = this.ctx.pendingTools.get(event.toolCallId);
|
|
1522
|
+
if (component) {
|
|
1523
|
+
const asyncState = (event.result.details as { async?: { state?: string } } | undefined)?.async?.state;
|
|
1524
|
+
const isBackgroundTask = event.toolName === "task" && asyncState === "running";
|
|
1525
|
+
component.updateResult({ ...event.result, isError: event.isError }, isBackgroundTask, event.toolCallId);
|
|
1526
|
+
if (isBackgroundTask) {
|
|
1527
|
+
this.#backgroundTaskCallIds.add(event.toolCallId);
|
|
1528
|
+
} else {
|
|
1529
|
+
this.ctx.pendingTools.delete(event.toolCallId);
|
|
1530
|
+
this.#backgroundTaskCallIds.delete(event.toolCallId);
|
|
1531
|
+
}
|
|
1532
|
+
if (component instanceof ToolExecutionComponent && component.isDisplaceableBlock()) {
|
|
1533
|
+
if (event.toolName === "hub" && component.canBeDisplacedBy("hub")) {
|
|
1534
|
+
// Remember the waiting poll so the next `hub` call can displace it.
|
|
1535
|
+
this.#displaceablePollComponent = component;
|
|
1536
|
+
} else if (event.toolName === "todo" && component.canBeDisplacedBy("todo")) {
|
|
1537
|
+
// Successful todo update supersedes the prior live snapshot. A failed
|
|
1538
|
+
// follow-up never reaches this branch (canBeDisplacedBy("todo") returns
|
|
1539
|
+
// false for errored results), so the last-good panel stays on screen.
|
|
1540
|
+
const previous = this.#displaceableTodoComponent;
|
|
1541
|
+
if (previous && previous !== component && previous.isDisplaceableBlock()) {
|
|
1542
|
+
this.#displaceableTodoComponent = undefined;
|
|
1543
|
+
if (this.ctx.chatContainer.isBlockUncommitted(previous)) {
|
|
1544
|
+
this.ctx.chatContainer.removeChild(previous);
|
|
1545
|
+
}
|
|
1546
|
+
previous.seal();
|
|
1547
|
+
}
|
|
1548
|
+
this.#displaceableTodoComponent = component;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
this.ctx.ui.requestRender();
|
|
1552
|
+
} else if (event.toolName === "todo") {
|
|
1553
|
+
// No component yet: the streamed block that creates the card has not
|
|
1554
|
+
// been delivered (see #orphanedToolCompletions). Hold the completion
|
|
1555
|
+
// for replay instead of dropping it — scoped to `todo`, the only
|
|
1556
|
+
// tool whose completion is emitted synchronously mid-parse. The
|
|
1557
|
+
// panel/warning side effects below still run NOW, on first arrival;
|
|
1558
|
+
// the replay settles only the component
|
|
1559
|
+
// (`#settleHeldCompletion`), so neither is repeated.
|
|
1560
|
+
this.#orphanedToolCompletions.set(event.toolCallId, event);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (syntheticFailureCard) this.#syntheticFailureCards.set(event.toolCallId, syntheticFailureCard);
|
|
1564
|
+
// Update todo display when todo tool completes
|
|
1565
|
+
if (event.toolName === "todo" && !event.isError) {
|
|
1566
|
+
const details = event.result.details as { phases?: TodoPhase[] } | undefined;
|
|
1567
|
+
if (details?.phases) {
|
|
1568
|
+
this.ctx.setTodos(details.phases);
|
|
1569
|
+
}
|
|
1570
|
+
} else if (event.toolName === "todo" && event.isError) {
|
|
1571
|
+
const textContent = event.result.content.find(
|
|
1572
|
+
(content: { type: string; text?: string }) => content.type === "text",
|
|
1573
|
+
)?.text;
|
|
1574
|
+
// This text can be a provider error copied verbatim off the wire (the
|
|
1575
|
+
// Cursor todo bridge forwards the server's string), so it may carry
|
|
1576
|
+
// ANSI escapes, other C0/C1 controls, tabs, newlines, or a line far
|
|
1577
|
+
// wider than the terminal. `showWarning` renders through a plain
|
|
1578
|
+
// `Text`, which strips none of that — an escape reaches the terminal
|
|
1579
|
+
// and can repaint outside the row. `sanitizeText` drops the control
|
|
1580
|
+
// sequences (and returns the same reference when there are none),
|
|
1581
|
+
// then `previewLine` collapses the remaining whitespace and bounds
|
|
1582
|
+
// the width. Sanitizing first matters: truncating before stripping
|
|
1583
|
+
// can cut an escape mid-sequence and leave a dangling introducer.
|
|
1584
|
+
//
|
|
1585
|
+
// This is the render boundary, not the persisted result: the stored
|
|
1586
|
+
// error stays full-fidelity for the transcript and for replays.
|
|
1587
|
+
const detail = textContent ? previewLine(sanitizeText(textContent), TRUNCATE_LENGTHS.LINE) : "";
|
|
1588
|
+
this.ctx.showWarning(
|
|
1589
|
+
`Todo update failed${detail ? `: ${detail}` : ". Progress may be stale until todo succeeds."}`,
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
// Plan approval rides a `write` to xd://propose: the dispatch metadata on
|
|
1593
|
+
// the write details carries the approval payload as `inner`.
|
|
1594
|
+
if (!event.isError) {
|
|
1595
|
+
const dispatch = writeDeviceDispatch(event.toolName, event.result);
|
|
1596
|
+
const details =
|
|
1597
|
+
dispatch?.tool === PROPOSE_DEVICE_NAME && dispatch.mode === "execute" ? dispatch.inner : undefined;
|
|
1598
|
+
if (
|
|
1599
|
+
details &&
|
|
1600
|
+
typeof details === "object" &&
|
|
1601
|
+
"planFilePath" in details &&
|
|
1602
|
+
"title" in details &&
|
|
1603
|
+
"planExists" in details &&
|
|
1604
|
+
typeof details.planFilePath === "string" &&
|
|
1605
|
+
typeof details.title === "string" &&
|
|
1606
|
+
typeof details.planExists === "boolean"
|
|
1607
|
+
) {
|
|
1608
|
+
// Dispatch the approval WITHOUT blocking the serialized event
|
|
1609
|
+
// dispatch chain. `handlePlanApproval` -> `#approvePlan` awaits
|
|
1610
|
+
// `session.prompt` for the ENTIRE approved-execution turn; awaiting
|
|
1611
|
+
// it here (this handler runs inside `#runSerialized`) would hold the
|
|
1612
|
+
// single dispatch link for the whole run, so the run's own
|
|
1613
|
+
// agent_start / message_start / tool / coalesced message_update
|
|
1614
|
+
// events queue behind it on `#dispatchTail` and the chat stays blank
|
|
1615
|
+
// until execution finishes (issue #7684, follow-up to #5688 which
|
|
1616
|
+
// only closed the overlay). Detaching frees the link the moment this
|
|
1617
|
+
// handler returns; the approval overlay and the turn's live events
|
|
1618
|
+
// then render. The approval flow surfaces its own failures via
|
|
1619
|
+
// `showError`, so only an unexpected rejection is logged here.
|
|
1620
|
+
void this.ctx
|
|
1621
|
+
.handlePlanApproval({
|
|
1622
|
+
planFilePath: details.planFilePath,
|
|
1623
|
+
title: details.title,
|
|
1624
|
+
planExists: details.planExists,
|
|
1625
|
+
})
|
|
1626
|
+
.catch(err => {
|
|
1627
|
+
logger.warn("Plan approval dispatch failed", {
|
|
1628
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1629
|
+
});
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
async #handleAgentEnd(event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
1635
|
+
// A superseded agent_end: the agent is already streaming a fresh turn, so
|
|
1636
|
+
// this event belongs to a turn that has already been replaced. The session
|
|
1637
|
+
// dispatches to listeners fire-and-forget across an async extension-emit hop
|
|
1638
|
+
// (#emitSessionEvent), so an interrupted turn's agent_end can land AFTER the
|
|
1639
|
+
// resumed turn's agent_start (e.g. any post-turn agent.continue()). Running
|
|
1640
|
+
// the turn-end teardown now would stop the loader the live turn just created,
|
|
1641
|
+
// leaving "Working…" gone while the agent keeps running. The live turn owns
|
|
1642
|
+
// the loader and finalizes it at its own agent_end (isStreaming === false by
|
|
1643
|
+
// then). Mirrors the collab guest's !isStreaming loader reconciler.
|
|
1644
|
+
if (this.ctx.session.isStreaming) return;
|
|
1645
|
+
// A non-terminal settle (`isTerminal: false`) is a scheduling pause, not the
|
|
1646
|
+
// end of the run: an unsuppressed async job (a `/vibe` worker turn, a bash
|
|
1647
|
+
// `async` job, etc.) will re-wake the loop when its result is delivered.
|
|
1648
|
+
// `AgentSession` tags this on the deferred event (see `#hasPendingAsyncWake`
|
|
1649
|
+
// in agent-session.ts). Skip the idle title/loader teardown so the tab keeps
|
|
1650
|
+
// reading "working"; the later terminal `agent_end` performs it. Still flush
|
|
1651
|
+
// a deferred model switch — the plan-mode reconciler queues it to apply once
|
|
1652
|
+
// the current stream ends, and `#finishAgentEnd` is otherwise its only flush
|
|
1653
|
+
// site, so the automatic continuation would otherwise run on the old
|
|
1654
|
+
// model/thinking level until the terminal settle.
|
|
1655
|
+
if (event.isTerminal === false) {
|
|
1656
|
+
await this.ctx.flushPendingModelSwitch();
|
|
1657
|
+
// Reaching here means the first guard passed, so `isStreaming` is already
|
|
1658
|
+
// false: a command issued from now on mounts immediately. Leaving earlier
|
|
1659
|
+
// panels queued would render them out of order, minutes later, after the
|
|
1660
|
+
// user was told they were only waiting for the turn. The transcript is
|
|
1661
|
+
// quiescent at a settle, which is the condition #4806 wanted.
|
|
1662
|
+
this.ctx.flushPendingCommandOutput();
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
setTerminalTitleState("idle");
|
|
1666
|
+
|
|
1667
|
+
await this.#finishAgentEnd(event);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
async #finishAgentEnd(event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
1671
|
+
this.#setTerminalProgress(false);
|
|
1672
|
+
this.ctx.statusLine.markActivityEnd();
|
|
1673
|
+
this.#streamingReveal.stop();
|
|
1674
|
+
this.#toolArgsReveal.flushAll();
|
|
1675
|
+
if (this.ctx.loadingAnimation) {
|
|
1676
|
+
this.ctx.loadingAnimation.stop();
|
|
1677
|
+
this.ctx.loadingAnimation = undefined;
|
|
1678
|
+
this.ctx.statusContainer.disposeChildren();
|
|
1679
|
+
}
|
|
1680
|
+
if (this.ctx.streamingComponent) {
|
|
1681
|
+
this.ctx.chatContainer.removeChild(this.ctx.streamingComponent);
|
|
1682
|
+
this.ctx.streamingComponent = undefined;
|
|
1683
|
+
this.ctx.streamingMessage = undefined;
|
|
1684
|
+
}
|
|
1685
|
+
await this.ctx.flushPendingModelSwitch();
|
|
1686
|
+
for (const toolCallId of Array.from(this.ctx.pendingTools.keys())) {
|
|
1687
|
+
if (!this.#backgroundTaskCallIds.has(toolCallId)) {
|
|
1688
|
+
// A foreground tool still pending at turn end never delivered a result;
|
|
1689
|
+
// seal it so it freezes (and stops animating) rather than lingering in
|
|
1690
|
+
// the transcript live region as a streaming preview until the next thaw.
|
|
1691
|
+
const component = this.ctx.pendingTools.get(toolCallId);
|
|
1692
|
+
// A foreground read still pending at turn end shares a group component
|
|
1693
|
+
// keyed by every read's id; seal it too so a never-delivered read does
|
|
1694
|
+
// not keep the group live (and pinning the live region) indefinitely.
|
|
1695
|
+
if (component instanceof ToolExecutionComponent || component instanceof ReadToolGroupComponent) {
|
|
1696
|
+
component.seal();
|
|
1697
|
+
}
|
|
1698
|
+
this.ctx.pendingTools.delete(toolCallId);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
this.#backgroundTaskCallIds = new Set(
|
|
1702
|
+
Array.from(this.#backgroundTaskCallIds).filter(toolCallId => this.ctx.pendingTools.has(toolCallId)),
|
|
1703
|
+
);
|
|
1704
|
+
this.#approvalAttentionToolCallIds.clear();
|
|
1705
|
+
this.#readToolCallArgs.clear();
|
|
1706
|
+
this.#readToolCallAssistantComponents.clear();
|
|
1707
|
+
this.#toolTimelineComponents.clear();
|
|
1708
|
+
this.#streamedToolCallIdByIndex.clear();
|
|
1709
|
+
this.#retractedToolCallIds.clear();
|
|
1710
|
+
this.#syntheticFailureCards.clear();
|
|
1711
|
+
this.#orphanedToolCompletions.clear();
|
|
1712
|
+
this.#postToolAssistantComponents.clear();
|
|
1713
|
+
this.#resetReadGroup();
|
|
1714
|
+
// The turn is over: nothing else lands this turn, so the waiting poll is
|
|
1715
|
+
// final history — seal it instead of letting its spinner tick while idle.
|
|
1716
|
+
this.#resolveDisplaceablePoll();
|
|
1717
|
+
this.#resolveDisplaceableTodo();
|
|
1718
|
+
this.ctx.flushPendingCommandOutput();
|
|
1719
|
+
this.#lastAssistantComponent = undefined;
|
|
1720
|
+
this.ctx.ui.requestRender();
|
|
1721
|
+
this.#scheduleIdleCompaction();
|
|
1722
|
+
this.#scheduleIdleRecap();
|
|
1723
|
+
this.sendErrorNotification(event);
|
|
1724
|
+
this.sendCompletionNotification(event);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/**
|
|
1728
|
+
* Tear down the live "Working…" loader: stop its animation timer AND clear the
|
|
1729
|
+
* reference. A transient overlay (auto-compaction / auto-retry) can remove the
|
|
1730
|
+
* loader from the container while leaving `ctx.loadingAnimation` set, so the
|
|
1731
|
+
* resumed turn's `agent_start` →
|
|
1732
|
+
* `ensureLoadingAnimation()` (guarded by `if (!this.loadingAnimation)`) skipped
|
|
1733
|
+
* re-adding it and the spinner vanished while the agent kept streaming. Nulling
|
|
1734
|
+
* the reference here lets the next `agent_start` recreate and re-attach it.
|
|
1735
|
+
*/
|
|
1736
|
+
#stopWorkingLoader(): void {
|
|
1737
|
+
if (this.ctx.loadingAnimation) {
|
|
1738
|
+
this.ctx.loadingAnimation.stop();
|
|
1739
|
+
this.ctx.loadingAnimation = undefined;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/**
|
|
1744
|
+
* Restore the live "Working…" loader when a streaming event lands after a
|
|
1745
|
+
* transient status overlay cleared the container. Focus mode dispatches events
|
|
1746
|
+
* for `viewSession`, so key the reconciler on that session, not the main one.
|
|
1747
|
+
*/
|
|
1748
|
+
#ensureWorkingLoaderWhileStreaming(): void {
|
|
1749
|
+
if (!this.ctx.viewSession.isStreaming) return;
|
|
1750
|
+
if (this.ctx.autoCompactionLoader || this.ctx.retryLoader) return;
|
|
1751
|
+
this.ctx.ensureLoadingAnimation();
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* Trailing Esc hint for live maintenance loaders. While a subagent is
|
|
1756
|
+
* focused, Esc returns to main instead of cancelling its maintenance
|
|
1757
|
+
* (#2819), so the loader drops the hint entirely rather than advertise a
|
|
1758
|
+
* cancel that no longer happens. Includes the leading space so the focused
|
|
1759
|
+
* label carries no dangling whitespace.
|
|
1760
|
+
*/
|
|
1761
|
+
#maintenanceEscHint(): string {
|
|
1762
|
+
return this.ctx.focusedAgentId ? "" : " (esc to cancel)";
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
async #handleAutoCompactionStart(
|
|
1766
|
+
event: Extract<AgentSessionEvent, { type: "auto_compaction_start" }>,
|
|
1767
|
+
): Promise<void> {
|
|
1768
|
+
this.#cancelIdleCompaction();
|
|
1769
|
+
this.#cancelIdleRecap();
|
|
1770
|
+
this.#setTerminalProgress(true);
|
|
1771
|
+
this.#stopWorkingLoader();
|
|
1772
|
+
this.ctx.statusContainer.disposeChildren();
|
|
1773
|
+
const reasonText =
|
|
1774
|
+
event.reason === "overflow"
|
|
1775
|
+
? "Context overflow detected, "
|
|
1776
|
+
: event.reason === "incomplete"
|
|
1777
|
+
? "Response incomplete, "
|
|
1778
|
+
: event.reason === "idle"
|
|
1779
|
+
? "Idle "
|
|
1780
|
+
: "";
|
|
1781
|
+
const actionLabel =
|
|
1782
|
+
event.action === "handoff"
|
|
1783
|
+
? "Auto-handoff"
|
|
1784
|
+
: event.action === "shake"
|
|
1785
|
+
? "Auto-shake"
|
|
1786
|
+
: event.action === "snapcompact"
|
|
1787
|
+
? "Auto-snapcompact"
|
|
1788
|
+
: "Auto context-full maintenance";
|
|
1789
|
+
this.ctx.autoCompactionLoader = new Loader(
|
|
1790
|
+
this.ctx.ui,
|
|
1791
|
+
spinner => theme.fg("accent", spinner),
|
|
1792
|
+
text => theme.fg("muted", text),
|
|
1793
|
+
`${reasonText}${actionLabel}…${this.#maintenanceEscHint()}`,
|
|
1794
|
+
getSymbolTheme().spinnerFrames,
|
|
1795
|
+
);
|
|
1796
|
+
this.ctx.statusContainer.addChild(this.ctx.autoCompactionLoader);
|
|
1797
|
+
this.ctx.ui.requestRender();
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
async #handleAutoCompactionEnd(event: Extract<AgentSessionEvent, { type: "auto_compaction_end" }>): Promise<void> {
|
|
1801
|
+
this.#cancelIdleCompaction();
|
|
1802
|
+
this.#cancelIdleRecap();
|
|
1803
|
+
this.#setTerminalProgress(false);
|
|
1804
|
+
if (this.ctx.autoCompactionLoader) {
|
|
1805
|
+
this.ctx.autoCompactionLoader.stop();
|
|
1806
|
+
this.ctx.autoCompactionLoader = undefined;
|
|
1807
|
+
this.ctx.statusContainer.disposeChildren();
|
|
1808
|
+
}
|
|
1809
|
+
const isHandoffAction = event.action === "handoff";
|
|
1810
|
+
const isShakeAction = event.action === "shake";
|
|
1811
|
+
const isSnapcompactAction = event.action === "snapcompact";
|
|
1812
|
+
if (event.aborted) {
|
|
1813
|
+
this.ctx.showStatus(
|
|
1814
|
+
isHandoffAction
|
|
1815
|
+
? "Auto-handoff cancelled"
|
|
1816
|
+
: isShakeAction
|
|
1817
|
+
? "Auto-shake cancelled"
|
|
1818
|
+
: isSnapcompactAction
|
|
1819
|
+
? "Auto-snapcompact cancelled"
|
|
1820
|
+
: "Auto context-full maintenance cancelled",
|
|
1821
|
+
);
|
|
1822
|
+
} else if (isShakeAction) {
|
|
1823
|
+
// Shake produces no CompactionResult; rebuild on success, suppress benign skips.
|
|
1824
|
+
// The fallback path (`errorMessage` set, `skipped` false) means shake reclaimed
|
|
1825
|
+
// some tokens before deciding the threshold still wasn't cleared — rebuild so
|
|
1826
|
+
// the chat reflects the dropped regions even though a context-full pass follows.
|
|
1827
|
+
if (event.errorMessage) {
|
|
1828
|
+
if (!event.skipped) {
|
|
1829
|
+
this.ctx.rebuildChatFromMessages();
|
|
1830
|
+
this.ctx.statusLine.invalidate();
|
|
1831
|
+
this.ctx.ui.requestRender();
|
|
1832
|
+
}
|
|
1833
|
+
this.ctx.showWarning(event.errorMessage);
|
|
1834
|
+
} else if (!event.skipped) {
|
|
1835
|
+
this.ctx.lastAssistantUsage = undefined;
|
|
1836
|
+
this.ctx.rebuildChatFromMessages();
|
|
1837
|
+
this.ctx.statusLine.invalidate();
|
|
1838
|
+
this.ctx.ui.requestRender();
|
|
1839
|
+
this.ctx.showStatus("Auto-shake completed");
|
|
1840
|
+
}
|
|
1841
|
+
} else if (event.result) {
|
|
1842
|
+
this.ctx.lastAssistantUsage = undefined;
|
|
1843
|
+
this.ctx.rebuildChatFromMessages({ reuseSettledComponents: true });
|
|
1844
|
+
this.ctx.statusLine.invalidate();
|
|
1845
|
+
// When history collapses behind the summary divider, the frame
|
|
1846
|
+
// shrinks far below the committed row count; without clearing, the
|
|
1847
|
+
// differential renderer's "duplication, never loss" resync repaints
|
|
1848
|
+
// the whole collapsed transcript (welcome box included) BELOW the
|
|
1849
|
+
// stale pre-compaction scrollback. Compaction is an intentional
|
|
1850
|
+
// transcript replacement then — same as auto-handoff below. With
|
|
1851
|
+
// collapse disabled the rebuilt transcript keeps the full history,
|
|
1852
|
+
// so the resync handles it and scrollback stays.
|
|
1853
|
+
if (settings.get("display.collapseCompacted")) {
|
|
1854
|
+
this.ctx.ui.requestRender(true, { clearScrollback: true });
|
|
1855
|
+
} else {
|
|
1856
|
+
this.ctx.ui.requestRender();
|
|
1857
|
+
}
|
|
1858
|
+
} else if (event.errorMessage) {
|
|
1859
|
+
this.ctx.showWarning(event.errorMessage);
|
|
1860
|
+
} else if (isHandoffAction) {
|
|
1861
|
+
this.ctx.clearTransientSessionUi();
|
|
1862
|
+
this.ctx.lastAssistantUsage = undefined;
|
|
1863
|
+
this.ctx.renderInitialMessages();
|
|
1864
|
+
this.ctx.statusLine.invalidate();
|
|
1865
|
+
await this.ctx.reloadTodos();
|
|
1866
|
+
this.ctx.ui.requestRender(true, { clearScrollback: true });
|
|
1867
|
+
this.ctx.showStatus("Auto-handoff completed");
|
|
1868
|
+
} else if (event.skipped) {
|
|
1869
|
+
// Benign skip: no model selected, no candidate models available, or nothing
|
|
1870
|
+
// to compact yet. Not a failure — suppress the warning.
|
|
1871
|
+
} else if (isSnapcompactAction) {
|
|
1872
|
+
this.ctx.showWarning("Auto-snapcompact maintenance failed; continuing without maintenance");
|
|
1873
|
+
} else {
|
|
1874
|
+
this.ctx.showWarning("Auto context-full maintenance failed; continuing without maintenance");
|
|
1875
|
+
}
|
|
1876
|
+
await this.ctx.flushCompactionQueue({ willRetry: event.willRetry });
|
|
1877
|
+
this.#ensureWorkingLoaderWhileStreaming();
|
|
1878
|
+
this.ctx.ui.requestRender();
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
async #handleAutoRetryStart(event: Extract<AgentSessionEvent, { type: "auto_retry_start" }>): Promise<void> {
|
|
1882
|
+
this.#retryPending = true;
|
|
1883
|
+
this.#trackRetrySupersededAssistantComponent(this.#lastAssistantComponent);
|
|
1884
|
+
// A retry supersedes the just-failed turn: its assistant + tool calls are
|
|
1885
|
+
// pruned from context and re-streamed. Remove the cards that a synthetic
|
|
1886
|
+
// aborted/error completion settled in place at message_end so the retry's
|
|
1887
|
+
// fresh cards don't render the same call twice (#6879). Only uncommitted
|
|
1888
|
+
// cards are removable; one already on the scrollback tape stays as history.
|
|
1889
|
+
for (const [toolCallId, component] of this.#syntheticFailureCards) {
|
|
1890
|
+
if (this.ctx.chatContainer.isBlockUncommitted(component)) {
|
|
1891
|
+
this.#retractToolCardEntry(toolCallId, component);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
this.#syntheticFailureCards.clear();
|
|
1895
|
+
this.#stopWorkingLoader();
|
|
1896
|
+
this.ctx.statusContainer.disposeChildren();
|
|
1897
|
+
if (AIError.is(event.errorId, AIError.Flag.ThinkingLoop)) {
|
|
1898
|
+
// The retry path drops the failed assistant from runtime context. Do not
|
|
1899
|
+
// restore its inline Error row; just unpin the fixed-region banner so the
|
|
1900
|
+
// retry UI is the visible state.
|
|
1901
|
+
this.#pinnedErrorComponent = undefined;
|
|
1902
|
+
this.ctx.clearPinnedError();
|
|
1903
|
+
}
|
|
1904
|
+
const delaySeconds = Math.round(event.delayMs / 1000);
|
|
1905
|
+
this.ctx.retryLoader = new Loader(
|
|
1906
|
+
this.ctx.ui,
|
|
1907
|
+
spinner => theme.fg("warning", spinner),
|
|
1908
|
+
text => theme.fg("muted", text),
|
|
1909
|
+
`Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s…${this.#maintenanceEscHint()}`,
|
|
1910
|
+
getSymbolTheme().spinnerFrames,
|
|
1911
|
+
);
|
|
1912
|
+
this.ctx.statusContainer.addChild(this.ctx.retryLoader);
|
|
1913
|
+
this.ctx.ui.requestRender();
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
async #handleAutoRetryEnd(event: Extract<AgentSessionEvent, { type: "auto_retry_end" }>): Promise<void> {
|
|
1917
|
+
this.#retryPending = false;
|
|
1918
|
+
if (this.ctx.retryLoader) {
|
|
1919
|
+
this.ctx.retryLoader.stop();
|
|
1920
|
+
this.ctx.retryLoader = undefined;
|
|
1921
|
+
this.ctx.statusContainer.disposeChildren();
|
|
1922
|
+
}
|
|
1923
|
+
if (event.success) {
|
|
1924
|
+
let appliedRecovered = false;
|
|
1925
|
+
for (const recovered of event.recoveredErrors ?? []) {
|
|
1926
|
+
const component = this.#takeRetrySupersededAssistantComponent(recovered.persistenceKey);
|
|
1927
|
+
if (!component) continue;
|
|
1928
|
+
component.applyRetryRecovery(recovered.retryRecovery);
|
|
1929
|
+
if (this.#pinnedErrorComponent === component) this.#pinnedErrorComponent = undefined;
|
|
1930
|
+
appliedRecovered = true;
|
|
1931
|
+
}
|
|
1932
|
+
if (appliedRecovered || (event.recoveredErrors?.length ?? 0) > 0) {
|
|
1933
|
+
this.ctx.clearPinnedError();
|
|
1934
|
+
}
|
|
1935
|
+
this.#clearRetrySupersededAssistantComponents();
|
|
1936
|
+
} else {
|
|
1937
|
+
this.#clearRetrySupersededAssistantComponents();
|
|
1938
|
+
this.ctx.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
1939
|
+
}
|
|
1940
|
+
this.#ensureWorkingLoaderWhileStreaming();
|
|
1941
|
+
this.ctx.ui.requestRender();
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
async #handleRetryFallbackApplied(
|
|
1945
|
+
event: Extract<AgentSessionEvent, { type: "retry_fallback_applied" }>,
|
|
1946
|
+
): Promise<void> {
|
|
1947
|
+
this.ctx.showWarning(`Fallback: ${event.from} -> ${event.to}`);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
async #handleRetryFallbackSucceeded(
|
|
1951
|
+
event: Extract<AgentSessionEvent, { type: "retry_fallback_succeeded" }>,
|
|
1952
|
+
): Promise<void> {
|
|
1953
|
+
this.ctx.showStatus(`Fallback succeeded on ${event.model}`);
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
async #handleTtsrTriggered(event: Extract<AgentSessionEvent, { type: "ttsr_triggered" }>): Promise<void> {
|
|
1957
|
+
// Consecutive notifications (e.g. per-tool matches from one assistant
|
|
1958
|
+
// message) merge into the previous block instead of stacking. Mutating an
|
|
1959
|
+
// existing block is only safe while none of its rows have entered native
|
|
1960
|
+
// scrollback — committed rows are immutable visual history and a grown
|
|
1961
|
+
// block would shift them.
|
|
1962
|
+
const previous = this.#lastTtsrNotification;
|
|
1963
|
+
if (
|
|
1964
|
+
previous &&
|
|
1965
|
+
this.ctx.chatContainer.children.at(-1) === previous &&
|
|
1966
|
+
this.ctx.chatContainer.isBlockUncommitted(previous)
|
|
1967
|
+
) {
|
|
1968
|
+
previous.addRules(event.rules);
|
|
1969
|
+
this.ctx.ui.requestRender();
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
const component = new TtsrNotificationComponent(event.rules);
|
|
1973
|
+
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
1974
|
+
this.ctx.present(component);
|
|
1975
|
+
this.#lastTtsrNotification = component;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
async #handleTodoReminder(event: Extract<AgentSessionEvent, { type: "todo_reminder" }>): Promise<void> {
|
|
1979
|
+
const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
|
|
1980
|
+
this.ctx.present(component);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
async #handleTodoAutoClear(_event: Extract<AgentSessionEvent, { type: "todo_auto_clear" }>): Promise<void> {
|
|
1984
|
+
await this.ctx.reloadTodos();
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
#cancelIdleCompaction(): void {
|
|
1988
|
+
if (this.#idleCompactionTimer) {
|
|
1989
|
+
clearTimeout(this.#idleCompactionTimer);
|
|
1990
|
+
this.#idleCompactionTimer = undefined;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
#cancelIdleRecap(): void {
|
|
1995
|
+
if (this.#idleRecapTimer) {
|
|
1996
|
+
clearTimeout(this.#idleRecapTimer);
|
|
1997
|
+
this.#idleRecapTimer = undefined;
|
|
1998
|
+
}
|
|
1999
|
+
if (this.#idleRecapAbort) {
|
|
2000
|
+
this.#idleRecapAbort.abort();
|
|
2001
|
+
this.#idleRecapAbort = undefined;
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
#scheduleIdleCompaction(): void {
|
|
2006
|
+
this.#cancelIdleCompaction();
|
|
2007
|
+
// Don't schedule idle work while context maintenance is already running; the
|
|
2008
|
+
// maintenance flow may reset the session before this timer fires.
|
|
2009
|
+
if (this.ctx.viewSession.isCompacting) return;
|
|
2010
|
+
|
|
2011
|
+
const idleSettings = settings.getGroup("compaction");
|
|
2012
|
+
if (!idleSettings.idleEnabled) return;
|
|
2013
|
+
|
|
2014
|
+
// Only if input is empty
|
|
2015
|
+
if (this.ctx.editor.getText().trim()) return;
|
|
2016
|
+
|
|
2017
|
+
const threshold = idleSettings.idleThresholdTokens;
|
|
2018
|
+
if (threshold <= 0) return;
|
|
2019
|
+
if (this.#currentContextTokens() < threshold) return;
|
|
2020
|
+
|
|
2021
|
+
const timeoutMs = Math.max(60, Math.min(3600, idleSettings.idleTimeoutSeconds)) * 1000;
|
|
2022
|
+
this.#idleCompactionTimer = setTimeout(() => {
|
|
2023
|
+
this.#idleCompactionTimer = undefined;
|
|
2024
|
+
// Re-check conditions before firing. Pruning may have run between arming
|
|
2025
|
+
// the timer and now, dropping usage back below the idle threshold.
|
|
2026
|
+
if (this.ctx.viewSession.isStreaming) return;
|
|
2027
|
+
if (this.ctx.viewSession.isCompacting) return;
|
|
2028
|
+
if (this.ctx.editor.getText().trim()) return;
|
|
2029
|
+
if (this.#currentContextTokens() < threshold) return;
|
|
2030
|
+
void this.ctx.viewSession.runIdleCompaction();
|
|
2031
|
+
}, timeoutMs);
|
|
2032
|
+
this.#idleCompactionTimer.unref?.();
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
#scheduleIdleRecap(): void {
|
|
2036
|
+
this.#cancelIdleRecap();
|
|
2037
|
+
if (this.ctx.viewSession.isCompacting) return;
|
|
2038
|
+
|
|
2039
|
+
const recapSettings = settings.getGroup("recap");
|
|
2040
|
+
if (!recapSettings.enabled) return;
|
|
2041
|
+
if (this.ctx.editor.getText().trim()) return;
|
|
2042
|
+
|
|
2043
|
+
const timeoutMs =
|
|
2044
|
+
Math.max(IDLE_RECAP_MIN_SECONDS, Math.min(IDLE_RECAP_MAX_SECONDS, recapSettings.idleSeconds)) * 1000;
|
|
2045
|
+
this.#idleRecapTimer = setTimeout(() => {
|
|
2046
|
+
this.#idleRecapTimer = undefined;
|
|
2047
|
+
void this.#runIdleRecap();
|
|
2048
|
+
}, timeoutMs);
|
|
2049
|
+
this.#idleRecapTimer.unref?.();
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
/**
|
|
2053
|
+
* Generate the idle recap with an ephemeral side-channel turn over the
|
|
2054
|
+
* current conversation (same pipeline as `/btw`) and surface it as a status
|
|
2055
|
+
* line. Live goal/title and the active todo task are passed as anchoring
|
|
2056
|
+
* hints because the snapshot only carries conversation history, not the
|
|
2057
|
+
* controller's todo/goal state. The request is abortable: any activity
|
|
2058
|
+
* cancels it via #cancelIdleRecap, and idle conditions are re-checked after
|
|
2059
|
+
* the reply lands so a stale recap never paints over fresh work.
|
|
2060
|
+
*/
|
|
2061
|
+
async #runIdleRecap(): Promise<void> {
|
|
2062
|
+
if (!this.#idleConditionsHold()) return;
|
|
2063
|
+
if (!this.ctx.viewSession.model) return;
|
|
2064
|
+
if (this.ctx.viewSession.messages.length === 0) return;
|
|
2065
|
+
|
|
2066
|
+
const promptText = prompt.render(idleRecapPrompt, {
|
|
2067
|
+
goal: this.#idleRecapGoalText() ?? "",
|
|
2068
|
+
task: nextActionableTask(this.ctx.todoPhases)?.content ?? "",
|
|
2069
|
+
});
|
|
2070
|
+
|
|
2071
|
+
const abort = new AbortController();
|
|
2072
|
+
this.#idleRecapAbort = abort;
|
|
2073
|
+
try {
|
|
2074
|
+
const { replyText } = await this.ctx.viewSession.runEphemeralTurn({ promptText, signal: abort.signal });
|
|
2075
|
+
if (this.#idleRecapAbort !== abort || abort.signal.aborted || !this.#idleConditionsHold()) return;
|
|
2076
|
+
const recap = previewLine(replyText, TRUNCATE_LENGTHS.RECAP);
|
|
2077
|
+
if (!recap) return;
|
|
2078
|
+
this.ctx.showStatus(theme.fg("dim", theme.italic(`※ recap: ${recap}`)), { dim: false });
|
|
2079
|
+
} catch (error) {
|
|
2080
|
+
if (!abort.signal.aborted) logger.debug("Idle recap turn failed", { error: String(error) });
|
|
2081
|
+
} finally {
|
|
2082
|
+
if (this.#idleRecapAbort === abort) this.#idleRecapAbort = undefined;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
/** Idle gate shared by the recap timer fire and its post-reply re-check. */
|
|
2087
|
+
#idleConditionsHold(): boolean {
|
|
2088
|
+
if (this.ctx.viewSession.isStreaming) return false;
|
|
2089
|
+
if (this.ctx.viewSession.isCompacting) return false;
|
|
2090
|
+
if (this.ctx.editor.getText().trim()) return false;
|
|
2091
|
+
return true;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
#idleRecapGoalText(): string | undefined {
|
|
2095
|
+
const goal = this.ctx.viewSession.getGoalModeState?.()?.goal.objective.trim();
|
|
2096
|
+
if (goal) return goal;
|
|
2097
|
+
const title = this.ctx.sessionManager.getSessionName()?.trim();
|
|
2098
|
+
return title || undefined;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
#currentContextTokens(): number {
|
|
2102
|
+
return this.ctx.viewSession.getContextUsage()?.tokens ?? 0;
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
sendErrorNotification(event: Extract<AgentSessionEvent, { type: "agent_end" }>): void {
|
|
2106
|
+
// A running async job or queued delivery will wake the session again, so
|
|
2107
|
+
// its current agent_end is a scheduling pause rather than a user-visible
|
|
2108
|
+
// terminal failure. AgentSession marks that stable outcome before
|
|
2109
|
+
// dispatching the deferred event; do not infer it from mutable job state.
|
|
2110
|
+
if (event.isTerminal === false) return;
|
|
2111
|
+
|
|
2112
|
+
// `AgentSession` defers and coalesces the wire-level `agent_end` while a
|
|
2113
|
+
// prompt is still in flight (see `#emitSessionEvent` in agent-session.ts):
|
|
2114
|
+
// every mid-retry attempt's own settle is superseded, so this method often
|
|
2115
|
+
// sees only ONE `agent_end` for an entire multi-attempt retry saga — which
|
|
2116
|
+
// can be the final failure, not an intermediate one. Gate purely on the
|
|
2117
|
+
// retry lifecycle (`auto_retry_start`/`auto_retry_end`, which are never
|
|
2118
|
+
// deferred) rather than consuming this flag against whichever `agent_end`
|
|
2119
|
+
// happens to arrive first: `#retryPending` is true only for the actual
|
|
2120
|
+
// window a retry is outstanding, so the settled failure that survives
|
|
2121
|
+
// coalescing is never mistaken for a mid-retry blip.
|
|
2122
|
+
if (this.#retryPending) return;
|
|
2123
|
+
|
|
2124
|
+
// Warp structured OSC 777 already drives native completion UX when the
|
|
2125
|
+
// protocol is negotiated — avoid a second legacy desktop/OSC-9 toast.
|
|
2126
|
+
if (isWarpCliAgentProtocolActive()) return;
|
|
2127
|
+
|
|
2128
|
+
const notify = settings.get("error.notify");
|
|
2129
|
+
if (notify === "off") return;
|
|
2130
|
+
|
|
2131
|
+
// Read the turn's own outcome from `agent_end.messages`, not the mutable
|
|
2132
|
+
// active context: a classifier-refusal failure is final (stopReason ===
|
|
2133
|
+
// "error") but gets pruned from `viewSession`'s active context before this
|
|
2134
|
+
// handler runs (see `#removeAssistantMessageFromActiveContext` in
|
|
2135
|
+
// agent-session.ts), so `getLastAssistantMessage()` would see a stale or
|
|
2136
|
+
// absent assistant and silently drop the notification.
|
|
2137
|
+
const last = event.messages.findLast((message): message is AssistantMessage => message.role === "assistant");
|
|
2138
|
+
if (last?.stopReason !== "error") return;
|
|
2139
|
+
|
|
2140
|
+
const sessionName = this.ctx.sessionManager.getSessionName();
|
|
2141
|
+
TERMINAL.sendNotification({
|
|
2142
|
+
title: sessionName || "Oh My Pi",
|
|
2143
|
+
body: "Stopped with error",
|
|
2144
|
+
type: "error",
|
|
2145
|
+
actions: "focus",
|
|
2146
|
+
});
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
sendCompletionNotification(event: Extract<AgentSessionEvent, { type: "agent_end" }>): void {
|
|
2150
|
+
const notify = settings.get("completion.notify");
|
|
2151
|
+
if (notify === "off") return;
|
|
2152
|
+
|
|
2153
|
+
// Warp structured OSC 777 already drives native completion UX when the
|
|
2154
|
+
// protocol is negotiated — avoid a second legacy desktop/OSC-9 toast.
|
|
2155
|
+
if (isWarpCliAgentProtocolActive()) return;
|
|
2156
|
+
|
|
2157
|
+
// Read the turn's own outcome from `agent_end.messages`, not the mutable
|
|
2158
|
+
// active context (see `sendErrorNotification` above for why `viewSession`'s
|
|
2159
|
+
// snapshot can be stale): an aborted or errored turn is not "Task
|
|
2160
|
+
// complete", and using the same event `sendErrorNotification` just read
|
|
2161
|
+
// keeps the two notifications mutually exclusive for one settled turn.
|
|
2162
|
+
const last = event.messages.findLast((message): message is AssistantMessage => message.role === "assistant");
|
|
2163
|
+
if (last?.stopReason === "aborted" || last?.stopReason === "error") return;
|
|
2164
|
+
|
|
2165
|
+
const sessionName = this.ctx.sessionManager.getSessionName();
|
|
2166
|
+
TERMINAL.sendNotification({
|
|
2167
|
+
title: sessionName || "Oh My Pi",
|
|
2168
|
+
body: "Complete",
|
|
2169
|
+
type: "completion",
|
|
2170
|
+
actions: "focus",
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
}
|