@draht/coding-agent 2026.3.14 → 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/README.md +45 -30
- 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/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 +373 -53
- 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/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 -214
- 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/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/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 +5 -3
- package/examples/extensions/built-in-tool-renderer.ts +8 -8
- package/examples/extensions/commands.ts +3 -3
- package/examples/extensions/minimal-mode.ts +14 -14
- package/examples/extensions/question.ts +2 -2
- package/examples/extensions/questionnaire.ts +2 -2
- 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 +9 -7
- 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
|
@@ -6,20 +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";
|
|
22
|
-
import {
|
|
23
|
+
import { parseGitUrl } from "../../utils/git.js";
|
|
23
24
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
24
25
|
import { ArminComponent } from "./components/armin.js";
|
|
25
26
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
@@ -35,7 +36,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
|
35
36
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
36
37
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
37
38
|
import { FooterComponent } from "./components/footer.js";
|
|
38
|
-
import {
|
|
39
|
+
import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
39
40
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
40
41
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
41
42
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
@@ -65,6 +66,7 @@ export class InteractiveMode {
|
|
|
65
66
|
editorContainer;
|
|
66
67
|
footer;
|
|
67
68
|
footerDataProvider;
|
|
69
|
+
// Stored so the same manager can be injected into custom editors, selectors, and extension UI.
|
|
68
70
|
keybindings;
|
|
69
71
|
version;
|
|
70
72
|
isInitialized = false;
|
|
@@ -148,6 +150,7 @@ export class InteractiveMode {
|
|
|
148
150
|
this.widgetContainerAbove = new Container();
|
|
149
151
|
this.widgetContainerBelow = new Container();
|
|
150
152
|
this.keybindings = KeybindingsManager.create();
|
|
153
|
+
setKeybindings(this.keybindings);
|
|
151
154
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
152
155
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
153
156
|
this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
|
|
@@ -166,6 +169,48 @@ export class InteractiveMode {
|
|
|
166
169
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
167
170
|
initTheme(this.settingsManager.getTheme(), true);
|
|
168
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
|
+
}
|
|
169
214
|
setupAutocomplete(fdPath) {
|
|
170
215
|
// Define commands for autocomplete
|
|
171
216
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
@@ -201,13 +246,13 @@ export class InteractiveMode {
|
|
|
201
246
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
202
247
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
203
248
|
name: cmd.name,
|
|
204
|
-
description: cmd.description,
|
|
249
|
+
description: this.prefixAutocompleteDescription(cmd.description, cmd.sourceInfo),
|
|
205
250
|
}));
|
|
206
251
|
// Convert extension commands to SlashCommand format
|
|
207
252
|
const builtinCommandNames = new Set(slashCommands.map((c) => c.name));
|
|
208
|
-
const extensionCommands = (this.session.extensionRunner?.getRegisteredCommands(builtinCommandNames) ?? []).map((cmd) => ({
|
|
209
|
-
name: cmd.
|
|
210
|
-
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),
|
|
211
256
|
getArgumentCompletions: cmd.getArgumentCompletions,
|
|
212
257
|
}));
|
|
213
258
|
// Build skill commands from session.skills (if enabled)
|
|
@@ -217,7 +262,10 @@ export class InteractiveMode {
|
|
|
217
262
|
for (const skill of this.session.resourceLoader.getSkills().skills) {
|
|
218
263
|
const commandName = `skill:${skill.name}`;
|
|
219
264
|
this.skillCommands.set(commandName, skill.filePath);
|
|
220
|
-
skillCommandList.push({
|
|
265
|
+
skillCommandList.push({
|
|
266
|
+
name: commandName,
|
|
267
|
+
description: this.prefixAutocompleteDescription(skill.description, skill.sourceInfo),
|
|
268
|
+
});
|
|
221
269
|
}
|
|
222
270
|
}
|
|
223
271
|
// Setup autocomplete
|
|
@@ -242,27 +290,26 @@ export class InteractiveMode {
|
|
|
242
290
|
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
|
|
243
291
|
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
|
244
292
|
// Build startup instructions using keybinding hint helpers
|
|
245
|
-
const
|
|
246
|
-
const hint = (action, desc) => appKeyHint(kb, action, desc);
|
|
293
|
+
const hint = (keybinding, description) => keyHint(keybinding, description);
|
|
247
294
|
const instructions = [
|
|
248
|
-
hint("interrupt", "to interrupt"),
|
|
249
|
-
hint("clear", "to clear"),
|
|
250
|
-
rawKeyHint(`${
|
|
251
|
-
hint("exit", "to exit (empty)"),
|
|
252
|
-
hint("suspend", "to suspend"),
|
|
253
|
-
keyHint("deleteToLineEnd", "to delete to end"),
|
|
254
|
-
hint("
|
|
255
|
-
rawKeyHint(`${
|
|
256
|
-
hint("
|
|
257
|
-
hint("
|
|
258
|
-
hint("
|
|
259
|
-
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"),
|
|
260
307
|
rawKeyHint("/", "for commands"),
|
|
261
308
|
rawKeyHint("!", "to run bash"),
|
|
262
309
|
rawKeyHint("!!", "to run bash (no context)"),
|
|
263
|
-
hint("followUp", "to queue follow-up"),
|
|
264
|
-
hint("dequeue", "to edit all queued messages"),
|
|
265
|
-
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"),
|
|
266
313
|
rawKeyHint("drop files", "to attach"),
|
|
267
314
|
].join("\n");
|
|
268
315
|
this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
|
|
@@ -312,13 +359,13 @@ export class InteractiveMode {
|
|
|
312
359
|
this.ui.setFocus(this.editor);
|
|
313
360
|
this.setupKeyHandlers();
|
|
314
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;
|
|
315
365
|
// Initialize extensions first so resources are shown before messages
|
|
316
366
|
await this.initExtensions();
|
|
317
367
|
// Render initial messages AFTER showing loaded resources
|
|
318
368
|
this.renderInitialMessages();
|
|
319
|
-
// Start the UI
|
|
320
|
-
this.ui.start();
|
|
321
|
-
this.isInitialized = true;
|
|
322
369
|
// Set terminal title
|
|
323
370
|
this.updateTerminalTitle();
|
|
324
371
|
// Subscribe to agent events
|
|
@@ -343,10 +390,10 @@ export class InteractiveMode {
|
|
|
343
390
|
const cwdBasename = path.basename(process.cwd());
|
|
344
391
|
const sessionName = this.sessionManager.getSessionName();
|
|
345
392
|
if (sessionName) {
|
|
346
|
-
this.ui.terminal.setTitle(
|
|
393
|
+
this.ui.terminal.setTitle(`D - ${sessionName} - ${cwdBasename}`);
|
|
347
394
|
}
|
|
348
395
|
else {
|
|
349
|
-
this.ui.terminal.setTitle(
|
|
396
|
+
this.ui.terminal.setTitle(`D - ${cwdBasename}`);
|
|
350
397
|
}
|
|
351
398
|
}
|
|
352
399
|
/**
|
|
@@ -361,6 +408,12 @@ export class InteractiveMode {
|
|
|
361
408
|
this.showNewVersionNotification(newVersion);
|
|
362
409
|
}
|
|
363
410
|
});
|
|
411
|
+
// Start package update check asynchronously
|
|
412
|
+
this.checkForPackageUpdates().then((updates) => {
|
|
413
|
+
if (updates.length > 0) {
|
|
414
|
+
this.showPackageUpdateNotification(updates);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
364
417
|
// Check tmux keyboard setup asynchronously
|
|
365
418
|
this.checkTmuxKeyboardSetup().then((warning) => {
|
|
366
419
|
if (warning) {
|
|
@@ -416,7 +469,7 @@ export class InteractiveMode {
|
|
|
416
469
|
* Check npm registry for a newer version.
|
|
417
470
|
*/
|
|
418
471
|
async checkForNewVersion() {
|
|
419
|
-
if (process.env.
|
|
472
|
+
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE)
|
|
420
473
|
return undefined;
|
|
421
474
|
try {
|
|
422
475
|
const response = await fetch("https://registry.npmjs.org/@draht/coding-agent/latest", {
|
|
@@ -435,6 +488,23 @@ export class InteractiveMode {
|
|
|
435
488
|
return undefined;
|
|
436
489
|
}
|
|
437
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
|
+
}
|
|
438
508
|
async checkTmuxKeyboardSetup() {
|
|
439
509
|
if (!process.env.TMUX)
|
|
440
510
|
return undefined;
|
|
@@ -465,6 +535,9 @@ export class InteractiveMode {
|
|
|
465
535
|
runTmuxShow("extended-keys"),
|
|
466
536
|
runTmuxShow("extended-keys-format"),
|
|
467
537
|
]);
|
|
538
|
+
// If we couldn't query tmux (timeout, sandbox, etc.), don't warn
|
|
539
|
+
if (extendedKeys === undefined)
|
|
540
|
+
return undefined;
|
|
468
541
|
if (extendedKeys !== "on" && extendedKeys !== "always") {
|
|
469
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.";
|
|
470
543
|
}
|
|
@@ -520,21 +593,21 @@ export class InteractiveMode {
|
|
|
520
593
|
/**
|
|
521
594
|
* Get a short path relative to the package root for display.
|
|
522
595
|
*/
|
|
523
|
-
getShortPath(fullPath,
|
|
524
|
-
|
|
596
|
+
getShortPath(fullPath, sourceInfo) {
|
|
597
|
+
const source = sourceInfo?.source ?? "";
|
|
525
598
|
const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
|
|
526
599
|
if (npmMatch && source.startsWith("npm:")) {
|
|
527
600
|
return npmMatch[2];
|
|
528
601
|
}
|
|
529
|
-
// For git packages, show path relative to repo root
|
|
530
602
|
const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
|
|
531
603
|
if (gitMatch && source.startsWith("git:")) {
|
|
532
604
|
return gitMatch[1];
|
|
533
605
|
}
|
|
534
|
-
// For local/auto, just use formatDisplayPath
|
|
535
606
|
return this.formatDisplayPath(fullPath);
|
|
536
607
|
}
|
|
537
|
-
getDisplaySourceInfo(
|
|
608
|
+
getDisplaySourceInfo(sourceInfo) {
|
|
609
|
+
const source = sourceInfo?.source ?? "local";
|
|
610
|
+
const scope = sourceInfo?.scope ?? "project";
|
|
538
611
|
if (source === "local") {
|
|
539
612
|
if (scope === "user") {
|
|
540
613
|
return { label: "user", color: "muted" };
|
|
@@ -553,7 +626,9 @@ export class InteractiveMode {
|
|
|
553
626
|
const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
|
|
554
627
|
return { label: source, scopeLabel, color: "accent" };
|
|
555
628
|
}
|
|
556
|
-
getScopeGroup(
|
|
629
|
+
getScopeGroup(sourceInfo) {
|
|
630
|
+
const source = sourceInfo?.source ?? "local";
|
|
631
|
+
const scope = sourceInfo?.scope ?? "project";
|
|
557
632
|
if (source === "cli" || scope === "temporary")
|
|
558
633
|
return "path";
|
|
559
634
|
if (scope === "user")
|
|
@@ -562,28 +637,27 @@ export class InteractiveMode {
|
|
|
562
637
|
return "project";
|
|
563
638
|
return "path";
|
|
564
639
|
}
|
|
565
|
-
isPackageSource(
|
|
640
|
+
isPackageSource(sourceInfo) {
|
|
641
|
+
const source = sourceInfo?.source ?? "";
|
|
566
642
|
return source.startsWith("npm:") || source.startsWith("git:");
|
|
567
643
|
}
|
|
568
|
-
buildScopeGroups(
|
|
644
|
+
buildScopeGroups(items) {
|
|
569
645
|
const groups = {
|
|
570
646
|
user: { scope: "user", paths: [], packages: new Map() },
|
|
571
647
|
project: { scope: "project", paths: [], packages: new Map() },
|
|
572
648
|
path: { scope: "path", paths: [], packages: new Map() },
|
|
573
649
|
};
|
|
574
|
-
for (const
|
|
575
|
-
const
|
|
576
|
-
const source = meta?.source ?? "local";
|
|
577
|
-
const scope = meta?.scope ?? "project";
|
|
578
|
-
const groupKey = this.getScopeGroup(source, scope);
|
|
650
|
+
for (const item of items) {
|
|
651
|
+
const groupKey = this.getScopeGroup(item.sourceInfo);
|
|
579
652
|
const group = groups[groupKey];
|
|
580
|
-
|
|
653
|
+
const source = item.sourceInfo?.source ?? "local";
|
|
654
|
+
if (this.isPackageSource(item.sourceInfo)) {
|
|
581
655
|
const list = group.packages.get(source) ?? [];
|
|
582
|
-
list.push(
|
|
656
|
+
list.push(item);
|
|
583
657
|
group.packages.set(source, list);
|
|
584
658
|
}
|
|
585
659
|
else {
|
|
586
|
-
group.paths.push(
|
|
660
|
+
group.paths.push(item);
|
|
587
661
|
}
|
|
588
662
|
}
|
|
589
663
|
return [groups.project, groups.user, groups.path].filter((group) => group.paths.length > 0 || group.packages.size > 0);
|
|
@@ -592,57 +666,44 @@ export class InteractiveMode {
|
|
|
592
666
|
const lines = [];
|
|
593
667
|
for (const group of groups) {
|
|
594
668
|
lines.push(` ${theme.fg("accent", group.scope)}`);
|
|
595
|
-
const sortedPaths = [...group.paths].sort((a, b) => a.localeCompare(b));
|
|
596
|
-
for (const
|
|
597
|
-
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)}`));
|
|
598
672
|
}
|
|
599
673
|
const sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
600
|
-
for (const [source,
|
|
674
|
+
for (const [source, items] of sortedPackages) {
|
|
601
675
|
lines.push(` ${theme.fg("mdLink", source)}`);
|
|
602
|
-
const sortedPackagePaths = [...
|
|
603
|
-
for (const
|
|
604
|
-
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)}`));
|
|
605
679
|
}
|
|
606
680
|
}
|
|
607
681
|
}
|
|
608
682
|
return lines.join("\n");
|
|
609
683
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
* Package manager stores metadata for directories, but we display file paths.
|
|
613
|
-
*/
|
|
614
|
-
findMetadata(p, metadata) {
|
|
615
|
-
// Try exact match first
|
|
616
|
-
const exact = metadata.get(p);
|
|
684
|
+
findSourceInfoForPath(p, sourceInfos) {
|
|
685
|
+
const exact = sourceInfos.get(p);
|
|
617
686
|
if (exact)
|
|
618
687
|
return exact;
|
|
619
|
-
// Try parent directories (package manager stores directory paths)
|
|
620
688
|
let current = p;
|
|
621
689
|
while (current.includes("/")) {
|
|
622
690
|
current = current.substring(0, current.lastIndexOf("/"));
|
|
623
|
-
const parent =
|
|
691
|
+
const parent = sourceInfos.get(current);
|
|
624
692
|
if (parent)
|
|
625
693
|
return parent;
|
|
626
694
|
}
|
|
627
695
|
return undefined;
|
|
628
696
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const meta = this.findMetadata(p, metadata);
|
|
634
|
-
if (meta) {
|
|
635
|
-
const shortPath = this.getShortPath(p, meta.source);
|
|
636
|
-
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);
|
|
637
701
|
const labelText = scopeLabel ? `${label} (${scopeLabel})` : label;
|
|
638
702
|
return `${labelText} ${shortPath}`;
|
|
639
703
|
}
|
|
640
704
|
return this.formatDisplayPath(p);
|
|
641
705
|
}
|
|
642
|
-
|
|
643
|
-
* Format resource diagnostics with nice collision display using metadata.
|
|
644
|
-
*/
|
|
645
|
-
formatDiagnostics(diagnostics, metadata) {
|
|
706
|
+
formatDiagnostics(diagnostics, sourceInfos) {
|
|
646
707
|
const lines = [];
|
|
647
708
|
// Group collision diagnostics by name
|
|
648
709
|
const collisions = new Map();
|
|
@@ -663,21 +724,17 @@ export class InteractiveMode {
|
|
|
663
724
|
if (!first)
|
|
664
725
|
continue;
|
|
665
726
|
lines.push(theme.fg("warning", ` "${name}" collision:`));
|
|
666
|
-
|
|
667
|
-
lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, metadata)}`));
|
|
668
|
-
// Show all losers
|
|
727
|
+
lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, this.findSourceInfoForPath(first.winnerPath, sourceInfos))}`));
|
|
669
728
|
for (const d of collisionList) {
|
|
670
729
|
if (d.collision) {
|
|
671
|
-
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)`));
|
|
672
731
|
}
|
|
673
732
|
}
|
|
674
733
|
}
|
|
675
|
-
// Format other diagnostics (skill name collisions, parse errors, etc.)
|
|
676
734
|
for (const d of otherDiagnostics) {
|
|
677
735
|
if (d.path) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
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}`));
|
|
681
738
|
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
|
|
682
739
|
}
|
|
683
740
|
else {
|
|
@@ -692,11 +749,36 @@ export class InteractiveMode {
|
|
|
692
749
|
if (!showListing && !showDiagnostics) {
|
|
693
750
|
return;
|
|
694
751
|
}
|
|
695
|
-
const metadata = this.session.resourceLoader.getPathMetadata();
|
|
696
752
|
const sectionHeader = (name, color = "mdHeading") => theme.fg(color, `[${name}]`);
|
|
697
753
|
const skillsResult = this.session.resourceLoader.getSkills();
|
|
698
754
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
699
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
|
+
}
|
|
700
782
|
if (showListing) {
|
|
701
783
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
|
702
784
|
if (contextFiles.length > 0) {
|
|
@@ -709,39 +791,36 @@ export class InteractiveMode {
|
|
|
709
791
|
}
|
|
710
792
|
const skills = skillsResult.skills;
|
|
711
793
|
if (skills.length > 0) {
|
|
712
|
-
const
|
|
713
|
-
const groups = this.buildScopeGroups(skillPaths, metadata);
|
|
794
|
+
const groups = this.buildScopeGroups(skills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })));
|
|
714
795
|
const skillList = this.formatScopeGroups(groups, {
|
|
715
|
-
formatPath: (
|
|
716
|
-
formatPackagePath: (
|
|
796
|
+
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
797
|
+
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
717
798
|
});
|
|
718
799
|
this.chatContainer.addChild(new Text(`${sectionHeader("Skills")}\n${skillList}`, 0, 0));
|
|
719
800
|
this.chatContainer.addChild(new Spacer(1));
|
|
720
801
|
}
|
|
721
802
|
const templates = this.session.promptTemplates;
|
|
722
803
|
if (templates.length > 0) {
|
|
723
|
-
const
|
|
724
|
-
const groups = this.buildScopeGroups(templatePaths, metadata);
|
|
804
|
+
const groups = this.buildScopeGroups(templates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })));
|
|
725
805
|
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
|
726
806
|
const templateList = this.formatScopeGroups(groups, {
|
|
727
|
-
formatPath: (
|
|
728
|
-
const template = templateByPath.get(
|
|
729
|
-
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);
|
|
730
810
|
},
|
|
731
|
-
formatPackagePath: (
|
|
732
|
-
const template = templateByPath.get(
|
|
733
|
-
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);
|
|
734
814
|
},
|
|
735
815
|
});
|
|
736
816
|
this.chatContainer.addChild(new Text(`${sectionHeader("Prompts")}\n${templateList}`, 0, 0));
|
|
737
817
|
this.chatContainer.addChild(new Spacer(1));
|
|
738
818
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const groups = this.buildScopeGroups(extensionPaths, metadata);
|
|
819
|
+
if (extensions.length > 0) {
|
|
820
|
+
const groups = this.buildScopeGroups(extensions);
|
|
742
821
|
const extList = this.formatScopeGroups(groups, {
|
|
743
|
-
formatPath: (
|
|
744
|
-
formatPackagePath: (
|
|
822
|
+
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
823
|
+
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
745
824
|
});
|
|
746
825
|
this.chatContainer.addChild(new Text(`${sectionHeader("Extensions", "mdHeading")}\n${extList}`, 0, 0));
|
|
747
826
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -750,11 +829,13 @@ export class InteractiveMode {
|
|
|
750
829
|
const loadedThemes = themesResult.themes;
|
|
751
830
|
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
|
752
831
|
if (customThemes.length > 0) {
|
|
753
|
-
const
|
|
754
|
-
|
|
832
|
+
const groups = this.buildScopeGroups(customThemes.map((loadedTheme) => ({
|
|
833
|
+
path: loadedTheme.sourcePath,
|
|
834
|
+
sourceInfo: loadedTheme.sourceInfo,
|
|
835
|
+
})));
|
|
755
836
|
const themeList = this.formatScopeGroups(groups, {
|
|
756
|
-
formatPath: (
|
|
757
|
-
formatPackagePath: (
|
|
837
|
+
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
838
|
+
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
758
839
|
});
|
|
759
840
|
this.chatContainer.addChild(new Text(`${sectionHeader("Themes")}\n${themeList}`, 0, 0));
|
|
760
841
|
this.chatContainer.addChild(new Spacer(1));
|
|
@@ -763,13 +844,13 @@ export class InteractiveMode {
|
|
|
763
844
|
if (showDiagnostics) {
|
|
764
845
|
const skillDiagnostics = skillsResult.diagnostics;
|
|
765
846
|
if (skillDiagnostics.length > 0) {
|
|
766
|
-
const warningLines = this.formatDiagnostics(skillDiagnostics,
|
|
847
|
+
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
|
767
848
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
768
849
|
this.chatContainer.addChild(new Spacer(1));
|
|
769
850
|
}
|
|
770
851
|
const promptDiagnostics = promptsResult.diagnostics;
|
|
771
852
|
if (promptDiagnostics.length > 0) {
|
|
772
|
-
const warningLines = this.formatDiagnostics(promptDiagnostics,
|
|
853
|
+
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
|
773
854
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
774
855
|
this.chatContainer.addChild(new Spacer(1));
|
|
775
856
|
}
|
|
@@ -782,16 +863,17 @@ export class InteractiveMode {
|
|
|
782
863
|
}
|
|
783
864
|
const commandDiagnostics = this.session.extensionRunner?.getCommandDiagnostics() ?? [];
|
|
784
865
|
extensionDiagnostics.push(...commandDiagnostics);
|
|
866
|
+
extensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));
|
|
785
867
|
const shortcutDiagnostics = this.session.extensionRunner?.getShortcutDiagnostics() ?? [];
|
|
786
868
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
787
869
|
if (extensionDiagnostics.length > 0) {
|
|
788
|
-
const warningLines = this.formatDiagnostics(extensionDiagnostics,
|
|
870
|
+
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
789
871
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
790
872
|
this.chatContainer.addChild(new Spacer(1));
|
|
791
873
|
}
|
|
792
874
|
const themeDiagnostics = themesResult.diagnostics;
|
|
793
875
|
if (themeDiagnostics.length > 0) {
|
|
794
|
-
const warningLines = this.formatDiagnostics(themeDiagnostics,
|
|
876
|
+
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
795
877
|
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
796
878
|
this.chatContainer.addChild(new Spacer(1));
|
|
797
879
|
}
|
|
@@ -880,19 +962,17 @@ export class InteractiveMode {
|
|
|
880
962
|
this.setupAutocomplete(this.fdPath);
|
|
881
963
|
const extensionRunner = this.session.extensionRunner;
|
|
882
964
|
if (!extensionRunner) {
|
|
883
|
-
this.showLoadedResources({
|
|
965
|
+
this.showLoadedResources({ extensions: [], force: false });
|
|
884
966
|
return;
|
|
885
967
|
}
|
|
886
968
|
this.setupExtensionShortcuts(extensionRunner);
|
|
887
|
-
this.showLoadedResources({
|
|
969
|
+
this.showLoadedResources({ force: false });
|
|
888
970
|
}
|
|
889
971
|
/**
|
|
890
972
|
* Get a registered tool definition by name (for custom rendering).
|
|
891
973
|
*/
|
|
892
974
|
getRegisteredToolDefinition(toolName) {
|
|
893
|
-
|
|
894
|
-
const registeredTool = tools.find((t) => t.definition.name === toolName);
|
|
895
|
-
return registeredTool?.definition;
|
|
975
|
+
return this.session.getToolDefinition(toolName);
|
|
896
976
|
}
|
|
897
977
|
/**
|
|
898
978
|
* Set up keyboard shortcuts registered by extensions.
|
|
@@ -1023,7 +1103,7 @@ export class InteractiveMode {
|
|
|
1023
1103
|
this.defaultEditor.onExtensionShortcut = undefined;
|
|
1024
1104
|
this.updateTerminalTitle();
|
|
1025
1105
|
if (this.loadingAnimation) {
|
|
1026
|
-
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${
|
|
1106
|
+
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1027
1107
|
}
|
|
1028
1108
|
}
|
|
1029
1109
|
// Maximum total widget lines to prevent viewport overflow
|
|
@@ -1146,7 +1226,7 @@ export class InteractiveMode {
|
|
|
1146
1226
|
this.loadingAnimation.setMessage(message);
|
|
1147
1227
|
}
|
|
1148
1228
|
else {
|
|
1149
|
-
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${
|
|
1229
|
+
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1150
1230
|
}
|
|
1151
1231
|
}
|
|
1152
1232
|
else {
|
|
@@ -1161,7 +1241,7 @@ export class InteractiveMode {
|
|
|
1161
1241
|
custom: (factory, options) => this.showExtensionCustom(factory, options),
|
|
1162
1242
|
pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
|
|
1163
1243
|
setEditorText: (text) => this.editor.setText(text),
|
|
1164
|
-
getEditorText: () => this.editor.getText(),
|
|
1244
|
+
getEditorText: () => this.editor.getExpandedText?.() ?? this.editor.getText(),
|
|
1165
1245
|
editor: (title, prefill) => this.showExtensionEditor(title, prefill),
|
|
1166
1246
|
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
1167
1247
|
get theme() {
|
|
@@ -1504,24 +1584,24 @@ export class InteractiveMode {
|
|
|
1504
1584
|
}
|
|
1505
1585
|
};
|
|
1506
1586
|
// Register app action handlers
|
|
1507
|
-
this.defaultEditor.onAction("clear", () => this.handleCtrlC());
|
|
1587
|
+
this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
|
|
1508
1588
|
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
|
1509
|
-
this.defaultEditor.onAction("suspend", () => this.handleCtrlZ());
|
|
1510
|
-
this.defaultEditor.onAction("
|
|
1511
|
-
this.defaultEditor.onAction("
|
|
1512
|
-
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"));
|
|
1513
1593
|
// Global debug handler on TUI (works regardless of focus)
|
|
1514
1594
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
1515
|
-
this.defaultEditor.onAction("
|
|
1516
|
-
this.defaultEditor.onAction("
|
|
1517
|
-
this.defaultEditor.onAction("
|
|
1518
|
-
this.defaultEditor.onAction("
|
|
1519
|
-
this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
|
|
1520
|
-
this.defaultEditor.onAction("dequeue", () => this.handleDequeue());
|
|
1521
|
-
this.defaultEditor.onAction("
|
|
1522
|
-
this.defaultEditor.onAction("tree", () => this.showTreeSelector());
|
|
1523
|
-
this.defaultEditor.onAction("fork", () => this.showUserMessageSelector());
|
|
1524
|
-
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());
|
|
1525
1605
|
this.defaultEditor.onChange = (text) => {
|
|
1526
1606
|
const wasBashMode = this.isBashMode;
|
|
1527
1607
|
this.isBashMode = text.trimStart().startsWith("!");
|
|
@@ -1581,13 +1661,18 @@ export class InteractiveMode {
|
|
|
1581
1661
|
this.editor.setText("");
|
|
1582
1662
|
return;
|
|
1583
1663
|
}
|
|
1664
|
+
if (text.startsWith("/import")) {
|
|
1665
|
+
await this.handleImportCommand(text);
|
|
1666
|
+
this.editor.setText("");
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1584
1669
|
if (text === "/share") {
|
|
1585
1670
|
await this.handleShareCommand();
|
|
1586
1671
|
this.editor.setText("");
|
|
1587
1672
|
return;
|
|
1588
1673
|
}
|
|
1589
1674
|
if (text === "/copy") {
|
|
1590
|
-
this.handleCopyCommand();
|
|
1675
|
+
await this.handleCopyCommand();
|
|
1591
1676
|
this.editor.setText("");
|
|
1592
1677
|
return;
|
|
1593
1678
|
}
|
|
@@ -1777,7 +1862,7 @@ export class InteractiveMode {
|
|
|
1777
1862
|
for (const content of this.streamingMessage.content) {
|
|
1778
1863
|
if (content.type === "toolCall") {
|
|
1779
1864
|
if (!this.pendingTools.has(content.id)) {
|
|
1780
|
-
const component = new ToolExecutionComponent(content.name, content.arguments, {
|
|
1865
|
+
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
1781
1866
|
showImages: this.settingsManager.getShowImages(),
|
|
1782
1867
|
}, this.getRegisteredToolDefinition(content.name), this.ui);
|
|
1783
1868
|
component.setExpanded(this.toolOutputExpanded);
|
|
@@ -1835,15 +1920,17 @@ export class InteractiveMode {
|
|
|
1835
1920
|
this.ui.requestRender();
|
|
1836
1921
|
break;
|
|
1837
1922
|
case "tool_execution_start": {
|
|
1838
|
-
|
|
1839
|
-
|
|
1923
|
+
let component = this.pendingTools.get(event.toolCallId);
|
|
1924
|
+
if (!component) {
|
|
1925
|
+
component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
|
|
1840
1926
|
showImages: this.settingsManager.getShowImages(),
|
|
1841
1927
|
}, this.getRegisteredToolDefinition(event.toolName), this.ui);
|
|
1842
1928
|
component.setExpanded(this.toolOutputExpanded);
|
|
1843
1929
|
this.chatContainer.addChild(component);
|
|
1844
1930
|
this.pendingTools.set(event.toolCallId, component);
|
|
1845
|
-
this.ui.requestRender();
|
|
1846
1931
|
}
|
|
1932
|
+
component.markExecutionStarted();
|
|
1933
|
+
this.ui.requestRender();
|
|
1847
1934
|
break;
|
|
1848
1935
|
}
|
|
1849
1936
|
case "tool_execution_update": {
|
|
@@ -1876,7 +1963,6 @@ export class InteractiveMode {
|
|
|
1876
1963
|
}
|
|
1877
1964
|
this.pendingTools.clear();
|
|
1878
1965
|
await this.checkShutdownRequested();
|
|
1879
|
-
sendTerminalNotification(APP_NAME, "Ready for input");
|
|
1880
1966
|
this.ui.requestRender();
|
|
1881
1967
|
break;
|
|
1882
1968
|
case "auto_compaction_start": {
|
|
@@ -1889,7 +1975,7 @@ export class InteractiveMode {
|
|
|
1889
1975
|
// Show compacting indicator with reason
|
|
1890
1976
|
this.statusContainer.clear();
|
|
1891
1977
|
const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
|
|
1892
|
-
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)`);
|
|
1893
1979
|
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
1894
1980
|
this.ui.requestRender();
|
|
1895
1981
|
break;
|
|
@@ -1941,7 +2027,7 @@ export class InteractiveMode {
|
|
|
1941
2027
|
// Show retry indicator
|
|
1942
2028
|
this.statusContainer.clear();
|
|
1943
2029
|
const delaySeconds = Math.round(event.delayMs / 1000);
|
|
1944
|
-
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)`);
|
|
1945
2031
|
this.statusContainer.addChild(this.retryLoader);
|
|
1946
2032
|
this.ui.requestRender();
|
|
1947
2033
|
break;
|
|
@@ -2092,7 +2178,7 @@ export class InteractiveMode {
|
|
|
2092
2178
|
// Render tool call components
|
|
2093
2179
|
for (const content of message.content) {
|
|
2094
2180
|
if (content.type === "toolCall") {
|
|
2095
|
-
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);
|
|
2096
2182
|
component.setExpanded(this.toolOutputExpanded);
|
|
2097
2183
|
this.chatContainer.addChild(component);
|
|
2098
2184
|
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
@@ -2210,20 +2296,32 @@ export class InteractiveMode {
|
|
|
2210
2296
|
await this.shutdown();
|
|
2211
2297
|
}
|
|
2212
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);
|
|
2213
2303
|
// Ignore SIGINT while suspended so Ctrl+C in the terminal does not
|
|
2214
2304
|
// kill the backgrounded process. The handler is removed on resume.
|
|
2215
2305
|
const ignoreSigint = () => { };
|
|
2216
2306
|
process.on("SIGINT", ignoreSigint);
|
|
2217
2307
|
// Set up handler to restore TUI when resumed
|
|
2218
2308
|
process.once("SIGCONT", () => {
|
|
2309
|
+
clearInterval(suspendKeepAlive);
|
|
2219
2310
|
process.removeListener("SIGINT", ignoreSigint);
|
|
2220
2311
|
this.ui.start();
|
|
2221
2312
|
this.ui.requestRender(true);
|
|
2222
2313
|
});
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
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
|
+
}
|
|
2227
2325
|
}
|
|
2228
2326
|
async handleFollowUp() {
|
|
2229
2327
|
const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
|
|
@@ -2337,7 +2435,7 @@ export class InteractiveMode {
|
|
|
2337
2435
|
return;
|
|
2338
2436
|
}
|
|
2339
2437
|
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
2340
|
-
const tmpFile = path.join(os.tmpdir(), `
|
|
2438
|
+
const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
|
|
2341
2439
|
try {
|
|
2342
2440
|
// Write current content to temp file
|
|
2343
2441
|
fs.writeFileSync(tmpFile, currentText, "utf-8");
|
|
@@ -2399,6 +2497,16 @@ export class InteractiveMode {
|
|
|
2399
2497
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2400
2498
|
this.ui.requestRender();
|
|
2401
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
|
+
}
|
|
2402
2510
|
/**
|
|
2403
2511
|
* Get all queued messages (read-only).
|
|
2404
2512
|
* Combines session queue and compaction queue.
|
|
@@ -2446,7 +2554,7 @@ export class InteractiveMode {
|
|
|
2446
2554
|
const text = theme.fg("dim", `Follow-up: ${message}`);
|
|
2447
2555
|
this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
|
|
2448
2556
|
}
|
|
2449
|
-
const dequeueHint = this.getAppKeyDisplay("dequeue");
|
|
2557
|
+
const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
|
|
2450
2558
|
const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
|
|
2451
2559
|
this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
|
|
2452
2560
|
}
|
|
@@ -2736,28 +2844,8 @@ export class InteractiveMode {
|
|
|
2736
2844
|
this.showModelSelector(searchTerm);
|
|
2737
2845
|
}
|
|
2738
2846
|
async findExactModelMatch(searchTerm) {
|
|
2739
|
-
const term = searchTerm.trim();
|
|
2740
|
-
if (!term)
|
|
2741
|
-
return undefined;
|
|
2742
|
-
let targetProvider;
|
|
2743
|
-
let targetModelId = "";
|
|
2744
|
-
if (term.includes("/")) {
|
|
2745
|
-
const parts = term.split("/", 2);
|
|
2746
|
-
targetProvider = parts[0]?.trim().toLowerCase();
|
|
2747
|
-
targetModelId = parts[1]?.trim().toLowerCase() ?? "";
|
|
2748
|
-
}
|
|
2749
|
-
else {
|
|
2750
|
-
targetModelId = term.toLowerCase();
|
|
2751
|
-
}
|
|
2752
|
-
if (!targetModelId)
|
|
2753
|
-
return undefined;
|
|
2754
2847
|
const models = await this.getModelCandidates();
|
|
2755
|
-
|
|
2756
|
-
const idMatch = item.id.toLowerCase() === targetModelId;
|
|
2757
|
-
const providerMatch = !targetProvider || item.provider.toLowerCase() === targetProvider;
|
|
2758
|
-
return idMatch && providerMatch;
|
|
2759
|
-
});
|
|
2760
|
-
return exactMatches.length === 1 ? exactMatches[0] : undefined;
|
|
2848
|
+
return findExactModelReferenceMatch(searchTerm, models);
|
|
2761
2849
|
}
|
|
2762
2850
|
async getModelCandidates() {
|
|
2763
2851
|
if (this.session.scopedModels.length > 0) {
|
|
@@ -2988,7 +3076,7 @@ export class InteractiveMode {
|
|
|
2988
3076
|
this.session.abortBranchSummary();
|
|
2989
3077
|
};
|
|
2990
3078
|
this.chatContainer.addChild(new Spacer(1));
|
|
2991
|
-
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)`);
|
|
2992
3080
|
this.statusContainer.addChild(summaryLoader);
|
|
2993
3081
|
this.ui.requestRender();
|
|
2994
3082
|
}
|
|
@@ -3208,7 +3296,7 @@ export class InteractiveMode {
|
|
|
3208
3296
|
return;
|
|
3209
3297
|
}
|
|
3210
3298
|
this.resetExtensionUI();
|
|
3211
|
-
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...", {
|
|
3212
3300
|
cancellable: false,
|
|
3213
3301
|
});
|
|
3214
3302
|
const previousEditor = this.editor;
|
|
@@ -3225,6 +3313,7 @@ export class InteractiveMode {
|
|
|
3225
3313
|
};
|
|
3226
3314
|
try {
|
|
3227
3315
|
await this.session.reload();
|
|
3316
|
+
this.keybindings.reload();
|
|
3228
3317
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
3229
3318
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
3230
3319
|
const themeName = this.settingsManager.getTheme();
|
|
@@ -3250,7 +3339,6 @@ export class InteractiveMode {
|
|
|
3250
3339
|
this.rebuildChatFromMessages();
|
|
3251
3340
|
dismissLoader(this.editor);
|
|
3252
3341
|
this.showLoadedResources({
|
|
3253
|
-
extensionPaths: runner?.getExtensionPaths() ?? [],
|
|
3254
3342
|
force: false,
|
|
3255
3343
|
showDiagnosticsWhenQuiet: true,
|
|
3256
3344
|
});
|
|
@@ -3258,7 +3346,7 @@ export class InteractiveMode {
|
|
|
3258
3346
|
if (modelsJsonError) {
|
|
3259
3347
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
3260
3348
|
}
|
|
3261
|
-
this.showStatus("Reloaded extensions, skills, prompts, themes");
|
|
3349
|
+
this.showStatus("Reloaded keybindings, extensions, skills, prompts, themes");
|
|
3262
3350
|
}
|
|
3263
3351
|
catch (error) {
|
|
3264
3352
|
dismissLoader(previousEditor);
|
|
@@ -3269,13 +3357,58 @@ export class InteractiveMode {
|
|
|
3269
3357
|
const parts = text.split(/\s+/);
|
|
3270
3358
|
const outputPath = parts.length > 1 ? parts[1] : undefined;
|
|
3271
3359
|
try {
|
|
3272
|
-
|
|
3273
|
-
|
|
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
|
+
}
|
|
3274
3368
|
}
|
|
3275
3369
|
catch (error) {
|
|
3276
3370
|
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3277
3371
|
}
|
|
3278
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
|
+
}
|
|
3279
3412
|
async handleShareCommand() {
|
|
3280
3413
|
// Check if gh is available and logged in
|
|
3281
3414
|
try {
|
|
@@ -3363,14 +3496,14 @@ export class InteractiveMode {
|
|
|
3363
3496
|
}
|
|
3364
3497
|
}
|
|
3365
3498
|
}
|
|
3366
|
-
handleCopyCommand() {
|
|
3499
|
+
async handleCopyCommand() {
|
|
3367
3500
|
const text = this.session.getLastAssistantText();
|
|
3368
3501
|
if (!text) {
|
|
3369
3502
|
this.showError("No agent messages to copy yet.");
|
|
3370
3503
|
return;
|
|
3371
3504
|
}
|
|
3372
3505
|
try {
|
|
3373
|
-
copyToClipboard(text);
|
|
3506
|
+
await copyToClipboard(text);
|
|
3374
3507
|
this.showStatus("Copied last agent message to clipboard");
|
|
3375
3508
|
}
|
|
3376
3509
|
catch (error) {
|
|
@@ -3463,53 +3596,59 @@ export class InteractiveMode {
|
|
|
3463
3596
|
* Get capitalized display string for an app keybinding action.
|
|
3464
3597
|
*/
|
|
3465
3598
|
getAppKeyDisplay(action) {
|
|
3466
|
-
return this.capitalizeKey(
|
|
3599
|
+
return this.capitalizeKey(keyText(action));
|
|
3467
3600
|
}
|
|
3468
3601
|
/**
|
|
3469
3602
|
* Get capitalized display string for an editor keybinding action.
|
|
3470
3603
|
*/
|
|
3471
3604
|
getEditorKeyDisplay(action) {
|
|
3472
|
-
return this.capitalizeKey(
|
|
3605
|
+
return this.capitalizeKey(keyText(action));
|
|
3473
3606
|
}
|
|
3474
3607
|
handleHotkeysCommand() {
|
|
3475
3608
|
// Navigation keybindings
|
|
3476
|
-
const
|
|
3477
|
-
const
|
|
3478
|
-
const
|
|
3479
|
-
const
|
|
3480
|
-
const
|
|
3481
|
-
const
|
|
3482
|
-
const
|
|
3483
|
-
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");
|
|
3484
3621
|
// Editing keybindings
|
|
3485
|
-
const submit = this.getEditorKeyDisplay("submit");
|
|
3486
|
-
const newLine = this.getEditorKeyDisplay("newLine");
|
|
3487
|
-
const deleteWordBackward = this.getEditorKeyDisplay("deleteWordBackward");
|
|
3488
|
-
const deleteWordForward = this.getEditorKeyDisplay("deleteWordForward");
|
|
3489
|
-
const deleteToLineStart = this.getEditorKeyDisplay("deleteToLineStart");
|
|
3490
|
-
const deleteToLineEnd = this.getEditorKeyDisplay("deleteToLineEnd");
|
|
3491
|
-
const yank = this.getEditorKeyDisplay("yank");
|
|
3492
|
-
const yankPop = this.getEditorKeyDisplay("yankPop");
|
|
3493
|
-
const undo = this.getEditorKeyDisplay("undo");
|
|
3494
|
-
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");
|
|
3495
3632
|
// App keybindings
|
|
3496
|
-
const interrupt = this.getAppKeyDisplay("interrupt");
|
|
3497
|
-
const clear = this.getAppKeyDisplay("clear");
|
|
3498
|
-
const exit = this.getAppKeyDisplay("exit");
|
|
3499
|
-
const suspend = this.getAppKeyDisplay("suspend");
|
|
3500
|
-
const cycleThinkingLevel = this.getAppKeyDisplay("
|
|
3501
|
-
const cycleModelForward = this.getAppKeyDisplay("
|
|
3502
|
-
const selectModel = this.getAppKeyDisplay("
|
|
3503
|
-
const expandTools = this.getAppKeyDisplay("
|
|
3504
|
-
const toggleThinking = this.getAppKeyDisplay("
|
|
3505
|
-
const externalEditor = this.getAppKeyDisplay("
|
|
3506
|
-
const
|
|
3507
|
-
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");
|
|
3508
3647
|
let hotkeys = `
|
|
3509
3648
|
**Navigation**
|
|
3510
3649
|
| Key | Action |
|
|
3511
3650
|
|-----|--------|
|
|
3512
|
-
| \`
|
|
3651
|
+
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history (Up when empty) |
|
|
3513
3652
|
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
|
3514
3653
|
| \`${cursorLineStart}\` | Start of line |
|
|
3515
3654
|
| \`${cursorLineEnd}\` | End of line |
|
|
@@ -3539,14 +3678,14 @@ export class InteractiveMode {
|
|
|
3539
3678
|
| \`${exit}\` | Exit (when editor is empty) |
|
|
3540
3679
|
| \`${suspend}\` | Suspend to background |
|
|
3541
3680
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
3542
|
-
| \`${cycleModelForward}\` | Cycle models |
|
|
3681
|
+
| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
|
|
3543
3682
|
| \`${selectModel}\` | Open model selector |
|
|
3544
3683
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
3545
3684
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
3546
3685
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
3547
3686
|
| \`${followUp}\` | Queue follow-up message |
|
|
3548
3687
|
| \`${dequeue}\` | Restore queued messages |
|
|
3549
|
-
| \`
|
|
3688
|
+
| \`${pasteImage}\` | Paste image from clipboard |
|
|
3550
3689
|
| \`/\` | Slash commands |
|
|
3551
3690
|
| \`!\` | Run bash command |
|
|
3552
3691
|
| \`!!\` | Run bash command (excluded from context) |
|
|
@@ -3729,7 +3868,7 @@ export class InteractiveMode {
|
|
|
3729
3868
|
};
|
|
3730
3869
|
// Show compacting status
|
|
3731
3870
|
this.chatContainer.addChild(new Spacer(1));
|
|
3732
|
-
const cancelHint = `(${
|
|
3871
|
+
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
3733
3872
|
const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
|
|
3734
3873
|
const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
3735
3874
|
this.statusContainer.addChild(compactingLoader);
|