oma-coding-agent 1.1.4
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 +12164 -0
- package/README.md +35 -0
- package/dist/cli.js +18266 -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 +79 -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 +172 -0
- package/package.json +573 -0
- package/scripts/bench-guard.ts +71 -0
- package/scripts/build-binary.ts +108 -0
- package/scripts/bundle-dist.ts +110 -0
- package/scripts/embed-mupdf-wasm.ts +67 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-docs-index.ts +56 -0
- package/scripts/generate-share-viewer.ts +34 -0
- package/scripts/measure-prompt-tokens.ts +63 -0
- package/scripts/omp +42 -0
- package/scripts/omp.ts +19 -0
- package/src/advisor/__tests__/advisor.test.ts +915 -0
- package/src/advisor/advise-tool.ts +165 -0
- package/src/advisor/index.ts +4 -0
- package/src/advisor/runtime.ts +270 -0
- package/src/advisor/transcript-recorder.ts +136 -0
- package/src/advisor/watchdog.ts +83 -0
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +674 -0
- package/src/auto-thinking/classifier.ts +190 -0
- package/src/autolearn/controller.ts +139 -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 +319 -0
- package/src/autoresearch/helpers.ts +218 -0
- package/src/autoresearch/index.ts +536 -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 +269 -0
- package/src/autoresearch/tools/log-experiment.ts +521 -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 +436 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +76 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule-buckets.ts +66 -0
- package/src/capability/rule.ts +261 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +63 -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 +168 -0
- package/src/cli/agents-cli.ts +138 -0
- package/src/cli/args.ts +361 -0
- package/src/cli/auth-broker-cli.ts +893 -0
- package/src/cli/auth-gateway-cli.ts +608 -0
- package/src/cli/bench-cli.ts +552 -0
- package/src/cli/classify-install-target.ts +76 -0
- package/src/cli/claude-trace-cli.ts +795 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/completion-gen.ts +550 -0
- package/src/cli/config-cli.ts +418 -0
- package/src/cli/dry-balance-cli.ts +858 -0
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/file-processor.ts +133 -0
- package/src/cli/flag-tables.ts +280 -0
- package/src/cli/gallery-cli.ts +231 -0
- package/src/cli/gallery-fixtures/agentic.ts +407 -0
- package/src/cli/gallery-fixtures/codeintel.ts +187 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +220 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -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/grep-cli.ts +160 -0
- package/src/cli/grievances-cli.ts +256 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/models-cli.ts +427 -0
- package/src/cli/plugin-cli.ts +996 -0
- package/src/cli/profile-alias.ts +338 -0
- package/src/cli/profile-bootstrap.ts +243 -0
- package/src/cli/read-cli.ts +57 -0
- package/src/cli/session-picker.ts +80 -0
- package/src/cli/setup-cli.ts +332 -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/tiny-models-cli.ts +127 -0
- package/src/cli/ttsr-cli.ts +995 -0
- package/src/cli/update-cli.ts +671 -0
- package/src/cli/usage-cli.ts +774 -0
- package/src/cli/web-search-cli.ts +132 -0
- package/src/cli/worktree-cli.ts +291 -0
- package/src/cli-commands.ts +85 -0
- package/src/cli.ts +326 -0
- package/src/collab/crypto.ts +63 -0
- package/src/collab/guest.ts +450 -0
- package/src/collab/host.ts +577 -0
- package/src/collab/protocol.ts +274 -0
- package/src/collab/relay-client.ts +216 -0
- package/src/commands/acp.ts +24 -0
- package/src/commands/agents.ts +57 -0
- package/src/commands/auth-broker.ts +99 -0
- package/src/commands/auth-gateway.ts +69 -0
- package/src/commands/bench.ts +42 -0
- package/src/commands/commit.ts +46 -0
- package/src/commands/complete.ts +66 -0
- package/src/commands/completions.ts +60 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/dry-balance.ts +43 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/grep.ts +48 -0
- package/src/commands/grievances.ts +51 -0
- package/src/commands/install.ts +107 -0
- package/src/commands/join.ts +39 -0
- package/src/commands/launch.ts +182 -0
- package/src/commands/models.ts +61 -0
- package/src/commands/plugin.ts +78 -0
- package/src/commands/read.ts +38 -0
- package/src/commands/say.ts +102 -0
- package/src/commands/setup.ts +67 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/ssh.ts +60 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/tiny-models.ts +36 -0
- package/src/commands/token.ts +108 -0
- package/src/commands/ttsr.ts +125 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/usage.ts +43 -0
- package/src/commands/web-search.ts +42 -0
- package/src/commands/worktree.ts +56 -0
- package/src/commit/agentic/agent.ts +318 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +355 -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 +149 -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 +81 -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 +89 -0
- package/src/commit/pipeline.ts +243 -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 +67 -0
- package/src/config/append-only-context-mode.ts +76 -0
- package/src/config/config-file.ts +315 -0
- package/src/config/file-lock.ts +164 -0
- package/src/config/keybindings.ts +634 -0
- package/src/config/mcp-schema.json +238 -0
- package/src/config/model-discovery.ts +589 -0
- package/src/config/model-registry.ts +2260 -0
- package/src/config/model-resolver.ts +1819 -0
- package/src/config/model-roles.ts +99 -0
- package/src/config/models-config-schema.ts +266 -0
- package/src/config/models-config.ts +131 -0
- package/src/config/prompt-templates.ts +185 -0
- package/src/config/resolve-config-value.ts +94 -0
- package/src/config/settings-schema.ts +4740 -0
- package/src/config/settings.ts +1243 -0
- package/src/config.ts +242 -0
- package/src/cursor.ts +340 -0
- package/src/dap/client.ts +760 -0
- package/src/dap/config.ts +189 -0
- package/src/dap/defaults.json +212 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1441 -0
- package/src/dap/types.ts +610 -0
- package/src/debug/index.ts +559 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +908 -0
- package/src/debug/profiler.ts +162 -0
- package/src/debug/protocol-probe.ts +267 -0
- package/src/debug/raw-sse-buffer.ts +294 -0
- package/src/debug/raw-sse.ts +292 -0
- package/src/debug/remote-debugger.ts +151 -0
- package/src/debug/report-bundle.ts +375 -0
- package/src/debug/system-info.ts +111 -0
- package/src/debug/terminal-info.ts +124 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +230 -0
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-defaults.ts +39 -0
- package/src/discovery/builtin-rules/index.ts +63 -0
- package/src/discovery/builtin-rules/low-end/no-hallucinated-apis.md +14 -0
- package/src/discovery/builtin-rules/low-end/no-hallucinated-paths.md +14 -0
- package/src/discovery/builtin-rules/low-end/no-premature-completion.md +14 -0
- package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
- package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
- package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
- package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
- package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
- package/src/discovery/builtin-rules/rs-result-type.md +19 -0
- package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
- package/src/discovery/builtin-rules/ts-import-type.md +42 -0
- package/src/discovery/builtin-rules/ts-no-any.md +65 -0
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
- package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
- package/src/discovery/builtin-rules/ts-no-return-type.md +44 -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 +65 -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 +934 -0
- package/src/discovery/claude-plugins.ts +386 -0
- package/src/discovery/claude.ts +584 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +522 -0
- package/src/discovery/cursor.ts +220 -0
- package/src/discovery/gemini.ts +383 -0
- package/src/discovery/github.ts +337 -0
- package/src/discovery/helpers.ts +1092 -0
- package/src/discovery/index.ts +81 -0
- package/src/discovery/mcp-json.ts +172 -0
- package/src/discovery/omp-extension-roots.ts +190 -0
- package/src/discovery/omp-plugins.ts +383 -0
- package/src/discovery/opencode.ts +398 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +29 -0
- package/src/discovery/vscode.ts +105 -0
- package/src/discovery/windsurf.ts +147 -0
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +999 -0
- package/src/edit/file-snapshot-store.ts +143 -0
- package/src/edit/hashline/block-resolver.ts +33 -0
- package/src/edit/hashline/diff.ts +290 -0
- package/src/edit/hashline/execute.ts +237 -0
- package/src/edit/hashline/filesystem.ts +130 -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 +19 -0
- package/src/edit/index.ts +620 -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 +1888 -0
- package/src/edit/modes/replace.ts +1133 -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 +823 -0
- package/src/edit/streaming.ts +517 -0
- package/src/eval/__tests__/agent-bridge.test.ts +769 -0
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/budget-bridge.test.ts +69 -0
- package/src/eval/__tests__/completion-bridge.test.ts +412 -0
- package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
- package/src/eval/__tests__/idle-timeout.test.ts +80 -0
- package/src/eval/__tests__/js-context-manager.test.ts +291 -0
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/__tests__/prelude-agent.test.ts +73 -0
- package/src/eval/agent-bridge.ts +319 -0
- package/src/eval/backend.ts +71 -0
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/completion-bridge.ts +211 -0
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/idle-timeout.ts +91 -0
- package/src/eval/index.ts +4 -0
- package/src/eval/js/context-manager.ts +621 -0
- package/src/eval/js/executor.ts +173 -0
- package/src/eval/js/index.ts +51 -0
- package/src/eval/js/shared/helpers.ts +283 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/local-module-loader.ts +342 -0
- package/src/eval/js/shared/prelude.ts +2 -0
- package/src/eval/js/shared/prelude.txt +307 -0
- package/src/eval/js/shared/rewrite-imports.ts +532 -0
- package/src/eval/js/shared/runtime.ts +580 -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 +151 -0
- package/src/eval/js/worker-entry.ts +37 -0
- package/src/eval/js/worker-protocol.ts +47 -0
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +742 -0
- package/src/eval/py/index.ts +68 -0
- package/src/eval/py/kernel.ts +748 -0
- package/src/eval/py/prelude.py +683 -0
- package/src/eval/py/prelude.ts +3 -0
- package/src/eval/py/runner.py +1177 -0
- package/src/eval/py/runtime.ts +276 -0
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/eval/py/tool-bridge.ts +182 -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 +434 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/non-interactive-env.ts +119 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/index.ts +266 -0
- package/src/export/html/share-loader.js +102 -0
- package/src/export/html/template.css +1337 -0
- package/src/export/html/template.html +49 -0
- package/src/export/html/template.js +1626 -0
- package/src/export/html/tool-views.generated.js +37 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/share.ts +268 -0
- package/src/export/ttsr.ts +583 -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 +268 -0
- package/src/extensibility/custom-tools/types.ts +277 -0
- package/src/extensibility/custom-tools/wrapper.ts +47 -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/loader.ts +587 -0
- package/src/extensibility/extensions/model-api.ts +41 -0
- package/src/extensibility/extensions/runner.ts +989 -0
- package/src/extensibility/extensions/types.ts +1394 -0
- package/src/extensibility/extensions/wrapper.ts +259 -0
- package/src/extensibility/hooks/index.ts +6 -0
- package/src/extensibility/hooks/loader.ts +262 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +107 -0
- package/src/extensibility/hooks/types.ts +613 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +61 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +128 -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 +192 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +712 -0
- package/src/extensibility/plugins/loader.ts +458 -0
- package/src/extensibility/plugins/manager.ts +1026 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +315 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +770 -0
- package/src/extensibility/plugins/marketplace/registry.ts +196 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +191 -0
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/extensibility/plugins/parser.ts +105 -0
- package/src/extensibility/plugins/runtime-config.ts +9 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/shared-events.ts +367 -0
- package/src/extensibility/skills.ts +408 -0
- package/src/extensibility/slash-commands.ts +131 -0
- package/src/extensibility/tool-proxy.ts +28 -0
- package/src/extensibility/typebox.ts +945 -0
- package/src/extensibility/utils.ts +44 -0
- package/src/goals/guided-setup.ts +142 -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 +623 -0
- package/src/hindsight/config.ts +175 -0
- package/src/hindsight/content.ts +210 -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 +492 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/index.ts +66 -0
- package/src/internal-urls/agent-protocol.ts +146 -0
- package/src/internal-urls/artifact-protocol.ts +107 -0
- package/src/internal-urls/docs-index.generated.txt +2 -0
- package/src/internal-urls/docs-index.ts +102 -0
- package/src/internal-urls/history-protocol.ts +118 -0
- package/src/internal-urls/index.ts +25 -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 +309 -0
- package/src/internal-urls/mcp-protocol.ts +151 -0
- package/src/internal-urls/memory-protocol.ts +169 -0
- package/src/internal-urls/omp-protocol.ts +94 -0
- package/src/internal-urls/parse.ts +72 -0
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +105 -0
- package/src/internal-urls/rule-protocol.ts +45 -0
- package/src/internal-urls/skill-protocol.ts +96 -0
- package/src/internal-urls/types.ts +152 -0
- package/src/internal-urls/vault-protocol.ts +936 -0
- package/src/irc/bus.ts +311 -0
- package/src/lib/xai-http.ts +124 -0
- package/src/lsp/client.ts +1217 -0
- package/src/lsp/clients/biome-client.ts +264 -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 +502 -0
- package/src/lsp/defaults.json +499 -0
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/edits.ts +267 -0
- package/src/lsp/format-options.ts +119 -0
- package/src/lsp/index.ts +2480 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/render.ts +668 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +444 -0
- package/src/lsp/utils.ts +718 -0
- package/src/main.ts +1421 -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/mammoth.d.ts +24 -0
- package/src/markit/converters/pdf/columns.ts +103 -0
- package/src/markit/converters/pdf/extract.ts +574 -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 +509 -0
- package/src/mcp/config-writer.ts +229 -0
- package/src/mcp/config.ts +365 -0
- package/src/mcp/index.ts +29 -0
- package/src/mcp/json-rpc.ts +122 -0
- package/src/mcp/loader.ts +124 -0
- package/src/mcp/manager.ts +1326 -0
- package/src/mcp/oauth-credentials.ts +104 -0
- package/src/mcp/oauth-discovery.ts +467 -0
- package/src/mcp/oauth-flow.ts +555 -0
- package/src/mcp/render.ts +155 -0
- package/src/mcp/smithery-auth.ts +104 -0
- package/src/mcp/smithery-connect.ts +145 -0
- package/src/mcp/smithery-registry.ts +477 -0
- package/src/mcp/startup-events.ts +21 -0
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/tool-bridge.ts +429 -0
- package/src/mcp/tool-cache.ts +117 -0
- package/src/mcp/transports/http.ts +519 -0
- package/src/mcp/transports/index.ts +6 -0
- package/src/mcp/transports/stdio.ts +606 -0
- package/src/mcp/types.ts +427 -0
- package/src/memories/index.ts +1281 -0
- package/src/memories/storage.ts +578 -0
- package/src/memory-backend/index.ts +18 -0
- package/src/memory-backend/local-backend.ts +45 -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/types.ts +166 -0
- package/src/mnemopi/backend.ts +612 -0
- package/src/mnemopi/config.ts +265 -0
- package/src/mnemopi/embed-client.ts +401 -0
- package/src/mnemopi/embed-protocol.ts +35 -0
- package/src/mnemopi/embed-worker.ts +113 -0
- package/src/mnemopi/index.ts +3 -0
- package/src/mnemopi/state.ts +657 -0
- package/src/modes/acp/acp-agent.ts +2362 -0
- package/src/modes/acp/acp-client-bridge.ts +154 -0
- package/src/modes/acp/acp-event-mapper.ts +933 -0
- package/src/modes/acp/acp-mode.ts +23 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/__tests__/skill-message.test.ts +92 -0
- package/src/modes/components/advisor-message.ts +99 -0
- package/src/modes/components/agent-dashboard.ts +1206 -0
- package/src/modes/components/agent-hub.ts +566 -0
- package/src/modes/components/agent-transcript-viewer.ts +461 -0
- package/src/modes/components/assistant-message.ts +612 -0
- package/src/modes/components/background-tan-message.ts +36 -0
- package/src/modes/components/bash-execution.ts +220 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/btw-panel.ts +112 -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 +476 -0
- package/src/modes/components/collab-prompt-message.ts +32 -0
- package/src/modes/components/compaction-summary-message.ts +215 -0
- package/src/modes/components/copy-selector.ts +206 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.test.ts +142 -0
- package/src/modes/components/custom-editor.ts +620 -0
- package/src/modes/components/custom-message.ts +67 -0
- package/src/modes/components/diff.ts +254 -0
- package/src/modes/components/dynamic-border.ts +34 -0
- package/src/modes/components/error-banner.ts +33 -0
- package/src/modes/components/eval-execution.ts +158 -0
- package/src/modes/components/execution-shared.ts +101 -0
- package/src/modes/components/extensions/extension-dashboard.ts +399 -0
- package/src/modes/components/extensions/extension-list.ts +502 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +321 -0
- package/src/modes/components/extensions/state-manager.ts +627 -0
- package/src/modes/components/extensions/types.ts +186 -0
- package/src/modes/components/footer.ts +275 -0
- package/src/modes/components/history-search.ts +280 -0
- package/src/modes/components/hook-editor.ts +167 -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 +659 -0
- package/src/modes/components/index.ts +38 -0
- package/src/modes/components/keybinding-hints.ts +65 -0
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/login-dialog.ts +164 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1360 -0
- package/src/modes/components/message-frame.ts +92 -0
- package/src/modes/components/model-selector.ts +1315 -0
- package/src/modes/components/oauth-selector.ts +457 -0
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/overlay-box.ts +109 -0
- package/src/modes/components/plan-review-overlay.ts +847 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/plugin-selector.ts +95 -0
- package/src/modes/components/plugin-settings.ts +739 -0
- package/src/modes/components/queue-mode-selector.ts +56 -0
- package/src/modes/components/read-tool-group.ts +676 -0
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/segment-track.ts +89 -0
- package/src/modes/components/session-selector.ts +631 -0
- package/src/modes/components/settings-defs.ts +225 -0
- package/src/modes/components/settings-selector.ts +1095 -0
- package/src/modes/components/show-images-selector.ts +45 -0
- package/src/modes/components/skill-message.ts +110 -0
- package/src/modes/components/snapcompact-shape-preview-doc.md +18 -0
- package/src/modes/components/snapcompact-shape-preview.ts +192 -0
- package/src/modes/components/status-line/component.ts +1001 -0
- package/src/modes/components/status-line/context-thresholds.ts +78 -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 +616 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/token-rate.ts +66 -0
- package/src/modes/components/status-line/types.ts +124 -0
- package/src/modes/components/theme-selector.ts +63 -0
- package/src/modes/components/thinking-selector.ts +52 -0
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +24 -0
- package/src/modes/components/todo-reminder.ts +39 -0
- package/src/modes/components/tool-execution.ts +1165 -0
- package/src/modes/components/transcript-container.ts +806 -0
- package/src/modes/components/tree-selector.ts +994 -0
- package/src/modes/components/ttsr-notification.ts +123 -0
- package/src/modes/components/usage-row.ts +18 -0
- package/src/modes/components/user-message-selector.ts +227 -0
- package/src/modes/components/user-message.ts +68 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +581 -0
- package/src/modes/controllers/btw-controller.ts +173 -0
- package/src/modes/controllers/command-controller-shared.ts +109 -0
- package/src/modes/controllers/command-controller.ts +1653 -0
- package/src/modes/controllers/event-controller.ts +1153 -0
- package/src/modes/controllers/extension-ui-controller.ts +893 -0
- package/src/modes/controllers/input-controller.ts +1627 -0
- package/src/modes/controllers/mcp-command-controller.ts +2162 -0
- package/src/modes/controllers/omfg-controller.ts +283 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +1285 -0
- package/src/modes/controllers/session-focus-controller.ts +112 -0
- package/src/modes/controllers/ssh-command-controller.ts +384 -0
- package/src/modes/controllers/streaming-reveal.ts +295 -0
- package/src/modes/controllers/tan-command-controller.ts +190 -0
- package/src/modes/controllers/todo-command-controller.ts +485 -0
- package/src/modes/controllers/tool-args-reveal.ts +174 -0
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/gradient-highlight.ts +99 -0
- package/src/modes/image-references.ts +137 -0
- package/src/modes/index.ts +17 -0
- package/src/modes/interactive-mode.ts +3940 -0
- package/src/modes/internal-url-autocomplete.ts +143 -0
- package/src/modes/loop-limit.ts +192 -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 +42 -0
- package/src/modes/print-mode.ts +130 -0
- package/src/modes/prompt-action-autocomplete.ts +260 -0
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-client.ts +995 -0
- package/src/modes/rpc/rpc-mode.ts +1156 -0
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +487 -0
- package/src/modes/runtime-init.ts +142 -0
- package/src/modes/session-observer-registry.ts +215 -0
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +101 -0
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +114 -0
- package/src/modes/setup-wizard/scenes/outro.ts +35 -0
- package/src/modes/setup-wizard/scenes/providers.ts +103 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +286 -0
- package/src/modes/setup-wizard/scenes/splash.ts +201 -0
- package/src/modes/setup-wizard/scenes/theme.ts +326 -0
- package/src/modes/setup-wizard/scenes/types.ts +57 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +145 -0
- package/src/modes/setup-wizard/startup-splash.ts +107 -0
- package/src/modes/setup-wizard/wizard-overlay.ts +334 -0
- package/src/modes/shared.ts +49 -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 +142 -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 +142 -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 +235 -0
- package/src/modes/theme/theme-schema.json +459 -0
- package/src/modes/theme/theme.ts +2915 -0
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +406 -0
- package/src/modes/ultrathink.ts +41 -0
- package/src/modes/utils/context-usage.ts +432 -0
- package/src/modes/utils/copy-targets.ts +360 -0
- package/src/modes/utils/hotkeys-markdown.ts +62 -0
- package/src/modes/utils/keybinding-matchers.ts +51 -0
- package/src/modes/utils/tools-markdown.ts +27 -0
- package/src/modes/utils/ui-helpers.ts +886 -0
- package/src/modes/workflow.ts +42 -0
- package/src/plan-mode/approved-plan.ts +186 -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 +45 -0
- package/src/prompts/advisor/advise-tool.md +3 -0
- package/src/prompts/advisor/system.md +113 -0
- package/src/prompts/agents/designer.md +74 -0
- package/src/prompts/agents/explore.md +58 -0
- package/src/prompts/agents/frontmatter.md +11 -0
- package/src/prompts/agents/init.md +33 -0
- package/src/prompts/agents/librarian.md +119 -0
- package/src/prompts/agents/oracle.md +54 -0
- package/src/prompts/agents/plan.md +48 -0
- package/src/prompts/agents/reviewer.md +139 -0
- package/src/prompts/agents/task.md +17 -0
- package/src/prompts/bench.md +12 -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/guided-goal-interview.md +8 -0
- package/src/prompts/goals/guided-goal-system.md +12 -0
- package/src/prompts/low-end/system.md +47 -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 +22 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +69 -0
- package/src/prompts/steering/user-interjection.md +9 -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 +12 -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.md +3 -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/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/irc-autoreply.md +6 -0
- package/src/prompts/system/irc-incoming.md +7 -0
- package/src/prompts/system/manual-continue.md +7 -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/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 +109 -0
- package/src/prompts/system/plan-mode-approved.md +25 -0
- package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +11 -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/project-prompt.md +52 -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/subagent-system-prompt.md +71 -0
- package/src/prompts/system/subagent-user-prompt.md +3 -0
- package/src/prompts/system/subagent-yield-reminder.md +12 -0
- package/src/prompts/system/system-prompt.md +251 -0
- package/src/prompts/system/tiny-title-system.md +8 -0
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system-marker.md +16 -0
- package/src/prompts/system/title-system.md +16 -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/web-search.md +25 -0
- package/src/prompts/system/workflow-notice.md +70 -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 +22 -0
- package/src/prompts/tools/ast-grep.md +25 -0
- package/src/prompts/tools/async-result.md +8 -0
- package/src/prompts/tools/bash.md +45 -0
- package/src/prompts/tools/browser.md +42 -0
- package/src/prompts/tools/checkpoint.md +15 -0
- package/src/prompts/tools/debug.md +17 -0
- package/src/prompts/tools/eval.md +70 -0
- package/src/prompts/tools/find.md +19 -0
- package/src/prompts/tools/github.md +17 -0
- package/src/prompts/tools/goal.md +11 -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/irc.md +33 -0
- package/src/prompts/tools/job.md +17 -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 +39 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/memory-edit.md +8 -0
- package/src/prompts/tools/patch.md +57 -0
- package/src/prompts/tools/read.md +76 -0
- package/src/prompts/tools/recall.md +5 -0
- package/src/prompts/tools/reflect.md +5 -0
- package/src/prompts/tools/replace.md +29 -0
- package/src/prompts/tools/resolve.md +4 -0
- package/src/prompts/tools/retain.md +6 -0
- package/src/prompts/tools/rewind.md +13 -0
- package/src/prompts/tools/search-tool-bm25.md +32 -0
- package/src/prompts/tools/search.md +22 -0
- package/src/prompts/tools/ssh.md +22 -0
- package/src/prompts/tools/task-summary.md +17 -0
- package/src/prompts/tools/task.md +91 -0
- package/src/prompts/tools/todo.md +39 -0
- package/src/prompts/tools/web-search.md +6 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/registry/agent-lifecycle.ts +270 -0
- package/src/registry/agent-registry.ts +190 -0
- package/src/sdk.ts +2919 -0
- package/src/secrets/index.ts +123 -0
- package/src/secrets/obfuscator.ts +298 -0
- package/src/secrets/regex.ts +21 -0
- package/src/session/agent-session.ts +12539 -0
- package/src/session/agent-storage.ts +478 -0
- package/src/session/artifacts.ts +153 -0
- package/src/session/auth-broker-config.ts +92 -0
- package/src/session/auth-storage.ts +24 -0
- package/src/session/blob-store.ts +255 -0
- package/src/session/client-bridge.ts +85 -0
- package/src/session/codex-auto-reset.ts +202 -0
- package/src/session/compact-modes.ts +105 -0
- package/src/session/history-storage.ts +361 -0
- package/src/session/indexed-session-storage.ts +427 -0
- package/src/session/messages.ts +546 -0
- package/src/session/redis-session-storage.ts +170 -0
- package/src/session/session-context.ts +399 -0
- package/src/session/session-dump-format.ts +216 -0
- package/src/session/session-entries.ts +198 -0
- package/src/session/session-history-format.ts +308 -0
- package/src/session/session-listing.ts +588 -0
- package/src/session/session-loader.ts +93 -0
- package/src/session/session-manager.ts +1748 -0
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +193 -0
- package/src/session/session-persistence.ts +147 -0
- package/src/session/session-storage.ts +590 -0
- package/src/session/shake-types.ts +43 -0
- package/src/session/snapcompact-inline.ts +542 -0
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/sql-session-storage.ts +314 -0
- package/src/session/streaming-output.ts +1330 -0
- package/src/session/tool-choice-queue.ts +290 -0
- package/src/session/unexpected-stop-classifier.ts +129 -0
- package/src/session/yield-queue.ts +183 -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 +2332 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +44 -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 +88 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +532 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/reset-usage.ts +66 -0
- package/src/slash-commands/helpers/ssh.ts +195 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/slash-commands/helpers/todo.ts +279 -0
- package/src/slash-commands/helpers/usage-report.ts +128 -0
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/slash-commands/types.ts +135 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +510 -0
- package/src/ssh/ssh-executor.ts +189 -0
- package/src/ssh/sshfs-mount.ts +140 -0
- package/src/ssh/utils.ts +8 -0
- package/src/startup-splash.ts +19 -0
- package/src/stt/asr-client.ts +521 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +790 -0
- package/src/stt/downloader.ts +138 -0
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +7 -0
- package/src/stt/models.ts +150 -0
- package/src/stt/recorder.ts +538 -0
- package/src/stt/stt-controller.ts +380 -0
- package/src/stt/transcriber.ts +60 -0
- package/src/stt/wav.ts +173 -0
- package/src/system-prompt.ts +709 -0
- package/src/task/agents.ts +166 -0
- package/src/task/commands.ts +132 -0
- package/src/task/discovery.ts +122 -0
- package/src/task/executor.ts +2356 -0
- package/src/task/index.ts +1580 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/omp-command.ts +26 -0
- package/src/task/output-manager.ts +93 -0
- package/src/task/parallel.ts +116 -0
- package/src/task/persisted-revive.ts +128 -0
- package/src/task/render.ts +1558 -0
- package/src/task/repair-args.ts +129 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/types.ts +401 -0
- package/src/task/worktree.ts +514 -0
- package/src/telemetry-export.ts +144 -0
- package/src/thinking.ts +187 -0
- package/src/tiny/device.ts +111 -0
- package/src/tiny/dtype.ts +101 -0
- package/src/tiny/models.ts +252 -0
- package/src/tiny/text.ts +169 -0
- package/src/tiny/title-client.ts +538 -0
- package/src/tiny/title-protocol.ts +56 -0
- package/src/tiny/worker.ts +491 -0
- package/src/tool-discovery/mode.ts +24 -0
- package/src/tool-discovery/tool-index.ts +271 -0
- package/src/tools/__tests__/json-tree.test.ts +35 -0
- package/src/tools/approval.ts +189 -0
- package/src/tools/ask.ts +977 -0
- package/src/tools/ast-edit.ts +700 -0
- package/src/tools/ast-grep.ts +483 -0
- package/src/tools/auto-generated-guard.ts +322 -0
- package/src/tools/bash-command-fixup.ts +37 -0
- package/src/tools/bash-interactive.ts +408 -0
- package/src/tools/bash-interceptor.ts +67 -0
- package/src/tools/bash-pty-selection.ts +14 -0
- package/src/tools/bash-skill-urls.ts +248 -0
- package/src/tools/bash.ts +1405 -0
- package/src/tools/browser/attach.ts +194 -0
- package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
- package/src/tools/browser/cmux/rpc.ts +156 -0
- package/src/tools/browser/cmux/socket-client.ts +309 -0
- package/src/tools/browser/launch.ts +673 -0
- package/src/tools/browser/readable.ts +112 -0
- package/src/tools/browser/registry.ts +241 -0
- package/src/tools/browser/render.ts +221 -0
- package/src/tools/browser/tab-protocol.ts +107 -0
- package/src/tools/browser/tab-supervisor.ts +799 -0
- package/src/tools/browser/tab-worker-entry.ts +29 -0
- package/src/tools/browser/tab-worker.ts +1226 -0
- package/src/tools/browser.ts +403 -0
- package/src/tools/builtin-names.ts +34 -0
- package/src/tools/checkpoint.ts +136 -0
- package/src/tools/conflict-detect.ts +718 -0
- package/src/tools/context.ts +39 -0
- package/src/tools/debug.ts +1087 -0
- package/src/tools/eval-backends.ts +27 -0
- package/src/tools/eval-render.ts +762 -0
- package/src/tools/eval.ts +600 -0
- package/src/tools/fetch.ts +1902 -0
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/find.ts +629 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gh-cache-invalidation.ts +255 -0
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +481 -0
- package/src/tools/gh.ts +3752 -0
- package/src/tools/github-cache.ts +663 -0
- package/src/tools/grouped-file-output.ts +210 -0
- package/src/tools/image-gen.ts +1586 -0
- package/src/tools/index.ts +649 -0
- package/src/tools/inspect-image-renderer.ts +132 -0
- package/src/tools/inspect-image.ts +260 -0
- package/src/tools/irc.ts +788 -0
- package/src/tools/job.ts +612 -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 +100 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/memory-edit.ts +59 -0
- package/src/tools/memory-recall.ts +102 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +202 -0
- package/src/tools/memory-retain.ts +89 -0
- package/src/tools/output-meta.ts +768 -0
- package/src/tools/output-schema-validator.ts +132 -0
- package/src/tools/path-utils.ts +1116 -0
- package/src/tools/plan-mode-guard.ts +142 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +208 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
- package/src/tools/read.ts +3124 -0
- package/src/tools/render-utils.ts +895 -0
- package/src/tools/renderers.ts +86 -0
- package/src/tools/report-tool-issue.ts +530 -0
- package/src/tools/resolve.ts +302 -0
- package/src/tools/review.ts +251 -0
- package/src/tools/search-tool-bm25.ts +351 -0
- package/src/tools/search.ts +1583 -0
- package/src/tools/sqlite-reader.ts +828 -0
- package/src/tools/ssh.ts +369 -0
- package/src/tools/todo.ts +938 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +102 -0
- package/src/tools/tool-timeouts.ts +30 -0
- package/src/tools/tts.ts +265 -0
- package/src/tools/write.ts +1182 -0
- package/src/tools/yield.ts +269 -0
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/player.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/streaming-player.ts +266 -0
- package/src/tts/tts-client.ts +642 -0
- package/src/tts/tts-protocol.ts +60 -0
- package/src/tts/tts-worker.ts +505 -0
- package/src/tts/vocalizer.ts +162 -0
- package/src/tts/wav.ts +58 -0
- package/src/tui/code-cell.ts +257 -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 +240 -0
- package/src/tui/status-line.ts +54 -0
- package/src/tui/tree-list.ts +133 -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/block-context.ts +312 -0
- package/src/utils/changelog.ts +132 -0
- package/src/utils/clipboard.ts +262 -0
- package/src/utils/command-args.ts +76 -0
- package/src/utils/commit-message-generator.ts +147 -0
- package/src/utils/edit-mode.ts +41 -0
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +78 -0
- package/src/utils/file-display-mode.ts +45 -0
- package/src/utils/file-mentions.ts +284 -0
- package/src/utils/git.ts +1838 -0
- package/src/utils/image-loading.ts +231 -0
- package/src/utils/image-resize.ts +309 -0
- package/src/utils/image-vision-fallback.ts +197 -0
- package/src/utils/ipc.ts +38 -0
- package/src/utils/jj.ts +248 -0
- package/src/utils/lang-from-path.ts +244 -0
- package/src/utils/markit.ts +143 -0
- package/src/utils/mupdf-wasm-embed.ts +12 -0
- package/src/utils/open.ts +55 -0
- package/src/utils/qrcode.ts +535 -0
- package/src/utils/session-color.ts +142 -0
- package/src/utils/shell-snapshot.ts +187 -0
- package/src/utils/sixel.ts +69 -0
- package/src/utils/thinking-display.ts +11 -0
- package/src/utils/title-generator.ts +416 -0
- package/src/utils/tool-choice.ts +49 -0
- package/src/utils/tools-manager.ts +372 -0
- package/src/utils/turndown.ts +83 -0
- package/src/utils/zip.ts +1091 -0
- package/src/web/kagi.ts +304 -0
- package/src/web/parallel.ts +353 -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 +652 -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 +140 -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 +120 -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 +317 -0
- package/src/web/search/provider.ts +169 -0
- package/src/web/search/providers/anthropic.ts +343 -0
- package/src/web/search/providers/base.ts +90 -0
- package/src/web/search/providers/brave.ts +152 -0
- package/src/web/search/providers/codex.ts +593 -0
- package/src/web/search/providers/exa.ts +400 -0
- package/src/web/search/providers/gemini.ts +518 -0
- package/src/web/search/providers/jina.ts +111 -0
- package/src/web/search/providers/kagi.ts +86 -0
- package/src/web/search/providers/kimi.ts +196 -0
- package/src/web/search/providers/parallel.ts +225 -0
- package/src/web/search/providers/perplexity-auth.ts +133 -0
- package/src/web/search/providers/perplexity.ts +866 -0
- package/src/web/search/providers/searxng.ts +325 -0
- package/src/web/search/providers/synthetic.ts +114 -0
- package/src/web/search/providers/tavily.ts +176 -0
- package/src/web/search/providers/utils.ts +128 -0
- package/src/web/search/providers/zai.ts +333 -0
- package/src/web/search/render.ts +262 -0
- package/src/web/search/types.ts +462 -0
- package/src/web/search/utils.ts +17 -0
- package/src/workspace-tree.ts +326 -0
|
@@ -0,0 +1,1627 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { type AutocompleteProvider, matchesKey, type SlashCommand } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { $env, isEnoent, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
6
|
+
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
7
|
+
import { resolveLocalRoot } from "../../internal-urls";
|
|
8
|
+
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
9
|
+
import { renderSegmentTrack } from "../../modes/components/segment-track";
|
|
10
|
+
import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
|
|
11
|
+
import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
|
12
|
+
import { materializeImageReferenceLinks, shiftImageMarkers } from "../../modes/image-references";
|
|
13
|
+
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
14
|
+
import type { InteractiveModeContext } from "../../modes/types";
|
|
15
|
+
import manualContinuePrompt from "../../prompts/system/manual-continue.md" with { type: "text" };
|
|
16
|
+
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails, USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
17
|
+
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
18
|
+
import { isTinyTitleLocalModelKey } from "../../tiny/models";
|
|
19
|
+
import { isLowSignalTitleInput } from "../../tiny/text";
|
|
20
|
+
import { tinyTitleClient } from "../../tiny/title-client";
|
|
21
|
+
import type { TinyTitleProgressEvent } from "../../tiny/title-protocol";
|
|
22
|
+
import { shortenPath, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
|
|
23
|
+
import { copyToClipboard, readImageFromClipboard, readTextFromClipboard } from "../../utils/clipboard";
|
|
24
|
+
import { EnhancedPasteController } from "../../utils/enhanced-paste";
|
|
25
|
+
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
26
|
+
import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
|
|
27
|
+
import { resizeImage } from "../../utils/image-resize";
|
|
28
|
+
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
29
|
+
|
|
30
|
+
interface Expandable {
|
|
31
|
+
setExpanded(expanded: boolean): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isExpandable(obj: unknown): obj is Expandable {
|
|
35
|
+
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Minimal contract for any component that can receive a paste payload directly. */
|
|
39
|
+
interface PasteTarget {
|
|
40
|
+
pasteText(text: string): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function hasPasteText(value: unknown): value is PasteTarget {
|
|
44
|
+
return typeof value === "object" && value !== null && typeof (value as PasteTarget).pasteText === "function";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function pythonCommandPrefixLength(trimmedText: string): 0 | 1 | 2 {
|
|
48
|
+
if (trimmedText.charCodeAt(0) !== 36 /* $ */) return 0;
|
|
49
|
+
if (trimmedText.charCodeAt(1) === 123 /* { */) return 0;
|
|
50
|
+
|
|
51
|
+
const prefixLength = trimmedText.charCodeAt(1) === 36 /* $ */ ? 2 : 1;
|
|
52
|
+
const next = trimmedText.charCodeAt(prefixLength);
|
|
53
|
+
if (Number.isNaN(next)) return prefixLength;
|
|
54
|
+
return next === 32 || next === 9 || next === 10 || next === 13 ? prefixLength : 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parsePythonCommandInput(text: string): { code: string; isExcluded: boolean } | undefined {
|
|
58
|
+
const trimmed = text.trimStart();
|
|
59
|
+
const prefixLength = pythonCommandPrefixLength(trimmed);
|
|
60
|
+
if (prefixLength === 0) return undefined;
|
|
61
|
+
return {
|
|
62
|
+
code: trimmed.slice(prefixLength).trim(),
|
|
63
|
+
isExcluded: prefixLength === 2,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Wrap pasted text in `<attachment>` tags so the model treats it as one quoted block. */
|
|
68
|
+
function wrapPasteInAttachmentBlock(content: string): string {
|
|
69
|
+
return `<attachment>\n${content}\n</attachment>`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const TINY_TITLE_PROGRESS_DONE_TTL_MS = 3_000;
|
|
73
|
+
// A cached model fires its file-load events in a short burst and then goes silent
|
|
74
|
+
// while onnxruntime builds the session; a genuine download keeps streaming progress
|
|
75
|
+
// events for seconds. Only reveal the bar once a still-incomplete event arrives after
|
|
76
|
+
// this grace window, so an already-downloaded model never flashes the bar.
|
|
77
|
+
const TINY_TITLE_PROGRESS_REVEAL_DELAY_MS = 1_000;
|
|
78
|
+
// Double-tap ← on an empty editor opens the Agent Hub (and, in a focused
|
|
79
|
+
// subagent view, ←← returns to the main session). The second tap must land
|
|
80
|
+
// inside this window. The lower bound rejects terminal-synthesized arrow-key
|
|
81
|
+
// bursts: "click to move cursor" / pointer features in iTerm2, WezTerm, kitty,
|
|
82
|
+
// and tmux emit several arrow keys in a single stdin read (sub-millisecond
|
|
83
|
+
// apart) on a stray click, which used to pop the hub with no key ever pressed.
|
|
84
|
+
// Three or more rapid taps are likewise treated as a burst, not a gesture. A
|
|
85
|
+
// deliberate human double-tap is always tens of milliseconds apart.
|
|
86
|
+
const LEFT_DOUBLE_TAP_MIN_GAP_MS = 40;
|
|
87
|
+
const LEFT_DOUBLE_TAP_MAX_GAP_MS = 500;
|
|
88
|
+
|
|
89
|
+
export class InputController {
|
|
90
|
+
constructor(
|
|
91
|
+
private ctx: InteractiveModeContext,
|
|
92
|
+
/** Injectable clipboard reads so tests can drive paste flows without a real clipboard. */
|
|
93
|
+
private clipboard: {
|
|
94
|
+
readImage: typeof readImageFromClipboard;
|
|
95
|
+
readText: typeof readTextFromClipboard;
|
|
96
|
+
} = { readImage: readImageFromClipboard, readText: readTextFromClipboard },
|
|
97
|
+
) {}
|
|
98
|
+
|
|
99
|
+
#enhancedPaste?: EnhancedPasteController;
|
|
100
|
+
#focusedLeftTapListenerInstalled = false;
|
|
101
|
+
#btwBranchListenerInstalled = false;
|
|
102
|
+
// Tap counter for the double-← gesture; reset whenever a quiet gap
|
|
103
|
+
// (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See
|
|
104
|
+
// #detectLeftDoubleTap.
|
|
105
|
+
#leftTapCount = 0;
|
|
106
|
+
// Sequential index for `local://attachment-N` references created by the large-paste local-file
|
|
107
|
+
// action. Seeded from 0 and bumped past any existing attachment files in #attachPasteAsFile.
|
|
108
|
+
#attachmentCounter = 0;
|
|
109
|
+
|
|
110
|
+
#showTinyTitleDownloadProgress(modelKey: string): void {
|
|
111
|
+
if (!isTinyTitleLocalModelKey(modelKey)) return;
|
|
112
|
+
const component = new TinyTitleDownloadProgressComponent(modelKey);
|
|
113
|
+
let added = false;
|
|
114
|
+
let disposed = false;
|
|
115
|
+
let removeTimer: NodeJS.Timeout | undefined;
|
|
116
|
+
const remove = (): void => {
|
|
117
|
+
if (disposed) return;
|
|
118
|
+
disposed = true;
|
|
119
|
+
unsubscribe();
|
|
120
|
+
if (removeTimer) {
|
|
121
|
+
clearTimeout(removeTimer);
|
|
122
|
+
removeTimer = undefined;
|
|
123
|
+
}
|
|
124
|
+
if (added) {
|
|
125
|
+
this.ctx.chatContainer.removeChild(component);
|
|
126
|
+
this.ctx.ui.requestRender();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const scheduleRemove = (): void => {
|
|
130
|
+
if (removeTimer) clearTimeout(removeTimer);
|
|
131
|
+
removeTimer = setTimeout(remove, TINY_TITLE_PROGRESS_DONE_TTL_MS);
|
|
132
|
+
removeTimer.unref?.();
|
|
133
|
+
};
|
|
134
|
+
let revealAt = 0;
|
|
135
|
+
const update = (event: TinyTitleProgressEvent): void => {
|
|
136
|
+
if (disposed || event.modelKey !== modelKey) return;
|
|
137
|
+
component.update(event);
|
|
138
|
+
if (revealAt === 0) revealAt = performance.now() + TINY_TITLE_PROGRESS_REVEAL_DELAY_MS;
|
|
139
|
+
const complete = component.isComplete();
|
|
140
|
+
// Reveal only for a download still in flight past the grace window. Cache hits
|
|
141
|
+
// either complete or fall silent (onnx init emits no events) before this fires.
|
|
142
|
+
if (!added && !complete && performance.now() >= revealAt) {
|
|
143
|
+
this.ctx.chatContainer.addChild(component);
|
|
144
|
+
added = true;
|
|
145
|
+
}
|
|
146
|
+
if (added) this.ctx.ui.requestRender();
|
|
147
|
+
if (complete) {
|
|
148
|
+
if (added) scheduleRemove();
|
|
149
|
+
else remove();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
const unsubscribe = tinyTitleClient.onProgress(update);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
setupKeyHandlers(): void {
|
|
156
|
+
this.ctx.editor.setActionKeys("app.interrupt", this.ctx.keybindings.getKeys("app.interrupt"));
|
|
157
|
+
if (!this.#focusedLeftTapListenerInstalled) {
|
|
158
|
+
this.#focusedLeftTapListenerInstalled = true;
|
|
159
|
+
this.ctx.ui.addInputListener(data => {
|
|
160
|
+
if (!this.ctx.focusedAgentId) return undefined;
|
|
161
|
+
if (!matchesKey(data, "left")) return undefined;
|
|
162
|
+
if (this.ctx.editor.getText().trim()) return undefined;
|
|
163
|
+
this.#handleFocusedLeftTap();
|
|
164
|
+
return { consume: true };
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (!this.#btwBranchListenerInstalled) {
|
|
168
|
+
this.#btwBranchListenerInstalled = true;
|
|
169
|
+
this.ctx.ui.addInputListener(data => {
|
|
170
|
+
if (!matchesKey(data, "b")) return undefined;
|
|
171
|
+
if (!this.ctx.canBranchBtw()) return undefined;
|
|
172
|
+
if (this.ctx.editor.getText().trim()) return undefined;
|
|
173
|
+
void this.ctx.handleBtwBranchKey();
|
|
174
|
+
return { consume: true };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
this.ctx.editor.onEscape = () => {
|
|
178
|
+
// Active context maintenance owns Esc: auto/manual compaction,
|
|
179
|
+
// handoff generation, and auto-retry backoff all advertise
|
|
180
|
+
// "(esc to cancel)". Dispatch on live session state instead of
|
|
181
|
+
// swapping onEscape handlers — interleaved start/end events used
|
|
182
|
+
// to clobber the single saved-handler slot (auto-compaction start
|
|
183
|
+
// → /compact → auto end → manual finally), leaving Esc wired to a
|
|
184
|
+
// stale no-op closure until restart.
|
|
185
|
+
const viewSession = this.ctx.viewSession;
|
|
186
|
+
let aborted = false;
|
|
187
|
+
if (viewSession.isCompacting) {
|
|
188
|
+
try {
|
|
189
|
+
viewSession.abortCompaction();
|
|
190
|
+
} catch {}
|
|
191
|
+
aborted = true;
|
|
192
|
+
}
|
|
193
|
+
if (viewSession.isGeneratingHandoff) {
|
|
194
|
+
try {
|
|
195
|
+
viewSession.abortHandoff();
|
|
196
|
+
} catch {}
|
|
197
|
+
aborted = true;
|
|
198
|
+
}
|
|
199
|
+
if (viewSession.isRetrying) {
|
|
200
|
+
try {
|
|
201
|
+
viewSession.abortRetry();
|
|
202
|
+
} catch {}
|
|
203
|
+
aborted = true;
|
|
204
|
+
}
|
|
205
|
+
if (aborted) return;
|
|
206
|
+
|
|
207
|
+
if (this.ctx.loopModeEnabled) {
|
|
208
|
+
this.ctx.pauseLoop();
|
|
209
|
+
if (this.ctx.session.isStreaming) {
|
|
210
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
211
|
+
} else {
|
|
212
|
+
this.ctx.cancelPendingSubmission();
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (this.ctx.hasActiveBtw() && this.ctx.handleBtwEscape()) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (this.ctx.hasActiveOmfg() && this.ctx.handleOmfgEscape()) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (this.ctx.focusedAgentId) {
|
|
223
|
+
// Esc never interrupts the focused agent's turn: clear typed text,
|
|
224
|
+
// else return the view to the main session. Interrupt via empty
|
|
225
|
+
// steer-flush submit if needed.
|
|
226
|
+
if (this.ctx.editor.getText().trim()) {
|
|
227
|
+
this.ctx.editor.setText("");
|
|
228
|
+
this.ctx.ui.requestRender();
|
|
229
|
+
} else {
|
|
230
|
+
void this.ctx.unfocusSession();
|
|
231
|
+
}
|
|
232
|
+
return; // double-escape backtrack (/tree, /branch) stays main-only
|
|
233
|
+
}
|
|
234
|
+
if (this.ctx.collabGuest) {
|
|
235
|
+
// Guest Esc: ask the host to interrupt its agent; the local replica
|
|
236
|
+
// session is never streaming, so the native abort path below would
|
|
237
|
+
// no-op.
|
|
238
|
+
if (this.ctx.collabGuest.state?.isStreaming || this.ctx.loadingAnimation) {
|
|
239
|
+
this.ctx.collabGuest.sendAbort();
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (this.ctx.loadingAnimation) {
|
|
244
|
+
if (this.ctx.cancelPendingSubmission()) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
248
|
+
} else if (this.ctx.session.isBashRunning) {
|
|
249
|
+
this.ctx.session.abortBash();
|
|
250
|
+
} else if (this.ctx.isBashMode) {
|
|
251
|
+
this.ctx.editor.setText("");
|
|
252
|
+
this.ctx.isBashMode = false;
|
|
253
|
+
this.ctx.updateEditorBorderColor();
|
|
254
|
+
} else if (this.ctx.session.isEvalRunning) {
|
|
255
|
+
this.ctx.session.abortEval();
|
|
256
|
+
} else if (this.ctx.isPythonMode) {
|
|
257
|
+
this.ctx.editor.setText("");
|
|
258
|
+
this.ctx.isPythonMode = false;
|
|
259
|
+
this.ctx.updateEditorBorderColor();
|
|
260
|
+
} else if (this.ctx.session.isStreaming) {
|
|
261
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
262
|
+
} else if (this.ctx.editor.getText().trim()) {
|
|
263
|
+
// Esc with typed text clears the draft instead of (or before) any double-Esc action
|
|
264
|
+
this.ctx.editor.setText("");
|
|
265
|
+
this.ctx.ui.requestRender();
|
|
266
|
+
this.ctx.lastEscapeTime = 0;
|
|
267
|
+
} else {
|
|
268
|
+
// Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
|
|
269
|
+
const action = settings.get("doubleEscapeAction");
|
|
270
|
+
if (action !== "none") {
|
|
271
|
+
const now = Date.now();
|
|
272
|
+
if (now - this.ctx.lastEscapeTime < 500) {
|
|
273
|
+
if (action === "tree") {
|
|
274
|
+
this.ctx.showTreeSelector();
|
|
275
|
+
} else {
|
|
276
|
+
this.ctx.showUserMessageSelector();
|
|
277
|
+
}
|
|
278
|
+
this.ctx.ui.resetDisplay();
|
|
279
|
+
this.ctx.lastEscapeTime = 0;
|
|
280
|
+
} else {
|
|
281
|
+
this.ctx.lastEscapeTime = now;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
this.ctx.editor.setActionKeys("app.clear", this.ctx.keybindings.getKeys("app.clear"));
|
|
288
|
+
this.ctx.editor.onClear = () => this.handleCtrlC();
|
|
289
|
+
this.ctx.editor.setActionKeys("app.exit", this.ctx.keybindings.getKeys("app.exit"));
|
|
290
|
+
this.ctx.editor.setActionKeys("app.display.reset", this.ctx.keybindings.getKeys("app.display.reset"));
|
|
291
|
+
this.ctx.editor.onDisplayReset = () => this.ctx.ui.resetDisplay();
|
|
292
|
+
this.ctx.editor.onExit = () => this.handleCtrlD();
|
|
293
|
+
this.ctx.editor.setActionKeys("app.suspend", this.ctx.keybindings.getKeys("app.suspend"));
|
|
294
|
+
this.ctx.editor.onSuspend = () => this.handleCtrlZ();
|
|
295
|
+
this.ctx.editor.setActionKeys("app.thinking.cycle", this.ctx.keybindings.getKeys("app.thinking.cycle"));
|
|
296
|
+
this.ctx.editor.onCycleThinkingLevel = () => this.cycleThinkingLevel();
|
|
297
|
+
this.ctx.editor.setActionKeys("app.model.cycleForward", this.ctx.keybindings.getKeys("app.model.cycleForward"));
|
|
298
|
+
this.ctx.editor.onCycleModelForward = () => this.cycleRoleModel("forward");
|
|
299
|
+
this.ctx.editor.setActionKeys("app.model.cycleBackward", this.ctx.keybindings.getKeys("app.model.cycleBackward"));
|
|
300
|
+
this.ctx.editor.onCycleModelBackward = () => this.cycleRoleModel("backward");
|
|
301
|
+
this.ctx.editor.setActionKeys(
|
|
302
|
+
"app.model.selectTemporary",
|
|
303
|
+
this.ctx.keybindings.getKeys("app.model.selectTemporary"),
|
|
304
|
+
);
|
|
305
|
+
this.ctx.editor.onSelectModelTemporary = () => this.ctx.showModelSelector({ temporaryOnly: true });
|
|
306
|
+
|
|
307
|
+
// Global debug handler on TUI (works regardless of focus)
|
|
308
|
+
this.ctx.ui.onDebug = () => this.ctx.showDebugSelector();
|
|
309
|
+
this.ctx.editor.setActionKeys("app.model.select", this.ctx.keybindings.getKeys("app.model.select"));
|
|
310
|
+
this.ctx.editor.onSelectModel = () => this.ctx.showModelSelector();
|
|
311
|
+
this.ctx.editor.setActionKeys("app.history.search", this.ctx.keybindings.getKeys("app.history.search"));
|
|
312
|
+
this.ctx.editor.onHistorySearch = () => this.ctx.showHistorySearch();
|
|
313
|
+
this.ctx.editor.setActionKeys("app.thinking.toggle", this.ctx.keybindings.getKeys("app.thinking.toggle"));
|
|
314
|
+
this.ctx.editor.onToggleThinking = () => this.ctx.toggleThinkingBlockVisibility();
|
|
315
|
+
this.ctx.editor.setActionKeys("app.editor.external", this.ctx.keybindings.getKeys("app.editor.external"));
|
|
316
|
+
this.ctx.editor.onExternalEditor = () => void this.openExternalEditor();
|
|
317
|
+
this.ctx.editor.setActionKeys(
|
|
318
|
+
"app.clipboard.pasteImage",
|
|
319
|
+
this.ctx.keybindings.getKeys("app.clipboard.pasteImage"),
|
|
320
|
+
);
|
|
321
|
+
this.ctx.editor.onPasteImage = () => this.handleImagePaste();
|
|
322
|
+
this.ctx.editor.onPasteImagePath = path => this.handleImagePathPaste(path);
|
|
323
|
+
this.ctx.editor.setActionKeys(
|
|
324
|
+
"app.clipboard.pasteTextRaw",
|
|
325
|
+
this.ctx.keybindings.getKeys("app.clipboard.pasteTextRaw"),
|
|
326
|
+
);
|
|
327
|
+
this.ctx.editor.onPasteTextRaw = () => void this.handleClipboardTextRawPaste();
|
|
328
|
+
this.ctx.editor.onLargePaste = (text, lineCount) => this.handleLargePaste(text, lineCount);
|
|
329
|
+
this.ctx.editor.setActionKeys(
|
|
330
|
+
"app.clipboard.copyPrompt",
|
|
331
|
+
this.ctx.keybindings.getKeys("app.clipboard.copyPrompt"),
|
|
332
|
+
);
|
|
333
|
+
this.ctx.editor.onCopyPrompt = () => this.handleCopyPrompt();
|
|
334
|
+
this.ctx.editor.setActionKeys("app.tools.expand", this.ctx.keybindings.getKeys("app.tools.expand"));
|
|
335
|
+
this.ctx.editor.onExpandTools = () => this.toggleToolOutputExpansion();
|
|
336
|
+
this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
|
|
337
|
+
this.ctx.editor.onDequeue = () => this.handleDequeue();
|
|
338
|
+
this.ctx.editor.setActionKeys("app.retry", this.ctx.keybindings.getKeys("app.retry"));
|
|
339
|
+
this.ctx.editor.onRetry = () => void this.handleRetry();
|
|
340
|
+
this.ctx.editor.clearCustomKeyHandlers();
|
|
341
|
+
// Wire up extension shortcuts
|
|
342
|
+
this.registerExtensionShortcuts();
|
|
343
|
+
const planModeKeys = this.ctx.keybindings.getKeys("app.plan.toggle");
|
|
344
|
+
for (const key of planModeKeys) {
|
|
345
|
+
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handlePlanModeCommand());
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.new")) {
|
|
349
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.handleClearCommand());
|
|
350
|
+
}
|
|
351
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.tree")) {
|
|
352
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showTreeSelector());
|
|
353
|
+
}
|
|
354
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.fork")) {
|
|
355
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showUserMessageSelector());
|
|
356
|
+
}
|
|
357
|
+
for (const key of this.ctx.keybindings.getKeys("app.session.resume")) {
|
|
358
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showSessionSelector());
|
|
359
|
+
}
|
|
360
|
+
for (const key of this.ctx.keybindings.getKeys("app.message.followUp")) {
|
|
361
|
+
this.ctx.editor.setCustomKeyHandler(key, () => void this.handleFollowUp());
|
|
362
|
+
}
|
|
363
|
+
for (const key of this.ctx.keybindings.getKeys("app.stt.toggle")) {
|
|
364
|
+
this.ctx.editor.setCustomKeyHandler(key, () => void this.ctx.handleSTTToggle());
|
|
365
|
+
}
|
|
366
|
+
// Hold the space bar to push-to-talk: the editor recognizes the auto-repeat burst, tracks
|
|
367
|
+
// the spam back out, and toggles STT on hold start / release. Gated on `stt.enabled` so a
|
|
368
|
+
// disabled STT leaves the space bar typing normally.
|
|
369
|
+
this.ctx.editor.sttHoldEnabled = () => settings.get("stt.enabled");
|
|
370
|
+
this.ctx.editor.onSpaceHoldStart = () => void this.ctx.handleSTTToggle();
|
|
371
|
+
this.ctx.editor.onSpaceHoldEnd = () => void this.ctx.handleSTTToggle();
|
|
372
|
+
for (const key of this.ctx.keybindings.getKeys("app.clipboard.copyLine")) {
|
|
373
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.handleCopyCurrentLine());
|
|
374
|
+
}
|
|
375
|
+
const hubKeys = new Set([
|
|
376
|
+
...this.ctx.keybindings.getKeys("app.agents.hub"),
|
|
377
|
+
...this.ctx.keybindings.getKeys("app.session.observe"),
|
|
378
|
+
]);
|
|
379
|
+
for (const key of hubKeys) {
|
|
380
|
+
this.ctx.editor.setCustomKeyHandler(key, () => this.ctx.showAgentHub());
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Double-tap left arrow on an empty editor: opens the agent hub from the
|
|
384
|
+
// main session, or returns the focused subagent view to the main session.
|
|
385
|
+
// Focused ←← intentionally matches Esc. From the main session the gesture
|
|
386
|
+
// stays inert when there are no subagents (requireContent); the explicit
|
|
387
|
+
// hub key still opens the empty roster.
|
|
388
|
+
this.ctx.editor.onLeftAtStart = () => {
|
|
389
|
+
if (this.ctx.focusedAgentId) {
|
|
390
|
+
this.#handleFocusedLeftTap();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (this.#detectLeftDoubleTap()) {
|
|
394
|
+
this.ctx.showAgentHub({ requireContent: true });
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
this.#setupEnhancedPaste();
|
|
399
|
+
|
|
400
|
+
this.ctx.editor.onChange = (text: string) => {
|
|
401
|
+
const wasBashMode = this.ctx.isBashMode;
|
|
402
|
+
const wasPythonMode = this.ctx.isPythonMode;
|
|
403
|
+
const trimmed = text.trimStart();
|
|
404
|
+
this.ctx.isBashMode = trimmed.startsWith("!");
|
|
405
|
+
this.ctx.isPythonMode = pythonCommandPrefixLength(trimmed) > 0;
|
|
406
|
+
if (wasBashMode !== this.ctx.isBashMode || wasPythonMode !== this.ctx.isPythonMode) {
|
|
407
|
+
this.ctx.updateEditorBorderColor();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
#handleFocusedLeftTap(): void {
|
|
413
|
+
if (this.#detectLeftDoubleTap()) {
|
|
414
|
+
void this.ctx.unfocusSession();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Detect a deliberate double-← gesture, rejecting terminal-synthesized arrow
|
|
420
|
+
* bursts. Returns true only on the *second* tap of a fresh sequence when it
|
|
421
|
+
* lands a human-plausible interval after the first
|
|
422
|
+
* (`[LEFT_DOUBLE_TAP_MIN_GAP_MS, LEFT_DOUBLE_TAP_MAX_GAP_MS)`). Taps closer
|
|
423
|
+
* than the lower bound, or any third-and-later tap before a quiet gap, are a
|
|
424
|
+
* burst and never fire — so a stray click that makes the terminal emit a run
|
|
425
|
+
* of ← keys can no longer pop the Agent Hub.
|
|
426
|
+
*/
|
|
427
|
+
#detectLeftDoubleTap(): boolean {
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
const sinceLast = now - this.ctx.lastLeftTapTime;
|
|
430
|
+
this.ctx.lastLeftTapTime = now;
|
|
431
|
+
if (sinceLast >= LEFT_DOUBLE_TAP_MAX_GAP_MS) {
|
|
432
|
+
// Quiet gap: this tap starts a fresh sequence.
|
|
433
|
+
this.#leftTapCount = 1;
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
this.#leftTapCount += 1;
|
|
437
|
+
if (this.#leftTapCount === 2 && sinceLast >= LEFT_DOUBLE_TAP_MIN_GAP_MS) {
|
|
438
|
+
// Exactly two taps, the second a human-plausible interval after the first.
|
|
439
|
+
this.#leftTapCount = 0;
|
|
440
|
+
this.ctx.lastLeftTapTime = 0;
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
#setupEnhancedPaste(): void {
|
|
447
|
+
if (this.#enhancedPaste) return;
|
|
448
|
+
|
|
449
|
+
this.#enhancedPaste = new EnhancedPasteController({
|
|
450
|
+
write: data => this.ctx.ui.terminal.write(data),
|
|
451
|
+
pasteText: text => {
|
|
452
|
+
// Route enhanced-paste text to the currently focused component when it
|
|
453
|
+
// exposes a `pasteText` hook (modal Input prompts: OAuth API-key entry,
|
|
454
|
+
// Perplexity OTP, GitHub Enterprise URL, manual redirect URL). Falling
|
|
455
|
+
// back to the main editor would have buried the text in the detached
|
|
456
|
+
// editor while the modal Input had focus (#2127).
|
|
457
|
+
const focused = this.ctx.ui.getFocused();
|
|
458
|
+
const target = focused && focused !== this.ctx.editor && hasPasteText(focused) ? focused : this.ctx.editor;
|
|
459
|
+
target.pasteText(text);
|
|
460
|
+
this.ctx.ui.requestRender();
|
|
461
|
+
},
|
|
462
|
+
pasteImage: async image => {
|
|
463
|
+
// Images can only land in the main editor — when a modal Input is
|
|
464
|
+
// focused, refuse rather than dump the binary blob in a hidden buffer.
|
|
465
|
+
const focused = this.ctx.ui.getFocused();
|
|
466
|
+
if (focused && focused !== this.ctx.editor && hasPasteText(focused)) {
|
|
467
|
+
this.ctx.showStatus("Image paste is not supported in this prompt");
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
await this.#normalizeAndInsertPastedImage(image, `Unsupported pasted image format: ${image.mimeType}`);
|
|
471
|
+
},
|
|
472
|
+
showStatus: message => this.ctx.showStatus(message),
|
|
473
|
+
});
|
|
474
|
+
this.ctx.ui.addInputListener(data => (this.#enhancedPaste?.handleInput(data) ? { consume: true } : undefined));
|
|
475
|
+
this.ctx.ui.addStartListener(() => this.#enhancedPaste?.enable());
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
setupEditorSubmitHandler(): void {
|
|
479
|
+
this.ctx.editor.onSubmit = async (text: string) => {
|
|
480
|
+
text = text.trim();
|
|
481
|
+
if ((!isSettingsInitialized() || settings.get("emojiAutocomplete")) && text) text = expandEmoticons(text);
|
|
482
|
+
|
|
483
|
+
// Focused subagent session: the editor is a plain chat box for it.
|
|
484
|
+
// Everything below (continue shortcuts, slash/bash/python, loop,
|
|
485
|
+
// compaction queueing) is main-session-only.
|
|
486
|
+
if (this.ctx.focusedAgentId) {
|
|
487
|
+
await this.#submitToFocusedSession(text, "steer");
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Empty submit while streaming with queued messages: abort the active
|
|
492
|
+
// turn and let the post-unwind drain deliver the agent-core queue.
|
|
493
|
+
if (!text && this.ctx.session.isStreaming) {
|
|
494
|
+
if (this.ctx.session.queuedMessageCount > 0) {
|
|
495
|
+
const aborting = this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
496
|
+
await aborting;
|
|
497
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
498
|
+
this.ctx.ui.requestRender();
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!text) return;
|
|
504
|
+
|
|
505
|
+
// Continue shortcuts: "." or "c" resume the agent with a hidden agent-authored
|
|
506
|
+
// developer directive (no visible user message) instead of an empty turn, so the
|
|
507
|
+
// model continues the prior intent rather than second-guessing the interrupt.
|
|
508
|
+
if (text === "." || text === "c") {
|
|
509
|
+
if (this.ctx.onInputCallback) {
|
|
510
|
+
this.ctx.editor.setText("");
|
|
511
|
+
this.ctx.pendingImages = [];
|
|
512
|
+
this.ctx.pendingImageLinks = [];
|
|
513
|
+
this.ctx.editor.imageLinks = undefined;
|
|
514
|
+
this.ctx.onInputCallback({
|
|
515
|
+
text: manualContinuePrompt,
|
|
516
|
+
cancelled: false,
|
|
517
|
+
started: true,
|
|
518
|
+
synthetic: true,
|
|
519
|
+
userInitiated: true,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const runner = this.ctx.session.extensionRunner;
|
|
526
|
+
let inputImages = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
527
|
+
let inputImageLinks = this.ctx.pendingImageLinks.length > 0 ? [...this.ctx.pendingImageLinks] : undefined;
|
|
528
|
+
|
|
529
|
+
if (runner?.hasHandlers("input")) {
|
|
530
|
+
const result = await runner.emitInput(text, inputImages, "interactive");
|
|
531
|
+
if (result?.handled) {
|
|
532
|
+
this.ctx.editor.setText("");
|
|
533
|
+
this.ctx.pendingImages = [];
|
|
534
|
+
this.ctx.pendingImageLinks = [];
|
|
535
|
+
this.ctx.editor.imageLinks = undefined;
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (result?.text !== undefined) {
|
|
539
|
+
text = result.text.trim();
|
|
540
|
+
}
|
|
541
|
+
if (result?.images !== undefined) {
|
|
542
|
+
inputImages = result.images;
|
|
543
|
+
inputImageLinks = await materializeImageReferenceLinks(
|
|
544
|
+
inputImages,
|
|
545
|
+
this.ctx.sessionManager.putBlob.bind(this.ctx.sessionManager),
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (!text) return;
|
|
551
|
+
|
|
552
|
+
// Handle built-in slash commands
|
|
553
|
+
const slashResult = await executeBuiltinSlashCommand(text, {
|
|
554
|
+
ctx: this.ctx,
|
|
555
|
+
});
|
|
556
|
+
if (slashResult === true) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (typeof slashResult === "string") {
|
|
560
|
+
// Command handled but returned remaining text to use as prompt
|
|
561
|
+
text = slashResult;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Collab guest: prompts execute on the host; local slash/skill/bash/
|
|
565
|
+
// python execution is host-only (builtins are gated inside
|
|
566
|
+
// executeBuiltinSlashCommand, which already consumed allowed ones).
|
|
567
|
+
if (this.ctx.collabGuest) {
|
|
568
|
+
if (text.startsWith("/")) {
|
|
569
|
+
this.ctx.showStatus(`${text.split(/\s+/, 1)[0]} is host-only during a collab session`);
|
|
570
|
+
this.ctx.editor.setText("");
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (text.startsWith("!") || parsePythonCommandInput(text)) {
|
|
574
|
+
this.ctx.showStatus("Local execution is host-only during a collab session");
|
|
575
|
+
this.ctx.editor.setText("");
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (this.ctx.collabGuest.readOnly) {
|
|
579
|
+
// Keep the typed text: the prompt was not consumed.
|
|
580
|
+
this.ctx.showStatus("This collab link is read-only — prompting is disabled");
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
this.ctx.editor.addToHistory(text);
|
|
584
|
+
this.ctx.editor.setText("");
|
|
585
|
+
this.ctx.editor.imageLinks = undefined;
|
|
586
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
587
|
+
this.ctx.pendingImages = [];
|
|
588
|
+
this.ctx.pendingImageLinks = [];
|
|
589
|
+
// No local render: the prompt comes back from the host as a
|
|
590
|
+
// collab-prompt event/entry and renders with the author badge.
|
|
591
|
+
this.ctx.collabGuest.sendPrompt(text, images);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Handle skill commands (/skill:name [args]). Enter ⇒ steer (matches the
|
|
596
|
+
// free-text Enter semantics applied a few lines below at the streaming
|
|
597
|
+
// branch). Ctrl+Enter routes through `handleFollowUp` and dispatches the
|
|
598
|
+
// same helper with `"followUp"`.
|
|
599
|
+
if (await this.#invokeSkillCommand(text, "steer")) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Handle bash command (! for normal, !! for excluded from context)
|
|
604
|
+
if (text.startsWith("!")) {
|
|
605
|
+
const isExcluded = text.startsWith("!!");
|
|
606
|
+
const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
|
|
607
|
+
if (command) {
|
|
608
|
+
if (this.ctx.session.isBashRunning) {
|
|
609
|
+
this.ctx.showWarning("A bash command is already running. Press Esc to cancel it first.");
|
|
610
|
+
this.ctx.editor.setText(text);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
this.ctx.editor.addToHistory(text);
|
|
614
|
+
await this.ctx.handleBashCommand(command, isExcluded);
|
|
615
|
+
this.ctx.isBashMode = false;
|
|
616
|
+
this.ctx.updateEditorBorderColor();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Handle python command (`$ <code>` for normal, `$$ <code>` for excluded from context).
|
|
622
|
+
// Shell-style variables such as `$HOME` are normal prose unless a space follows the sigil.
|
|
623
|
+
const pythonCommand = parsePythonCommandInput(text);
|
|
624
|
+
if (pythonCommand) {
|
|
625
|
+
const { code, isExcluded } = pythonCommand;
|
|
626
|
+
if (code) {
|
|
627
|
+
if (this.ctx.session.isEvalRunning) {
|
|
628
|
+
this.ctx.showWarning("A Python execution is already running. Press Esc to cancel it first.");
|
|
629
|
+
this.ctx.editor.setText(text);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
this.ctx.editor.addToHistory(text);
|
|
633
|
+
await this.ctx.handlePythonCommand(code, isExcluded);
|
|
634
|
+
this.ctx.isPythonMode = false;
|
|
635
|
+
this.ctx.updateEditorBorderColor();
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// While loop mode is on, every user-typed prompt becomes the new loop
|
|
641
|
+
// prompt that auto-resubmits after each yield.
|
|
642
|
+
if (this.ctx.loopModeEnabled) {
|
|
643
|
+
this.ctx.loopPrompt = text;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Queue input during compaction
|
|
647
|
+
if (this.ctx.session.isCompacting) {
|
|
648
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
649
|
+
this.ctx.queueCompactionMessage(text, "steer", images);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// If streaming, use prompt() with steer behavior
|
|
654
|
+
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
655
|
+
if (this.ctx.session.isStreaming) {
|
|
656
|
+
this.ctx.editor.addToHistory(text);
|
|
657
|
+
this.ctx.editor.setText("");
|
|
658
|
+
this.ctx.editor.imageLinks = undefined;
|
|
659
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
660
|
+
this.ctx.pendingImages = [];
|
|
661
|
+
this.ctx.pendingImageLinks = [];
|
|
662
|
+
// Record the signature so the queued message's eventual delivery
|
|
663
|
+
// (a user-role `message_start` event) leaves any draft the user has
|
|
664
|
+
// typed since queuing intact. Same protection as #783, applied to
|
|
665
|
+
// the streaming/queue path.
|
|
666
|
+
await this.ctx.withLocalSubmission(
|
|
667
|
+
text,
|
|
668
|
+
() => this.ctx.session.prompt(text, { streamingBehavior: "steer", images }),
|
|
669
|
+
{ imageCount: images?.length ?? 0 },
|
|
670
|
+
);
|
|
671
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
672
|
+
this.ctx.ui.requestRender();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Normal message submission
|
|
677
|
+
// First, move any pending bash components to chat
|
|
678
|
+
this.ctx.flushPendingBashComponents();
|
|
679
|
+
|
|
680
|
+
// Auto-generate a session title while the session is still unnamed.
|
|
681
|
+
// Greetings / acknowledgements / empty input carry no task, so they are
|
|
682
|
+
// skipped deterministically (no model invoked, no download-progress UI)
|
|
683
|
+
// and the session stays unnamed — the next user message gets a fresh
|
|
684
|
+
// chance, so titling defers past "hi" instead of latching onto it.
|
|
685
|
+
if (!this.ctx.sessionManager.getSessionName() && !$env.PI_NO_TITLE && !isLowSignalTitleInput(text)) {
|
|
686
|
+
this.#showTinyTitleDownloadProgress(this.ctx.settings.get("providers.tinyModel"));
|
|
687
|
+
const registry = this.ctx.session.modelRegistry;
|
|
688
|
+
generateSessionTitle(
|
|
689
|
+
text,
|
|
690
|
+
registry,
|
|
691
|
+
this.ctx.settings,
|
|
692
|
+
this.ctx.session.sessionId,
|
|
693
|
+
this.ctx.session.model,
|
|
694
|
+
provider => this.ctx.session.agent.metadataForProvider(provider),
|
|
695
|
+
this.ctx.titleSystemPrompt,
|
|
696
|
+
)
|
|
697
|
+
.then(async title => {
|
|
698
|
+
// Re-check: a concurrent attempt for an earlier message may have
|
|
699
|
+
// already named the session. Don't clobber it.
|
|
700
|
+
if (title && !this.ctx.sessionManager.getSessionName()) {
|
|
701
|
+
const applied = await this.ctx.sessionManager.setSessionName(title, "auto");
|
|
702
|
+
if (applied) {
|
|
703
|
+
setSessionTerminalTitle(
|
|
704
|
+
this.ctx.sessionManager.getSessionName()!,
|
|
705
|
+
this.ctx.sessionManager.getCwd(),
|
|
706
|
+
);
|
|
707
|
+
this.ctx.updateEditorBorderColor();
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
})
|
|
711
|
+
.catch(err => {
|
|
712
|
+
logger.warn("title-generator: uncaught auto-title error", {
|
|
713
|
+
sessionId: this.ctx.session.sessionId,
|
|
714
|
+
reason: "uncaught-auto-title-error",
|
|
715
|
+
error: err instanceof Error ? err.message : String(err),
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (this.ctx.onInputCallback) {
|
|
721
|
+
// Include any pending images from clipboard paste
|
|
722
|
+
this.ctx.editor.imageLinks = undefined;
|
|
723
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
724
|
+
this.ctx.pendingImages = [];
|
|
725
|
+
this.ctx.pendingImageLinks = [];
|
|
726
|
+
|
|
727
|
+
// Render user message immediately, then let session events catch up.
|
|
728
|
+
// Tag the submission as "steer": this is a normal Enter the controller
|
|
729
|
+
// believed was idle, but a background turn can start in the gap before
|
|
730
|
+
// `submitInteractiveInput` dispatches it. Steering matches the
|
|
731
|
+
// streaming-branch Enter (above) and keeps the message from throwing
|
|
732
|
+
// AgentBusyError on that race.
|
|
733
|
+
const submission = this.ctx.startPendingSubmission({
|
|
734
|
+
text,
|
|
735
|
+
images,
|
|
736
|
+
imageLinks: inputImageLinks,
|
|
737
|
+
streamingBehavior: "steer",
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
this.ctx.onInputCallback(submission);
|
|
741
|
+
} else {
|
|
742
|
+
// No input waiter: the main loop is between turns (post-turn
|
|
743
|
+
// epilogue, retry backoff, or a scheduled continue) with the agent
|
|
744
|
+
// momentarily idle. The editor already cleared itself on Enter, so
|
|
745
|
+
// falling through here would silently swallow the message. Submit a
|
|
746
|
+
// real prompt directly; if a background turn starts in the gap,
|
|
747
|
+
// `streamingBehavior: "steer"` preserves the typed-message queueing
|
|
748
|
+
// semantics instead of throwing AgentBusyError.
|
|
749
|
+
this.ctx.editor.imageLinks = undefined;
|
|
750
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
751
|
+
this.ctx.pendingImages = [];
|
|
752
|
+
this.ctx.pendingImageLinks = [];
|
|
753
|
+
try {
|
|
754
|
+
await this.ctx.withLocalSubmission(
|
|
755
|
+
text,
|
|
756
|
+
() => this.ctx.session.prompt(text, { streamingBehavior: "steer", images }),
|
|
757
|
+
{
|
|
758
|
+
imageCount: images?.length ?? 0,
|
|
759
|
+
},
|
|
760
|
+
);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
// Don't lose the message: hand the text and images back to the
|
|
763
|
+
// editor so the user can retry (e.g. prompt dispatch rejecting an
|
|
764
|
+
// extension command).
|
|
765
|
+
this.ctx.editor.setText(text);
|
|
766
|
+
if (images && images.length > 0) {
|
|
767
|
+
this.ctx.pendingImages = [...images];
|
|
768
|
+
this.ctx.pendingImageLinks = inputImageLinks ? [...inputImageLinks] : images.map(() => undefined);
|
|
769
|
+
this.ctx.editor.imageLinks = this.ctx.pendingImageLinks;
|
|
770
|
+
}
|
|
771
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
772
|
+
}
|
|
773
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
774
|
+
this.ctx.ui.requestRender();
|
|
775
|
+
}
|
|
776
|
+
this.ctx.editor.addToHistory(text);
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** Submit editor text to the focused subagent session (chat-only focus policy). */
|
|
781
|
+
async #submitToFocusedSession(text: string, streamingBehavior: "steer" | "followUp"): Promise<void> {
|
|
782
|
+
const target = this.ctx.viewSession;
|
|
783
|
+
if (!text) {
|
|
784
|
+
if (target.isStreaming && target.queuedMessageCount > 0) {
|
|
785
|
+
const aborting = target.abort({ reason: USER_INTERRUPT_LABEL });
|
|
786
|
+
await aborting;
|
|
787
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
788
|
+
this.ctx.ui.requestRender();
|
|
789
|
+
}
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (text.startsWith("/") || text.startsWith("!") || parsePythonCommandInput(text)) {
|
|
793
|
+
this.ctx.showStatus("Commands run in the main session — press ←← to return first");
|
|
794
|
+
return; // editor text not cleared: Editor does not auto-clear on submit
|
|
795
|
+
}
|
|
796
|
+
const images = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
797
|
+
this.ctx.editor.addToHistory(text);
|
|
798
|
+
this.ctx.editor.setText("");
|
|
799
|
+
this.ctx.editor.imageLinks = undefined;
|
|
800
|
+
this.ctx.pendingImages = [];
|
|
801
|
+
this.ctx.pendingImageLinks = [];
|
|
802
|
+
try {
|
|
803
|
+
// prompt() handles idle (new turn) and streaming (queues per streamingBehavior).
|
|
804
|
+
await this.ctx.withLocalSubmission(text, () => target.prompt(text, { streamingBehavior, images }), {
|
|
805
|
+
imageCount: images?.length ?? 0,
|
|
806
|
+
});
|
|
807
|
+
} catch (error) {
|
|
808
|
+
this.ctx.editor.setText(text); // hand the message back, mirroring the main submit error path
|
|
809
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
810
|
+
}
|
|
811
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
812
|
+
this.ctx.ui.requestRender();
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
handleCtrlC(): void {
|
|
816
|
+
// Sync-flush the session JSONL so in-flight writes survive a hard exit.
|
|
817
|
+
// The TUI consumes Ctrl+C as a key event in raw mode, so postmortem's
|
|
818
|
+
// process-level SIGINT handler never fires. shutdown() awaits its own
|
|
819
|
+
// async flush — this sync pass is a superset that also covers the
|
|
820
|
+
// first-press case and the hard-abort path below.
|
|
821
|
+
try {
|
|
822
|
+
this.ctx.sessionManager.flushSync();
|
|
823
|
+
} catch (err) {
|
|
824
|
+
logger.warn("session-manager sync flush on Ctrl+C failed", {
|
|
825
|
+
error: err instanceof Error ? err.message : String(err),
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Hard-abort: a Ctrl+C arriving while shutdown() is already running
|
|
830
|
+
// means the user has waited long enough for whatever teardown step is
|
|
831
|
+
// stuck (typically an extension's session_shutdown handler hanging on
|
|
832
|
+
// IPC). The 2s session_shutdown cap (see runner.ts) already bounds the
|
|
833
|
+
// common case; this is the defense-in-depth ladder for everything
|
|
834
|
+
// else. See issue #2600.
|
|
835
|
+
if (this.ctx.isShuttingDown) {
|
|
836
|
+
process.exit(130); // 128 + SIGINT
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const now = Date.now();
|
|
840
|
+
if (now - this.ctx.lastSigintTime < 500) {
|
|
841
|
+
void this.ctx.shutdown();
|
|
842
|
+
} else {
|
|
843
|
+
this.ctx.clearEditor();
|
|
844
|
+
this.ctx.lastSigintTime = now;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
handleCtrlD(): void {
|
|
849
|
+
// Editor text (if any) is snapshotted at the start of shutdown() and
|
|
850
|
+
// persisted as a draft for the next resume. Empty text is also fine —
|
|
851
|
+
// shutdown clears any stale sidecar in that case.
|
|
852
|
+
void this.ctx.shutdown();
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
handleCtrlZ(): void {
|
|
856
|
+
// SIGTSTP is POSIX job-control: Windows has no equivalent and
|
|
857
|
+
// `process.kill(_, "SIGTSTP")` throws `TypeError: Unknown signal:
|
|
858
|
+
// SIGTSTP` there, taking the whole agent down via an uncaught
|
|
859
|
+
// exception (issue #2036). No-op on platforms that cannot suspend.
|
|
860
|
+
if (process.platform === "win32") {
|
|
861
|
+
this.ctx.showStatus("Suspend (Ctrl+Z) is not supported on this platform");
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Capture the listener so we can detach it if the signal never
|
|
866
|
+
// fires; otherwise a failed suspend would leave a stale SIGCONT
|
|
867
|
+
// handler that fires on the next unrelated continue and tries to
|
|
868
|
+
// re-`start()` an already-running TUI.
|
|
869
|
+
const onResume = (): void => {
|
|
870
|
+
this.ctx.ui.start();
|
|
871
|
+
this.ctx.ui.requestRender(true);
|
|
872
|
+
};
|
|
873
|
+
process.once("SIGCONT", onResume);
|
|
874
|
+
|
|
875
|
+
// Stop the TUI (restore terminal to normal mode) before sending the
|
|
876
|
+
// signal so the parent shell sees a sane terminal state.
|
|
877
|
+
this.ctx.ui.stop();
|
|
878
|
+
|
|
879
|
+
try {
|
|
880
|
+
// pid=0 → entire foreground process group; the shell receives
|
|
881
|
+
// SIGTSTP and parks the job.
|
|
882
|
+
process.kill(0, "SIGTSTP");
|
|
883
|
+
} catch (err) {
|
|
884
|
+
// Either the runtime refused the signal or the kernel rejected
|
|
885
|
+
// it (some sandboxes block sending to pid=0). Tear the resume
|
|
886
|
+
// hook down and bring the TUI back so the user is not stranded
|
|
887
|
+
// on a frozen prompt.
|
|
888
|
+
process.removeListener("SIGCONT", onResume);
|
|
889
|
+
this.ctx.ui.start();
|
|
890
|
+
this.ctx.ui.requestRender(true);
|
|
891
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
892
|
+
this.ctx.showError(`Failed to suspend: ${reason}`);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
handleDequeue(): void {
|
|
897
|
+
const restored = this.restoreQueuedMessagesToEditor();
|
|
898
|
+
if (restored === 0) {
|
|
899
|
+
this.ctx.showStatus("No queued messages to restore");
|
|
900
|
+
} else {
|
|
901
|
+
this.ctx.showStatus(`Restored ${restored} queued message${restored > 1 ? "s" : ""} to editor`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Dispatch a `/skill:<name> [args]` invocation through `promptCustomMessage`
|
|
907
|
+
* using the supplied `streamingBehavior`. Returns true if the text was a
|
|
908
|
+
* recognised skill command and was dispatched. A failure to load the skill
|
|
909
|
+
* file is surfaced via `showError` but still returns true — the editor was
|
|
910
|
+
* already cleared on the success path, so falling through to plain-text
|
|
911
|
+
* handling at that point would double-submit. Returns false when the text
|
|
912
|
+
* isn't a `/skill:` prefix or the command name isn't a registered skill,
|
|
913
|
+
* so the caller can fall through to plain-text handling (this branch
|
|
914
|
+
* leaves the editor state untouched). `streamingBehavior` is only consulted
|
|
915
|
+
* while the agent is streaming; the idle path of `promptCustomMessage`
|
|
916
|
+
* ignores it.
|
|
917
|
+
*/
|
|
918
|
+
async #invokeSkillCommand(text: string, streamingBehavior: "steer" | "followUp"): Promise<boolean> {
|
|
919
|
+
if (!text.startsWith("/skill:")) return false;
|
|
920
|
+
const spaceIndex = text.indexOf(" ");
|
|
921
|
+
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
922
|
+
const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
|
|
923
|
+
const skillPath = this.ctx.skillCommands?.get(commandName);
|
|
924
|
+
if (!skillPath) return false;
|
|
925
|
+
this.ctx.editor.addToHistory(text);
|
|
926
|
+
this.ctx.editor.setText("");
|
|
927
|
+
try {
|
|
928
|
+
const content = await Bun.file(skillPath).text();
|
|
929
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
|
|
930
|
+
const metaLines = [`Skill: ${skillPath}`];
|
|
931
|
+
if (args) {
|
|
932
|
+
metaLines.push(`User: ${args}`);
|
|
933
|
+
}
|
|
934
|
+
const message = `${body}\n\n---\n\n${metaLines.join("\n")}`;
|
|
935
|
+
const skillName = commandName.slice("skill:".length);
|
|
936
|
+
const details: SkillPromptDetails = {
|
|
937
|
+
name: skillName || commandName,
|
|
938
|
+
path: skillPath,
|
|
939
|
+
args: args || undefined,
|
|
940
|
+
lineCount: body ? body.split("\n").length : 0,
|
|
941
|
+
};
|
|
942
|
+
await this.ctx.session.promptCustomMessage(
|
|
943
|
+
{
|
|
944
|
+
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
945
|
+
content: message,
|
|
946
|
+
display: true,
|
|
947
|
+
details,
|
|
948
|
+
attribution: "user",
|
|
949
|
+
},
|
|
950
|
+
{ streamingBehavior, queueChipText: text },
|
|
951
|
+
);
|
|
952
|
+
if (this.ctx.session.isStreaming) {
|
|
953
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
954
|
+
this.ctx.ui.requestRender();
|
|
955
|
+
}
|
|
956
|
+
} catch (err) {
|
|
957
|
+
this.ctx.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
|
|
958
|
+
}
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async handleRetry(): Promise<void> {
|
|
963
|
+
if (this.ctx.collabGuest) {
|
|
964
|
+
this.ctx.showStatus("/retry is host-only during a collab session");
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const didRetry = await this.ctx.viewSession.retry();
|
|
968
|
+
if (didRetry) {
|
|
969
|
+
this.ctx.editor.setText("");
|
|
970
|
+
this.ctx.pendingImages = [];
|
|
971
|
+
this.ctx.pendingImageLinks = [];
|
|
972
|
+
this.ctx.editor.imageLinks = undefined;
|
|
973
|
+
} else {
|
|
974
|
+
this.ctx.showStatus("Nothing to retry");
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/** Send editor text as a follow-up message (queued behind current stream). */
|
|
979
|
+
async handleFollowUp(): Promise<void> {
|
|
980
|
+
let text = this.ctx.editor.getText().trim();
|
|
981
|
+
if (!text) return;
|
|
982
|
+
|
|
983
|
+
// Focused subagent session: follow-ups go to it; non-chat input is gated.
|
|
984
|
+
if (this.ctx.focusedAgentId) {
|
|
985
|
+
await this.#submitToFocusedSession(text, "followUp");
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Compaction first: while compacting, free text gets queued via
|
|
990
|
+
// `queueCompactionMessage`, and `/skill:*` rides the same queue so a
|
|
991
|
+
// skill typed during compaction is not lost or short-circuited through
|
|
992
|
+
// `promptCustomMessage`. The skill text is queued verbatim; whether
|
|
993
|
+
// the queued entry is later re-parsed into a skill invocation is a
|
|
994
|
+
// separate concern owned by the compaction-resume path.
|
|
995
|
+
if (this.ctx.session.isCompacting) {
|
|
996
|
+
const images = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
997
|
+
this.ctx.queueCompactionMessage(text, "followUp", images);
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
const slashResult = await executeBuiltinSlashCommand(text, {
|
|
1002
|
+
ctx: this.ctx,
|
|
1003
|
+
});
|
|
1004
|
+
if (slashResult === true) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
if (typeof slashResult === "string") {
|
|
1008
|
+
text = slashResult;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Skill commands invoke through the custom-message path regardless of
|
|
1012
|
+
// which keybinding submitted them. Enter routes them as `steer`;
|
|
1013
|
+
// Ctrl+Enter (this handler) routes them as `followUp`.
|
|
1014
|
+
if (await this.#invokeSkillCommand(text, "followUp")) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Forward any pending clipboard-pasted images alongside the queued text;
|
|
1019
|
+
// otherwise the follow-up would drop the image (mirrors the Enter/steer path).
|
|
1020
|
+
const images = this.ctx.pendingImages.length > 0 ? [...this.ctx.pendingImages] : undefined;
|
|
1021
|
+
|
|
1022
|
+
if (this.ctx.session.isStreaming) {
|
|
1023
|
+
this.ctx.editor.addToHistory(text);
|
|
1024
|
+
this.ctx.editor.setText("");
|
|
1025
|
+
this.ctx.editor.imageLinks = undefined;
|
|
1026
|
+
this.ctx.pendingImages = [];
|
|
1027
|
+
this.ctx.pendingImageLinks = [];
|
|
1028
|
+
await this.ctx.withLocalSubmission(
|
|
1029
|
+
text,
|
|
1030
|
+
() => this.ctx.session.prompt(text, { streamingBehavior: "followUp", images }),
|
|
1031
|
+
{ imageCount: images?.length ?? 0 },
|
|
1032
|
+
);
|
|
1033
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
1034
|
+
this.ctx.ui.requestRender();
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Not streaming — just submit normally
|
|
1039
|
+
this.ctx.editor.addToHistory(text);
|
|
1040
|
+
this.ctx.editor.setText("");
|
|
1041
|
+
this.ctx.editor.imageLinks = undefined;
|
|
1042
|
+
this.ctx.pendingImages = [];
|
|
1043
|
+
this.ctx.pendingImageLinks = [];
|
|
1044
|
+
await this.ctx.withLocalSubmission(text, () => this.ctx.session.prompt(text, { images }), {
|
|
1045
|
+
imageCount: images?.length ?? 0,
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
restoreQueuedMessagesToEditor(options?: { abort?: boolean; currentText?: string }): number {
|
|
1050
|
+
this.ctx.locallySubmittedUserSignatures.clear();
|
|
1051
|
+
// On Esc (abort) drop non-user internal steers so the post-abort drain can't
|
|
1052
|
+
// auto-resume; plain Alt+Up dequeue preserves them for the continuing stream.
|
|
1053
|
+
const { steering, followUp } = this.ctx.session.clearQueue({ forInterrupt: options?.abort });
|
|
1054
|
+
// Messages typed while compacting live in `compactionQueuedMessages`, not the
|
|
1055
|
+
// agent queue `clearQueue()` drains — but the pending bar shows the same
|
|
1056
|
+
// "Alt+Up to edit" hint for them (ui-helpers `updatePendingMessagesDisplay`).
|
|
1057
|
+
// Drain them here too so the dequeue restores every message the hint
|
|
1058
|
+
// advertises; otherwise a skill/text queued during compaction is stranded and
|
|
1059
|
+
// Alt+Up reports "No queued messages to restore".
|
|
1060
|
+
const compactionQueued = this.ctx.compactionQueuedMessages;
|
|
1061
|
+
this.ctx.compactionQueuedMessages = [];
|
|
1062
|
+
const allQueued = [
|
|
1063
|
+
...steering,
|
|
1064
|
+
...compactionQueued.filter(e => e.mode === "steer").map(e => ({ text: e.text, images: e.images })),
|
|
1065
|
+
...followUp,
|
|
1066
|
+
...compactionQueued.filter(e => e.mode === "followUp").map(e => ({ text: e.text, images: e.images })),
|
|
1067
|
+
];
|
|
1068
|
+
if (allQueued.length === 0) {
|
|
1069
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
1070
|
+
if (options?.abort) {
|
|
1071
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
1072
|
+
}
|
|
1073
|
+
return 0;
|
|
1074
|
+
}
|
|
1075
|
+
// Image markers are positional: `[Image #N]` ↔ `pendingImages[N-1]`. Each
|
|
1076
|
+
// queued message numbered its markers against its own local image list
|
|
1077
|
+
// (1..K). Because we prepend the queued text but append the queued images
|
|
1078
|
+
// to `pendingImages`, any existing draft images (M of them) — plus images
|
|
1079
|
+
// already pulled in by earlier queued messages — shift the slot index that
|
|
1080
|
+
// every marker must point to. Bumping each message's markers by the
|
|
1081
|
+
// running offset keeps the merged text aligned with the merged
|
|
1082
|
+
// `pendingImages` order; draft markers stay valid because draft images
|
|
1083
|
+
// keep their original positions.
|
|
1084
|
+
const queuedImages = allQueued.flatMap(e => e.images ?? []);
|
|
1085
|
+
let queuedText: string;
|
|
1086
|
+
if (queuedImages.length > 0) {
|
|
1087
|
+
const parts: string[] = [];
|
|
1088
|
+
let imageOffset = this.ctx.pendingImages.length;
|
|
1089
|
+
for (const entry of allQueued) {
|
|
1090
|
+
parts.push(shiftImageMarkers(entry.text, imageOffset));
|
|
1091
|
+
if (entry.images && entry.images.length > 0) imageOffset += entry.images.length;
|
|
1092
|
+
}
|
|
1093
|
+
queuedText = parts.join("\n\n");
|
|
1094
|
+
} else {
|
|
1095
|
+
queuedText = allQueued.map(e => e.text).join("\n\n");
|
|
1096
|
+
}
|
|
1097
|
+
const currentText = options?.currentText ?? this.ctx.editor.getText();
|
|
1098
|
+
const combinedText = [queuedText, currentText].filter(t => t.trim()).join("\n\n");
|
|
1099
|
+
this.ctx.editor.setText(combinedText);
|
|
1100
|
+
// Hand queued images back to the pending-image buffer (links are
|
|
1101
|
+
// re-materialized lazily; the restored text already carries the
|
|
1102
|
+
// renumbered `[Image #N, WxH]` markers).
|
|
1103
|
+
if (queuedImages.length > 0) {
|
|
1104
|
+
this.ctx.pendingImages.push(...queuedImages);
|
|
1105
|
+
this.ctx.pendingImageLinks.push(...queuedImages.map(() => undefined));
|
|
1106
|
+
this.ctx.editor.imageLinks = this.ctx.pendingImageLinks;
|
|
1107
|
+
}
|
|
1108
|
+
this.ctx.updatePendingMessagesDisplay();
|
|
1109
|
+
if (options?.abort) {
|
|
1110
|
+
void this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
1111
|
+
}
|
|
1112
|
+
return allQueued.length;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
async #insertPendingImage(imageData: ImageContent): Promise<void> {
|
|
1116
|
+
const imageLink = (
|
|
1117
|
+
await materializeImageReferenceLinks(
|
|
1118
|
+
[
|
|
1119
|
+
{
|
|
1120
|
+
type: "image",
|
|
1121
|
+
data: imageData.data,
|
|
1122
|
+
mimeType: imageData.mimeType,
|
|
1123
|
+
},
|
|
1124
|
+
],
|
|
1125
|
+
this.ctx.sessionManager.putBlob.bind(this.ctx.sessionManager),
|
|
1126
|
+
)
|
|
1127
|
+
)?.[0];
|
|
1128
|
+
this.ctx.pendingImages.push({
|
|
1129
|
+
type: "image",
|
|
1130
|
+
data: imageData.data,
|
|
1131
|
+
mimeType: imageData.mimeType,
|
|
1132
|
+
});
|
|
1133
|
+
this.ctx.pendingImageLinks.push(imageLink);
|
|
1134
|
+
this.ctx.editor.imageLinks = this.ctx.pendingImageLinks;
|
|
1135
|
+
const imageNum = this.ctx.pendingImages.length;
|
|
1136
|
+
const dims = await this.#imageDimensions(imageData);
|
|
1137
|
+
const label = dims ? `[Image #${imageNum}, ${dims.width}x${dims.height}]` : `[Image #${imageNum}]`;
|
|
1138
|
+
this.ctx.editor.insertText(`${label} `);
|
|
1139
|
+
this.ctx.ui.requestRender();
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/** Probe pixel dimensions for the marker label (`[Image #N, WxH]`). Returns undefined when the
|
|
1143
|
+
* header can't be decoded, so the caller falls back to a bare `[Image #N]`. */
|
|
1144
|
+
async #imageDimensions(image: ImageContent): Promise<{ width: number; height: number } | undefined> {
|
|
1145
|
+
try {
|
|
1146
|
+
const { width, height } = await new Bun.Image(Buffer.from(image.data, "base64")).metadata();
|
|
1147
|
+
if (width && height) return { width, height };
|
|
1148
|
+
} catch {
|
|
1149
|
+
// Unknown/corrupt header — fall back to a bare label.
|
|
1150
|
+
}
|
|
1151
|
+
return undefined;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
async #normalizeAndInsertPastedImage(image: ImageContent, unsupportedMessage: string): Promise<boolean> {
|
|
1155
|
+
let imageData = await ensureSupportedImageInput(image);
|
|
1156
|
+
if (!imageData) {
|
|
1157
|
+
this.ctx.showStatus(unsupportedMessage);
|
|
1158
|
+
return false;
|
|
1159
|
+
}
|
|
1160
|
+
if (settings.get("images.autoResize")) {
|
|
1161
|
+
try {
|
|
1162
|
+
const resized = await resizeImage({
|
|
1163
|
+
type: "image",
|
|
1164
|
+
data: imageData.data,
|
|
1165
|
+
mimeType: imageData.mimeType,
|
|
1166
|
+
});
|
|
1167
|
+
imageData = { type: "image", data: resized.data, mimeType: resized.mimeType };
|
|
1168
|
+
} catch {
|
|
1169
|
+
// Keep the normalized image when resize fails.
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
await this.#insertPendingImage(imageData);
|
|
1173
|
+
return true;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Win+Shift+S on Windows 11 leaves the screenshot bitmap on the clipboard
|
|
1178
|
+
* while the terminal pastes a transient packaged-app TempState path
|
|
1179
|
+
* (…\MicrosoftWindows.Client.Core_*\TempState\…) that is already gone — or
|
|
1180
|
+
* never materialized — by the time we read it. Whenever a pasted image path
|
|
1181
|
+
* can't be turned into an image locally, those clipboard bytes are the real
|
|
1182
|
+
* payload, so prefer them before degrading to a text paste.
|
|
1183
|
+
*
|
|
1184
|
+
* Skipped over SSH: the clipboard read would hit the remote host, not the
|
|
1185
|
+
* terminal that holds the screenshot. Returns true when the clipboard owned
|
|
1186
|
+
* the outcome (image attached, or an unsupported-format status surfaced), so
|
|
1187
|
+
* the caller stops without emitting its own degraded diagnostic.
|
|
1188
|
+
*/
|
|
1189
|
+
async #tryPasteClipboardImage(): Promise<boolean> {
|
|
1190
|
+
const env = process.env;
|
|
1191
|
+
if (env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT) return false;
|
|
1192
|
+
try {
|
|
1193
|
+
const image = await this.clipboard.readImage();
|
|
1194
|
+
if (!image) return false;
|
|
1195
|
+
await this.#normalizeAndInsertPastedImage(
|
|
1196
|
+
{ type: "image", data: image.data.toBase64(), mimeType: image.mimeType },
|
|
1197
|
+
`Unsupported clipboard image format: ${image.mimeType}`,
|
|
1198
|
+
);
|
|
1199
|
+
return true;
|
|
1200
|
+
} catch {
|
|
1201
|
+
return false;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
async handleImagePathPaste(path: string): Promise<void> {
|
|
1206
|
+
try {
|
|
1207
|
+
const image = await loadImageInput({
|
|
1208
|
+
path,
|
|
1209
|
+
cwd: this.ctx.sessionManager.getCwd(),
|
|
1210
|
+
autoResize: false,
|
|
1211
|
+
});
|
|
1212
|
+
if (!image) {
|
|
1213
|
+
// Path resolved but is not a readable image (e.g. a zero-byte or
|
|
1214
|
+
// locked transient screenshot file). Prefer the clipboard bytes.
|
|
1215
|
+
if (await this.#tryPasteClipboardImage()) return;
|
|
1216
|
+
this.ctx.editor.pasteText(path);
|
|
1217
|
+
this.ctx.ui.requestRender();
|
|
1218
|
+
this.ctx.showStatus("Pasted path is not a supported image");
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
await this.#normalizeAndInsertPastedImage(
|
|
1222
|
+
{ type: "image", data: image.data, mimeType: image.mimeType },
|
|
1223
|
+
`Unsupported pasted image format: ${image.mimeType}`,
|
|
1224
|
+
);
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
if (error instanceof ImageInputTooLargeError) {
|
|
1227
|
+
this.ctx.editor.pasteText(path);
|
|
1228
|
+
this.ctx.ui.requestRender();
|
|
1229
|
+
this.ctx.showStatus(error.message);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
if (isEnoent(error)) {
|
|
1233
|
+
// #2375: the bracketed paste forwarded by a local terminal carries a
|
|
1234
|
+
// path on the *local* filesystem. The bytes may still be on the
|
|
1235
|
+
// clipboard (Win+Shift+S), so try those before giving up.
|
|
1236
|
+
if (await this.#tryPasteClipboardImage()) return;
|
|
1237
|
+
// Over SSH the clipboard lives on the remote host, so the path is
|
|
1238
|
+
// genuinely unreachable; pasting it as text would look like the
|
|
1239
|
+
// image was attached when nothing was sent. Surface an SSH-aware
|
|
1240
|
+
// diagnostic instead. The pasted path is untrusted terminal input —
|
|
1241
|
+
// strip control/ANSI/newlines, collapse home to `~`, and bound the
|
|
1242
|
+
// displayed length before splicing it into the status string.
|
|
1243
|
+
const env = process.env;
|
|
1244
|
+
const overSsh = Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
|
|
1245
|
+
const displayPath = truncateToWidth(
|
|
1246
|
+
shortenPath(
|
|
1247
|
+
sanitizeText(path)
|
|
1248
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
1249
|
+
.trim(),
|
|
1250
|
+
),
|
|
1251
|
+
TRUNCATE_LENGTHS.CONTENT,
|
|
1252
|
+
);
|
|
1253
|
+
this.ctx.showStatus(
|
|
1254
|
+
overSsh
|
|
1255
|
+
? `Image not found at ${displayPath}. Over SSH this path is local to your terminal — paste the image directly (clipboard image-paste shortcut) to send its bytes.`
|
|
1256
|
+
: `Image not found at ${displayPath}`,
|
|
1257
|
+
);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (await this.#tryPasteClipboardImage()) return;
|
|
1261
|
+
this.ctx.editor.pasteText(path);
|
|
1262
|
+
this.ctx.ui.requestRender();
|
|
1263
|
+
this.ctx.showStatus("Failed to read pasted image path");
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async handleImagePaste(): Promise<boolean> {
|
|
1268
|
+
try {
|
|
1269
|
+
const image = await this.clipboard.readImage();
|
|
1270
|
+
if (!image) {
|
|
1271
|
+
// Smart paste (#1628): no image on the clipboard — fall back to
|
|
1272
|
+
// pasting its text so the same chord covers both payload kinds.
|
|
1273
|
+
// Hosts that pre-empt the terminal's own paste (VS Code's
|
|
1274
|
+
// integrated terminal, Win+V clipboard history) deliver only
|
|
1275
|
+
// this keypress, so a miss here must not dead-end.
|
|
1276
|
+
const text = await this.clipboard.readText();
|
|
1277
|
+
if (!text) {
|
|
1278
|
+
this.ctx.showStatus("Clipboard is empty");
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
// Route to the focused component when it accepts pastes (modal
|
|
1282
|
+
// Input prompts), matching the enhanced-paste text path (#2127).
|
|
1283
|
+
const focused = this.ctx.ui.getFocused();
|
|
1284
|
+
const target = focused && focused !== this.ctx.editor && hasPasteText(focused) ? focused : this.ctx.editor;
|
|
1285
|
+
target.pasteText(text);
|
|
1286
|
+
this.ctx.ui.requestRender();
|
|
1287
|
+
return true;
|
|
1288
|
+
}
|
|
1289
|
+
return await this.#normalizeAndInsertPastedImage(
|
|
1290
|
+
{
|
|
1291
|
+
type: "image",
|
|
1292
|
+
data: image.data.toBase64(),
|
|
1293
|
+
mimeType: image.mimeType,
|
|
1294
|
+
},
|
|
1295
|
+
`Unsupported clipboard image format: ${image.mimeType}`,
|
|
1296
|
+
);
|
|
1297
|
+
} catch {
|
|
1298
|
+
this.ctx.showStatus("Failed to read clipboard");
|
|
1299
|
+
return false;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
async handleClipboardTextRawPaste(): Promise<void> {
|
|
1304
|
+
try {
|
|
1305
|
+
const text = await this.clipboard.readText();
|
|
1306
|
+
if (text) {
|
|
1307
|
+
this.ctx.editor.insertText(text);
|
|
1308
|
+
this.ctx.ui.requestRender();
|
|
1309
|
+
} else {
|
|
1310
|
+
this.ctx.showStatus("No text in clipboard to paste raw");
|
|
1311
|
+
}
|
|
1312
|
+
} catch {
|
|
1313
|
+
this.ctx.showStatus("Failed to paste raw text from clipboard");
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Editor `onLargePaste` hook: gate a marker-sized paste behind the large-paste menu. Returns
|
|
1319
|
+
* `true` to intercept (the editor skips its default `[Paste]` marker) once the paste reaches the
|
|
1320
|
+
* configured `paste.largeMenuThreshold` line count; otherwise `false` for default collapse-to-marker
|
|
1321
|
+
* behavior. The async menu is fired and forgotten — the editor only needs the synchronous verdict.
|
|
1322
|
+
*/
|
|
1323
|
+
handleLargePaste(text: string, lineCount: number): boolean {
|
|
1324
|
+
const threshold = this.ctx.settings.get("paste.largeMenuThreshold");
|
|
1325
|
+
if (!(threshold > 0) || lineCount < threshold) return false;
|
|
1326
|
+
void this.presentLargePasteMenu(text, lineCount);
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* Present the large-paste menu and apply the chosen action: wrap in `<attachment>` tags (collapsed
|
|
1332
|
+
* to a `[Paste]` marker that expands on submit), save the text to a file and reference its path so
|
|
1333
|
+
* the agent can `read` it on demand, or paste inline. Cancelling (Esc) falls back to the default
|
|
1334
|
+
* inline paste marker, so the pasted content is never lost.
|
|
1335
|
+
*/
|
|
1336
|
+
async presentLargePasteMenu(text: string, lineCount: number): Promise<void> {
|
|
1337
|
+
const WRAPPED_BLOCK = "Attach as a wrapped block";
|
|
1338
|
+
const LOCAL_FILE = "Attach as local file";
|
|
1339
|
+
const INLINE = "Paste inline";
|
|
1340
|
+
|
|
1341
|
+
let choice: string | undefined;
|
|
1342
|
+
try {
|
|
1343
|
+
choice = await this.ctx.showHookSelector(
|
|
1344
|
+
`Pasted ${lineCount} lines`,
|
|
1345
|
+
[
|
|
1346
|
+
{ label: WRAPPED_BLOCK, description: "Wrap the text in <attachment> tags, collapsed to a marker" },
|
|
1347
|
+
{ label: LOCAL_FILE, description: "Save the text to a local://attachment file" },
|
|
1348
|
+
{ label: INLINE, description: "Collapse the text to an inline paste marker" },
|
|
1349
|
+
],
|
|
1350
|
+
{ helpText: "Esc to paste inline" },
|
|
1351
|
+
);
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
logger.warn("large-paste menu failed", { error: error instanceof Error ? error.message : String(error) });
|
|
1354
|
+
choice = undefined;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
switch (choice) {
|
|
1358
|
+
case WRAPPED_BLOCK:
|
|
1359
|
+
this.ctx.editor.insertPaste(wrapPasteInAttachmentBlock(text));
|
|
1360
|
+
break;
|
|
1361
|
+
case LOCAL_FILE:
|
|
1362
|
+
await this.#attachPasteAsFile(text, lineCount);
|
|
1363
|
+
break;
|
|
1364
|
+
case INLINE:
|
|
1365
|
+
this.ctx.editor.insertPaste(text);
|
|
1366
|
+
break;
|
|
1367
|
+
default:
|
|
1368
|
+
// Esc / cancel: keep the original behavior — collapse to an inline paste marker.
|
|
1369
|
+
this.ctx.editor.insertPaste(text);
|
|
1370
|
+
break;
|
|
1371
|
+
}
|
|
1372
|
+
this.ctx.ui.requestRender();
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Save a large paste to the session's `local://` store and insert a clean `local://attachment-N`
|
|
1377
|
+
* reference into the editor so the agent can `read` it on demand — instead of inlining the text or
|
|
1378
|
+
* leaking a raw temp path. Falls back to an inline paste marker when the write fails, so the
|
|
1379
|
+
* content is never lost.
|
|
1380
|
+
*/
|
|
1381
|
+
async #attachPasteAsFile(text: string, lineCount: number): Promise<void> {
|
|
1382
|
+
try {
|
|
1383
|
+
// Mirror the exact mapping the read tool's local:// resolver uses so a later
|
|
1384
|
+
// `read local://attachment-N` lands on the file written here.
|
|
1385
|
+
const localRoot = resolveLocalRoot({
|
|
1386
|
+
getArtifactsDir: () => this.ctx.sessionManager.getArtifactsDir(),
|
|
1387
|
+
getSessionId: () => this.ctx.sessionManager.getSessionId(),
|
|
1388
|
+
});
|
|
1389
|
+
let name: string;
|
|
1390
|
+
let filePath: string;
|
|
1391
|
+
do {
|
|
1392
|
+
this.#attachmentCounter++;
|
|
1393
|
+
name = `attachment-${this.#attachmentCounter}`;
|
|
1394
|
+
filePath = path.join(localRoot, name);
|
|
1395
|
+
} while (await Bun.file(filePath).exists());
|
|
1396
|
+
await Bun.write(filePath, text);
|
|
1397
|
+
this.ctx.editor.insertText(`local://${name} `);
|
|
1398
|
+
this.ctx.showStatus(`Saved ${lineCount} pasted lines to local://${name}`);
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
logger.warn("failed to save large paste to file", {
|
|
1401
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1402
|
+
});
|
|
1403
|
+
this.ctx.editor.insertPaste(text);
|
|
1404
|
+
this.ctx.showError("Failed to save paste to a file — pasted inline instead");
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
createAutocompleteProvider(commands: SlashCommand[], basePath: string): AutocompleteProvider {
|
|
1409
|
+
return createPromptActionAutocompleteProvider({
|
|
1410
|
+
commands,
|
|
1411
|
+
basePath,
|
|
1412
|
+
keybindings: this.ctx.keybindings,
|
|
1413
|
+
copyCurrentLine: () => this.handleCopyCurrentLine(),
|
|
1414
|
+
copyPrompt: () => this.handleCopyPrompt(),
|
|
1415
|
+
undo: prefix => this.ctx.editor.undoPastTransientText(prefix),
|
|
1416
|
+
moveCursorToMessageEnd: () => this.ctx.editor.moveToMessageEnd(),
|
|
1417
|
+
moveCursorToMessageStart: () => this.ctx.editor.moveToMessageStart(),
|
|
1418
|
+
moveCursorToLineStart: () => this.ctx.editor.moveToLineStart(),
|
|
1419
|
+
moveCursorToLineEnd: () => this.ctx.editor.moveToLineEnd(),
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/** Copy the current editor line to the system clipboard. */
|
|
1424
|
+
handleCopyCurrentLine(): void {
|
|
1425
|
+
const { line } = this.ctx.editor.getCursor();
|
|
1426
|
+
const text = this.ctx.editor.getLines()[line] || "";
|
|
1427
|
+
if (!text) {
|
|
1428
|
+
this.ctx.showStatus("Nothing to copy");
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
try {
|
|
1432
|
+
copyToClipboard(text);
|
|
1433
|
+
const sanitized = sanitizeText(text);
|
|
1434
|
+
const preview = sanitized.length > 30 ? `${sanitized.slice(0, 30)}...` : sanitized;
|
|
1435
|
+
this.ctx.showStatus(`Copied line: ${preview}`);
|
|
1436
|
+
} catch {
|
|
1437
|
+
this.ctx.showWarning("Failed to copy to clipboard");
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/** Copy current prompt text to system clipboard. */
|
|
1442
|
+
handleCopyPrompt(): void {
|
|
1443
|
+
const text = this.ctx.editor.getText();
|
|
1444
|
+
if (!text) {
|
|
1445
|
+
this.ctx.showStatus("Nothing to copy");
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
try {
|
|
1449
|
+
copyToClipboard(text);
|
|
1450
|
+
const sanitized = sanitizeText(text);
|
|
1451
|
+
const preview = sanitized.length > 30 ? `${sanitized.slice(0, 30)}...` : sanitized;
|
|
1452
|
+
this.ctx.showStatus(`Copied: ${preview}`);
|
|
1453
|
+
} catch {
|
|
1454
|
+
this.ctx.showWarning("Failed to copy to clipboard");
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
cycleThinkingLevel(): void {
|
|
1459
|
+
if (this.ctx.focusedAgentId) {
|
|
1460
|
+
this.ctx.showStatus("Model/thinking apply to the main session — press ←← to return first");
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
const newLevel = this.ctx.session.cycleThinkingLevel();
|
|
1464
|
+
if (newLevel === undefined) {
|
|
1465
|
+
this.ctx.showStatus("Current model does not support thinking");
|
|
1466
|
+
} else {
|
|
1467
|
+
this.ctx.statusLine.invalidate();
|
|
1468
|
+
this.ctx.updateEditorBorderColor();
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
async cycleRoleModel(direction: "forward" | "backward" = "forward"): Promise<void> {
|
|
1473
|
+
if (this.ctx.focusedAgentId) {
|
|
1474
|
+
this.ctx.showStatus("Model/thinking apply to the main session — press ←← to return first");
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
try {
|
|
1478
|
+
const cycleOrder = settings.get("cycleOrder");
|
|
1479
|
+
const result = await this.ctx.session.cycleRoleModels(cycleOrder, direction);
|
|
1480
|
+
if (!result) {
|
|
1481
|
+
this.ctx.showStatus("Only one role model available");
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
this.ctx.statusLine.invalidate();
|
|
1486
|
+
this.ctx.updateEditorBorderColor();
|
|
1487
|
+
// The status line already reports the resolved model + thinking level, so
|
|
1488
|
+
// the cycle status is just a status-line-style chip track (active role
|
|
1489
|
+
// filled), matching the plan-approval model slider. It renders into its
|
|
1490
|
+
// own anchored container above the editor (cleared+rebuilt each cycle),
|
|
1491
|
+
// so it updates in place instead of stacking duplicates in the scrollback.
|
|
1492
|
+
const track = renderSegmentTrack(
|
|
1493
|
+
cycleOrder.map(role => ({ label: role })),
|
|
1494
|
+
cycleOrder.indexOf(result.role),
|
|
1495
|
+
);
|
|
1496
|
+
this.ctx.showModelCycleTrack(track);
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
toggleToolOutputExpansion(): void {
|
|
1503
|
+
this.setToolsExpanded(!this.ctx.toolOutputExpanded);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
setToolsExpanded(expanded: boolean): void {
|
|
1507
|
+
this.ctx.toolOutputExpanded = expanded;
|
|
1508
|
+
for (const child of this.ctx.chatContainer.children) {
|
|
1509
|
+
if (isExpandable(child)) {
|
|
1510
|
+
child.setExpanded(expanded);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
// Toggling expansion mutates every block, but on ED3-risk terminals the
|
|
1514
|
+
// transcript freezes a snapshot of each block once it scrolls past the live
|
|
1515
|
+
// region (committed native scrollback is immutable there). A plain repaint
|
|
1516
|
+
// replays those stale snapshots, so the toggle appears to do nothing above
|
|
1517
|
+
// the live block. resetDisplay() invalidates the snapshots and forces a
|
|
1518
|
+
// full clear + replay — the keyboard-accessible resize-reset equivalent —
|
|
1519
|
+
// which is the only path that re-emits the whole transcript at its new
|
|
1520
|
+
// heights.
|
|
1521
|
+
this.ctx.ui.resetDisplay();
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
toggleThinkingBlockVisibility(): void {
|
|
1525
|
+
this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
|
|
1526
|
+
this.ctx.settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
|
|
1527
|
+
this.ctx.session.agent.hideThinkingSummary = this.ctx.hideThinkingBlock;
|
|
1528
|
+
|
|
1529
|
+
for (const child of this.ctx.chatContainer.children) {
|
|
1530
|
+
if (child instanceof AssistantMessageComponent) {
|
|
1531
|
+
child.setHideThinkingBlock(this.ctx.hideThinkingBlock);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
if (this.ctx.streamingComponent && this.ctx.streamingMessage) {
|
|
1536
|
+
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.hideThinkingBlock);
|
|
1537
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// Every block now carries the new flag, but on ED3-risk terminals the
|
|
1541
|
+
// blocks that scrolled past the live region are frozen snapshots in
|
|
1542
|
+
// committed scrollback — a plain repaint replays them stale, so scrolling
|
|
1543
|
+
// up still shows the old thinking expanded. resetDisplay() retires those
|
|
1544
|
+
// snapshots (it invalidates every block) and forces a full clear + replay
|
|
1545
|
+
// of the whole transcript, matching setToolsExpanded()'s redraw.
|
|
1546
|
+
this.ctx.ui.resetDisplay();
|
|
1547
|
+
|
|
1548
|
+
this.ctx.showStatus(`Thinking blocks: ${this.ctx.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
#getEditorTerminalPath(): string | null {
|
|
1552
|
+
if (process.platform === "win32") {
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
return "/dev/tty";
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
async #openEditorTerminalHandle(): Promise<fs.FileHandle | null> {
|
|
1559
|
+
const terminalPath = this.#getEditorTerminalPath();
|
|
1560
|
+
if (!terminalPath) {
|
|
1561
|
+
return null;
|
|
1562
|
+
}
|
|
1563
|
+
try {
|
|
1564
|
+
return await fs.open(terminalPath, "r+");
|
|
1565
|
+
} catch {
|
|
1566
|
+
return null;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
async openExternalEditor(): Promise<void> {
|
|
1571
|
+
const editorCmd = getEditorCommand();
|
|
1572
|
+
if (!editorCmd) {
|
|
1573
|
+
this.ctx.showWarning("No editor configured. Set $VISUAL or $EDITOR environment variable.");
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const currentText = this.ctx.editor.getExpandedText?.() ?? this.ctx.editor.getText();
|
|
1578
|
+
|
|
1579
|
+
let ttyHandle: fs.FileHandle | null = null;
|
|
1580
|
+
try {
|
|
1581
|
+
ttyHandle = await this.#openEditorTerminalHandle();
|
|
1582
|
+
this.ctx.ui.stop();
|
|
1583
|
+
|
|
1584
|
+
const stdio: [number | "inherit", number | "inherit", number | "inherit"] = ttyHandle
|
|
1585
|
+
? [ttyHandle.fd, ttyHandle.fd, ttyHandle.fd]
|
|
1586
|
+
: ["inherit", "inherit", "inherit"];
|
|
1587
|
+
|
|
1588
|
+
const result = await openInEditor(editorCmd, currentText, { extension: ".omp.md", stdio });
|
|
1589
|
+
if (result !== null) {
|
|
1590
|
+
this.ctx.editor.setText(result);
|
|
1591
|
+
}
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
this.ctx.showWarning(
|
|
1594
|
+
`Failed to open external editor: ${error instanceof Error ? error.message : String(error)}`,
|
|
1595
|
+
);
|
|
1596
|
+
} finally {
|
|
1597
|
+
if (ttyHandle) {
|
|
1598
|
+
await ttyHandle.close();
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
this.ctx.ui.start();
|
|
1602
|
+
this.ctx.ui.requestRender();
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
registerExtensionShortcuts(): void {
|
|
1607
|
+
const runner = this.ctx.session.extensionRunner;
|
|
1608
|
+
if (!runner) return;
|
|
1609
|
+
|
|
1610
|
+
const shortcuts = runner.getShortcuts();
|
|
1611
|
+
for (const [keyId, shortcut] of shortcuts) {
|
|
1612
|
+
this.ctx.editor.setCustomKeyHandler(keyId, () => {
|
|
1613
|
+
const ctx = runner.createCommandContext();
|
|
1614
|
+
try {
|
|
1615
|
+
shortcut.handler(ctx);
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
runner.emitError({
|
|
1618
|
+
extensionPath: shortcut.extensionPath,
|
|
1619
|
+
event: "shortcut",
|
|
1620
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1621
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|