@draht/coding-agent 2026.3.11-1 → 2026.3.25-1
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 +45 -0
- package/README.md +45 -30
- package/bin/draht-tools.cjs +187 -32
- package/dist/bun/cli.d.ts +3 -0
- package/dist/bun/cli.d.ts.map +1 -0
- package/dist/bun/cli.js +7 -0
- package/dist/bun/cli.js.map +1 -0
- package/dist/bun/register-bedrock.d.ts +2 -0
- package/dist/bun/register-bedrock.d.ts.map +1 -0
- package/dist/bun/register-bedrock.js +4 -0
- package/dist/bun/register-bedrock.js.map +1 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +11 -6
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +4 -0
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/initial-message.d.ts +18 -0
- package/dist/cli/initial-message.d.ts.map +1 -0
- package/dist/cli/initial-message.js +22 -0
- package/dist/cli/initial-message.js.map +1 -0
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +2 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -3
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +38 -5
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +201 -73
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts +6 -7
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +8 -107
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +1 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +2 -0
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +7 -3
- package/dist/core/exec.js.map +1 -1
- package/dist/core/export-html/index.d.ts +2 -2
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +7 -6
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.css +43 -13
- package/dist/core/export-html/template.html +1 -0
- package/dist/core/export-html/template.js +107 -0
- package/dist/core/export-html/tool-renderer.d.ts +2 -2
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +41 -16
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/index.d.ts +4 -3
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +16 -6
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +9 -9
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +89 -71
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +49 -13
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/wrapper.d.ts +4 -11
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +6 -86
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +13 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +155 -37
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +270 -50
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +222 -134
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-registry.d.ts +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +49 -23
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +6 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +41 -17
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/output-guard.d.ts +6 -0
- package/dist/core/output-guard.d.ts.map +1 -0
- package/dist/core/output-guard.js +59 -0
- package/dist/core/output-guard.js.map +1 -0
- package/dist/core/package-manager.d.ts +22 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +374 -54
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +2 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +39 -39
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +43 -8
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader.d.ts +6 -7
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +141 -118
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +4 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +6 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +9 -10
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +5 -3
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +54 -9
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts +2 -3
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +3 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/source-info.d.ts +18 -0
- package/dist/core/source-info.d.ts.map +1 -0
- package/dist/core/source-info.js +19 -0
- package/dist/core/source-info.js.map +1 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +17 -60
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +24 -6
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +210 -110
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +1 -0
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +14 -2
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +95 -23
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts +6 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/core/tools/file-mutation-queue.js +37 -0
- package/dist/core/tools/file-mutation-queue.js.map +1 -0
- package/dist/core/tools/find.d.ts +11 -4
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +82 -30
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +15 -4
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +83 -29
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +58 -19
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +51 -26
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +9 -3
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +67 -13
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +10 -3
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +110 -51
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +21 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -0
- package/dist/core/tools/render-utils.js +49 -0
- package/dist/core/tools/render-utils.js.map +1 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts +14 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -0
- package/dist/core/tools/tool-definition-wrapper.js +30 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -0
- package/dist/core/tools/write.d.ts +9 -3
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +168 -30
- package/dist/core/tools/write.js.map +1 -1
- package/dist/gsd/domain.d.ts +5 -1
- package/dist/gsd/domain.d.ts.map +1 -1
- package/dist/gsd/domain.js +71 -1
- package/dist/gsd/domain.js.map +1 -1
- package/dist/gsd/git.d.ts.map +1 -1
- package/dist/gsd/git.js +18 -0
- package/dist/gsd/git.js.map +1 -1
- package/dist/gsd/index.d.ts +1 -0
- package/dist/gsd/index.d.ts.map +1 -1
- package/dist/gsd/index.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +105 -226
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +22 -9
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +1 -1
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +2 -2
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +2 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +8 -8
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +6 -6
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +9 -9
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-input.js +5 -5
- package/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +8 -8
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +3 -36
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +5 -44
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +6 -6
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +13 -9
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +6 -6
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +4 -4
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +32 -35
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +5 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/show-images-selector.js +5 -1
- package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/theme-selector.js +5 -1
- package/dist/modes/interactive/components/theme-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +5 -1
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +16 -34
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +128 -636
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +27 -16
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +6 -6
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +7 -11
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +353 -212
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +3 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +63 -37
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +5 -11
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +27 -17
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +3 -4
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/prompts/commands/execute-phase.md +2 -2
- package/dist/prompts/commands/fix.md +2 -2
- package/dist/prompts/commands/plan-phase.md +5 -1
- package/dist/prompts/commands/quick.md +5 -1
- package/dist/utils/changelog.d.ts +12 -0
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +25 -14
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/child-process.d.ts +11 -0
- package/dist/utils/child-process.d.ts.map +1 -0
- package/dist/utils/child-process.js +78 -0
- package/dist/utils/child-process.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +94 -11
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts +1 -0
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/clipboard.d.ts +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +27 -16
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/exif-orientation.d.ts +5 -0
- package/dist/utils/exif-orientation.d.ts.map +1 -0
- package/dist/utils/exif-orientation.js +158 -0
- package/dist/utils/exif-orientation.js.map +1 -0
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js +5 -1
- package/dist/utils/image-convert.js.map +1 -1
- package/dist/utils/image-resize.d.ts +5 -5
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +51 -95
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/notify.d.ts +12 -0
- package/dist/utils/notify.d.ts.map +1 -0
- package/dist/utils/notify.js +41 -0
- package/dist/utils/notify.js.map +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +5 -4
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/custom-provider.md +6 -2
- package/docs/extensions.md +108 -21
- package/docs/keybindings.md +103 -112
- package/docs/models.md +39 -1
- package/docs/packages.md +9 -0
- package/docs/providers.md +7 -0
- package/docs/rpc.md +15 -6
- package/docs/sdk.md +2 -2
- package/docs/settings.md +9 -0
- package/docs/terminal-setup.md +11 -0
- package/docs/tui.md +2 -2
- package/examples/extensions/README.md +2 -2
- package/examples/extensions/antigravity-image-gen.ts +9 -6
- package/examples/extensions/built-in-tool-renderer.ts +8 -8
- package/examples/extensions/commands.ts +3 -3
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +2 -2
- package/examples/extensions/minimal-mode.ts +14 -14
- package/examples/extensions/notify.ts +9 -2
- package/examples/extensions/preset.ts +2 -3
- package/examples/extensions/question.ts +2 -2
- package/examples/extensions/questionnaire.ts +2 -2
- package/examples/extensions/sandbox/index.ts +2 -3
- package/examples/extensions/subagent/index.ts +30 -8
- package/examples/extensions/titlebar-spinner.ts +2 -2
- package/examples/extensions/todo.ts +2 -2
- package/examples/extensions/tool-override.ts +10 -9
- package/examples/extensions/truncated-tool.ts +8 -5
- package/examples/sdk/04-skills.ts +8 -2
- package/examples/sdk/08-prompt-templates.ts +8 -2
- package/examples/sdk/12-full-control.ts +0 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +4 -4
- package/prompts/commands/execute-phase.md +2 -2
- package/prompts/commands/fix.md +2 -2
- package/prompts/commands/plan-phase.md +5 -1
- package/prompts/commands/quick.md +5 -1
|
@@ -6,19 +6,21 @@ import * as crypto from "node:crypto";
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@draht/tui";
|
|
9
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@draht/tui";
|
|
10
10
|
import { spawn, spawnSync } from "child_process";
|
|
11
|
-
import { APP_NAME, getAuthPath, getDebugLogPath, getShareViewerUrl, getUpdateInstruction, VERSION, } from "../../config.js";
|
|
11
|
+
import { APP_NAME, getAgentDir, getAuthPath, getDebugLogPath, getShareViewerUrl, getUpdateInstruction, VERSION, } from "../../config.js";
|
|
12
12
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
13
13
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
14
14
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
15
15
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
16
|
-
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
16
|
+
import { findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
17
|
+
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
17
18
|
import { SessionManager } from "../../core/session-manager.js";
|
|
18
19
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
19
20
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
20
21
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
21
22
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
23
|
+
import { parseGitUrl } from "../../utils/git.js";
|
|
22
24
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
23
25
|
import { ArminComponent } from "./components/armin.js";
|
|
24
26
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
@@ -34,7 +36,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
|
34
36
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
35
37
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
36
38
|
import { FooterComponent } from "./components/footer.js";
|
|
37
|
-
import {
|
|
39
|
+
import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
38
40
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
39
41
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
40
42
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
@@ -64,6 +66,7 @@ export class InteractiveMode {
|
|
|
64
66
|
editorContainer;
|
|
65
67
|
footer;
|
|
66
68
|
footerDataProvider;
|
|
69
|
+
// Stored so the same manager can be injected into custom editors, selectors, and extension UI.
|
|
67
70
|
keybindings;
|
|
68
71
|
version;
|
|
69
72
|
isInitialized = false;
|
|
@@ -147,6 +150,7 @@ export class InteractiveMode {
|
|
|
147
150
|
this.widgetContainerAbove = new Container();
|
|
148
151
|
this.widgetContainerBelow = new Container();
|
|
149
152
|
this.keybindings = KeybindingsManager.create();
|
|
153
|
+
setKeybindings(this.keybindings);
|
|
150
154
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
151
155
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
152
156
|
this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
|
|
@@ -165,6 +169,48 @@ export class InteractiveMode {
|
|
|
165
169
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
166
170
|
initTheme(this.settingsManager.getTheme(), true);
|
|
167
171
|
}
|
|
172
|
+
getAutocompleteSourceTag(sourceInfo) {
|
|
173
|
+
if (!sourceInfo) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const scopePrefix = sourceInfo.scope === "user" ? "u" : sourceInfo.scope === "project" ? "p" : "t";
|
|
177
|
+
const source = sourceInfo.source.trim();
|
|
178
|
+
if (source === "auto" || source === "local" || source === "cli") {
|
|
179
|
+
return scopePrefix;
|
|
180
|
+
}
|
|
181
|
+
if (source.startsWith("npm:")) {
|
|
182
|
+
return `${scopePrefix}:${source}`;
|
|
183
|
+
}
|
|
184
|
+
const gitSource = parseGitUrl(source);
|
|
185
|
+
if (gitSource) {
|
|
186
|
+
const ref = gitSource.ref ? `@${gitSource.ref}` : "";
|
|
187
|
+
return `${scopePrefix}:git:${gitSource.host}/${gitSource.path}${ref}`;
|
|
188
|
+
}
|
|
189
|
+
return scopePrefix;
|
|
190
|
+
}
|
|
191
|
+
prefixAutocompleteDescription(description, sourceInfo) {
|
|
192
|
+
const sourceTag = this.getAutocompleteSourceTag(sourceInfo);
|
|
193
|
+
if (!sourceTag) {
|
|
194
|
+
return description;
|
|
195
|
+
}
|
|
196
|
+
return description ? `[${sourceTag}] ${description}` : `[${sourceTag}]`;
|
|
197
|
+
}
|
|
198
|
+
getBuiltInCommandConflictDiagnostics(extensionRunner) {
|
|
199
|
+
if (!extensionRunner) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
const builtinNames = new Set(BUILTIN_SLASH_COMMANDS.map((command) => command.name));
|
|
203
|
+
return extensionRunner
|
|
204
|
+
.getRegisteredCommands()
|
|
205
|
+
.filter((command) => builtinNames.has(command.name))
|
|
206
|
+
.map((command) => ({
|
|
207
|
+
type: "warning",
|
|
208
|
+
message: command.invocationName === command.name
|
|
209
|
+
? `Extension command '/${command.name}' conflicts with built-in interactive command. Skipping in autocomplete.`
|
|
210
|
+
: `Extension command '/${command.name}' conflicts with built-in interactive command. Available as '/${command.invocationName}'.`,
|
|
211
|
+
path: command.sourceInfo.path,
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
168
214
|
setupAutocomplete(fdPath) {
|
|
169
215
|
// Define commands for autocomplete
|
|
170
216
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
@@ -200,13 +246,13 @@ export class InteractiveMode {
|
|
|
200
246
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
201
247
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
202
248
|
name: cmd.name,
|
|
203
|
-
description: cmd.description,
|
|
249
|
+
description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo),
|
|
204
250
|
}));
|
|
205
251
|
// Convert extension commands to SlashCommand format
|
|
206
252
|
const builtinCommandNames = new Set(slashCommands.map((c) => c.name));
|
|
207
|
-
const extensionCommands = (this.session.extensionRunner?.getRegisteredCommands(builtinCommandNames) ?? []).map((cmd) => ({
|
|
208
|
-
name: cmd.
|
|
209
|
-
description: cmd.description
|
|
253
|
+
const extensionCommands = (this.session.extensionRunner?.getRegisteredCommands().filter((cmd) => !builtinCommandNames.has(cmd.name)) ?? []).map((cmd) => ({
|
|
254
|
+
name: cmd.invocationName,
|
|
255
|
+
description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo),
|
|
210
256
|
getArgumentCompletions: cmd.getArgumentCompletions,
|
|
211
257
|
}));
|
|
212
258
|
// Build skill commands from session.skills (if enabled)
|
|
@@ -216,7 +262,10 @@ export class InteractiveMode {
|
|
|
216
262
|
for (const skill of this.session.resourceLoader.getSkills().skills) {
|
|
217
263
|
const commandName = `skill:${skill.name}`;
|
|
218
264
|
this.skillCommands.set(commandName, skill.filePath);
|
|
219
|
-
skillCommandList.push({
|
|
265
|
+
skillCommandList.push({
|
|
266
|
+
name: commandName,
|
|
267
|
+
description: this.prefixAutocompleteDescription(skill.description, skill.sourceInfo),
|
|
268
|
+
});
|
|
220
269
|
}
|
|
221
270
|
}
|
|
222
271
|
// Setup autocomplete
|
|
@@ -241,27 +290,26 @@ export class InteractiveMode {
|
|
|
241
290
|
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
|
|
242
291
|
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
|
243
292
|
// Build startup instructions using keybinding hint helpers
|
|
244
|
-
const
|
|
245
|
-
const hint = (action, desc) => appKeyHint(kb, action, desc);
|
|
293
|
+
const hint = (keybinding, description) => keyHint(keybinding, description);
|
|
246
294
|
const instructions = [
|
|
247
|
-
hint("interrupt", "to interrupt"),
|
|
248
|
-
hint("clear", "to clear"),
|
|
249
|
-
rawKeyHint(`${
|
|
250
|
-
hint("exit", "to exit (empty)"),
|
|
251
|
-
hint("suspend", "to suspend"),
|
|
252
|
-
keyHint("deleteToLineEnd", "to delete to end"),
|
|
253
|
-
hint("
|
|
254
|
-
rawKeyHint(`${
|
|
255
|
-
hint("
|
|
256
|
-
hint("
|
|
257
|
-
hint("
|
|
258
|
-
hint("
|
|
295
|
+
hint("app.interrupt", "to interrupt"),
|
|
296
|
+
hint("app.clear", "to clear"),
|
|
297
|
+
rawKeyHint(`${keyText("app.clear")} twice`, "to exit"),
|
|
298
|
+
hint("app.exit", "to exit (empty)"),
|
|
299
|
+
hint("app.suspend", "to suspend"),
|
|
300
|
+
keyHint("tui.editor.deleteToLineEnd", "to delete to end"),
|
|
301
|
+
hint("app.thinking.cycle", "to cycle thinking level"),
|
|
302
|
+
rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
|
|
303
|
+
hint("app.model.select", "to select model"),
|
|
304
|
+
hint("app.tools.expand", "to expand tools"),
|
|
305
|
+
hint("app.thinking.toggle", "to expand thinking"),
|
|
306
|
+
hint("app.editor.external", "for external editor"),
|
|
259
307
|
rawKeyHint("/", "for commands"),
|
|
260
308
|
rawKeyHint("!", "to run bash"),
|
|
261
309
|
rawKeyHint("!!", "to run bash (no context)"),
|
|
262
|
-
hint("followUp", "to queue follow-up"),
|
|
263
|
-
hint("dequeue", "to edit all queued messages"),
|
|
264
|
-
hint("pasteImage", "to paste image"),
|
|
310
|
+
hint("app.message.followUp", "to queue follow-up"),
|
|
311
|
+
hint("app.message.dequeue", "to edit all queued messages"),
|
|
312
|
+
hint("app.clipboard.pasteImage", "to paste image"),
|
|
265
313
|
rawKeyHint("drop files", "to attach"),
|
|
266
314
|
].join("\n");
|
|
267
315
|
this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
|
|
@@ -311,13 +359,13 @@ export class InteractiveMode {
|
|
|
311
359
|
this.ui.setFocus(this.editor);
|
|
312
360
|
this.setupKeyHandlers();
|
|
313
361
|
this.setupEditorSubmitHandler();
|
|
362
|
+
// Start the UI before initializing extensions so session_start handlers can use interactive dialogs
|
|
363
|
+
this.ui.start();
|
|
364
|
+
this.isInitialized = true;
|
|
314
365
|
// Initialize extensions first so resources are shown before messages
|
|
315
366
|
await this.initExtensions();
|
|
316
367
|
// Render initial messages AFTER showing loaded resources
|
|
317
368
|
this.renderInitialMessages();
|
|
318
|
-
// Start the UI
|
|
319
|
-
this.ui.start();
|
|
320
|
-
this.isInitialized = true;
|
|
321
369
|
// Set terminal title
|
|
322
370
|
this.updateTerminalTitle();
|
|
323
371
|
// Subscribe to agent events
|
|
@@ -342,10 +390,10 @@ export class InteractiveMode {
|
|
|
342
390
|
const cwdBasename = path.basename(process.cwd());
|
|
343
391
|
const sessionName = this.sessionManager.getSessionName();
|
|
344
392
|
if (sessionName) {
|
|
345
|
-
this.ui.terminal.setTitle(
|
|
393
|
+
this.ui.terminal.setTitle(`D - ${sessionName} - ${cwdBasename}`);
|
|
346
394
|
}
|
|
347
395
|
else {
|
|
348
|
-
this.ui.terminal.setTitle(
|
|
396
|
+
this.ui.terminal.setTitle(`D - ${cwdBasename}`);
|
|
349
397
|
}
|
|
350
398
|
}
|
|
351
399
|
/**
|
|
@@ -360,6 +408,12 @@ export class InteractiveMode {
|
|
|
360
408
|
this.showNewVersionNotification(newVersion);
|
|
361
409
|
}
|
|
362
410
|
});
|
|
411
|
+
// Start package update check asynchronously
|
|
412
|
+
this.checkForPackageUpdates().then((updates) => {
|
|
413
|
+
if (updates.length > 0) {
|
|
414
|
+
this.showPackageUpdateNotification(updates);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
363
417
|
// Check tmux keyboard setup asynchronously
|
|
364
418
|
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
365
419
|
if (warning) {
|
|
@@ -415,7 +469,7 @@ export class InteractiveMode {
|
|
|
415
469
|
* Check npm registry for a newer version.
|
|
416
470
|
*/
|
|
417
471
|
async checkForNewVersion() {
|
|
418
|
-
if (process.env.
|
|
472
|
+
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
|
|
419
473
|
return undefined;
|
|
420
474
|
try {
|
|
421
475
|
const response = await fetch("https://registry.npmjs.org/@draht/coding-agent/latest", {
|
|
@@ -434,6 +488,23 @@ export class InteractiveMode {
|
|
|
434
488
|
return undefined;
|
|
435
489
|
}
|
|
436
490
|
}
|
|
491
|
+
async checkForPackageUpdates() {
|
|
492
|
+
if (process.env.PI_OFFLINE) {
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
495
|
+
try {
|
|
496
|
+
const packageManager = new DefaultPackageManager({
|
|
497
|
+
cwd: process.cwd(),
|
|
498
|
+
agentDir: getAgentDir(),
|
|
499
|
+
settingsManager: this.settingsManager,
|
|
500
|
+
});
|
|
501
|
+
const updates = await packageManager.checkForAvailableUpdates();
|
|
502
|
+
return updates.map((update) => update.displayName);
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
}
|
|
437
508
|
async checkTmuxKeyboardSetup() {
|
|
438
509
|
if (!process.env.TMUX)
|
|
439
510
|
return undefined;
|
|
@@ -464,6 +535,9 @@ export class InteractiveMode {
|
|
|
464
535
|
runTmuxShow("extended-keys"),
|
|
465
536
|
runTmuxShow("extended-keys-format"),
|
|
466
537
|
]);
|
|
538
|
+
// If we couldn't query tmux (timeout, sandbox, etc.), don't warn
|
|
539
|
+
if (extendedKeys === undefined)
|
|
540
|
+
return undefined;
|
|
467
541
|
if (extendedKeys !== "on" && extendedKeys !== "always") {
|
|
468
542
|
return "tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.";
|
|
469
543
|
}
|
|
@@ -519,21 +593,21 @@ export class InteractiveMode {
|
|
|
519
593
|
/**
|
|
520
594
|
* Get a short path relative to the package root for display.
|
|
521
595
|
*/
|
|
522
|
-
getShortPath(fullPath,
|
|
523
|
-
|
|
596
|
+
getShortPath(fullPath, sourceInfo) {
|
|
597
|
+
const source = sourceInfo?.source ?? "";
|
|
524
598
|
const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
|
|
525
599
|
if (npmMatch && source.startsWith("npm:")) {
|
|
526
600
|
return npmMatch[2];
|
|
527
601
|
}
|
|
528
|
-
// For git packages, show path relative to repo root
|
|
529
602
|
const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
|
|
530
603
|
if (gitMatch && source.startsWith("git:")) {
|
|
531
604
|
return gitMatch[1];
|
|
532
605
|
}
|
|
533
|
-
// For local/auto, just use formatDisplayPath
|
|
534
606
|
return this.formatDisplayPath(fullPath);
|
|
535
607
|
}
|
|
536
|
-
getDisplaySourceInfo(
|
|
608
|
+
getDisplaySourceInfo(sourceInfo) {
|
|
609
|
+
const source = sourceInfo?.source ?? "local";
|
|
610
|
+
const scope = sourceInfo?.scope ?? "project";
|
|
537
611
|
if (source === "local") {
|
|
538
612
|
if (scope === "user") {
|
|
539
613
|
return { label: "user", color: "muted" };
|
|
@@ -552,7 +626,9 @@ export class InteractiveMode {
|
|
|
552
626
|
const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
|
|
553
627
|
return { label: source, scopeLabel, color: "accent" };
|
|
554
628
|
}
|
|
555
|
-
getScopeGroup(
|
|
629
|
+
getScopeGroup(sourceInfo) {
|
|
630
|
+
const source = sourceInfo?.source ?? "local";
|
|
631
|
+
const scope = sourceInfo?.scope ?? "project";
|
|
556
632
|
if (source === "cli" || scope === "temporary")
|
|
557
633
|
return "path";
|
|
558
634
|
if (scope === "user")
|
|
@@ -561,28 +637,27 @@ export class InteractiveMode {
|
|
|
561
637
|
return "project";
|
|
562
638
|
return "path";
|
|
563
639
|
}
|
|
564
|
-
isPackageSource(
|
|
640
|
+
isPackageSource(sourceInfo) {
|
|
641
|
+
const source = sourceInfo?.source ?? "";
|
|
565
642
|
return source.startsWith("npm:") || source.startsWith("git:");
|
|
566
643
|
}
|
|
567
|
-
buildScopeGroups(
|
|
644
|
+
buildScopeGroups(items) {
|
|
568
645
|
const groups = {
|
|
569
646
|
user: { scope: "user", paths: [], packages: new Map() },
|
|
570
647
|
project: { scope: "project", paths: [], packages: new Map() },
|
|
571
648
|
path: { scope: "path", paths: [], packages: new Map() },
|
|
572
649
|
};
|
|
573
|
-
for (const
|
|
574
|
-
const
|
|
575
|
-
const source = meta?.source ?? "local";
|
|
576
|
-
const scope = meta?.scope ?? "project";
|
|
577
|
-
const groupKey = this.getScopeGroup(source, scope);
|
|
650
|
+
for (const item of items) {
|
|
651
|
+
const groupKey = this.getScopeGroup(item.sourceInfo);
|
|
578
652
|
const group = groups[groupKey];
|
|
579
|
-
|
|
653
|
+
const source = item.sourceInfo?.source ?? "local";
|
|
654
|
+
if (this.isPackageSource(item.sourceInfo)) {
|
|
580
655
|
const list = group.packages.get(source) ?? [];
|
|
581
|
-
list.push(
|
|
656
|
+
list.push(item);
|
|
582
657
|
group.packages.set(source, list);
|
|
583
658
|
}
|
|
584
659
|
else {
|
|
585
|
-
group.paths.push(
|
|
660
|
+
group.paths.push(item);
|
|
586
661
|
}
|
|
587
662
|
}
|
|
588
663
|
return [groups.project, groups.user, groups.path].filter((group) => group.paths.length > 0 || group.packages.size > 0);
|
|
@@ -591,57 +666,44 @@ export class InteractiveMode {
|
|
|
591
666
|
const lines = [];
|
|
592
667
|
for (const group of groups) {
|
|
593
668
|
lines.push(` ${theme.fg("accent", group.scope)}`);
|
|
594
|
-
const sortedPaths = [...group.paths].sort((a, b) => a.localeCompare(b));
|
|
595
|
-
for (const
|
|
596
|
-
lines.push(theme.fg("dim", ` ${options.formatPath(
|
|
669
|
+
const sortedPaths = [...group.paths].sort((a, b) => a.path.localeCompare(b.path));
|
|
670
|
+
for (const item of sortedPaths) {
|
|
671
|
+
lines.push(theme.fg("dim", ` ${options.formatPath(item)}`));
|
|
597
672
|
}
|
|
598
673
|
const sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
599
|
-
for (const [source,
|
|
674
|
+
for (const [source, items] of sortedPackages) {
|
|
600
675
|
lines.push(` ${theme.fg("mdLink", source)}`);
|
|
601
|
-
const sortedPackagePaths = [...
|
|
602
|
-
for (const
|
|
603
|
-
lines.push(theme.fg("dim", ` ${options.formatPackagePath(
|
|
676
|
+
const sortedPackagePaths = [...items].sort((a, b) => a.path.localeCompare(b.path));
|
|
677
|
+
for (const item of sortedPackagePaths) {
|
|
678
|
+
lines.push(theme.fg("dim", ` ${options.formatPackagePath(item, source)}`));
|
|
604
679
|
}
|
|
605
680
|
}
|
|
606
681
|
}
|
|
607
682
|
return lines.join("\n");
|
|
608
683
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
* Package manager stores metadata for directories, but we display file paths.
|
|
612
|
-
*/
|
|
613
|
-
findMetadata(p, metadata) {
|
|
614
|
-
// Try exact match first
|
|
615
|
-
const exact = metadata.get(p);
|
|
684
|
+
findSourceInfoForPath(p, sourceInfos) {
|
|
685
|
+
const exact = sourceInfos.get(p);
|
|
616
686
|
if (exact)
|
|
617
687
|
return exact;
|
|
618
|
-
// Try parent directories (package manager stores directory paths)
|
|
619
688
|
let current = p;
|
|
620
689
|
while (current.includes("/")) {
|
|
621
690
|
current = current.substring(0, current.lastIndexOf("/"));
|
|
622
|
-
const parent =
|
|
691
|
+
const parent = sourceInfos.get(current);
|
|
623
692
|
if (parent)
|
|
624
693
|
return parent;
|
|
625
694
|
}
|
|
626
695
|
return undefined;
|
|
627
696
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
const meta = this.findMetadata(p, metadata);
|
|
633
|
-
if (meta) {
|
|
634
|
-
const shortPath = this.getShortPath(p, meta.source);
|
|
635
|
-
const { label, scopeLabel } = this.getDisplaySourceInfo(meta.source, meta.scope);
|
|
697
|
+
formatPathWithSource(p, sourceInfo) {
|
|
698
|
+
if (sourceInfo) {
|
|
699
|
+
const shortPath = this.getShortPath(p, sourceInfo);
|
|
700
|
+
const { label, scopeLabel } = this.getDisplaySourceInfo(sourceInfo);
|
|
636
701
|
const labelText = scopeLabel ? `${label} (${scopeLabel})` : label;
|
|
637
702
|
return `${labelText} ${shortPath}`;
|
|
638
703
|
}
|
|
639
704
|
return this.formatDisplayPath(p);
|
|
640
705
|
}
|
|
641
|
-
|
|
642
|
-
* Format resource diagnostics with nice collision display using metadata.
|
|
643
|
-
*/
|
|
644
|
-
formatDiagnostics(diagnostics, metadata) {
|
|
706
|
+
formatDiagnostics(diagnostics, sourceInfos) {
|
|
645
707
|
const lines = [];
|
|
646
708
|
// Group collision diagnostics by name
|
|
647
709
|
const collisions = new Map();
|
|
@@ -662,21 +724,17 @@ export class InteractiveMode {
|
|
|
662
724
|
if (!first)
|
|
663
725
|
continue;
|
|
664
726
|
lines.push(theme.fg("warning", ` "${name}" collision:`));
|
|
665
|
-
|
|
666
|
-
lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, metadata)}`));
|
|
667
|
-
// Show all losers
|
|
727
|
+
lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, this.findSourceInfoForPath(first.winnerPath, sourceInfos))}`));
|
|
668
728
|
for (const d of collisionList) {
|
|
669
729
|
if (d.collision) {
|
|
670
|
-
lines.push(theme.fg("dim", ` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath,
|
|
730
|
+
lines.push(theme.fg("dim", ` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath, this.findSourceInfoForPath(d.collision.loserPath, sourceInfos))} (skipped)`));
|
|
671
731
|
}
|
|
672
732
|
}
|
|
673
733
|
}
|
|
674
|
-
// Format other diagnostics (skill name collisions, parse errors, etc.)
|
|
675
734
|
for (const d of otherDiagnostics) {
|
|
676
735
|
if (d.path) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${sourceInfo}`));
|
|
736
|
+
const formattedPath = this.formatPathWithSource(d.path, this.findSourceInfoForPath(d.path, sourceInfos));
|
|
737
|
+
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${formattedPath}`));
|
|
680
738
|
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
|
|
681
739
|
}
|
|
682
740
|
else {
|
|
@@ -691,11 +749,36 @@ export class InteractiveMode {
|
|
|
691
749
|
if (!showListing && !showDiagnostics) {
|
|
692
750
|
return;
|
|
693
751
|
}
|
|
694
|
-
const metadata = this.session.resourceLoader.getPathMetadata();
|
|
695
752
|
const sectionHeader = (name, color = "mdHeading") => theme.fg(color, `[${name}]`);
|
|
696
753
|
const skillsResult = this.session.resourceLoader.getSkills();
|
|
697
754
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
698
755
|
const themesResult = this.session.resourceLoader.getThemes();
|
|
756
|
+
const extensions = options?.extensions ??
|
|
757
|
+
this.session.resourceLoader.getExtensions().extensions.map((extension) => ({
|
|
758
|
+
path: extension.path,
|
|
759
|
+
sourceInfo: extension.sourceInfo,
|
|
760
|
+
}));
|
|
761
|
+
const sourceInfos = new Map();
|
|
762
|
+
for (const extension of extensions) {
|
|
763
|
+
if (extension.sourceInfo) {
|
|
764
|
+
sourceInfos.set(extension.path, extension.sourceInfo);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
for (const skill of skillsResult.skills) {
|
|
768
|
+
if (skill.sourceInfo) {
|
|
769
|
+
sourceInfos.set(skill.filePath, skill.sourceInfo);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
for (const prompt of promptsResult.prompts) {
|
|
773
|
+
if (prompt.sourceInfo) {
|
|
774
|
+
sourceInfos.set(prompt.filePath, prompt.sourceInfo);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
for (const loadedTheme of themesResult.themes) {
|
|
778
|
+
if (loadedTheme.sourcePath && loadedTheme.sourceInfo) {
|
|
779
|
+
sourceInfos.set(loadedTheme.sourcePath, loadedTheme.sourceInfo);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
699
782
|
if (showListing) {
|
|
700
783
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
|
701
784
|
if (contextFiles.length > 0) {
|
|
@@ -708,39 +791,36 @@ export class InteractiveMode {
|
|
|
708
791
|
}
|
|
709
792
|
const skills = skillsResult.skills;
|
|
710
793
|
if (skills.length > 0) {
|
|
711
|
-
const
|
|
712
|
-
const groups = this.buildScopeGroups(skillPaths, metadata);
|
|
794
|
+
const groups = this.buildScopeGroups(skills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })));
|
|
713
795
|
const skillList = this.formatScopeGroups(groups, {
|
|
714
|
-
formatPath: (
|
|
715
|
-
formatPackagePath: (
|
|
796
|
+
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
797
|
+
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
716
798
|
});
|
|
717
799
|
this.chatContainer.addChild(new Text(`${sectionHeader("Skills")}\n${skillList}`, 0, 0));
|
|
718
800
|
this.chatContainer.addChild(new Spacer(1));
|
|
719
801
|
}
|
|
720
802
|
const templates = this.session.promptTemplates;
|
|
721
803
|
if (templates.length > 0) {
|
|
722
|
-
const
|
|
723
|
-
const groups = this.buildScopeGroups(templatePaths, metadata);
|
|
804
|
+
const groups = this.buildScopeGroups(templates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })));
|
|
724
805
|
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
|
725
806
|
const templateList = this.formatScopeGroups(groups, {
|
|
726
|
-
formatPath: (
|
|
727
|
-
const template = templateByPath.get(
|
|
728
|
-
return template ? `/${template.name}` : this.formatDisplayPath(
|
|
807
|
+
formatPath: (item) => {
|
|
808
|
+
const template = templateByPath.get(item.path);
|
|
809
|
+
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
729
810
|
},
|
|
730
|
-
formatPackagePath: (
|
|
731
|
-
const template = templateByPath.get(
|
|
732
|
-
return template ? `/${template.name}` : this.formatDisplayPath(
|
|
811
|
+
formatPackagePath: (item) => {
|
|
812
|
+
const template = templateByPath.get(item.path);
|
|
813
|
+
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
733
814
|
},
|
|
734
815
|
});
|
|
735
816
|
this.chatContainer.addChild(new Text(`${sectionHeader("Prompts")}\n${templateList}`, 0, 0));
|
|
736
817
|
this.chatContainer.addChild(new Spacer(1));
|
|
737
818
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
const groups = this.buildScopeGroups(extensionPaths, metadata);
|
|
819
|
+
if (extensions.length > 0) {
|
|
820
|
+
const groups = this.buildScopeGroups(extensions);
|
|
741
821
|
const extList = this.formatScopeGroups(groups, {
|
|
742
|
-
formatPath: (
|
|
743
|
-
formatPackagePath: (
|
|
822
|
+
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
823
|
+
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
744
824
|
});
|
|
745
825
|
this.chatContainer.addChild(new Text(`${sectionHeader("Extensions", "mdHeading")}\n${extList}`, 0, 0));
|
|
746
826
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -749,11 +829,13 @@ export class InteractiveMode {
|
|
|
749
829
|
const loadedThemes = themesResult.themes;
|
|
750
830
|
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
|
751
831
|
if (customThemes.length > 0) {
|
|
752
|
-
const
|
|
753
|
-
|
|
832
|
+
const groups = this.buildScopeGroups(customThemes.map((loadedTheme) => ({
|
|
833
|
+
path: loadedTheme.sourcePath,
|
|
834
|
+
sourceInfo: loadedTheme.sourceInfo,
|
|
835
|
+
})));
|
|
754
836
|
const themeList = this.formatScopeGroups(groups, {
|
|
755
|
-
formatPath: (
|
|
756
|
-
formatPackagePath: (
|
|
837
|
+
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
838
|
+
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
757
839
|
});
|
|
758
840
|
this.chatContainer.addChild(new Text(`${sectionHeader("Themes")}\n${themeList}`, 0, 0));
|
|
759
841
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -762,13 +844,13 @@ export class InteractiveMode {
|
|
|
762
844
|
if (showDiagnostics) {
|
|
763
845
|
const skillDiagnostics = skillsResult.diagnostics;
|
|
764
846
|
if (skillDiagnostics.length > 0) {
|
|
765
|
-
const warningLines = this.formatDiagnostics(skillDiagnostics,
|
|
847
|
+
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
|
766
848
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
767
849
|
this.chatContainer.addChild(new Spacer(1));
|
|
768
850
|
}
|
|
769
851
|
const promptDiagnostics = promptsResult.diagnostics;
|
|
770
852
|
if (promptDiagnostics.length > 0) {
|
|
771
|
-
const warningLines = this.formatDiagnostics(promptDiagnostics,
|
|
853
|
+
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
|
772
854
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
773
855
|
this.chatContainer.addChild(new Spacer(1));
|
|
774
856
|
}
|
|
@@ -781,16 +863,17 @@ export class InteractiveMode {
|
|
|
781
863
|
}
|
|
782
864
|
const commandDiagnostics = this.session.extensionRunner?.getCommandDiagnostics() ?? [];
|
|
783
865
|
extensionDiagnostics.push(...commandDiagnostics);
|
|
866
|
+
extensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));
|
|
784
867
|
const shortcutDiagnostics = this.session.extensionRunner?.getShortcutDiagnostics() ?? [];
|
|
785
868
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
786
869
|
if (extensionDiagnostics.length > 0) {
|
|
787
|
-
const warningLines = this.formatDiagnostics(extensionDiagnostics,
|
|
870
|
+
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
788
871
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
789
872
|
this.chatContainer.addChild(new Spacer(1));
|
|
790
873
|
}
|
|
791
874
|
const themeDiagnostics = themesResult.diagnostics;
|
|
792
875
|
if (themeDiagnostics.length > 0) {
|
|
793
|
-
const warningLines = this.formatDiagnostics(themeDiagnostics,
|
|
876
|
+
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
794
877
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
795
878
|
this.chatContainer.addChild(new Spacer(1));
|
|
796
879
|
}
|
|
@@ -879,19 +962,17 @@ export class InteractiveMode {
|
|
|
879
962
|
this.setupAutocomplete(this.fdPath);
|
|
880
963
|
const extensionRunner = this.session.extensionRunner;
|
|
881
964
|
if (!extensionRunner) {
|
|
882
|
-
this.showLoadedResources({
|
|
965
|
+
this.showLoadedResources({ extensions: [], force: false });
|
|
883
966
|
return;
|
|
884
967
|
}
|
|
885
968
|
this.setupExtensionShortcuts(extensionRunner);
|
|
886
|
-
this.showLoadedResources({
|
|
969
|
+
this.showLoadedResources({ force: false });
|
|
887
970
|
}
|
|
888
971
|
/**
|
|
889
972
|
* Get a registered tool definition by name (for custom rendering).
|
|
890
973
|
*/
|
|
891
974
|
getRegisteredToolDefinition(toolName) {
|
|
892
|
-
|
|
893
|
-
const registeredTool = tools.find((t) => t.definition.name === toolName);
|
|
894
|
-
return registeredTool?.definition;
|
|
975
|
+
return this.session.getToolDefinition(toolName);
|
|
895
976
|
}
|
|
896
977
|
/**
|
|
897
978
|
* Set up keyboard shortcuts registered by extensions.
|
|
@@ -1022,7 +1103,7 @@ export class InteractiveMode {
|
|
|
1022
1103
|
this.defaultEditor.onExtensionShortcut = undefined;
|
|
1023
1104
|
this.updateTerminalTitle();
|
|
1024
1105
|
if (this.loadingAnimation) {
|
|
1025
|
-
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${
|
|
1106
|
+
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1026
1107
|
}
|
|
1027
1108
|
}
|
|
1028
1109
|
// Maximum total widget lines to prevent viewport overflow
|
|
@@ -1145,7 +1226,7 @@ export class InteractiveMode {
|
|
|
1145
1226
|
this.loadingAnimation.setMessage(message);
|
|
1146
1227
|
}
|
|
1147
1228
|
else {
|
|
1148
|
-
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${
|
|
1229
|
+
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1149
1230
|
}
|
|
1150
1231
|
}
|
|
1151
1232
|
else {
|
|
@@ -1160,7 +1241,7 @@ export class InteractiveMode {
|
|
|
1160
1241
|
custom: (factory, options) => this.showExtensionCustom(factory, options),
|
|
1161
1242
|
pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
|
|
1162
1243
|
setEditorText: (text) => this.editor.setText(text),
|
|
1163
|
-
getEditorText: () => this.editor.getText(),
|
|
1244
|
+
getEditorText: () => this.editor.getExpandedText?.() ?? this.editor.getText(),
|
|
1164
1245
|
editor: (title, prefill) => this.showExtensionEditor(title, prefill),
|
|
1165
1246
|
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
1166
1247
|
get theme() {
|
|
@@ -1503,24 +1584,24 @@ export class InteractiveMode {
|
|
|
1503
1584
|
}
|
|
1504
1585
|
};
|
|
1505
1586
|
// Register app action handlers
|
|
1506
|
-
this.defaultEditor.onAction("clear", () => this.handleCtrlC());
|
|
1587
|
+
this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
|
|
1507
1588
|
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
|
1508
|
-
this.defaultEditor.onAction("suspend", () => this.handleCtrlZ());
|
|
1509
|
-
this.defaultEditor.onAction("
|
|
1510
|
-
this.defaultEditor.onAction("
|
|
1511
|
-
this.defaultEditor.onAction("
|
|
1589
|
+
this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
|
|
1590
|
+
this.defaultEditor.onAction("app.thinking.cycle", () => this.cycleThinkingLevel());
|
|
1591
|
+
this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
|
|
1592
|
+
this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
|
|
1512
1593
|
// Global debug handler on TUI (works regardless of focus)
|
|
1513
1594
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
1514
|
-
this.defaultEditor.onAction("
|
|
1515
|
-
this.defaultEditor.onAction("
|
|
1516
|
-
this.defaultEditor.onAction("
|
|
1517
|
-
this.defaultEditor.onAction("
|
|
1518
|
-
this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
|
|
1519
|
-
this.defaultEditor.onAction("dequeue", () => this.handleDequeue());
|
|
1520
|
-
this.defaultEditor.onAction("
|
|
1521
|
-
this.defaultEditor.onAction("tree", () => this.showTreeSelector());
|
|
1522
|
-
this.defaultEditor.onAction("fork", () => this.showUserMessageSelector());
|
|
1523
|
-
this.defaultEditor.onAction("resume", () => this.showSessionSelector());
|
|
1595
|
+
this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
|
|
1596
|
+
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
1597
|
+
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
1598
|
+
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
1599
|
+
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
1600
|
+
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
1601
|
+
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
1602
|
+
this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
|
|
1603
|
+
this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
|
|
1604
|
+
this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
|
|
1524
1605
|
this.defaultEditor.onChange = (text) => {
|
|
1525
1606
|
const wasBashMode = this.isBashMode;
|
|
1526
1607
|
this.isBashMode = text.trimStart().startsWith("!");
|
|
@@ -1580,13 +1661,18 @@ export class InteractiveMode {
|
|
|
1580
1661
|
this.editor.setText("");
|
|
1581
1662
|
return;
|
|
1582
1663
|
}
|
|
1664
|
+
if (text.startsWith("/import")) {
|
|
1665
|
+
await this.handleImportCommand(text);
|
|
1666
|
+
this.editor.setText("");
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1583
1669
|
if (text === "/share") {
|
|
1584
1670
|
await this.handleShareCommand();
|
|
1585
1671
|
this.editor.setText("");
|
|
1586
1672
|
return;
|
|
1587
1673
|
}
|
|
1588
1674
|
if (text === "/copy") {
|
|
1589
|
-
this.handleCopyCommand();
|
|
1675
|
+
await this.handleCopyCommand();
|
|
1590
1676
|
this.editor.setText("");
|
|
1591
1677
|
return;
|
|
1592
1678
|
}
|
|
@@ -1776,7 +1862,7 @@ export class InteractiveMode {
|
|
|
1776
1862
|
for (const content of this.streamingMessage.content) {
|
|
1777
1863
|
if (content.type === "toolCall") {
|
|
1778
1864
|
if (!this.pendingTools.has(content.id)) {
|
|
1779
|
-
const component = new ToolExecutionComponent(content.name, content.arguments, {
|
|
1865
|
+
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
1780
1866
|
showImages: this.settingsManager.getShowImages(),
|
|
1781
1867
|
}, this.getRegisteredToolDefinition(content.name), this.ui);
|
|
1782
1868
|
component.setExpanded(this.toolOutputExpanded);
|
|
@@ -1834,15 +1920,17 @@ export class InteractiveMode {
|
|
|
1834
1920
|
this.ui.requestRender();
|
|
1835
1921
|
break;
|
|
1836
1922
|
case "tool_execution_start": {
|
|
1837
|
-
|
|
1838
|
-
|
|
1923
|
+
let component = this.pendingTools.get(event.toolCallId);
|
|
1924
|
+
if (!component) {
|
|
1925
|
+
component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
|
|
1839
1926
|
showImages: this.settingsManager.getShowImages(),
|
|
1840
1927
|
}, this.getRegisteredToolDefinition(event.toolName), this.ui);
|
|
1841
1928
|
component.setExpanded(this.toolOutputExpanded);
|
|
1842
1929
|
this.chatContainer.addChild(component);
|
|
1843
1930
|
this.pendingTools.set(event.toolCallId, component);
|
|
1844
|
-
this.ui.requestRender();
|
|
1845
1931
|
}
|
|
1932
|
+
component.markExecutionStarted();
|
|
1933
|
+
this.ui.requestRender();
|
|
1846
1934
|
break;
|
|
1847
1935
|
}
|
|
1848
1936
|
case "tool_execution_update": {
|
|
@@ -1887,7 +1975,7 @@ export class InteractiveMode {
|
|
|
1887
1975
|
// Show compacting indicator with reason
|
|
1888
1976
|
this.statusContainer.clear();
|
|
1889
1977
|
const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
|
|
1890
|
-
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (${
|
|
1978
|
+
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (${keyText("app.interrupt")} to cancel)`);
|
|
1891
1979
|
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
1892
1980
|
this.ui.requestRender();
|
|
1893
1981
|
break;
|
|
@@ -1939,7 +2027,7 @@ export class InteractiveMode {
|
|
|
1939
2027
|
// Show retry indicator
|
|
1940
2028
|
this.statusContainer.clear();
|
|
1941
2029
|
const delaySeconds = Math.round(event.delayMs / 1000);
|
|
1942
|
-
this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${
|
|
2030
|
+
this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${keyText("app.interrupt")} to cancel)`);
|
|
1943
2031
|
this.statusContainer.addChild(this.retryLoader);
|
|
1944
2032
|
this.ui.requestRender();
|
|
1945
2033
|
break;
|
|
@@ -2090,7 +2178,7 @@ export class InteractiveMode {
|
|
|
2090
2178
|
// Render tool call components
|
|
2091
2179
|
for (const content of message.content) {
|
|
2092
2180
|
if (content.type === "toolCall") {
|
|
2093
|
-
const component = new ToolExecutionComponent(content.name, content.arguments, { showImages: this.settingsManager.getShowImages() }, this.getRegisteredToolDefinition(content.name), this.ui);
|
|
2181
|
+
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, { showImages: this.settingsManager.getShowImages() }, this.getRegisteredToolDefinition(content.name), this.ui);
|
|
2094
2182
|
component.setExpanded(this.toolOutputExpanded);
|
|
2095
2183
|
this.chatContainer.addChild(component);
|
|
2096
2184
|
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
@@ -2208,20 +2296,32 @@ export class InteractiveMode {
|
|
|
2208
2296
|
await this.shutdown();
|
|
2209
2297
|
}
|
|
2210
2298
|
handleCtrlZ() {
|
|
2299
|
+
// Keep the event loop alive while suspended. Without this, stopping the TUI
|
|
2300
|
+
// can leave Node with no ref'ed handles, causing the process to exit on fg
|
|
2301
|
+
// before the SIGCONT handler gets a chance to restore the terminal.
|
|
2302
|
+
const suspendKeepAlive = setInterval(() => { }, 2 ** 30);
|
|
2211
2303
|
// Ignore SIGINT while suspended so Ctrl+C in the terminal does not
|
|
2212
2304
|
// kill the backgrounded process. The handler is removed on resume.
|
|
2213
2305
|
const ignoreSigint = () => { };
|
|
2214
2306
|
process.on("SIGINT", ignoreSigint);
|
|
2215
2307
|
// Set up handler to restore TUI when resumed
|
|
2216
2308
|
process.once("SIGCONT", () => {
|
|
2309
|
+
clearInterval(suspendKeepAlive);
|
|
2217
2310
|
process.removeListener("SIGINT", ignoreSigint);
|
|
2218
2311
|
this.ui.start();
|
|
2219
2312
|
this.ui.requestRender(true);
|
|
2220
2313
|
});
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2314
|
+
try {
|
|
2315
|
+
// Stop the TUI (restore terminal to normal mode)
|
|
2316
|
+
this.ui.stop();
|
|
2317
|
+
// Send SIGTSTP to process group (pid=0 means all processes in group)
|
|
2318
|
+
process.kill(0, "SIGTSTP");
|
|
2319
|
+
}
|
|
2320
|
+
catch (error) {
|
|
2321
|
+
clearInterval(suspendKeepAlive);
|
|
2322
|
+
process.removeListener("SIGINT", ignoreSigint);
|
|
2323
|
+
throw error;
|
|
2324
|
+
}
|
|
2225
2325
|
}
|
|
2226
2326
|
async handleFollowUp() {
|
|
2227
2327
|
const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
|
|
@@ -2335,7 +2435,7 @@ export class InteractiveMode {
|
|
|
2335
2435
|
return;
|
|
2336
2436
|
}
|
|
2337
2437
|
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
2338
|
-
const tmpFile = path.join(os.tmpdir(), `
|
|
2438
|
+
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
|
|
2339
2439
|
try {
|
|
2340
2440
|
// Write current content to temp file
|
|
2341
2441
|
fs.writeFileSync(tmpFile, currentText, "utf-8");
|
|
@@ -2397,6 +2497,16 @@ export class InteractiveMode {
|
|
|
2397
2497
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2398
2498
|
this.ui.requestRender();
|
|
2399
2499
|
}
|
|
2500
|
+
showPackageUpdateNotification(packages) {
|
|
2501
|
+
const action = theme.fg("accent", `${APP_NAME} update`);
|
|
2502
|
+
const updateInstruction = theme.fg("muted", "Package updates are available. Run ") + action;
|
|
2503
|
+
const packageLines = packages.map((pkg) => `- ${pkg}`).join("\n");
|
|
2504
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2505
|
+
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2506
|
+
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Package Updates Available"))}\n${updateInstruction}\n${theme.fg("muted", "Packages:")}\n${packageLines}`, 1, 0));
|
|
2507
|
+
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2508
|
+
this.ui.requestRender();
|
|
2509
|
+
}
|
|
2400
2510
|
/**
|
|
2401
2511
|
* Get all queued messages (read-only).
|
|
2402
2512
|
* Combines session queue and compaction queue.
|
|
@@ -2444,7 +2554,7 @@ export class InteractiveMode {
|
|
|
2444
2554
|
const text = theme.fg("dim", `Follow-up: ${message}`);
|
|
2445
2555
|
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
|
2446
2556
|
}
|
|
2447
|
-
const dequeueHint = this.getAppKeyDisplay("dequeue");
|
|
2557
|
+
const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
|
|
2448
2558
|
const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
|
|
2449
2559
|
this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
|
|
2450
2560
|
}
|
|
@@ -2734,28 +2844,8 @@ export class InteractiveMode {
|
|
|
2734
2844
|
this.showModelSelector(searchTerm);
|
|
2735
2845
|
}
|
|
2736
2846
|
async findExactModelMatch(searchTerm) {
|
|
2737
|
-
const term = searchTerm.trim();
|
|
2738
|
-
if (!term)
|
|
2739
|
-
return undefined;
|
|
2740
|
-
let targetProvider;
|
|
2741
|
-
let targetModelId = "";
|
|
2742
|
-
if (term.includes("/")) {
|
|
2743
|
-
const parts = term.split("/", 2);
|
|
2744
|
-
targetProvider = parts[0]?.trim().toLowerCase();
|
|
2745
|
-
targetModelId = parts[1]?.trim().toLowerCase() ?? "";
|
|
2746
|
-
}
|
|
2747
|
-
else {
|
|
2748
|
-
targetModelId = term.toLowerCase();
|
|
2749
|
-
}
|
|
2750
|
-
if (!targetModelId)
|
|
2751
|
-
return undefined;
|
|
2752
2847
|
const models = await this.getModelCandidates();
|
|
2753
|
-
|
|
2754
|
-
const idMatch = item.id.toLowerCase() === targetModelId;
|
|
2755
|
-
const providerMatch = !targetProvider || item.provider.toLowerCase() === targetProvider;
|
|
2756
|
-
return idMatch && providerMatch;
|
|
2757
|
-
});
|
|
2758
|
-
return exactMatches.length === 1 ? exactMatches[0] : undefined;
|
|
2848
|
+
return findExactModelReferenceMatch(searchTerm, models);
|
|
2759
2849
|
}
|
|
2760
2850
|
async getModelCandidates() {
|
|
2761
2851
|
if (this.session.scopedModels.length > 0) {
|
|
@@ -2986,7 +3076,7 @@ export class InteractiveMode {
|
|
|
2986
3076
|
this.session.abortBranchSummary();
|
|
2987
3077
|
};
|
|
2988
3078
|
this.chatContainer.addChild(new Spacer(1));
|
|
2989
|
-
summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${
|
|
3079
|
+
summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${keyText("app.interrupt")} to cancel)`);
|
|
2990
3080
|
this.statusContainer.addChild(summaryLoader);
|
|
2991
3081
|
this.ui.requestRender();
|
|
2992
3082
|
}
|
|
@@ -3206,7 +3296,7 @@ export class InteractiveMode {
|
|
|
3206
3296
|
return;
|
|
3207
3297
|
}
|
|
3208
3298
|
this.resetExtensionUI();
|
|
3209
|
-
const loader = new BorderedLoader(this.ui, theme, "Reloading extensions, skills, prompts, themes...", {
|
|
3299
|
+
const loader = new BorderedLoader(this.ui, theme, "Reloading keybindings, extensions, skills, prompts, themes...", {
|
|
3210
3300
|
cancellable: false,
|
|
3211
3301
|
});
|
|
3212
3302
|
const previousEditor = this.editor;
|
|
@@ -3223,6 +3313,7 @@ export class InteractiveMode {
|
|
|
3223
3313
|
};
|
|
3224
3314
|
try {
|
|
3225
3315
|
await this.session.reload();
|
|
3316
|
+
this.keybindings.reload();
|
|
3226
3317
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
3227
3318
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
3228
3319
|
const themeName = this.settingsManager.getTheme();
|
|
@@ -3248,7 +3339,6 @@ export class InteractiveMode {
|
|
|
3248
3339
|
this.rebuildChatFromMessages();
|
|
3249
3340
|
dismissLoader(this.editor);
|
|
3250
3341
|
this.showLoadedResources({
|
|
3251
|
-
extensionPaths: runner?.getExtensionPaths() ?? [],
|
|
3252
3342
|
force: false,
|
|
3253
3343
|
showDiagnosticsWhenQuiet: true,
|
|
3254
3344
|
});
|
|
@@ -3256,7 +3346,7 @@ export class InteractiveMode {
|
|
|
3256
3346
|
if (modelsJsonError) {
|
|
3257
3347
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
3258
3348
|
}
|
|
3259
|
-
this.showStatus("Reloaded extensions, skills, prompts, themes");
|
|
3349
|
+
this.showStatus("Reloaded keybindings, extensions, skills, prompts, themes");
|
|
3260
3350
|
}
|
|
3261
3351
|
catch (error) {
|
|
3262
3352
|
dismissLoader(previousEditor);
|
|
@@ -3267,13 +3357,58 @@ export class InteractiveMode {
|
|
|
3267
3357
|
const parts = text.split(/\s+/);
|
|
3268
3358
|
const outputPath = parts.length > 1 ? parts[1] : undefined;
|
|
3269
3359
|
try {
|
|
3270
|
-
|
|
3271
|
-
|
|
3360
|
+
if (outputPath?.endsWith(".jsonl")) {
|
|
3361
|
+
const filePath = this.session.exportToJsonl(outputPath);
|
|
3362
|
+
this.showStatus(`Session exported to: ${filePath}`);
|
|
3363
|
+
}
|
|
3364
|
+
else {
|
|
3365
|
+
const filePath = await this.session.exportToHtml(outputPath);
|
|
3366
|
+
this.showStatus(`Session exported to: ${filePath}`);
|
|
3367
|
+
}
|
|
3272
3368
|
}
|
|
3273
3369
|
catch (error) {
|
|
3274
3370
|
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3275
3371
|
}
|
|
3276
3372
|
}
|
|
3373
|
+
async handleImportCommand(text) {
|
|
3374
|
+
const parts = text.split(/\s+/);
|
|
3375
|
+
if (parts.length < 2 || !parts[1]) {
|
|
3376
|
+
this.showError("Usage: /import <path.jsonl>");
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
const inputPath = parts[1];
|
|
3380
|
+
const confirmed = await this.showExtensionConfirm("Import session", `Replace current session with ${inputPath}?`);
|
|
3381
|
+
if (!confirmed) {
|
|
3382
|
+
this.showStatus("Import cancelled");
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
try {
|
|
3386
|
+
// Stop loading animation
|
|
3387
|
+
if (this.loadingAnimation) {
|
|
3388
|
+
this.loadingAnimation.stop();
|
|
3389
|
+
this.loadingAnimation = undefined;
|
|
3390
|
+
}
|
|
3391
|
+
this.statusContainer.clear();
|
|
3392
|
+
// Clear UI state
|
|
3393
|
+
this.pendingMessagesContainer.clear();
|
|
3394
|
+
this.compactionQueuedMessages = [];
|
|
3395
|
+
this.streamingComponent = undefined;
|
|
3396
|
+
this.streamingMessage = undefined;
|
|
3397
|
+
this.pendingTools.clear();
|
|
3398
|
+
const success = await this.session.importFromJsonl(inputPath);
|
|
3399
|
+
if (!success) {
|
|
3400
|
+
this.showWarning("Import cancelled");
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
// Clear and re-render the chat
|
|
3404
|
+
this.chatContainer.clear();
|
|
3405
|
+
this.renderInitialMessages();
|
|
3406
|
+
this.showStatus(`Session imported from: ${inputPath}`);
|
|
3407
|
+
}
|
|
3408
|
+
catch (error) {
|
|
3409
|
+
this.showError(`Failed to import session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3277
3412
|
async handleShareCommand() {
|
|
3278
3413
|
// Check if gh is available and logged in
|
|
3279
3414
|
try {
|
|
@@ -3361,14 +3496,14 @@ export class InteractiveMode {
|
|
|
3361
3496
|
}
|
|
3362
3497
|
}
|
|
3363
3498
|
}
|
|
3364
|
-
handleCopyCommand() {
|
|
3499
|
+
async handleCopyCommand() {
|
|
3365
3500
|
const text = this.session.getLastAssistantText();
|
|
3366
3501
|
if (!text) {
|
|
3367
3502
|
this.showError("No agent messages to copy yet.");
|
|
3368
3503
|
return;
|
|
3369
3504
|
}
|
|
3370
3505
|
try {
|
|
3371
|
-
copyToClipboard(text);
|
|
3506
|
+
await copyToClipboard(text);
|
|
3372
3507
|
this.showStatus("Copied last agent message to clipboard");
|
|
3373
3508
|
}
|
|
3374
3509
|
catch (error) {
|
|
@@ -3461,53 +3596,59 @@ export class InteractiveMode {
|
|
|
3461
3596
|
* Get capitalized display string for an app keybinding action.
|
|
3462
3597
|
*/
|
|
3463
3598
|
getAppKeyDisplay(action) {
|
|
3464
|
-
return this.capitalizeKey(
|
|
3599
|
+
return this.capitalizeKey(keyText(action));
|
|
3465
3600
|
}
|
|
3466
3601
|
/**
|
|
3467
3602
|
* Get capitalized display string for an editor keybinding action.
|
|
3468
3603
|
*/
|
|
3469
3604
|
getEditorKeyDisplay(action) {
|
|
3470
|
-
return this.capitalizeKey(
|
|
3605
|
+
return this.capitalizeKey(keyText(action));
|
|
3471
3606
|
}
|
|
3472
3607
|
handleHotkeysCommand() {
|
|
3473
3608
|
// Navigation keybindings
|
|
3474
|
-
const
|
|
3475
|
-
const
|
|
3476
|
-
const
|
|
3477
|
-
const
|
|
3478
|
-
const
|
|
3479
|
-
const
|
|
3480
|
-
const
|
|
3481
|
-
const
|
|
3609
|
+
const cursorUp = this.getEditorKeyDisplay("tui.editor.cursorUp");
|
|
3610
|
+
const cursorDown = this.getEditorKeyDisplay("tui.editor.cursorDown");
|
|
3611
|
+
const cursorLeft = this.getEditorKeyDisplay("tui.editor.cursorLeft");
|
|
3612
|
+
const cursorRight = this.getEditorKeyDisplay("tui.editor.cursorRight");
|
|
3613
|
+
const cursorWordLeft = this.getEditorKeyDisplay("tui.editor.cursorWordLeft");
|
|
3614
|
+
const cursorWordRight = this.getEditorKeyDisplay("tui.editor.cursorWordRight");
|
|
3615
|
+
const cursorLineStart = this.getEditorKeyDisplay("tui.editor.cursorLineStart");
|
|
3616
|
+
const cursorLineEnd = this.getEditorKeyDisplay("tui.editor.cursorLineEnd");
|
|
3617
|
+
const jumpForward = this.getEditorKeyDisplay("tui.editor.jumpForward");
|
|
3618
|
+
const jumpBackward = this.getEditorKeyDisplay("tui.editor.jumpBackward");
|
|
3619
|
+
const pageUp = this.getEditorKeyDisplay("tui.editor.pageUp");
|
|
3620
|
+
const pageDown = this.getEditorKeyDisplay("tui.editor.pageDown");
|
|
3482
3621
|
// Editing keybindings
|
|
3483
|
-
const submit = this.getEditorKeyDisplay("submit");
|
|
3484
|
-
const newLine = this.getEditorKeyDisplay("newLine");
|
|
3485
|
-
const deleteWordBackward = this.getEditorKeyDisplay("deleteWordBackward");
|
|
3486
|
-
const deleteWordForward = this.getEditorKeyDisplay("deleteWordForward");
|
|
3487
|
-
const deleteToLineStart = this.getEditorKeyDisplay("deleteToLineStart");
|
|
3488
|
-
const deleteToLineEnd = this.getEditorKeyDisplay("deleteToLineEnd");
|
|
3489
|
-
const yank = this.getEditorKeyDisplay("yank");
|
|
3490
|
-
const yankPop = this.getEditorKeyDisplay("yankPop");
|
|
3491
|
-
const undo = this.getEditorKeyDisplay("undo");
|
|
3492
|
-
const tab = this.getEditorKeyDisplay("tab");
|
|
3622
|
+
const submit = this.getEditorKeyDisplay("tui.input.submit");
|
|
3623
|
+
const newLine = this.getEditorKeyDisplay("tui.input.newLine");
|
|
3624
|
+
const deleteWordBackward = this.getEditorKeyDisplay("tui.editor.deleteWordBackward");
|
|
3625
|
+
const deleteWordForward = this.getEditorKeyDisplay("tui.editor.deleteWordForward");
|
|
3626
|
+
const deleteToLineStart = this.getEditorKeyDisplay("tui.editor.deleteToLineStart");
|
|
3627
|
+
const deleteToLineEnd = this.getEditorKeyDisplay("tui.editor.deleteToLineEnd");
|
|
3628
|
+
const yank = this.getEditorKeyDisplay("tui.editor.yank");
|
|
3629
|
+
const yankPop = this.getEditorKeyDisplay("tui.editor.yankPop");
|
|
3630
|
+
const undo = this.getEditorKeyDisplay("tui.editor.undo");
|
|
3631
|
+
const tab = this.getEditorKeyDisplay("tui.input.tab");
|
|
3493
3632
|
// App keybindings
|
|
3494
|
-
const interrupt = this.getAppKeyDisplay("interrupt");
|
|
3495
|
-
const clear = this.getAppKeyDisplay("clear");
|
|
3496
|
-
const exit = this.getAppKeyDisplay("exit");
|
|
3497
|
-
const suspend = this.getAppKeyDisplay("suspend");
|
|
3498
|
-
const cycleThinkingLevel = this.getAppKeyDisplay("
|
|
3499
|
-
const cycleModelForward = this.getAppKeyDisplay("
|
|
3500
|
-
const selectModel = this.getAppKeyDisplay("
|
|
3501
|
-
const expandTools = this.getAppKeyDisplay("
|
|
3502
|
-
const toggleThinking = this.getAppKeyDisplay("
|
|
3503
|
-
const externalEditor = this.getAppKeyDisplay("
|
|
3504
|
-
const
|
|
3505
|
-
const
|
|
3633
|
+
const interrupt = this.getAppKeyDisplay("app.interrupt");
|
|
3634
|
+
const clear = this.getAppKeyDisplay("app.clear");
|
|
3635
|
+
const exit = this.getAppKeyDisplay("app.exit");
|
|
3636
|
+
const suspend = this.getAppKeyDisplay("app.suspend");
|
|
3637
|
+
const cycleThinkingLevel = this.getAppKeyDisplay("app.thinking.cycle");
|
|
3638
|
+
const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
|
|
3639
|
+
const selectModel = this.getAppKeyDisplay("app.model.select");
|
|
3640
|
+
const expandTools = this.getAppKeyDisplay("app.tools.expand");
|
|
3641
|
+
const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
|
|
3642
|
+
const externalEditor = this.getAppKeyDisplay("app.editor.external");
|
|
3643
|
+
const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
|
|
3644
|
+
const followUp = this.getAppKeyDisplay("app.message.followUp");
|
|
3645
|
+
const dequeue = this.getAppKeyDisplay("app.message.dequeue");
|
|
3646
|
+
const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
|
|
3506
3647
|
let hotkeys = `
|
|
3507
3648
|
**Navigation**
|
|
3508
3649
|
| Key | Action |
|
|
3509
3650
|
|-----|--------|
|
|
3510
|
-
| \`
|
|
3651
|
+
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history (Up when empty) |
|
|
3511
3652
|
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
|
3512
3653
|
| \`${cursorLineStart}\` | Start of line |
|
|
3513
3654
|
| \`${cursorLineEnd}\` | End of line |
|
|
@@ -3537,14 +3678,14 @@ export class InteractiveMode {
|
|
|
3537
3678
|
| \`${exit}\` | Exit (when editor is empty) |
|
|
3538
3679
|
| \`${suspend}\` | Suspend to background |
|
|
3539
3680
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
3540
|
-
| \`${cycleModelForward}\` | Cycle models |
|
|
3681
|
+
| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
|
|
3541
3682
|
| \`${selectModel}\` | Open model selector |
|
|
3542
3683
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
3543
3684
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
3544
3685
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
3545
3686
|
| \`${followUp}\` | Queue follow-up message |
|
|
3546
3687
|
| \`${dequeue}\` | Restore queued messages |
|
|
3547
|
-
| \`
|
|
3688
|
+
| \`${pasteImage}\` | Paste image from clipboard |
|
|
3548
3689
|
| \`/\` | Slash commands |
|
|
3549
3690
|
| \`!\` | Run bash command |
|
|
3550
3691
|
| \`!!\` | Run bash command (excluded from context) |
|
|
@@ -3727,7 +3868,7 @@ export class InteractiveMode {
|
|
|
3727
3868
|
};
|
|
3728
3869
|
// Show compacting status
|
|
3729
3870
|
this.chatContainer.addChild(new Spacer(1));
|
|
3730
|
-
const cancelHint = `(${
|
|
3871
|
+
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
3731
3872
|
const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
|
|
3732
3873
|
const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
3733
3874
|
this.statusContainer.addChild(compactingLoader);
|