@kolisachint/hoocode-agent 0.4.106 → 0.4.107
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 +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -4
- package/dist/cli/args.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +0 -1
- 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 +1 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +2 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +1 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/extension-mcp-servers.d.ts +1 -1
- package/dist/core/extension-mcp-servers.d.ts.map +1 -1
- package/dist/core/extension-mcp-servers.js +1 -1
- package/dist/core/extension-mcp-servers.js.map +1 -1
- package/dist/core/extensions/plugins/index.d.ts.map +1 -1
- package/dist/core/extensions/plugins/index.js +1 -1
- package/dist/core/extensions/plugins/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +1 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mode-prompts.d.ts +1 -1
- package/dist/core/mode-prompts.d.ts.map +1 -1
- package/dist/core/mode-prompts.js +1 -1
- package/dist/core/mode-prompts.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +1 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -97
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +4 -65
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +2 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/telemetry.d.ts.map +1 -1
- package/dist/core/telemetry.js +1 -1
- package/dist/core/telemetry.js.map +1 -1
- package/dist/core/timings.d.ts +1 -1
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +2 -2
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/{browser-continue.d.ts → browser/browser-continue.d.ts} +1 -1
- package/dist/core/tools/browser/browser-continue.d.ts.map +1 -0
- package/dist/core/tools/{browser-continue.js → browser/browser-continue.js} +2 -2
- package/dist/core/tools/browser/browser-continue.js.map +1 -0
- package/dist/core/tools/{browser-run.d.ts → browser/browser-run.d.ts} +2 -2
- package/dist/core/tools/browser/browser-run.d.ts.map +1 -0
- package/dist/core/tools/{browser-run.js → browser/browser-run.js} +2 -2
- package/dist/core/tools/browser/browser-run.js.map +1 -0
- package/dist/core/tools/browser/browsertools-shared.d.ts.map +1 -0
- package/dist/core/tools/{browsertools-shared.js → browser/browsertools-shared.js} +1 -1
- package/dist/core/tools/browser/browsertools-shared.js.map +1 -0
- package/dist/core/tools/browser/index.d.ts +8 -0
- package/dist/core/tools/browser/index.d.ts.map +1 -0
- package/dist/core/tools/browser/index.js +8 -0
- package/dist/core/tools/browser/index.js.map +1 -0
- package/dist/core/tools/{docedit.d.ts → doc/docedit.d.ts} +2 -2
- package/dist/core/tools/doc/docedit.d.ts.map +1 -0
- package/dist/core/tools/{docedit.js → doc/docedit.js} +7 -7
- package/dist/core/tools/doc/docedit.js.map +1 -0
- package/dist/core/tools/{docgrep.d.ts → doc/docgrep.d.ts} +2 -2
- package/dist/core/tools/doc/docgrep.d.ts.map +1 -0
- package/dist/core/tools/{docgrep.js → doc/docgrep.js} +6 -6
- package/dist/core/tools/doc/docgrep.js.map +1 -0
- package/dist/core/tools/{docpeek.d.ts → doc/docpeek.d.ts} +2 -2
- package/dist/core/tools/{docpeek.js.map → doc/docpeek.d.ts.map} +1 -1
- package/dist/core/tools/{docpeek.js → doc/docpeek.js} +6 -6
- package/dist/core/tools/doc/docpeek.js.map +1 -0
- package/dist/core/tools/{docread.d.ts → doc/docread.d.ts} +2 -2
- package/dist/core/tools/doc/docread.d.ts.map +1 -0
- package/dist/core/tools/{docread.js → doc/docread.js} +6 -6
- package/dist/core/tools/doc/docread.js.map +1 -0
- package/dist/core/tools/{docscan.d.ts → doc/docscan.d.ts} +2 -2
- package/dist/core/tools/doc/docscan.d.ts.map +1 -0
- package/dist/core/tools/{docscan.js → doc/docscan.js} +6 -6
- package/dist/core/tools/doc/docscan.js.map +1 -0
- package/dist/core/tools/{docwrite.d.ts → doc/docwrite.d.ts} +1 -1
- package/dist/core/tools/doc/docwrite.d.ts.map +1 -0
- package/dist/core/tools/{docwrite.js → doc/docwrite.js} +6 -6
- package/dist/core/tools/doc/docwrite.js.map +1 -0
- package/dist/core/tools/doc/index.d.ts +11 -0
- package/dist/core/tools/doc/index.d.ts.map +1 -0
- package/dist/core/tools/doc/index.js +11 -0
- package/dist/core/tools/doc/index.js.map +1 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +42 -28
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +61 -199
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +1 -1
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/extensions/core/ask-options.d.ts +10 -0
- package/dist/extensions/core/ask-options.d.ts.map +1 -0
- package/dist/extensions/core/ask-options.js +83 -0
- package/dist/extensions/core/ask-options.js.map +1 -0
- package/dist/extensions/core/config.d.ts +80 -0
- package/dist/extensions/core/config.d.ts.map +1 -0
- package/dist/extensions/core/config.js +110 -0
- package/dist/extensions/core/config.js.map +1 -0
- package/dist/extensions/core/cost.d.ts +11 -0
- package/dist/extensions/core/cost.d.ts.map +1 -0
- package/dist/extensions/core/cost.js +64 -0
- package/dist/extensions/core/cost.js.map +1 -0
- package/dist/extensions/core/hoo-core.d.ts +16 -179
- package/dist/extensions/core/hoo-core.d.ts.map +1 -1
- package/dist/extensions/core/hoo-core.js +27 -1663
- package/dist/extensions/core/hoo-core.js.map +1 -1
- package/dist/extensions/core/loop.d.ts +15 -0
- package/dist/extensions/core/loop.d.ts.map +1 -0
- package/dist/extensions/core/loop.js +235 -0
- package/dist/extensions/core/loop.js.map +1 -0
- package/dist/extensions/core/marketplace.d.ts +17 -0
- package/dist/extensions/core/marketplace.d.ts.map +1 -0
- package/dist/extensions/core/marketplace.js +167 -0
- package/dist/extensions/core/marketplace.js.map +1 -0
- package/dist/extensions/core/mcp-loader.d.ts +26 -0
- package/dist/extensions/core/mcp-loader.d.ts.map +1 -0
- package/dist/extensions/core/mcp-loader.js +391 -0
- package/dist/extensions/core/mcp-loader.js.map +1 -0
- package/dist/extensions/core/modes.d.ts +56 -0
- package/dist/extensions/core/modes.d.ts.map +1 -0
- package/dist/extensions/core/modes.js +268 -0
- package/dist/extensions/core/modes.js.map +1 -0
- package/dist/extensions/core/permission-gate.d.ts +10 -0
- package/dist/extensions/core/permission-gate.d.ts.map +1 -0
- package/dist/extensions/core/permission-gate.js +167 -0
- package/dist/extensions/core/permission-gate.js.map +1 -0
- package/dist/extensions/core/scaffold.d.ts +9 -0
- package/dist/extensions/core/scaffold.d.ts.map +1 -0
- package/dist/extensions/core/scaffold.js +146 -0
- package/dist/extensions/core/scaffold.js.map +1 -0
- package/dist/extensions/core/thinking-escalation.d.ts +18 -0
- package/dist/extensions/core/thinking-escalation.d.ts.map +1 -0
- package/dist/extensions/core/thinking-escalation.js +70 -0
- package/dist/extensions/core/thinking-escalation.js.map +1 -0
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/command-executor.d.ts +0 -5
- package/dist/modes/interactive/command-executor.d.ts.map +1 -1
- package/dist/modes/interactive/command-executor.js +0 -7
- package/dist/modes/interactive/command-executor.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +0 -2
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +0 -2
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/extension-chrome.d.ts +56 -0
- package/dist/modes/interactive/extension-chrome.d.ts.map +1 -0
- package/dist/modes/interactive/extension-chrome.js +172 -0
- package/dist/modes/interactive/extension-chrome.js.map +1 -0
- package/dist/modes/interactive/extension-dialogs.d.ts +82 -0
- package/dist/modes/interactive/extension-dialogs.d.ts.map +1 -0
- package/dist/modes/interactive/extension-dialogs.js +259 -0
- package/dist/modes/interactive/extension-dialogs.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +11 -161
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +261 -1584
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts +59 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -0
- package/dist/modes/interactive/resource-display.js +499 -0
- package/dist/modes/interactive/resource-display.js.map +1 -0
- package/dist/modes/interactive/startup-checks.d.ts +17 -0
- package/dist/modes/interactive/startup-checks.d.ts.map +1 -0
- package/dist/modes/interactive/startup-checks.js +102 -0
- package/dist/modes/interactive/startup-checks.js.map +1 -0
- package/dist/modes/interactive/team-focus.d.ts +56 -0
- package/dist/modes/interactive/team-focus.d.ts.map +1 -0
- package/dist/modes/interactive/team-focus.js +167 -0
- package/dist/modes/interactive/team-focus.js.map +1 -0
- package/dist/modes/interactive/voice/voice-controller.d.ts +66 -0
- package/dist/modes/interactive/voice/voice-controller.d.ts.map +1 -0
- package/dist/modes/interactive/voice/voice-controller.js +312 -0
- package/dist/modes/interactive/voice/voice-controller.js.map +1 -0
- package/dist/modes/interactive/voice/voice-panel.d.ts.map +1 -0
- package/dist/modes/interactive/voice/voice-panel.js.map +1 -0
- package/dist/modes/interactive/voice/voice-transcribe.d.ts.map +1 -0
- package/dist/modes/interactive/voice/voice-transcribe.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +1 -2
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/resources-cli.d.ts.map +1 -1
- package/dist/resources-cli.js +1 -1
- package/dist/resources-cli.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +1 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +2 -2
- package/dist/utils/version-check.js.map +1 -1
- package/docs/project-local-resources.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/dist/core/compaction/branch-summarization.d.ts +0 -88
- package/dist/core/compaction/branch-summarization.d.ts.map +0 -1
- package/dist/core/compaction/branch-summarization.js +0 -243
- package/dist/core/compaction/branch-summarization.js.map +0 -1
- package/dist/core/compaction/compaction.d.ts +0 -123
- package/dist/core/compaction/compaction.d.ts.map +0 -1
- package/dist/core/compaction/compaction.js +0 -635
- package/dist/core/compaction/compaction.js.map +0 -1
- package/dist/core/compaction/index.d.ts +0 -7
- package/dist/core/compaction/index.d.ts.map +0 -1
- package/dist/core/compaction/index.js +0 -7
- package/dist/core/compaction/index.js.map +0 -1
- package/dist/core/compaction/utils.d.ts +0 -38
- package/dist/core/compaction/utils.d.ts.map +0 -1
- package/dist/core/compaction/utils.js +0 -156
- package/dist/core/compaction/utils.js.map +0 -1
- package/dist/core/messages.d.ts +0 -124
- package/dist/core/messages.d.ts.map +0 -1
- package/dist/core/messages.js +0 -221
- package/dist/core/messages.js.map +0 -1
- package/dist/core/tools/browser-continue.d.ts.map +0 -1
- package/dist/core/tools/browser-continue.js.map +0 -1
- package/dist/core/tools/browser-run.d.ts.map +0 -1
- package/dist/core/tools/browser-run.js.map +0 -1
- package/dist/core/tools/browsertools-shared.d.ts.map +0 -1
- package/dist/core/tools/browsertools-shared.js.map +0 -1
- package/dist/core/tools/docedit.d.ts.map +0 -1
- package/dist/core/tools/docedit.js.map +0 -1
- package/dist/core/tools/docgrep.d.ts.map +0 -1
- package/dist/core/tools/docgrep.js.map +0 -1
- package/dist/core/tools/docpeek.d.ts.map +0 -1
- package/dist/core/tools/docread.d.ts.map +0 -1
- package/dist/core/tools/docread.js.map +0 -1
- package/dist/core/tools/docscan.d.ts.map +0 -1
- package/dist/core/tools/docscan.js.map +0 -1
- package/dist/core/tools/docwrite.d.ts.map +0 -1
- package/dist/core/tools/docwrite.js.map +0 -1
- package/dist/core/tools/output-compression.d.ts +0 -53
- package/dist/core/tools/output-compression.d.ts.map +0 -1
- package/dist/core/tools/output-compression.js +0 -392
- package/dist/core/tools/output-compression.js.map +0 -1
- package/dist/modes/interactive/components/armin.d.ts +0 -34
- package/dist/modes/interactive/components/armin.d.ts.map +0 -1
- package/dist/modes/interactive/components/armin.js +0 -333
- package/dist/modes/interactive/components/armin.js.map +0 -1
- package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
- package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
- package/dist/modes/interactive/components/daxnuts.js +0 -140
- package/dist/modes/interactive/components/daxnuts.js.map +0 -1
- package/dist/modes/interactive/components/voice-panel.d.ts.map +0 -1
- package/dist/modes/interactive/components/voice-panel.js.map +0 -1
- package/dist/modes/interactive/voice-transcribe.d.ts.map +0 -1
- package/dist/modes/interactive/voice-transcribe.js.map +0 -1
- /package/dist/core/tools/{browsertools-shared.d.ts → browser/browsertools-shared.d.ts} +0 -0
- /package/dist/modes/interactive/{components → voice}/voice-panel.d.ts +0 -0
- /package/dist/modes/interactive/{components → voice}/voice-panel.js +0 -0
- /package/dist/modes/interactive/{voice-transcribe.d.ts → voice/voice-transcribe.d.ts} +0 -0
- /package/dist/modes/interactive/{voice-transcribe.js → voice/voice-transcribe.js} +0 -0
|
@@ -6,32 +6,27 @@ 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 { createCompactionSummaryMessage } from "@kolisachint/hoocode-agent-core";
|
|
9
10
|
import { getProviders, } from "@kolisachint/hoocode-ai";
|
|
10
11
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, } from "@kolisachint/hoocode-tui";
|
|
11
|
-
import {
|
|
12
|
-
import { APP_NAME, APP_TITLE,
|
|
12
|
+
import { spawnSync } from "child_process";
|
|
13
|
+
import { APP_NAME, APP_TITLE, getAuthPath, getDocsPath, VERSION } from "../../config.js";
|
|
13
14
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
14
15
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
15
16
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
16
|
-
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
17
17
|
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
18
|
-
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
19
18
|
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
20
19
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
21
20
|
import { SessionManager } from "../../core/session-manager.js";
|
|
22
21
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
23
22
|
import { taskStore } from "../../core/task-store.js";
|
|
24
|
-
import { TeamApprovalCoordinator } from "../../core/team-approvals.js";
|
|
25
23
|
import { buildCompactWordmark } from "../../core/wordmark.js";
|
|
26
|
-
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
27
24
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
28
25
|
import { parseGitUrl } from "../../utils/git.js";
|
|
29
|
-
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
30
26
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
31
27
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
32
28
|
import { checkForNewHooCodeVersion } from "../../utils/version-check.js";
|
|
33
29
|
import { CommandExecutor } from "./command-executor.js";
|
|
34
|
-
import { AskOptionsComponent } from "./components/ask-options.js";
|
|
35
30
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
36
31
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
37
32
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
@@ -39,10 +34,7 @@ import { CompactionSummaryMessageComponent } from "./components/compaction-summa
|
|
|
39
34
|
import { CountdownTimer } from "./components/countdown-timer.js";
|
|
40
35
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
41
36
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
42
|
-
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
43
37
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
44
|
-
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
45
|
-
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
46
38
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
47
39
|
import { FooterComponent } from "./components/footer.js";
|
|
48
40
|
import { keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
@@ -54,29 +46,17 @@ import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
|
54
46
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
55
47
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
56
48
|
import { TaskPanelComponent } from "./components/task-panel.js";
|
|
57
|
-
import { TeamAttachPanelComponent } from "./components/team-attach-panel.js";
|
|
58
49
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
59
50
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
60
51
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
61
52
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
62
|
-
import {
|
|
53
|
+
import { ExtensionChrome } from "./extension-chrome.js";
|
|
54
|
+
import { ExtensionDialogs } from "./extension-dialogs.js";
|
|
55
|
+
import { ExpandableText, formatDisplayPath, isExpandable, showLoadedResources as renderLoadedResources, } from "./resource-display.js";
|
|
56
|
+
import { checkForPackageUpdates, checkTmuxKeyboardSetup, getChangelogForDisplay } from "./startup-checks.js";
|
|
57
|
+
import { TeamFocusController } from "./team-focus.js";
|
|
63
58
|
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
64
|
-
import {
|
|
65
|
-
function isExpandable(obj) {
|
|
66
|
-
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
67
|
-
}
|
|
68
|
-
class ExpandableText extends Text {
|
|
69
|
-
getCollapsedText;
|
|
70
|
-
getExpandedText;
|
|
71
|
-
constructor(getCollapsedText, getExpandedText, expanded = false, paddingX = 0, paddingY = 0) {
|
|
72
|
-
super(expanded ? getExpandedText() : getCollapsedText(), paddingX, paddingY);
|
|
73
|
-
this.getCollapsedText = getCollapsedText;
|
|
74
|
-
this.getExpandedText = getExpandedText;
|
|
75
|
-
}
|
|
76
|
-
setExpanded(expanded) {
|
|
77
|
-
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
78
|
-
}
|
|
79
|
-
}
|
|
59
|
+
import { VoiceController } from "./voice/voice-controller.js";
|
|
80
60
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
81
61
|
function isDeadTerminalError(error) {
|
|
82
62
|
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
@@ -85,18 +65,6 @@ function isDeadTerminalError(error) {
|
|
|
85
65
|
const code = error.code;
|
|
86
66
|
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
87
67
|
}
|
|
88
|
-
// Trailing-silence window: how long a pause while speaking lasts before the
|
|
89
|
-
// capture auto-stops. Passed to `voicetools serve` via `--silence-ms` (see
|
|
90
|
-
// VoiceDaemon.spawn) so the binary's real cutoff matches the on-screen
|
|
91
|
-
// countdown this same value drives. Kept generous so a thinking pause mid-
|
|
92
|
-
// sentence doesn't cut the user off (the binary's own default is 600ms).
|
|
93
|
-
const VOICE_SILENCE_MS = 3000;
|
|
94
|
-
/** How long to keep the warm voice model in memory after the last capture
|
|
95
|
-
* completes. The daemon auto-shuts down after this window, releasing the
|
|
96
|
-
* ~900 MB resident model; the next ctrl+r pays a cold-start respawn cost. */
|
|
97
|
-
const VOICE_IDLE_TIMEOUT_MS = 60_000;
|
|
98
|
-
const VOICE_UNAVAILABLE_MESSAGE = "Voice input failed: voicetools binary unavailable and could not be downloaded. " +
|
|
99
|
-
"Install it, set VOICETOOLS_BIN, or ensure a published release exists for this platform.";
|
|
100
68
|
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth: billed per token as extra usage, not plan limits.";
|
|
101
69
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
102
70
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
@@ -128,19 +96,7 @@ export class InteractiveMode {
|
|
|
128
96
|
defaultEditor;
|
|
129
97
|
editor;
|
|
130
98
|
editorComponentFactory;
|
|
131
|
-
|
|
132
|
-
// Persistent `voicetools serve` process, once probed successfully. Stays alive
|
|
133
|
-
// (and warm) across captures for the life of the interactive session.
|
|
134
|
-
voiceDaemon;
|
|
135
|
-
// Sticky for the session: set once `serve` is confirmed unsupported (old binary)
|
|
136
|
-
// so later presses skip straight to the per-press `transcribe` fallback.
|
|
137
|
-
voiceDaemonUnsupported = false;
|
|
138
|
-
/** True while the voicetools binary is being resolved/downloaded before a session starts. */
|
|
139
|
-
voiceStarting = false;
|
|
140
|
-
/** True while a capture (daemon or legacy) is in flight. */
|
|
141
|
-
voiceActive = false;
|
|
142
|
-
/** The live multi-line status panel for the current capture (undefined when idle). */
|
|
143
|
-
voicePanel;
|
|
99
|
+
voice;
|
|
144
100
|
autocompleteProvider;
|
|
145
101
|
autocompleteProviderWrappers = [];
|
|
146
102
|
fdPath;
|
|
@@ -201,30 +157,20 @@ export class InteractiveMode {
|
|
|
201
157
|
// Shutdown state
|
|
202
158
|
shutdownRequested = false;
|
|
203
159
|
// Extension UI state
|
|
204
|
-
|
|
205
|
-
extensionInput = undefined;
|
|
206
|
-
askOptions = undefined;
|
|
207
|
-
extensionEditor = undefined;
|
|
160
|
+
dialogs;
|
|
208
161
|
extensionTerminalInputUnsubscribers = new Set();
|
|
209
162
|
// Extension widgets (components rendered above/below the editor)
|
|
210
|
-
|
|
211
|
-
extensionWidgetsBelow = new Map();
|
|
163
|
+
chrome;
|
|
212
164
|
widgetContainerAbove;
|
|
213
165
|
widgetContainerBelow;
|
|
214
166
|
// Task panel shown just above the editor (active subagent tasks)
|
|
215
167
|
taskPanel;
|
|
216
168
|
// hooteams team client (--team): steering + attach share its single SSE stream
|
|
217
|
-
|
|
218
|
-
teamAttachPanel = undefined;
|
|
219
|
-
teamAttachHandle = undefined;
|
|
220
|
-
// Custom footer from extension (undefined = use built-in footer)
|
|
221
|
-
customFooter = undefined;
|
|
169
|
+
teamFocus;
|
|
222
170
|
// Header container that holds the built-in or custom header
|
|
223
171
|
headerContainer;
|
|
224
172
|
// Built-in header (logo + keybinding hints + changelog)
|
|
225
173
|
builtInHeader = undefined;
|
|
226
|
-
// Custom header from extension (undefined = use built-in header)
|
|
227
|
-
customHeader = undefined;
|
|
228
174
|
// Convenience accessors
|
|
229
175
|
get session() {
|
|
230
176
|
return this.runtimeHost.session;
|
|
@@ -279,9 +225,8 @@ export class InteractiveMode {
|
|
|
279
225
|
stopLoadingAnimation: () => self.stopLoadingAnimation(),
|
|
280
226
|
findExactModelMatch: (searchTerm) => self.findExactModelMatch(searchTerm),
|
|
281
227
|
maybeWarnAboutAnthropicSubscriptionAuth: (model) => self.maybeWarnAboutAnthropicSubscriptionAuth(model),
|
|
282
|
-
checkDaxnutsEasterEgg: (model) => self.checkDaxnutsEasterEgg(model),
|
|
283
228
|
showModelSelector: (searchTerm) => self.showModelSelector(searchTerm),
|
|
284
|
-
showExtensionConfirm: (title, message) => self.
|
|
229
|
+
showExtensionConfirm: (title, message) => self.dialogs.confirm(title, message),
|
|
285
230
|
promptForMissingSessionCwd: (error) => self.promptForMissingSessionCwd(error),
|
|
286
231
|
handleFatalRuntimeError: (prefix, error) => self.handleFatalRuntimeError(prefix, error),
|
|
287
232
|
};
|
|
@@ -338,10 +283,42 @@ export class InteractiveMode {
|
|
|
338
283
|
this.footer = new FooterComponent(this.session, this.footerDataProvider);
|
|
339
284
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
340
285
|
this.footerDataProvider.setSubagentEnabled(this.session.getActiveToolNames().includes("Task"));
|
|
286
|
+
this.chrome = new ExtensionChrome({
|
|
287
|
+
ui: this.ui,
|
|
288
|
+
widgetContainerAbove: this.widgetContainerAbove,
|
|
289
|
+
widgetContainerBelow: this.widgetContainerBelow,
|
|
290
|
+
headerContainer: this.headerContainer,
|
|
291
|
+
footer: this.footer,
|
|
292
|
+
footerDataProvider: this.footerDataProvider,
|
|
293
|
+
getBuiltInHeader: () => this.builtInHeader,
|
|
294
|
+
isToolOutputExpanded: () => this.toolOutputExpanded,
|
|
295
|
+
});
|
|
341
296
|
this.taskPanel = new TaskPanelComponent(this.ui);
|
|
342
|
-
this.
|
|
343
|
-
|
|
344
|
-
|
|
297
|
+
this.dialogs = new ExtensionDialogs({
|
|
298
|
+
ui: this.ui,
|
|
299
|
+
editorContainer: this.editorContainer,
|
|
300
|
+
keybindings: this.keybindings,
|
|
301
|
+
getEditor: () => this.editor,
|
|
302
|
+
});
|
|
303
|
+
this.teamFocus = new TeamFocusController({
|
|
304
|
+
ui: this.ui,
|
|
305
|
+
taskPanel: this.taskPanel,
|
|
306
|
+
editorContainer: this.editorContainer,
|
|
307
|
+
getEditor: () => this.editor,
|
|
308
|
+
showStatus: (message) => this.showStatus(message),
|
|
309
|
+
showWarning: (message) => this.showWarning(message),
|
|
310
|
+
showAskOptions: (questions, opts) => this.dialogs.showAskOptions(questions, opts),
|
|
311
|
+
isAskOptionsOpen: () => this.dialogs.isAskOptionsOpen(),
|
|
312
|
+
});
|
|
313
|
+
this.voice = new VoiceController({
|
|
314
|
+
ui: this.ui,
|
|
315
|
+
statusContainer: this.statusContainer,
|
|
316
|
+
sendEditorInput: (data) => this.editor.handleInput(data),
|
|
317
|
+
showError: (message) => this.showError(message),
|
|
318
|
+
});
|
|
319
|
+
this.taskPanel.onNudge = (role) => this.teamFocus.showNudgeInput(role);
|
|
320
|
+
this.taskPanel.onAttach = (role) => this.teamFocus.showAttach(role);
|
|
321
|
+
this.taskPanel.onExitFocus = () => this.teamFocus.exitFocus();
|
|
345
322
|
// Load hide thinking block setting
|
|
346
323
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
347
324
|
// Register themes from resource loader and initialize
|
|
@@ -492,7 +469,10 @@ export class InteractiveMode {
|
|
|
492
469
|
return;
|
|
493
470
|
this.registerSignalHandlers();
|
|
494
471
|
// Load changelog (only show new entries, skip for resumed sessions)
|
|
495
|
-
this.changelogMarkdown =
|
|
472
|
+
this.changelogMarkdown = getChangelogForDisplay({
|
|
473
|
+
hasMessages: this.session.state.messages.length > 0,
|
|
474
|
+
settingsManager: this.settingsManager,
|
|
475
|
+
});
|
|
496
476
|
// Ensure fd and rg are available (downloads if missing, adds to PATH via getBinDir)
|
|
497
477
|
// Both are needed: fd for autocomplete, rg for grep tool and bash commands
|
|
498
478
|
const [fdPath] = await Promise.all([ensureTool("fd"), ensureTool("rg")]);
|
|
@@ -506,7 +486,7 @@ export class InteractiveMode {
|
|
|
506
486
|
? buildCompactWordmark({
|
|
507
487
|
appName: APP_NAME,
|
|
508
488
|
version: this.version,
|
|
509
|
-
cwd:
|
|
489
|
+
cwd: formatDisplayPath(this.sessionManager.getCwd()),
|
|
510
490
|
accent: (text) => theme.fg("accent", text),
|
|
511
491
|
dim: (text) => theme.fg("dim", text),
|
|
512
492
|
muted: (text) => theme.fg("muted", text),
|
|
@@ -529,7 +509,7 @@ export class InteractiveMode {
|
|
|
529
509
|
hint("app.tools.expand", "to expand tools"),
|
|
530
510
|
hint("app.thinking.toggle", "to expand thinking"),
|
|
531
511
|
hint("app.tasks.cycleView", "to cycle task panel view"),
|
|
532
|
-
...(this.
|
|
512
|
+
...(this.teamFocus.connected ? [hint("app.team.focus", "to focus team roster")] : []),
|
|
533
513
|
hint("app.editor.external", "for external editor"),
|
|
534
514
|
rawKeyHint("/", "for commands"),
|
|
535
515
|
rawKeyHint("!", "to run bash"),
|
|
@@ -552,7 +532,7 @@ export class InteractiveMode {
|
|
|
552
532
|
this.ui.addChild(this.chatContainer);
|
|
553
533
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
554
534
|
this.ui.addChild(this.statusContainer);
|
|
555
|
-
this.renderWidgets(); // Initialize with default spacer
|
|
535
|
+
this.chrome.renderWidgets(); // Initialize with default spacer
|
|
556
536
|
this.ui.addChild(this.widgetContainerAbove);
|
|
557
537
|
this.ui.addChild(this.taskPanel);
|
|
558
538
|
this.ui.addChild(this.editorContainer);
|
|
@@ -611,13 +591,13 @@ export class InteractiveMode {
|
|
|
611
591
|
}
|
|
612
592
|
});
|
|
613
593
|
// Start package update check asynchronously
|
|
614
|
-
this.
|
|
594
|
+
checkForPackageUpdates(this.sessionManager.getCwd(), this.settingsManager).then((updates) => {
|
|
615
595
|
if (updates.length > 0) {
|
|
616
596
|
this.showPackageUpdateNotification(updates);
|
|
617
597
|
}
|
|
618
598
|
});
|
|
619
599
|
// Check tmux keyboard setup asynchronously
|
|
620
|
-
|
|
600
|
+
checkTmuxKeyboardSetup().then((warning) => {
|
|
621
601
|
if (warning) {
|
|
622
602
|
this.showWarning(warning);
|
|
623
603
|
}
|
|
@@ -668,98 +648,6 @@ export class InteractiveMode {
|
|
|
668
648
|
}
|
|
669
649
|
}
|
|
670
650
|
}
|
|
671
|
-
async checkForPackageUpdates() {
|
|
672
|
-
if (process.env.HOOCODE_OFFLINE ?? process.env.PI_OFFLINE) {
|
|
673
|
-
return [];
|
|
674
|
-
}
|
|
675
|
-
try {
|
|
676
|
-
const packageManager = new DefaultPackageManager({
|
|
677
|
-
cwd: this.sessionManager.getCwd(),
|
|
678
|
-
agentDir: getAgentDir(),
|
|
679
|
-
settingsManager: this.settingsManager,
|
|
680
|
-
});
|
|
681
|
-
const updates = await packageManager.checkForAvailableUpdates();
|
|
682
|
-
return updates.map((update) => update.displayName);
|
|
683
|
-
}
|
|
684
|
-
catch {
|
|
685
|
-
return [];
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
async checkTmuxKeyboardSetup() {
|
|
689
|
-
if (!process.env.TMUX)
|
|
690
|
-
return undefined;
|
|
691
|
-
const runTmuxShow = (option) => {
|
|
692
|
-
return new Promise((resolve) => {
|
|
693
|
-
const proc = spawn("tmux", ["show", "-gv", option], {
|
|
694
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
695
|
-
});
|
|
696
|
-
let stdout = "";
|
|
697
|
-
const timer = setTimeout(() => {
|
|
698
|
-
proc.kill();
|
|
699
|
-
resolve(undefined);
|
|
700
|
-
}, 2000);
|
|
701
|
-
proc.stdout?.on("data", (data) => {
|
|
702
|
-
stdout += data.toString();
|
|
703
|
-
});
|
|
704
|
-
proc.on("error", () => {
|
|
705
|
-
clearTimeout(timer);
|
|
706
|
-
resolve(undefined);
|
|
707
|
-
});
|
|
708
|
-
proc.on("close", (code) => {
|
|
709
|
-
clearTimeout(timer);
|
|
710
|
-
resolve(code === 0 ? stdout.trim() : undefined);
|
|
711
|
-
});
|
|
712
|
-
});
|
|
713
|
-
};
|
|
714
|
-
const [extendedKeys, extendedKeysFormat] = await Promise.all([
|
|
715
|
-
runTmuxShow("extended-keys"),
|
|
716
|
-
runTmuxShow("extended-keys-format"),
|
|
717
|
-
]);
|
|
718
|
-
// If we couldn't query tmux (timeout, sandbox, etc.), don't warn
|
|
719
|
-
if (extendedKeys === undefined)
|
|
720
|
-
return undefined;
|
|
721
|
-
if (extendedKeys !== "on" && extendedKeys !== "always") {
|
|
722
|
-
return "tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart tmux.";
|
|
723
|
-
}
|
|
724
|
-
if (extendedKeysFormat === "xterm") {
|
|
725
|
-
return `tmux extended-keys-format is xterm. ${APP_NAME} works best with csi-u. Add \`set -g extended-keys-format csi-u\` to ~/.tmux.conf and restart tmux.`;
|
|
726
|
-
}
|
|
727
|
-
return undefined;
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* Get changelog entries to display on startup.
|
|
731
|
-
* Only shows new entries since last seen version, skips for resumed sessions.
|
|
732
|
-
*/
|
|
733
|
-
getChangelogForDisplay() {
|
|
734
|
-
// Skip changelog for resumed/continued sessions (already have messages)
|
|
735
|
-
if (this.session.state.messages.length > 0) {
|
|
736
|
-
return undefined;
|
|
737
|
-
}
|
|
738
|
-
const lastVersion = this.settingsManager.getLastChangelogVersion();
|
|
739
|
-
const changelogPath = getChangelogPath();
|
|
740
|
-
const entries = parseChangelog(changelogPath);
|
|
741
|
-
if (!lastVersion) {
|
|
742
|
-
// Fresh install - record the latest entry's version (not VERSION, which may
|
|
743
|
-
// overshoot the latest entry and silently swallow it once it appears).
|
|
744
|
-
// Fall back to VERSION only if no entries exist yet.
|
|
745
|
-
const seedVersion = entries.length > 0 ? `${entries[0].major}.${entries[0].minor}.${entries[0].patch}` : VERSION;
|
|
746
|
-
this.settingsManager.setLastChangelogVersion(seedVersion);
|
|
747
|
-
this.reportInstallTelemetry(seedVersion);
|
|
748
|
-
return undefined;
|
|
749
|
-
}
|
|
750
|
-
const newEntries = getNewEntries(entries, lastVersion);
|
|
751
|
-
if (newEntries.length > 0) {
|
|
752
|
-
const latest = newEntries[0];
|
|
753
|
-
const latestVersion = `${latest.major}.${latest.minor}.${latest.patch}`;
|
|
754
|
-
this.settingsManager.setLastChangelogVersion(latestVersion);
|
|
755
|
-
this.reportInstallTelemetry(latestVersion);
|
|
756
|
-
return newEntries.map((e) => e.content).join("\n\n");
|
|
757
|
-
}
|
|
758
|
-
return undefined;
|
|
759
|
-
}
|
|
760
|
-
reportInstallTelemetry(_version) {
|
|
761
|
-
// Disabled in HooCode fork: this is forked from pi (upstream pi.dev install-telemetry endpoint is not run by this fork).
|
|
762
|
-
}
|
|
763
651
|
getMarkdownThemeWithSettings() {
|
|
764
652
|
return {
|
|
765
653
|
...getMarkdownTheme(),
|
|
@@ -769,475 +657,24 @@ export class InteractiveMode {
|
|
|
769
657
|
// =========================================================================
|
|
770
658
|
// Extension System
|
|
771
659
|
// =========================================================================
|
|
772
|
-
formatDisplayPath(p) {
|
|
773
|
-
const home = os.homedir();
|
|
774
|
-
let result = p;
|
|
775
|
-
// Replace home directory with ~
|
|
776
|
-
if (result.startsWith(home)) {
|
|
777
|
-
result = `~${result.slice(home.length)}`;
|
|
778
|
-
}
|
|
779
|
-
return result;
|
|
780
|
-
}
|
|
781
|
-
formatExtensionDisplayPath(path) {
|
|
782
|
-
let result = this.formatDisplayPath(path);
|
|
783
|
-
result = result.replace(/\/index\.ts$/, "").replace(/\/index\.js$/, "");
|
|
784
|
-
return result;
|
|
785
|
-
}
|
|
786
|
-
formatContextPath(p) {
|
|
787
|
-
const cwd = path.resolve(this.sessionManager.getCwd());
|
|
788
|
-
const absolutePath = path.isAbsolute(p) ? path.resolve(p) : path.resolve(cwd, p);
|
|
789
|
-
const relativePath = getCwdRelativePath(absolutePath, cwd);
|
|
790
|
-
if (relativePath !== undefined) {
|
|
791
|
-
return relativePath;
|
|
792
|
-
}
|
|
793
|
-
return this.formatDisplayPath(absolutePath);
|
|
794
|
-
}
|
|
795
660
|
getStartupExpansionState() {
|
|
796
661
|
return this.options.verbose || this.toolOutputExpanded;
|
|
797
662
|
}
|
|
798
|
-
/**
|
|
799
|
-
* Get a short path relative to the package root for display.
|
|
800
|
-
*/
|
|
801
|
-
getShortPath(fullPath, sourceInfo) {
|
|
802
|
-
const baseDir = sourceInfo?.baseDir;
|
|
803
|
-
if (baseDir && this.isPackageSource(sourceInfo)) {
|
|
804
|
-
const relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath));
|
|
805
|
-
if (relativePath &&
|
|
806
|
-
relativePath !== "." &&
|
|
807
|
-
!relativePath.startsWith("..") &&
|
|
808
|
-
!relativePath.startsWith(`..${path.sep}`) &&
|
|
809
|
-
!path.isAbsolute(relativePath)) {
|
|
810
|
-
return relativePath.replace(/\\/g, "/");
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
const source = sourceInfo?.source ?? "";
|
|
814
|
-
const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
|
|
815
|
-
if (npmMatch && source.startsWith("npm:")) {
|
|
816
|
-
return npmMatch[2];
|
|
817
|
-
}
|
|
818
|
-
const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
|
|
819
|
-
if (gitMatch && source.startsWith("git:")) {
|
|
820
|
-
return gitMatch[1];
|
|
821
|
-
}
|
|
822
|
-
return this.formatDisplayPath(fullPath);
|
|
823
|
-
}
|
|
824
|
-
getCompactPathLabel(resourcePath, sourceInfo) {
|
|
825
|
-
const shortPath = this.getShortPath(resourcePath, sourceInfo);
|
|
826
|
-
const normalizedPath = shortPath.replace(/\\/g, "/");
|
|
827
|
-
const segments = normalizedPath.split("/").filter((segment) => segment.length > 0 && segment !== "~");
|
|
828
|
-
if (segments.length > 0) {
|
|
829
|
-
return segments[segments.length - 1];
|
|
830
|
-
}
|
|
831
|
-
return shortPath;
|
|
832
|
-
}
|
|
833
|
-
getCompactPackageSourceLabel(sourceInfo) {
|
|
834
|
-
const source = sourceInfo?.source ?? "";
|
|
835
|
-
if (source.startsWith("npm:")) {
|
|
836
|
-
return source.slice("npm:".length) || source;
|
|
837
|
-
}
|
|
838
|
-
const gitSource = parseGitUrl(source);
|
|
839
|
-
if (gitSource) {
|
|
840
|
-
return gitSource.path || source;
|
|
841
|
-
}
|
|
842
|
-
return source;
|
|
843
|
-
}
|
|
844
|
-
getCompactExtensionLabel(resourcePath, sourceInfo) {
|
|
845
|
-
if (!this.isPackageSource(sourceInfo)) {
|
|
846
|
-
return this.getCompactPathLabel(resourcePath, sourceInfo);
|
|
847
|
-
}
|
|
848
|
-
const sourceLabel = this.getCompactPackageSourceLabel(sourceInfo);
|
|
849
|
-
if (!sourceLabel) {
|
|
850
|
-
return this.getCompactPathLabel(resourcePath, sourceInfo);
|
|
851
|
-
}
|
|
852
|
-
const shortPath = this.getShortPath(resourcePath, sourceInfo).replace(/\\/g, "/");
|
|
853
|
-
const packagePath = shortPath.startsWith("extensions/") ? shortPath.slice("extensions/".length) : shortPath;
|
|
854
|
-
const parsedPath = path.posix.parse(packagePath);
|
|
855
|
-
if (parsedPath.name === "index") {
|
|
856
|
-
return !parsedPath.dir || parsedPath.dir === "." ? sourceLabel : `${sourceLabel}:${parsedPath.dir}`;
|
|
857
|
-
}
|
|
858
|
-
return `${sourceLabel}:${packagePath}`;
|
|
859
|
-
}
|
|
860
|
-
getCompactDisplayPathSegments(resourcePath) {
|
|
861
|
-
return this.formatDisplayPath(resourcePath)
|
|
862
|
-
.replace(/\\/g, "/")
|
|
863
|
-
.split("/")
|
|
864
|
-
.filter((segment) => segment.length > 0 && segment !== "~");
|
|
865
|
-
}
|
|
866
|
-
getCompactNonPackageExtensionLabel(resourcePath, index, allPaths) {
|
|
867
|
-
const segments = allPaths[index]?.segments;
|
|
868
|
-
if (!segments || segments.length === 0) {
|
|
869
|
-
return this.getCompactPathLabel(resourcePath);
|
|
870
|
-
}
|
|
871
|
-
for (let segmentCount = 1; segmentCount <= segments.length; segmentCount += 1) {
|
|
872
|
-
const candidate = segments.slice(-segmentCount).join("/");
|
|
873
|
-
const isUnique = allPaths.every((item, itemIndex) => {
|
|
874
|
-
if (itemIndex === index) {
|
|
875
|
-
return true;
|
|
876
|
-
}
|
|
877
|
-
return item.segments.slice(-segmentCount).join("/") !== candidate;
|
|
878
|
-
});
|
|
879
|
-
if (isUnique) {
|
|
880
|
-
return candidate;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
return segments.join("/");
|
|
884
|
-
}
|
|
885
|
-
getCompactExtensionLabels(extensions) {
|
|
886
|
-
const nonPackageExtensions = extensions
|
|
887
|
-
.map((extension) => {
|
|
888
|
-
const segments = this.getCompactDisplayPathSegments(extension.path);
|
|
889
|
-
const lastSegment = segments[segments.length - 1];
|
|
890
|
-
if (segments.length > 1 && (lastSegment === "index.ts" || lastSegment === "index.js")) {
|
|
891
|
-
segments.pop();
|
|
892
|
-
}
|
|
893
|
-
return {
|
|
894
|
-
path: extension.path,
|
|
895
|
-
sourceInfo: extension.sourceInfo,
|
|
896
|
-
segments,
|
|
897
|
-
};
|
|
898
|
-
})
|
|
899
|
-
.filter((extension) => !this.isPackageSource(extension.sourceInfo));
|
|
900
|
-
return extensions.map((extension) => {
|
|
901
|
-
if (extension.displayName) {
|
|
902
|
-
return extension.displayName;
|
|
903
|
-
}
|
|
904
|
-
if (this.isPackageSource(extension.sourceInfo)) {
|
|
905
|
-
return this.getCompactExtensionLabel(extension.path, extension.sourceInfo);
|
|
906
|
-
}
|
|
907
|
-
const nonPackageIndex = nonPackageExtensions.findIndex((item) => item.path === extension.path);
|
|
908
|
-
if (nonPackageIndex === -1) {
|
|
909
|
-
return this.getCompactPathLabel(extension.path, extension.sourceInfo);
|
|
910
|
-
}
|
|
911
|
-
return this.getCompactNonPackageExtensionLabel(extension.path, nonPackageIndex, nonPackageExtensions);
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
getDisplaySourceInfo(sourceInfo) {
|
|
915
|
-
const source = sourceInfo?.source ?? "local";
|
|
916
|
-
const scope = sourceInfo?.scope ?? "project";
|
|
917
|
-
if (source === "local") {
|
|
918
|
-
if (scope === "user") {
|
|
919
|
-
return { label: "user", color: "muted" };
|
|
920
|
-
}
|
|
921
|
-
if (scope === "project") {
|
|
922
|
-
return { label: "project", color: "muted" };
|
|
923
|
-
}
|
|
924
|
-
if (scope === "temporary") {
|
|
925
|
-
return { label: "path", scopeLabel: "temp", color: "muted" };
|
|
926
|
-
}
|
|
927
|
-
return { label: "path", color: "muted" };
|
|
928
|
-
}
|
|
929
|
-
if (source === "cli") {
|
|
930
|
-
return { label: "path", scopeLabel: scope === "temporary" ? "temp" : undefined, color: "muted" };
|
|
931
|
-
}
|
|
932
|
-
const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
|
|
933
|
-
return { label: source, scopeLabel, color: "accent" };
|
|
934
|
-
}
|
|
935
|
-
getScopeGroup(sourceInfo) {
|
|
936
|
-
const source = sourceInfo?.source ?? "local";
|
|
937
|
-
const scope = sourceInfo?.scope ?? "project";
|
|
938
|
-
if (source === "cli" || scope === "temporary")
|
|
939
|
-
return "path";
|
|
940
|
-
if (scope === "user")
|
|
941
|
-
return "user";
|
|
942
|
-
if (scope === "project")
|
|
943
|
-
return "project";
|
|
944
|
-
return "path";
|
|
945
|
-
}
|
|
946
|
-
isPackageSource(sourceInfo) {
|
|
947
|
-
const source = sourceInfo?.source ?? "";
|
|
948
|
-
return source.startsWith("npm:") || source.startsWith("git:");
|
|
949
|
-
}
|
|
950
|
-
buildScopeGroups(items) {
|
|
951
|
-
const groups = {
|
|
952
|
-
user: { scope: "user", paths: [], packages: new Map() },
|
|
953
|
-
project: { scope: "project", paths: [], packages: new Map() },
|
|
954
|
-
path: { scope: "path", paths: [], packages: new Map() },
|
|
955
|
-
};
|
|
956
|
-
for (const item of items) {
|
|
957
|
-
const groupKey = this.getScopeGroup(item.sourceInfo);
|
|
958
|
-
const group = groups[groupKey];
|
|
959
|
-
const source = item.sourceInfo?.source ?? "local";
|
|
960
|
-
if (this.isPackageSource(item.sourceInfo)) {
|
|
961
|
-
const list = group.packages.get(source) ?? [];
|
|
962
|
-
list.push(item);
|
|
963
|
-
group.packages.set(source, list);
|
|
964
|
-
}
|
|
965
|
-
else {
|
|
966
|
-
group.paths.push(item);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
return [groups.project, groups.user, groups.path].filter((group) => group.paths.length > 0 || group.packages.size > 0);
|
|
970
|
-
}
|
|
971
|
-
formatScopeGroups(groups, options) {
|
|
972
|
-
const lines = [];
|
|
973
|
-
for (const group of groups) {
|
|
974
|
-
lines.push(` ${theme.fg("accent", group.scope)}`);
|
|
975
|
-
const sortedPaths = [...group.paths].sort((a, b) => a.path.localeCompare(b.path));
|
|
976
|
-
for (const item of sortedPaths) {
|
|
977
|
-
lines.push(theme.fg("dim", ` ${options.formatPath(item)}`));
|
|
978
|
-
}
|
|
979
|
-
const sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
980
|
-
for (const [source, items] of sortedPackages) {
|
|
981
|
-
lines.push(` ${theme.fg("mdLink", source)}`);
|
|
982
|
-
const sortedPackagePaths = [...items].sort((a, b) => a.path.localeCompare(b.path));
|
|
983
|
-
for (const item of sortedPackagePaths) {
|
|
984
|
-
lines.push(theme.fg("dim", ` ${options.formatPackagePath(item, source)}`));
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
return lines.join("\n");
|
|
989
|
-
}
|
|
990
|
-
findSourceInfoForPath(p, sourceInfos) {
|
|
991
|
-
const exact = sourceInfos.get(p);
|
|
992
|
-
if (exact)
|
|
993
|
-
return exact;
|
|
994
|
-
let current = p;
|
|
995
|
-
while (current.includes("/")) {
|
|
996
|
-
current = current.substring(0, current.lastIndexOf("/"));
|
|
997
|
-
const parent = sourceInfos.get(current);
|
|
998
|
-
if (parent)
|
|
999
|
-
return parent;
|
|
1000
|
-
}
|
|
1001
|
-
return undefined;
|
|
1002
|
-
}
|
|
1003
|
-
formatPathWithSource(p, sourceInfo) {
|
|
1004
|
-
if (sourceInfo) {
|
|
1005
|
-
const shortPath = this.getShortPath(p, sourceInfo);
|
|
1006
|
-
const { label, scopeLabel } = this.getDisplaySourceInfo(sourceInfo);
|
|
1007
|
-
const labelText = scopeLabel ? `${label} (${scopeLabel})` : label;
|
|
1008
|
-
return `${labelText} ${shortPath}`;
|
|
1009
|
-
}
|
|
1010
|
-
return this.formatDisplayPath(p);
|
|
1011
|
-
}
|
|
1012
|
-
formatDiagnostics(diagnostics, sourceInfos) {
|
|
1013
|
-
const lines = [];
|
|
1014
|
-
// Group collision diagnostics by name
|
|
1015
|
-
const collisions = new Map();
|
|
1016
|
-
const otherDiagnostics = [];
|
|
1017
|
-
for (const d of diagnostics) {
|
|
1018
|
-
if (d.type === "collision" && d.collision) {
|
|
1019
|
-
const list = collisions.get(d.collision.name) ?? [];
|
|
1020
|
-
list.push(d);
|
|
1021
|
-
collisions.set(d.collision.name, list);
|
|
1022
|
-
}
|
|
1023
|
-
else {
|
|
1024
|
-
otherDiagnostics.push(d);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
// Format collision diagnostics grouped by name
|
|
1028
|
-
for (const [name, collisionList] of collisions) {
|
|
1029
|
-
const first = collisionList[0]?.collision;
|
|
1030
|
-
if (!first)
|
|
1031
|
-
continue;
|
|
1032
|
-
lines.push(theme.fg("warning", ` "${name}" collision:`));
|
|
1033
|
-
lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, this.findSourceInfoForPath(first.winnerPath, sourceInfos))}`));
|
|
1034
|
-
for (const d of collisionList) {
|
|
1035
|
-
if (d.collision) {
|
|
1036
|
-
lines.push(theme.fg("dim", ` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath, this.findSourceInfoForPath(d.collision.loserPath, sourceInfos))} (skipped)`));
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
for (const d of otherDiagnostics) {
|
|
1041
|
-
if (d.path) {
|
|
1042
|
-
const formattedPath = this.formatPathWithSource(d.path, this.findSourceInfoForPath(d.path, sourceInfos));
|
|
1043
|
-
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${formattedPath}`));
|
|
1044
|
-
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
|
|
1045
|
-
}
|
|
1046
|
-
else {
|
|
1047
|
-
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
return lines.join("\n");
|
|
1051
|
-
}
|
|
663
|
+
/** Render the startup/reload resource listing (see resource-display.ts). */
|
|
1052
664
|
showLoadedResources(options) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
};
|
|
1066
|
-
const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
|
|
1067
|
-
const section = new ExpandableText(() => `${sectionHeader(name, color)}\n${collapsedBody}`, () => `${sectionHeader(name, color)}\n${expandedBody}`, this.getStartupExpansionState(), 0, 0);
|
|
1068
|
-
this.chatContainer.addChild(section);
|
|
1069
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1070
|
-
};
|
|
1071
|
-
const skillsResult = this.session.resourceLoader.getSkills();
|
|
1072
|
-
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
1073
|
-
const themesResult = this.session.resourceLoader.getThemes();
|
|
1074
|
-
const extensions = options?.extensions ??
|
|
1075
|
-
this.session.resourceLoader
|
|
1076
|
-
.getExtensions()
|
|
1077
|
-
.extensions.filter((extension) => !extension.internal)
|
|
1078
|
-
.map((extension) => ({
|
|
1079
|
-
path: extension.path,
|
|
1080
|
-
sourceInfo: extension.sourceInfo,
|
|
1081
|
-
displayName: extension.displayName,
|
|
1082
|
-
}));
|
|
1083
|
-
const sourceInfos = new Map();
|
|
1084
|
-
for (const extension of extensions) {
|
|
1085
|
-
if (extension.sourceInfo) {
|
|
1086
|
-
sourceInfos.set(extension.path, extension.sourceInfo);
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
for (const skill of skillsResult.skills) {
|
|
1090
|
-
if (skill.sourceInfo) {
|
|
1091
|
-
sourceInfos.set(skill.filePath, skill.sourceInfo);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
for (const prompt of promptsResult.prompts) {
|
|
1095
|
-
if (prompt.sourceInfo) {
|
|
1096
|
-
sourceInfos.set(prompt.filePath, prompt.sourceInfo);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
for (const loadedTheme of themesResult.themes) {
|
|
1100
|
-
if (loadedTheme.sourcePath && loadedTheme.sourceInfo) {
|
|
1101
|
-
sourceInfos.set(loadedTheme.sourcePath, loadedTheme.sourceInfo);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
if (showListing) {
|
|
1105
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1106
|
-
const { agentsFiles: contextFiles, warnings: contextWarnings } = this.session.resourceLoader.getAgentsFiles();
|
|
1107
|
-
const skills = skillsResult.skills;
|
|
1108
|
-
const templates = this.session.promptTemplates;
|
|
1109
|
-
const loadedThemes = themesResult.themes;
|
|
1110
|
-
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
|
1111
|
-
const totalItems = contextFiles.length + skills.length + templates.length + extensions.length + customThemes.length;
|
|
1112
|
-
// Meta items: active mode and subagent system prompt (always shown)
|
|
1113
|
-
const metaItems = [];
|
|
1114
|
-
const rawMode = this.footerDataProvider.getActiveMode().replace(" + subagent", "");
|
|
1115
|
-
metaItems.push(`mode/${rawMode}`);
|
|
1116
|
-
if (this.footerDataProvider.getSubagentEnabled()) {
|
|
1117
|
-
metaItems.push("subagent_system_prompt");
|
|
1118
|
-
}
|
|
1119
|
-
if (totalItems > 0 && totalItems <= 5) {
|
|
1120
|
-
const allCompactItems = [...metaItems];
|
|
1121
|
-
if (contextFiles.length > 0) {
|
|
1122
|
-
allCompactItems.push(...contextFiles.map((contextFile) => this.formatContextPath(contextFile.path)));
|
|
1123
|
-
}
|
|
1124
|
-
if (skills.length > 0) {
|
|
1125
|
-
allCompactItems.push(...skills.map((skill) => skill.name));
|
|
1126
|
-
}
|
|
1127
|
-
if (templates.length > 0) {
|
|
1128
|
-
allCompactItems.push(...templates.map((template) => `/${template.name}`));
|
|
1129
|
-
}
|
|
1130
|
-
if (extensions.length > 0) {
|
|
1131
|
-
allCompactItems.push(...this.getCompactExtensionLabels(extensions));
|
|
1132
|
-
}
|
|
1133
|
-
if (customThemes.length > 0) {
|
|
1134
|
-
allCompactItems.push(...customThemes.map((loadedTheme) => loadedTheme.name ?? this.getCompactPathLabel(loadedTheme.sourcePath, loadedTheme.sourceInfo)));
|
|
1135
|
-
}
|
|
1136
|
-
this.chatContainer.addChild(new Text(`${theme.fg("mdHeading", "[Resources]")} ${theme.fg("dim", allCompactItems.join(", "))}`, 0, 0));
|
|
1137
|
-
}
|
|
1138
|
-
else if (totalItems === 0) {
|
|
1139
|
-
this.chatContainer.addChild(new Text(`${theme.fg("mdHeading", "[Resources]")} ${theme.fg("dim", metaItems.join(", "))}`, 0, 0));
|
|
1140
|
-
}
|
|
1141
|
-
else {
|
|
1142
|
-
addLoadedSection("Resources", formatCompactList(metaItems), formatCompactList(metaItems));
|
|
1143
|
-
if (contextFiles.length > 0) {
|
|
1144
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1145
|
-
const contextList = contextFiles
|
|
1146
|
-
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
|
1147
|
-
.join("\n");
|
|
1148
|
-
const contextCompactList = formatCompactList(contextFiles.map((contextFile) => this.formatContextPath(contextFile.path)), { sort: false });
|
|
1149
|
-
addLoadedSection("Context", contextCompactList, contextList);
|
|
1150
|
-
}
|
|
1151
|
-
if (skills.length > 0) {
|
|
1152
|
-
const groups = this.buildScopeGroups(skills.map((skill) => ({ path: skill.filePath, sourceInfo: skill.sourceInfo })));
|
|
1153
|
-
const skillList = this.formatScopeGroups(groups, {
|
|
1154
|
-
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1155
|
-
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
1156
|
-
});
|
|
1157
|
-
const skillCompactList = formatCompactList(skills.map((skill) => skill.name));
|
|
1158
|
-
addLoadedSection("Skills", skillCompactList, skillList);
|
|
1159
|
-
}
|
|
1160
|
-
if (templates.length > 0) {
|
|
1161
|
-
const groups = this.buildScopeGroups(templates.map((template) => ({ path: template.filePath, sourceInfo: template.sourceInfo })));
|
|
1162
|
-
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
|
1163
|
-
const templateList = this.formatScopeGroups(groups, {
|
|
1164
|
-
formatPath: (item) => {
|
|
1165
|
-
const template = templateByPath.get(item.path);
|
|
1166
|
-
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
1167
|
-
},
|
|
1168
|
-
formatPackagePath: (item) => {
|
|
1169
|
-
const template = templateByPath.get(item.path);
|
|
1170
|
-
return template ? `/${template.name}` : this.formatDisplayPath(item.path);
|
|
1171
|
-
},
|
|
1172
|
-
});
|
|
1173
|
-
const promptCompactList = formatCompactList(templates.map((template) => `/${template.name}`));
|
|
1174
|
-
addLoadedSection("Prompts", promptCompactList, templateList);
|
|
1175
|
-
}
|
|
1176
|
-
if (extensions.length > 0) {
|
|
1177
|
-
const groups = this.buildScopeGroups(extensions);
|
|
1178
|
-
const extList = this.formatScopeGroups(groups, {
|
|
1179
|
-
formatPath: (item) => item.displayName ?? this.formatExtensionDisplayPath(item.path),
|
|
1180
|
-
formatPackagePath: (item) => item.displayName ?? this.formatExtensionDisplayPath(this.getShortPath(item.path, item.sourceInfo)),
|
|
1181
|
-
});
|
|
1182
|
-
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
|
|
1183
|
-
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");
|
|
1184
|
-
}
|
|
1185
|
-
if (customThemes.length > 0) {
|
|
1186
|
-
const groups = this.buildScopeGroups(customThemes.map((loadedTheme) => ({
|
|
1187
|
-
path: loadedTheme.sourcePath,
|
|
1188
|
-
sourceInfo: loadedTheme.sourceInfo,
|
|
1189
|
-
})));
|
|
1190
|
-
const themeList = this.formatScopeGroups(groups, {
|
|
1191
|
-
formatPath: (item) => this.formatDisplayPath(item.path),
|
|
1192
|
-
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
|
|
1193
|
-
});
|
|
1194
|
-
const themeCompactList = formatCompactList(customThemes.map((loadedTheme) => loadedTheme.name ?? this.getCompactPathLabel(loadedTheme.sourcePath, loadedTheme.sourceInfo)));
|
|
1195
|
-
addLoadedSection("Themes", themeCompactList, themeList);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
if (contextWarnings.length > 0) {
|
|
1199
|
-
for (const warning of contextWarnings) {
|
|
1200
|
-
this.chatContainer.addChild(new Text(theme.fg("warning", warning), 0, 0));
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
if (showDiagnostics) {
|
|
1205
|
-
const skillDiagnostics = skillsResult.diagnostics;
|
|
1206
|
-
if (skillDiagnostics.length > 0) {
|
|
1207
|
-
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
|
1208
|
-
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
1209
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1210
|
-
}
|
|
1211
|
-
const promptDiagnostics = promptsResult.diagnostics;
|
|
1212
|
-
if (promptDiagnostics.length > 0) {
|
|
1213
|
-
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
|
1214
|
-
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
1215
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1216
|
-
}
|
|
1217
|
-
const extensionDiagnostics = [];
|
|
1218
|
-
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
1219
|
-
if (extensionErrors.length > 0) {
|
|
1220
|
-
for (const error of extensionErrors) {
|
|
1221
|
-
extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
const commandDiagnostics = this.session.extensionRunner.getCommandDiagnostics();
|
|
1225
|
-
extensionDiagnostics.push(...commandDiagnostics);
|
|
1226
|
-
extensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));
|
|
1227
|
-
const shortcutDiagnostics = this.session.extensionRunner.getShortcutDiagnostics();
|
|
1228
|
-
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
1229
|
-
if (extensionDiagnostics.length > 0) {
|
|
1230
|
-
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
1231
|
-
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
1232
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1233
|
-
}
|
|
1234
|
-
const themeDiagnostics = themesResult.diagnostics;
|
|
1235
|
-
if (themeDiagnostics.length > 0) {
|
|
1236
|
-
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
1237
|
-
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
1238
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
665
|
+
renderLoadedResources({
|
|
666
|
+
chatContainer: this.chatContainer,
|
|
667
|
+
getCwd: () => this.sessionManager.getCwd(),
|
|
668
|
+
getResourceLoader: () => this.session.resourceLoader,
|
|
669
|
+
getPromptTemplates: () => this.session.promptTemplates,
|
|
670
|
+
getExtensionRunner: () => this.session.extensionRunner,
|
|
671
|
+
getActiveMode: () => this.footerDataProvider.getActiveMode(),
|
|
672
|
+
getSubagentEnabled: () => this.footerDataProvider.getSubagentEnabled(),
|
|
673
|
+
quietStartup: () => this.settingsManager.getQuietStartup(),
|
|
674
|
+
verbose: this.options.verbose ?? false,
|
|
675
|
+
isExpanded: () => this.getStartupExpansionState(),
|
|
676
|
+
getBuiltInCommandConflictDiagnostics: (runner) => this.getBuiltInCommandConflictDiagnostics(runner),
|
|
677
|
+
}, options);
|
|
1241
678
|
}
|
|
1242
679
|
/**
|
|
1243
680
|
* Initialize the extension system with TUI-based UI context.
|
|
@@ -1474,72 +911,11 @@ export class InteractiveMode {
|
|
|
1474
911
|
}
|
|
1475
912
|
this.ui.requestRender();
|
|
1476
913
|
}
|
|
1477
|
-
/**
|
|
1478
|
-
* Set an extension widget (string array or custom component).
|
|
1479
|
-
*/
|
|
1480
|
-
setExtensionWidget(key, content, options) {
|
|
1481
|
-
const placement = options?.placement ?? "aboveEditor";
|
|
1482
|
-
const removeExisting = (map) => {
|
|
1483
|
-
const existing = map.get(key);
|
|
1484
|
-
if (existing?.dispose)
|
|
1485
|
-
existing.dispose();
|
|
1486
|
-
map.delete(key);
|
|
1487
|
-
};
|
|
1488
|
-
removeExisting(this.extensionWidgetsAbove);
|
|
1489
|
-
removeExisting(this.extensionWidgetsBelow);
|
|
1490
|
-
if (content === undefined) {
|
|
1491
|
-
this.renderWidgets();
|
|
1492
|
-
return;
|
|
1493
|
-
}
|
|
1494
|
-
let component;
|
|
1495
|
-
if (Array.isArray(content)) {
|
|
1496
|
-
// Wrap string array in a Container with Text components
|
|
1497
|
-
const container = new Container();
|
|
1498
|
-
for (const line of content.slice(0, InteractiveMode.MAX_WIDGET_LINES)) {
|
|
1499
|
-
container.addChild(new Text(line, 1, 0));
|
|
1500
|
-
}
|
|
1501
|
-
if (content.length > InteractiveMode.MAX_WIDGET_LINES) {
|
|
1502
|
-
container.addChild(new Text(theme.fg("muted", "... (widget truncated)"), 1, 0));
|
|
1503
|
-
}
|
|
1504
|
-
component = container;
|
|
1505
|
-
}
|
|
1506
|
-
else {
|
|
1507
|
-
// Factory function - create component
|
|
1508
|
-
component = content(this.ui, theme);
|
|
1509
|
-
}
|
|
1510
|
-
const targetMap = placement === "belowEditor" ? this.extensionWidgetsBelow : this.extensionWidgetsAbove;
|
|
1511
|
-
targetMap.set(key, component);
|
|
1512
|
-
this.renderWidgets();
|
|
1513
|
-
}
|
|
1514
|
-
clearExtensionWidgets() {
|
|
1515
|
-
for (const widget of this.extensionWidgetsAbove.values()) {
|
|
1516
|
-
widget.dispose?.();
|
|
1517
|
-
}
|
|
1518
|
-
for (const widget of this.extensionWidgetsBelow.values()) {
|
|
1519
|
-
widget.dispose?.();
|
|
1520
|
-
}
|
|
1521
|
-
this.extensionWidgetsAbove.clear();
|
|
1522
|
-
this.extensionWidgetsBelow.clear();
|
|
1523
|
-
this.renderWidgets();
|
|
1524
|
-
}
|
|
1525
914
|
resetExtensionUI() {
|
|
1526
|
-
|
|
1527
|
-
this.hideExtensionSelector();
|
|
1528
|
-
}
|
|
1529
|
-
if (this.extensionInput) {
|
|
1530
|
-
this.hideExtensionInput();
|
|
1531
|
-
}
|
|
1532
|
-
if (this.askOptions) {
|
|
1533
|
-
this.hideAskOptions();
|
|
1534
|
-
}
|
|
1535
|
-
if (this.extensionEditor) {
|
|
1536
|
-
this.hideExtensionEditor();
|
|
1537
|
-
}
|
|
915
|
+
this.dialogs.reset();
|
|
1538
916
|
this.ui.hideOverlay();
|
|
1539
917
|
this.clearExtensionTerminalInputListeners();
|
|
1540
|
-
this.
|
|
1541
|
-
this.setExtensionHeader(undefined);
|
|
1542
|
-
this.clearExtensionWidgets();
|
|
918
|
+
this.chrome.reset();
|
|
1543
919
|
this.footerDataProvider.clearExtensionStatuses();
|
|
1544
920
|
this.footer.invalidate();
|
|
1545
921
|
this.autocompleteProviderWrappers = [];
|
|
@@ -1555,101 +931,6 @@ export class InteractiveMode {
|
|
|
1555
931
|
}
|
|
1556
932
|
this.setHiddenThinkingLabel();
|
|
1557
933
|
}
|
|
1558
|
-
// Maximum total widget lines to prevent viewport overflow
|
|
1559
|
-
static MAX_WIDGET_LINES = 10;
|
|
1560
|
-
/**
|
|
1561
|
-
* Render all extension widgets to the widget container.
|
|
1562
|
-
*/
|
|
1563
|
-
renderWidgets() {
|
|
1564
|
-
if (!this.widgetContainerAbove || !this.widgetContainerBelow)
|
|
1565
|
-
return;
|
|
1566
|
-
this.renderWidgetContainer(this.widgetContainerAbove, this.extensionWidgetsAbove, true, true);
|
|
1567
|
-
this.renderWidgetContainer(this.widgetContainerBelow, this.extensionWidgetsBelow, false, false);
|
|
1568
|
-
this.ui.requestRender();
|
|
1569
|
-
}
|
|
1570
|
-
renderWidgetContainer(container, widgets, spacerWhenEmpty, leadingSpacer) {
|
|
1571
|
-
container.clear();
|
|
1572
|
-
if (widgets.size === 0) {
|
|
1573
|
-
if (spacerWhenEmpty) {
|
|
1574
|
-
container.addChild(new Spacer(1));
|
|
1575
|
-
}
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
if (leadingSpacer) {
|
|
1579
|
-
container.addChild(new Spacer(1));
|
|
1580
|
-
}
|
|
1581
|
-
for (const component of widgets.values()) {
|
|
1582
|
-
container.addChild(component);
|
|
1583
|
-
}
|
|
1584
|
-
}
|
|
1585
|
-
/**
|
|
1586
|
-
* Set a custom footer component, or restore the built-in footer.
|
|
1587
|
-
*/
|
|
1588
|
-
setExtensionFooter(factory) {
|
|
1589
|
-
// Dispose existing custom footer
|
|
1590
|
-
if (this.customFooter?.dispose) {
|
|
1591
|
-
this.customFooter.dispose();
|
|
1592
|
-
}
|
|
1593
|
-
// Remove current footer from UI
|
|
1594
|
-
if (this.customFooter) {
|
|
1595
|
-
this.ui.removeChild(this.customFooter);
|
|
1596
|
-
}
|
|
1597
|
-
else {
|
|
1598
|
-
this.ui.removeChild(this.footer);
|
|
1599
|
-
}
|
|
1600
|
-
if (factory) {
|
|
1601
|
-
// Create and add custom footer, passing the data provider
|
|
1602
|
-
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
1603
|
-
this.ui.addChild(this.customFooter);
|
|
1604
|
-
}
|
|
1605
|
-
else {
|
|
1606
|
-
// Restore built-in footer
|
|
1607
|
-
this.customFooter = undefined;
|
|
1608
|
-
this.ui.addChild(this.footer);
|
|
1609
|
-
}
|
|
1610
|
-
this.ui.requestRender();
|
|
1611
|
-
}
|
|
1612
|
-
/**
|
|
1613
|
-
* Set a custom header component, or restore the built-in header.
|
|
1614
|
-
*/
|
|
1615
|
-
setExtensionHeader(factory) {
|
|
1616
|
-
// Header may not be initialized yet if called during early initialization
|
|
1617
|
-
if (!this.builtInHeader) {
|
|
1618
|
-
return;
|
|
1619
|
-
}
|
|
1620
|
-
// Dispose existing custom header
|
|
1621
|
-
if (this.customHeader?.dispose) {
|
|
1622
|
-
this.customHeader.dispose();
|
|
1623
|
-
}
|
|
1624
|
-
// Find the index of the current header in the header container
|
|
1625
|
-
const currentHeader = this.customHeader || this.builtInHeader;
|
|
1626
|
-
const index = this.headerContainer.children.indexOf(currentHeader);
|
|
1627
|
-
if (factory) {
|
|
1628
|
-
// Create and add custom header
|
|
1629
|
-
this.customHeader = factory(this.ui, theme);
|
|
1630
|
-
if (isExpandable(this.customHeader)) {
|
|
1631
|
-
this.customHeader.setExpanded(this.toolOutputExpanded);
|
|
1632
|
-
}
|
|
1633
|
-
if (index !== -1) {
|
|
1634
|
-
this.headerContainer.children[index] = this.customHeader;
|
|
1635
|
-
}
|
|
1636
|
-
else {
|
|
1637
|
-
// If not found (e.g. builtInHeader was never added), add at the top
|
|
1638
|
-
this.headerContainer.children.unshift(this.customHeader);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
else {
|
|
1642
|
-
// Restore built-in header
|
|
1643
|
-
this.customHeader = undefined;
|
|
1644
|
-
if (isExpandable(this.builtInHeader)) {
|
|
1645
|
-
this.builtInHeader.setExpanded(this.toolOutputExpanded);
|
|
1646
|
-
}
|
|
1647
|
-
if (index !== -1) {
|
|
1648
|
-
this.headerContainer.children[index] = this.builtInHeader;
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
this.ui.requestRender();
|
|
1652
|
-
}
|
|
1653
934
|
addExtensionTerminalInputListener(handler) {
|
|
1654
935
|
const unsubscribe = this.ui.addInputListener(handler);
|
|
1655
936
|
this.extensionTerminalInputUnsubscribers.add(unsubscribe);
|
|
@@ -1669,10 +950,10 @@ export class InteractiveMode {
|
|
|
1669
950
|
*/
|
|
1670
951
|
createExtensionUIContext() {
|
|
1671
952
|
return {
|
|
1672
|
-
select: (title, options, opts) => this.
|
|
1673
|
-
confirm: (title, message, opts) => this.
|
|
1674
|
-
input: (title, placeholder, opts) => this.
|
|
1675
|
-
askOptions: (questions, opts) => this.showAskOptions(questions, opts),
|
|
953
|
+
select: (title, options, opts) => this.dialogs.showSelector(title, options, opts),
|
|
954
|
+
confirm: (title, message, opts) => this.dialogs.confirm(title, message, opts),
|
|
955
|
+
input: (title, placeholder, opts) => this.dialogs.showInput(title, placeholder, opts),
|
|
956
|
+
askOptions: (questions, opts) => this.dialogs.showAskOptions(questions, opts),
|
|
1676
957
|
notify: (message, type) => this.showExtensionNotify(message, type),
|
|
1677
958
|
onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
|
|
1678
959
|
setStatus: (key, text) => this.setExtensionStatus(key, text),
|
|
@@ -1690,15 +971,15 @@ export class InteractiveMode {
|
|
|
1690
971
|
setWorkingVisible: (visible) => this.setWorkingVisible(visible),
|
|
1691
972
|
setWorkingIndicator: (options) => this.setWorkingIndicator(options),
|
|
1692
973
|
setHiddenThinkingLabel: (label) => this.setHiddenThinkingLabel(label),
|
|
1693
|
-
setWidget: (key, content, options) => this.
|
|
1694
|
-
setFooter: (factory) => this.
|
|
1695
|
-
setHeader: (factory) => this.
|
|
974
|
+
setWidget: (key, content, options) => this.chrome.setWidget(key, content, options),
|
|
975
|
+
setFooter: (factory) => this.chrome.setFooter(factory),
|
|
976
|
+
setHeader: (factory) => this.chrome.setHeader(factory),
|
|
1696
977
|
setTitle: (title) => this.ui.terminal.setTitle(title),
|
|
1697
|
-
custom: (factory, options) => this.
|
|
978
|
+
custom: (factory, options) => this.dialogs.showCustom(factory, options),
|
|
1698
979
|
pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
|
|
1699
980
|
setEditorText: (text) => this.editor.setText(text),
|
|
1700
981
|
getEditorText: () => this.editor.getExpandedText?.() ?? this.editor.getText(),
|
|
1701
|
-
editor: (title, prefill) => this.
|
|
982
|
+
editor: (title, prefill) => this.dialogs.showEditor(title, prefill),
|
|
1702
983
|
addAutocompleteProvider: (factory) => {
|
|
1703
984
|
this.autocompleteProviderWrappers.push(factory);
|
|
1704
985
|
this.setupAutocompleteProvider();
|
|
@@ -1727,168 +1008,11 @@ export class InteractiveMode {
|
|
|
1727
1008
|
},
|
|
1728
1009
|
getToolsExpanded: () => this.toolOutputExpanded,
|
|
1729
1010
|
setToolsExpanded: (expanded) => this.setToolsExpanded(expanded),
|
|
1730
|
-
};
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
showExtensionSelector(title, options, opts) {
|
|
1736
|
-
return new Promise((resolve) => {
|
|
1737
|
-
if (opts?.signal?.aborted) {
|
|
1738
|
-
resolve(undefined);
|
|
1739
|
-
return;
|
|
1740
|
-
}
|
|
1741
|
-
const onAbort = () => {
|
|
1742
|
-
this.hideExtensionSelector();
|
|
1743
|
-
resolve(undefined);
|
|
1744
|
-
};
|
|
1745
|
-
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1746
|
-
this.extensionSelector = new ExtensionSelectorComponent(title, options, (option) => {
|
|
1747
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1748
|
-
this.hideExtensionSelector();
|
|
1749
|
-
resolve(option);
|
|
1750
|
-
}, () => {
|
|
1751
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1752
|
-
this.hideExtensionSelector();
|
|
1753
|
-
resolve(undefined);
|
|
1754
|
-
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1755
|
-
this.editorContainer.clear();
|
|
1756
|
-
this.editorContainer.addChild(this.extensionSelector);
|
|
1757
|
-
this.ui.setFocus(this.extensionSelector);
|
|
1758
|
-
this.ui.requestRender();
|
|
1759
|
-
});
|
|
1760
|
-
}
|
|
1761
|
-
/**
|
|
1762
|
-
* Hide the extension selector.
|
|
1763
|
-
*/
|
|
1764
|
-
hideExtensionSelector() {
|
|
1765
|
-
this.extensionSelector?.dispose();
|
|
1766
|
-
this.editorContainer.clear();
|
|
1767
|
-
this.editorContainer.addChild(this.editor);
|
|
1768
|
-
this.extensionSelector = undefined;
|
|
1769
|
-
this.ui.setFocus(this.editor);
|
|
1770
|
-
this.ui.requestRender();
|
|
1771
|
-
}
|
|
1772
|
-
/**
|
|
1773
|
-
* Show the options pane — the agent asking the user one or more questions.
|
|
1774
|
-
* Resolves with one answer per question, or undefined if skipped/aborted.
|
|
1775
|
-
*/
|
|
1776
|
-
showAskOptions(questions, opts) {
|
|
1777
|
-
return new Promise((resolve) => {
|
|
1778
|
-
if (!questions.length || opts?.signal?.aborted) {
|
|
1779
|
-
resolve(undefined);
|
|
1780
|
-
return;
|
|
1781
|
-
}
|
|
1782
|
-
const onAbort = () => {
|
|
1783
|
-
this.hideAskOptions();
|
|
1784
|
-
resolve(undefined);
|
|
1785
|
-
};
|
|
1786
|
-
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1787
|
-
this.askOptions = new AskOptionsComponent(questions, (answers) => {
|
|
1788
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1789
|
-
this.hideAskOptions();
|
|
1790
|
-
resolve(answers);
|
|
1791
|
-
}, () => {
|
|
1792
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1793
|
-
this.hideAskOptions();
|
|
1794
|
-
resolve(undefined);
|
|
1795
|
-
});
|
|
1796
|
-
this.editorContainer.clear();
|
|
1797
|
-
this.editorContainer.addChild(this.askOptions);
|
|
1798
|
-
this.ui.setFocus(this.askOptions);
|
|
1799
|
-
this.ui.requestRender();
|
|
1800
|
-
});
|
|
1801
|
-
}
|
|
1802
|
-
/**
|
|
1803
|
-
* Hide the options pane and restore the editor.
|
|
1804
|
-
*/
|
|
1805
|
-
hideAskOptions() {
|
|
1806
|
-
if (!this.askOptions)
|
|
1807
|
-
return;
|
|
1808
|
-
this.editorContainer.clear();
|
|
1809
|
-
this.editorContainer.addChild(this.editor);
|
|
1810
|
-
this.askOptions = undefined;
|
|
1811
|
-
this.ui.setFocus(this.editor);
|
|
1812
|
-
this.ui.requestRender();
|
|
1813
|
-
}
|
|
1814
|
-
/**
|
|
1815
|
-
* Show a confirmation dialog for extensions.
|
|
1816
|
-
*/
|
|
1817
|
-
async showExtensionConfirm(title, message, opts) {
|
|
1818
|
-
const result = await this.showExtensionSelector(`${title}\n${message}`, ["Yes", "No"], opts);
|
|
1819
|
-
return result === "Yes";
|
|
1820
|
-
}
|
|
1821
|
-
async promptForMissingSessionCwd(error) {
|
|
1822
|
-
const confirmed = await this.showExtensionConfirm("Session cwd not found", formatMissingSessionCwdPrompt(error.issue));
|
|
1823
|
-
return confirmed ? error.issue.fallbackCwd : undefined;
|
|
1824
|
-
}
|
|
1825
|
-
/**
|
|
1826
|
-
* Show a text input for extensions.
|
|
1827
|
-
*/
|
|
1828
|
-
showExtensionInput(title, placeholder, opts) {
|
|
1829
|
-
return new Promise((resolve) => {
|
|
1830
|
-
if (opts?.signal?.aborted) {
|
|
1831
|
-
resolve(undefined);
|
|
1832
|
-
return;
|
|
1833
|
-
}
|
|
1834
|
-
const onAbort = () => {
|
|
1835
|
-
this.hideExtensionInput();
|
|
1836
|
-
resolve(undefined);
|
|
1837
|
-
};
|
|
1838
|
-
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1839
|
-
this.extensionInput = new ExtensionInputComponent(title, placeholder, (value) => {
|
|
1840
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1841
|
-
this.hideExtensionInput();
|
|
1842
|
-
resolve(value);
|
|
1843
|
-
}, () => {
|
|
1844
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1845
|
-
this.hideExtensionInput();
|
|
1846
|
-
resolve(undefined);
|
|
1847
|
-
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1848
|
-
this.editorContainer.clear();
|
|
1849
|
-
this.editorContainer.addChild(this.extensionInput);
|
|
1850
|
-
this.ui.setFocus(this.extensionInput);
|
|
1851
|
-
this.ui.requestRender();
|
|
1852
|
-
});
|
|
1853
|
-
}
|
|
1854
|
-
/**
|
|
1855
|
-
* Hide the extension input.
|
|
1856
|
-
*/
|
|
1857
|
-
hideExtensionInput() {
|
|
1858
|
-
this.extensionInput?.dispose();
|
|
1859
|
-
this.editorContainer.clear();
|
|
1860
|
-
this.editorContainer.addChild(this.editor);
|
|
1861
|
-
this.extensionInput = undefined;
|
|
1862
|
-
this.ui.setFocus(this.editor);
|
|
1863
|
-
this.ui.requestRender();
|
|
1864
|
-
}
|
|
1865
|
-
/**
|
|
1866
|
-
* Show a multi-line editor for extensions (with Ctrl+G support).
|
|
1867
|
-
*/
|
|
1868
|
-
showExtensionEditor(title, prefill) {
|
|
1869
|
-
return new Promise((resolve) => {
|
|
1870
|
-
this.extensionEditor = new ExtensionEditorComponent(this.ui, this.keybindings, title, prefill, (value) => {
|
|
1871
|
-
this.hideExtensionEditor();
|
|
1872
|
-
resolve(value);
|
|
1873
|
-
}, () => {
|
|
1874
|
-
this.hideExtensionEditor();
|
|
1875
|
-
resolve(undefined);
|
|
1876
|
-
});
|
|
1877
|
-
this.editorContainer.clear();
|
|
1878
|
-
this.editorContainer.addChild(this.extensionEditor);
|
|
1879
|
-
this.ui.setFocus(this.extensionEditor);
|
|
1880
|
-
this.ui.requestRender();
|
|
1881
|
-
});
|
|
1882
|
-
}
|
|
1883
|
-
/**
|
|
1884
|
-
* Hide the extension editor.
|
|
1885
|
-
*/
|
|
1886
|
-
hideExtensionEditor() {
|
|
1887
|
-
this.editorContainer.clear();
|
|
1888
|
-
this.editorContainer.addChild(this.editor);
|
|
1889
|
-
this.extensionEditor = undefined;
|
|
1890
|
-
this.ui.setFocus(this.editor);
|
|
1891
|
-
this.ui.requestRender();
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
async promptForMissingSessionCwd(error) {
|
|
1014
|
+
const confirmed = await this.dialogs.confirm("Session cwd not found", formatMissingSessionCwdPrompt(error.issue));
|
|
1015
|
+
return confirmed ? error.issue.fallbackCwd : undefined;
|
|
1892
1016
|
}
|
|
1893
1017
|
/**
|
|
1894
1018
|
* Set a custom editor component from an extension.
|
|
@@ -1964,75 +1088,6 @@ export class InteractiveMode {
|
|
|
1964
1088
|
this.showStatus(message);
|
|
1965
1089
|
}
|
|
1966
1090
|
}
|
|
1967
|
-
/** Show a custom component with keyboard focus. Overlay mode renders on top of existing content. */
|
|
1968
|
-
async showExtensionCustom(factory, options) {
|
|
1969
|
-
const savedText = this.editor.getText();
|
|
1970
|
-
const isOverlay = options?.overlay ?? false;
|
|
1971
|
-
const restoreEditor = () => {
|
|
1972
|
-
this.editorContainer.clear();
|
|
1973
|
-
this.editorContainer.addChild(this.editor);
|
|
1974
|
-
this.editor.setText(savedText);
|
|
1975
|
-
this.ui.setFocus(this.editor);
|
|
1976
|
-
this.ui.requestRender();
|
|
1977
|
-
};
|
|
1978
|
-
return new Promise((resolve, reject) => {
|
|
1979
|
-
let component;
|
|
1980
|
-
let closed = false;
|
|
1981
|
-
const close = (result) => {
|
|
1982
|
-
if (closed)
|
|
1983
|
-
return;
|
|
1984
|
-
closed = true;
|
|
1985
|
-
if (isOverlay)
|
|
1986
|
-
this.ui.hideOverlay();
|
|
1987
|
-
else
|
|
1988
|
-
restoreEditor();
|
|
1989
|
-
// Note: both branches above already call requestRender
|
|
1990
|
-
resolve(result);
|
|
1991
|
-
try {
|
|
1992
|
-
component?.dispose?.();
|
|
1993
|
-
}
|
|
1994
|
-
catch {
|
|
1995
|
-
/* ignore dispose errors */
|
|
1996
|
-
}
|
|
1997
|
-
};
|
|
1998
|
-
Promise.resolve(factory(this.ui, theme, this.keybindings, close))
|
|
1999
|
-
.then((c) => {
|
|
2000
|
-
if (closed)
|
|
2001
|
-
return;
|
|
2002
|
-
component = c;
|
|
2003
|
-
if (isOverlay) {
|
|
2004
|
-
// Resolve overlay options - can be static or dynamic function
|
|
2005
|
-
const resolveOptions = () => {
|
|
2006
|
-
if (options?.overlayOptions) {
|
|
2007
|
-
const opts = typeof options.overlayOptions === "function"
|
|
2008
|
-
? options.overlayOptions()
|
|
2009
|
-
: options.overlayOptions;
|
|
2010
|
-
return opts;
|
|
2011
|
-
}
|
|
2012
|
-
// Fallback: use component's width property if available
|
|
2013
|
-
const w = component.width;
|
|
2014
|
-
return w ? { width: w } : undefined;
|
|
2015
|
-
};
|
|
2016
|
-
const handle = this.ui.showOverlay(component, resolveOptions());
|
|
2017
|
-
// Expose handle to caller for visibility control
|
|
2018
|
-
options?.onHandle?.(handle);
|
|
2019
|
-
}
|
|
2020
|
-
else {
|
|
2021
|
-
this.editorContainer.clear();
|
|
2022
|
-
this.editorContainer.addChild(component);
|
|
2023
|
-
this.ui.setFocus(component);
|
|
2024
|
-
this.ui.requestRender();
|
|
2025
|
-
}
|
|
2026
|
-
})
|
|
2027
|
-
.catch((err) => {
|
|
2028
|
-
if (closed)
|
|
2029
|
-
return;
|
|
2030
|
-
if (!isOverlay)
|
|
2031
|
-
restoreEditor();
|
|
2032
|
-
reject(err);
|
|
2033
|
-
});
|
|
2034
|
-
});
|
|
2035
|
-
}
|
|
2036
1091
|
/**
|
|
2037
1092
|
* Show an extension error in the UI.
|
|
2038
1093
|
*/
|
|
@@ -2106,9 +1161,9 @@ export class InteractiveMode {
|
|
|
2106
1161
|
this.defaultEditor.onAction("app.tasks.cycleView", () => {
|
|
2107
1162
|
this.taskPanel.cycleView();
|
|
2108
1163
|
});
|
|
2109
|
-
this.defaultEditor.onAction("app.team.focus", () => this.
|
|
1164
|
+
this.defaultEditor.onAction("app.team.focus", () => this.teamFocus.enterFocus());
|
|
2110
1165
|
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
2111
|
-
this.defaultEditor.onAction("app.input.voiceTranscribe", () => this.
|
|
1166
|
+
this.defaultEditor.onAction("app.input.voiceTranscribe", () => this.voice.toggle());
|
|
2112
1167
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2113
1168
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2114
1169
|
this.defaultEditor.onAction("app.session.new", () => this.commandExecutor.handleClear());
|
|
@@ -2153,278 +1208,183 @@ export class InteractiveMode {
|
|
|
2153
1208
|
// =========================================================================
|
|
2154
1209
|
/**
|
|
2155
1210
|
* Wire a hooteams connection into the TUI. Called by main.ts when `--team`
|
|
2156
|
-
* is set, before run().
|
|
2157
|
-
* the attach side panel (a) on the task panel's teams view, and approval
|
|
2158
|
-
* gates: task_paused events (and gates already pending on the server)
|
|
2159
|
-
* surface inline in the attach panel when it shows the paused role,
|
|
2160
|
-
* otherwise in the options pane; the answer goes back over
|
|
2161
|
-
* POST /tasks/:id/resume.
|
|
1211
|
+
* is set, before run(). See TeamFocusController for the feature itself.
|
|
2162
1212
|
*/
|
|
2163
1213
|
attachTeamClient(client) {
|
|
2164
|
-
this.
|
|
2165
|
-
const approvals = new TeamApprovalCoordinator({
|
|
2166
|
-
present: (approval, signal) => this.presentTeamApproval(approval, signal),
|
|
2167
|
-
resume: (taskId, option) => client.resume(taskId, option),
|
|
2168
|
-
info: (message) => this.showStatus(message),
|
|
2169
|
-
warn: (message) => this.showWarning(message),
|
|
2170
|
-
});
|
|
2171
|
-
client.subscribe((event) => approvals.handleEvent(event));
|
|
2172
|
-
// Gates that opened before we attached don't replay as task_paused.
|
|
2173
|
-
void client.pendingApprovals().then((pending) => {
|
|
2174
|
-
for (const gate of pending)
|
|
2175
|
-
approvals.enqueuePending(gate);
|
|
2176
|
-
}, () => {
|
|
2177
|
-
// Best-effort like the rest of the bridge; live gates still arrive via SSE.
|
|
2178
|
-
});
|
|
1214
|
+
this.teamFocus.attachClient(client);
|
|
2179
1215
|
}
|
|
2180
1216
|
/**
|
|
2181
|
-
*
|
|
2182
|
-
*
|
|
2183
|
-
*
|
|
2184
|
-
*
|
|
2185
|
-
* while another ask is on screen. Either way the signal (gate answered
|
|
2186
|
-
* elsewhere) dismisses the prompt.
|
|
1217
|
+
* Built-in slash commands dispatched by the editor submit handler.
|
|
1218
|
+
* `withArgs` commands also match "/name <args>" and receive the full text.
|
|
1219
|
+
* Each handler keeps its own editor-clearing order (before vs after the
|
|
1220
|
+
* await) — some commands must show their UI before the prompt is wiped.
|
|
2187
1221
|
*/
|
|
2188
|
-
|
|
2189
|
-
const
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
if (answer !== undefined || signal.aborted || this.teamAttachPanel === panel)
|
|
2197
|
-
return answer;
|
|
2198
|
-
}
|
|
2199
|
-
while (this.askOptions && !signal.aborted) {
|
|
2200
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
2201
|
-
}
|
|
2202
|
-
if (signal.aborted)
|
|
2203
|
-
return undefined;
|
|
2204
|
-
const answers = await this.showAskOptions([
|
|
2205
|
-
{
|
|
2206
|
-
question: approval.question,
|
|
2207
|
-
short: approval.taskId,
|
|
2208
|
-
detail: `team task "${approval.taskId}"${approval.role ? ` (${approval.role})` : ""} is paused until answered`,
|
|
2209
|
-
options: approval.options.map((label) => ({ label })),
|
|
2210
|
-
allowCustom: true,
|
|
1222
|
+
createBuiltInSlashCommands() {
|
|
1223
|
+
const clearEditor = () => this.editor.setText("");
|
|
1224
|
+
return new Map(Object.entries({
|
|
1225
|
+
"/settings": {
|
|
1226
|
+
run: () => {
|
|
1227
|
+
this.showSettingsSelector();
|
|
1228
|
+
clearEditor();
|
|
1229
|
+
},
|
|
2211
1230
|
},
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
1231
|
+
"/scoped-models": {
|
|
1232
|
+
run: async () => {
|
|
1233
|
+
clearEditor();
|
|
1234
|
+
await this.showModelsSelector();
|
|
1235
|
+
},
|
|
1236
|
+
},
|
|
1237
|
+
"/model": {
|
|
1238
|
+
withArgs: true,
|
|
1239
|
+
run: async (text) => {
|
|
1240
|
+
const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
|
|
1241
|
+
clearEditor();
|
|
1242
|
+
await this.commandExecutor.handleModel(searchTerm);
|
|
1243
|
+
},
|
|
1244
|
+
},
|
|
1245
|
+
"/export": {
|
|
1246
|
+
withArgs: true,
|
|
1247
|
+
run: async (text) => {
|
|
1248
|
+
await this.commandExecutor.handleExport(text);
|
|
1249
|
+
clearEditor();
|
|
1250
|
+
},
|
|
1251
|
+
},
|
|
1252
|
+
"/import": {
|
|
1253
|
+
withArgs: true,
|
|
1254
|
+
run: async (text) => {
|
|
1255
|
+
await this.commandExecutor.handleImport(text);
|
|
1256
|
+
clearEditor();
|
|
1257
|
+
},
|
|
1258
|
+
},
|
|
1259
|
+
"/share": {
|
|
1260
|
+
run: async () => {
|
|
1261
|
+
await this.commandExecutor.handleShare();
|
|
1262
|
+
clearEditor();
|
|
1263
|
+
},
|
|
1264
|
+
},
|
|
1265
|
+
"/copy": {
|
|
1266
|
+
run: async () => {
|
|
1267
|
+
await this.commandExecutor.handleCopy();
|
|
1268
|
+
clearEditor();
|
|
1269
|
+
},
|
|
1270
|
+
},
|
|
1271
|
+
"/name": {
|
|
1272
|
+
withArgs: true,
|
|
1273
|
+
run: (text) => {
|
|
1274
|
+
this.commandExecutor.handleName(text);
|
|
1275
|
+
clearEditor();
|
|
1276
|
+
},
|
|
1277
|
+
},
|
|
1278
|
+
"/session": {
|
|
1279
|
+
run: () => {
|
|
1280
|
+
this.commandExecutor.handleSession();
|
|
1281
|
+
clearEditor();
|
|
1282
|
+
},
|
|
1283
|
+
},
|
|
1284
|
+
"/changelog": {
|
|
1285
|
+
run: () => {
|
|
1286
|
+
this.commandExecutor.handleChangelog();
|
|
1287
|
+
clearEditor();
|
|
1288
|
+
},
|
|
1289
|
+
},
|
|
1290
|
+
"/hotkeys": {
|
|
1291
|
+
run: () => {
|
|
1292
|
+
this.commandExecutor.handleHotkeys();
|
|
1293
|
+
clearEditor();
|
|
1294
|
+
},
|
|
1295
|
+
},
|
|
1296
|
+
"/fork": {
|
|
1297
|
+
run: () => {
|
|
1298
|
+
this.showUserMessageSelector();
|
|
1299
|
+
clearEditor();
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
"/clone": {
|
|
1303
|
+
run: async () => {
|
|
1304
|
+
clearEditor();
|
|
1305
|
+
await this.commandExecutor.handleClone();
|
|
1306
|
+
},
|
|
1307
|
+
},
|
|
1308
|
+
"/tree": {
|
|
1309
|
+
run: () => {
|
|
1310
|
+
this.showTreeSelector();
|
|
1311
|
+
clearEditor();
|
|
1312
|
+
},
|
|
1313
|
+
},
|
|
1314
|
+
"/login": {
|
|
1315
|
+
run: () => {
|
|
1316
|
+
this.showOAuthSelector("login");
|
|
1317
|
+
clearEditor();
|
|
1318
|
+
},
|
|
1319
|
+
},
|
|
1320
|
+
"/logout": {
|
|
1321
|
+
run: () => {
|
|
1322
|
+
this.showOAuthSelector("logout");
|
|
1323
|
+
clearEditor();
|
|
1324
|
+
},
|
|
1325
|
+
},
|
|
1326
|
+
"/new": {
|
|
1327
|
+
run: async () => {
|
|
1328
|
+
clearEditor();
|
|
1329
|
+
await this.commandExecutor.handleClear();
|
|
1330
|
+
},
|
|
1331
|
+
},
|
|
1332
|
+
"/compact": {
|
|
1333
|
+
withArgs: true,
|
|
1334
|
+
run: async (text) => {
|
|
1335
|
+
const prefix = "/compact ";
|
|
1336
|
+
const customInstructions = text.startsWith(prefix)
|
|
1337
|
+
? text.slice(prefix.length).trim() || undefined
|
|
1338
|
+
: undefined;
|
|
1339
|
+
clearEditor();
|
|
1340
|
+
await this.handleCompactCommand(customInstructions);
|
|
1341
|
+
},
|
|
1342
|
+
},
|
|
1343
|
+
"/reload": {
|
|
1344
|
+
run: async () => {
|
|
1345
|
+
clearEditor();
|
|
1346
|
+
await this.handleReloadCommand();
|
|
1347
|
+
},
|
|
1348
|
+
},
|
|
1349
|
+
"/debug": {
|
|
1350
|
+
run: () => {
|
|
1351
|
+
this.commandExecutor.handleDebug();
|
|
1352
|
+
clearEditor();
|
|
1353
|
+
},
|
|
1354
|
+
},
|
|
1355
|
+
"/resume": {
|
|
1356
|
+
run: () => {
|
|
1357
|
+
this.showSessionSelector();
|
|
1358
|
+
clearEditor();
|
|
1359
|
+
},
|
|
1360
|
+
},
|
|
1361
|
+
"/quit": {
|
|
1362
|
+
run: async () => {
|
|
1363
|
+
clearEditor();
|
|
1364
|
+
await this.shutdown();
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
"/subagent": {
|
|
1368
|
+
withArgs: true,
|
|
1369
|
+
run: async (text) => {
|
|
1370
|
+
clearEditor();
|
|
1371
|
+
await this.commandExecutor.handleSubagent(text);
|
|
1372
|
+
},
|
|
1373
|
+
},
|
|
1374
|
+
}));
|
|
2298
1375
|
}
|
|
2299
1376
|
setupEditorSubmitHandler() {
|
|
1377
|
+
const slashCommands = this.createBuiltInSlashCommands();
|
|
2300
1378
|
this.defaultEditor.onSubmit = async (text) => {
|
|
2301
1379
|
text = text.trim();
|
|
2302
1380
|
if (!text)
|
|
2303
1381
|
return;
|
|
2304
|
-
// Handle commands
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
if (text === "/scoped-models") {
|
|
2311
|
-
this.editor.setText("");
|
|
2312
|
-
await this.showModelsSelector();
|
|
2313
|
-
return;
|
|
2314
|
-
}
|
|
2315
|
-
if (text === "/model" || text.startsWith("/model ")) {
|
|
2316
|
-
const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
|
|
2317
|
-
this.editor.setText("");
|
|
2318
|
-
await this.commandExecutor.handleModel(searchTerm);
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
if (text === "/export" || text.startsWith("/export ")) {
|
|
2322
|
-
await this.commandExecutor.handleExport(text);
|
|
2323
|
-
this.editor.setText("");
|
|
2324
|
-
return;
|
|
2325
|
-
}
|
|
2326
|
-
if (text === "/import" || text.startsWith("/import ")) {
|
|
2327
|
-
await this.commandExecutor.handleImport(text);
|
|
2328
|
-
this.editor.setText("");
|
|
2329
|
-
return;
|
|
2330
|
-
}
|
|
2331
|
-
if (text === "/share") {
|
|
2332
|
-
await this.commandExecutor.handleShare();
|
|
2333
|
-
this.editor.setText("");
|
|
2334
|
-
return;
|
|
2335
|
-
}
|
|
2336
|
-
if (text === "/copy") {
|
|
2337
|
-
await this.commandExecutor.handleCopy();
|
|
2338
|
-
this.editor.setText("");
|
|
2339
|
-
return;
|
|
2340
|
-
}
|
|
2341
|
-
if (text === "/name" || text.startsWith("/name ")) {
|
|
2342
|
-
this.commandExecutor.handleName(text);
|
|
2343
|
-
this.editor.setText("");
|
|
2344
|
-
return;
|
|
2345
|
-
}
|
|
2346
|
-
if (text === "/session") {
|
|
2347
|
-
this.commandExecutor.handleSession();
|
|
2348
|
-
this.editor.setText("");
|
|
2349
|
-
return;
|
|
2350
|
-
}
|
|
2351
|
-
if (text === "/changelog") {
|
|
2352
|
-
this.commandExecutor.handleChangelog();
|
|
2353
|
-
this.editor.setText("");
|
|
2354
|
-
return;
|
|
2355
|
-
}
|
|
2356
|
-
if (text === "/hotkeys") {
|
|
2357
|
-
this.commandExecutor.handleHotkeys();
|
|
2358
|
-
this.editor.setText("");
|
|
2359
|
-
return;
|
|
2360
|
-
}
|
|
2361
|
-
if (text === "/fork") {
|
|
2362
|
-
this.showUserMessageSelector();
|
|
2363
|
-
this.editor.setText("");
|
|
2364
|
-
return;
|
|
2365
|
-
}
|
|
2366
|
-
if (text === "/clone") {
|
|
2367
|
-
this.editor.setText("");
|
|
2368
|
-
await this.commandExecutor.handleClone();
|
|
2369
|
-
return;
|
|
2370
|
-
}
|
|
2371
|
-
if (text === "/tree") {
|
|
2372
|
-
this.showTreeSelector();
|
|
2373
|
-
this.editor.setText("");
|
|
2374
|
-
return;
|
|
2375
|
-
}
|
|
2376
|
-
if (text === "/login") {
|
|
2377
|
-
this.showOAuthSelector("login");
|
|
2378
|
-
this.editor.setText("");
|
|
2379
|
-
return;
|
|
2380
|
-
}
|
|
2381
|
-
if (text === "/logout") {
|
|
2382
|
-
this.showOAuthSelector("logout");
|
|
2383
|
-
this.editor.setText("");
|
|
2384
|
-
return;
|
|
2385
|
-
}
|
|
2386
|
-
if (text === "/new") {
|
|
2387
|
-
this.editor.setText("");
|
|
2388
|
-
await this.commandExecutor.handleClear();
|
|
2389
|
-
return;
|
|
2390
|
-
}
|
|
2391
|
-
if (text === "/compact" || text.startsWith("/compact ")) {
|
|
2392
|
-
const prefix = "/compact ";
|
|
2393
|
-
const customInstructions = text.startsWith(prefix)
|
|
2394
|
-
? text.slice(prefix.length).trim() || undefined
|
|
2395
|
-
: undefined;
|
|
2396
|
-
this.editor.setText("");
|
|
2397
|
-
await this.handleCompactCommand(customInstructions);
|
|
2398
|
-
return;
|
|
2399
|
-
}
|
|
2400
|
-
if (text === "/reload") {
|
|
2401
|
-
this.editor.setText("");
|
|
2402
|
-
await this.handleReloadCommand();
|
|
2403
|
-
return;
|
|
2404
|
-
}
|
|
2405
|
-
if (text === "/debug") {
|
|
2406
|
-
this.commandExecutor.handleDebug();
|
|
2407
|
-
this.editor.setText("");
|
|
2408
|
-
return;
|
|
2409
|
-
}
|
|
2410
|
-
if (text === "/arminsayshi") {
|
|
2411
|
-
this.commandExecutor.handleArminSaysHi();
|
|
2412
|
-
this.editor.setText("");
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
if (text === "/resume") {
|
|
2416
|
-
this.showSessionSelector();
|
|
2417
|
-
this.editor.setText("");
|
|
2418
|
-
return;
|
|
2419
|
-
}
|
|
2420
|
-
if (text === "/quit") {
|
|
2421
|
-
this.editor.setText("");
|
|
2422
|
-
await this.shutdown();
|
|
2423
|
-
return;
|
|
2424
|
-
}
|
|
2425
|
-
if (text === "/subagent" || text.startsWith("/subagent ")) {
|
|
2426
|
-
this.editor.setText("");
|
|
2427
|
-
await this.commandExecutor.handleSubagent(text);
|
|
1382
|
+
// Handle built-in slash commands
|
|
1383
|
+
const spaceIdx = text.indexOf(" ");
|
|
1384
|
+
const commandName = spaceIdx === -1 ? text : text.slice(0, spaceIdx);
|
|
1385
|
+
const command = slashCommands.get(commandName);
|
|
1386
|
+
if (command && (spaceIdx === -1 || command.withArgs)) {
|
|
1387
|
+
await command.run(text);
|
|
2428
1388
|
return;
|
|
2429
1389
|
}
|
|
2430
1390
|
// Handle bash command (! for normal, !! for excluded from context)
|
|
@@ -2784,266 +1744,6 @@ export class InteractiveMode {
|
|
|
2784
1744
|
* If multiple status messages are emitted back-to-back (without anything else being added to the chat),
|
|
2785
1745
|
* we update the previous status line instead of appending new ones to avoid log spam.
|
|
2786
1746
|
*/
|
|
2787
|
-
/**
|
|
2788
|
-
* Toggle voice-to-text capture.
|
|
2789
|
-
*
|
|
2790
|
-
* Prefers a persistent `voicetools serve` daemon: the first press probes for
|
|
2791
|
-
* support and (if found) loads models once, showing a "warming up" spinner;
|
|
2792
|
-
* every later press reuses the already-warm daemon and jumps straight to
|
|
2793
|
-
* Listening. Binaries without `serve` support fall back to spawning
|
|
2794
|
-
* `voicetools transcribe` per press, same as before. Pressing the shortcut
|
|
2795
|
-
* again while listening (or while still warming up) cancels.
|
|
2796
|
-
*/
|
|
2797
|
-
toggleVoiceTranscribe() {
|
|
2798
|
-
if (this.voiceActive) {
|
|
2799
|
-
if (this.voiceDaemon?.isReady) {
|
|
2800
|
-
this.voiceDaemon.cancel();
|
|
2801
|
-
}
|
|
2802
|
-
else {
|
|
2803
|
-
this.voiceSession?.stop();
|
|
2804
|
-
}
|
|
2805
|
-
this.voiceSession = undefined;
|
|
2806
|
-
this.voiceActive = false;
|
|
2807
|
-
this.resetVoiceUI();
|
|
2808
|
-
return;
|
|
2809
|
-
}
|
|
2810
|
-
if (this.voiceStarting) {
|
|
2811
|
-
// A second press while resolving/warming up: honour the cancel. Any
|
|
2812
|
-
// daemon that finishes loading afterwards is kept warm for next time.
|
|
2813
|
-
this.voiceStarting = false;
|
|
2814
|
-
this.resetVoiceUI();
|
|
2815
|
-
return;
|
|
2816
|
-
}
|
|
2817
|
-
if (this.voiceDaemonUnsupported) {
|
|
2818
|
-
this.voiceStarting = true;
|
|
2819
|
-
this.showVoiceWarming("Starting voice input...");
|
|
2820
|
-
void this.resolveVoiceBin()
|
|
2821
|
-
.then((bin) => {
|
|
2822
|
-
if (!this.voiceStarting)
|
|
2823
|
-
return;
|
|
2824
|
-
this.voiceStarting = false;
|
|
2825
|
-
if (!bin) {
|
|
2826
|
-
this.resetVoiceUI();
|
|
2827
|
-
this.showError(VOICE_UNAVAILABLE_MESSAGE);
|
|
2828
|
-
return;
|
|
2829
|
-
}
|
|
2830
|
-
this.beginLegacyVoiceCapture(bin);
|
|
2831
|
-
})
|
|
2832
|
-
.catch((err) => {
|
|
2833
|
-
this.voiceStarting = false;
|
|
2834
|
-
this.resetVoiceUI();
|
|
2835
|
-
this.showError(`Voice input failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2836
|
-
});
|
|
2837
|
-
return;
|
|
2838
|
-
}
|
|
2839
|
-
if (this.voiceDaemon?.isReady) {
|
|
2840
|
-
this.beginDaemonVoiceCapture();
|
|
2841
|
-
return;
|
|
2842
|
-
}
|
|
2843
|
-
// No daemon yet: resolve the binary, then probe for `serve` support by
|
|
2844
|
-
// spawning it. `VoiceDaemon.spawn` doubles as the probe: it resolves to a
|
|
2845
|
-
// live daemon once READY arrives, to "unsupported" if the process exits
|
|
2846
|
-
// with no output at all (an old binary rejecting the unrecognized `serve`
|
|
2847
|
-
// subcommand), or to "error" if it printed a real ERROR first (e.g. no
|
|
2848
|
-
// model installed yet) — already surfaced via onError, so that case skips
|
|
2849
|
-
// the legacy fallback (it would just hit the same error) but leaves
|
|
2850
|
-
// daemon mode available to retry on the next press.
|
|
2851
|
-
this.voiceStarting = true;
|
|
2852
|
-
this.showVoiceWarming("Warming up voice input...");
|
|
2853
|
-
void this.resolveVoiceBin()
|
|
2854
|
-
.then(async (bin) => {
|
|
2855
|
-
if (!bin) {
|
|
2856
|
-
this.voiceStarting = false;
|
|
2857
|
-
this.resetVoiceUI();
|
|
2858
|
-
this.showError(VOICE_UNAVAILABLE_MESSAGE);
|
|
2859
|
-
return;
|
|
2860
|
-
}
|
|
2861
|
-
const result = await VoiceDaemon.spawn(bin, this.buildVoiceDaemonHandlers(), {
|
|
2862
|
-
silenceMs: VOICE_SILENCE_MS,
|
|
2863
|
-
idleTimeoutMs: VOICE_IDLE_TIMEOUT_MS,
|
|
2864
|
-
});
|
|
2865
|
-
if (!result.ok) {
|
|
2866
|
-
if (result.reason === "unsupported") {
|
|
2867
|
-
this.voiceDaemonUnsupported = true;
|
|
2868
|
-
if (!this.voiceStarting) {
|
|
2869
|
-
this.resetVoiceUI();
|
|
2870
|
-
return;
|
|
2871
|
-
}
|
|
2872
|
-
this.voiceStarting = false;
|
|
2873
|
-
this.beginLegacyVoiceCapture(bin);
|
|
2874
|
-
return;
|
|
2875
|
-
}
|
|
2876
|
-
this.voiceStarting = false;
|
|
2877
|
-
this.resetVoiceUI();
|
|
2878
|
-
return;
|
|
2879
|
-
}
|
|
2880
|
-
this.voiceDaemon = result.daemon;
|
|
2881
|
-
if (!this.voiceStarting) {
|
|
2882
|
-
// Cancelled while warming up: keep the loaded daemon warm for next time.
|
|
2883
|
-
this.resetVoiceUI();
|
|
2884
|
-
return;
|
|
2885
|
-
}
|
|
2886
|
-
this.voiceStarting = false;
|
|
2887
|
-
this.beginDaemonVoiceCapture();
|
|
2888
|
-
})
|
|
2889
|
-
.catch((err) => {
|
|
2890
|
-
this.voiceStarting = false;
|
|
2891
|
-
this.resetVoiceUI();
|
|
2892
|
-
this.showError(`Voice input failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2893
|
-
});
|
|
2894
|
-
}
|
|
2895
|
-
/**
|
|
2896
|
-
* Build the (stable, reused-across-captures) handlers for the daemon.
|
|
2897
|
-
*
|
|
2898
|
-
* Every handler bails out once `voiceActive` is false: CANCEL is a soft
|
|
2899
|
-
* request (unlike the legacy path's `proc.kill()`, it doesn't sever the
|
|
2900
|
-
* pipe), so the daemon can still have a trailing PARTIAL/FINAL/DONE for the
|
|
2901
|
-
* just-cancelled capture in flight when the user presses cancel. Without
|
|
2902
|
-
* this guard that stale text would land in the editor after the panel had
|
|
2903
|
-
* already collapsed.
|
|
2904
|
-
*
|
|
2905
|
-
* v0.1.4 serve streams `PARTIAL <full growing hypothesis>` (live preview,
|
|
2906
|
-
* never committed) and ends with a single `FINAL <complete text>` (the one
|
|
2907
|
-
* commit to the editor). `SEGMENT` is still handled for the legacy
|
|
2908
|
-
* transcribe path and any binary that streams committed chunks directly.
|
|
2909
|
-
*/
|
|
2910
|
-
buildVoiceDaemonHandlers() {
|
|
2911
|
-
return {
|
|
2912
|
-
onSegment: (text) => {
|
|
2913
|
-
if (!this.voiceActive)
|
|
2914
|
-
return;
|
|
2915
|
-
this.commitVoiceText(text);
|
|
2916
|
-
},
|
|
2917
|
-
onPartial: (text) => {
|
|
2918
|
-
if (!this.voiceActive)
|
|
2919
|
-
return;
|
|
2920
|
-
this.voicePanel?.setPartial(text);
|
|
2921
|
-
},
|
|
2922
|
-
onFinal: (text) => {
|
|
2923
|
-
if (!this.voiceActive)
|
|
2924
|
-
return;
|
|
2925
|
-
this.commitVoiceText(text);
|
|
2926
|
-
},
|
|
2927
|
-
onStatus: (status) => {
|
|
2928
|
-
if (!this.voiceActive)
|
|
2929
|
-
return;
|
|
2930
|
-
if (status === "done") {
|
|
2931
|
-
this.voiceActive = false;
|
|
2932
|
-
this.resetVoiceUI();
|
|
2933
|
-
return;
|
|
2934
|
-
}
|
|
2935
|
-
if (status === "transcribing") {
|
|
2936
|
-
this.voicePanel?.setTranscribing();
|
|
2937
|
-
}
|
|
2938
|
-
else if (status === "listening") {
|
|
2939
|
-
this.voicePanel?.startListening();
|
|
2940
|
-
}
|
|
2941
|
-
},
|
|
2942
|
-
onLevel: (rms) => {
|
|
2943
|
-
if (!this.voiceActive)
|
|
2944
|
-
return;
|
|
2945
|
-
this.voicePanel?.pushLevel(rms);
|
|
2946
|
-
},
|
|
2947
|
-
onPhase: (phase) => {
|
|
2948
|
-
if (!this.voiceActive)
|
|
2949
|
-
return;
|
|
2950
|
-
if (phase === "silence") {
|
|
2951
|
-
this.voicePanel?.beginSilence(VOICE_SILENCE_MS);
|
|
2952
|
-
}
|
|
2953
|
-
else {
|
|
2954
|
-
this.voicePanel?.endSilence();
|
|
2955
|
-
}
|
|
2956
|
-
},
|
|
2957
|
-
onError: (message) => {
|
|
2958
|
-
this.voiceActive = false;
|
|
2959
|
-
this.resetVoiceUI();
|
|
2960
|
-
this.showError(`Voice input failed: ${message}`);
|
|
2961
|
-
},
|
|
2962
|
-
onCrash: (message) => {
|
|
2963
|
-
this.voiceActive = false;
|
|
2964
|
-
this.voiceDaemon = undefined;
|
|
2965
|
-
this.resetVoiceUI();
|
|
2966
|
-
this.showError(`Voice input daemon crashed: ${message}. It will restart on next use.`);
|
|
2967
|
-
},
|
|
2968
|
-
onIdle: () => {
|
|
2969
|
-
// Daemon auto-shut down after idle timeout: drop the reference so the
|
|
2970
|
-
// next ctrl+r respawns (cold start). No user-facing message — this is
|
|
2971
|
-
// an expected memory-reclamation event, not an error.
|
|
2972
|
-
this.voiceDaemon = undefined;
|
|
2973
|
-
},
|
|
2974
|
-
};
|
|
2975
|
-
}
|
|
2976
|
-
/** Inject decoded text into the editor via bracketed paste (with a trailing space). */
|
|
2977
|
-
commitVoiceText(text) {
|
|
2978
|
-
this.editor.handleInput(`\x1b[200~${text} \x1b[201~`);
|
|
2979
|
-
}
|
|
2980
|
-
beginDaemonVoiceCapture() {
|
|
2981
|
-
if (!this.voiceDaemon?.isReady)
|
|
2982
|
-
return;
|
|
2983
|
-
this.voiceActive = true;
|
|
2984
|
-
this.showVoicePanel().startListening();
|
|
2985
|
-
this.voiceDaemon.startCapture();
|
|
2986
|
-
}
|
|
2987
|
-
beginLegacyVoiceCapture(bin) {
|
|
2988
|
-
this.voiceActive = true;
|
|
2989
|
-
const panel = this.showVoicePanel();
|
|
2990
|
-
panel.startListening();
|
|
2991
|
-
this.voiceSession = startVoiceTranscribe(bin, {
|
|
2992
|
-
onStatus: (status) => {
|
|
2993
|
-
if (status === "done") {
|
|
2994
|
-
this.voiceActive = false;
|
|
2995
|
-
this.voiceSession = undefined;
|
|
2996
|
-
this.resetVoiceUI();
|
|
2997
|
-
return;
|
|
2998
|
-
}
|
|
2999
|
-
// Old binaries emit no LEVEL/PARTIAL, so the panel shows a spinner
|
|
3000
|
-
// for the batch phases; committed words still stream into the editor.
|
|
3001
|
-
if (status === "transcribing")
|
|
3002
|
-
panel.setTranscribing();
|
|
3003
|
-
},
|
|
3004
|
-
onSegment: (text) => {
|
|
3005
|
-
this.commitVoiceText(text);
|
|
3006
|
-
},
|
|
3007
|
-
onError: (message) => {
|
|
3008
|
-
this.voiceActive = false;
|
|
3009
|
-
this.voiceSession = undefined;
|
|
3010
|
-
this.resetVoiceUI();
|
|
3011
|
-
this.showError(`Voice input failed: ${message}`);
|
|
3012
|
-
},
|
|
3013
|
-
});
|
|
3014
|
-
}
|
|
3015
|
-
/** Resolve the `voicetools` binary path. Prefers an explicit VOICETOOLS_BIN
|
|
3016
|
-
* override, otherwise resolves via the managed tools manager (bin dir / PATH /
|
|
3017
|
-
* download from the published release). Returns undefined when unavailable.
|
|
3018
|
-
*/
|
|
3019
|
-
async resolveVoiceBin() {
|
|
3020
|
-
const override = process.env.VOICETOOLS_BIN?.trim();
|
|
3021
|
-
if (override)
|
|
3022
|
-
return override;
|
|
3023
|
-
return ensureTool("voicetools", true);
|
|
3024
|
-
}
|
|
3025
|
-
/** Create (or reuse) the voice panel and mount it in the status container. */
|
|
3026
|
-
showVoicePanel() {
|
|
3027
|
-
if (!this.voicePanel) {
|
|
3028
|
-
this.statusContainer.clear();
|
|
3029
|
-
this.voicePanel = new VoicePanel(this.ui, keyHint("app.input.voiceTranscribe", "cancel"));
|
|
3030
|
-
this.statusContainer.addChild(this.voicePanel);
|
|
3031
|
-
}
|
|
3032
|
-
this.ui.requestRender();
|
|
3033
|
-
return this.voicePanel;
|
|
3034
|
-
}
|
|
3035
|
-
showVoiceWarming(message) {
|
|
3036
|
-
this.showVoicePanel().setWarming(message);
|
|
3037
|
-
}
|
|
3038
|
-
/** Collapse the voice panel back to nothing (idle) and stop its animation. */
|
|
3039
|
-
resetVoiceUI() {
|
|
3040
|
-
if (this.voicePanel) {
|
|
3041
|
-
this.voicePanel.dispose();
|
|
3042
|
-
this.voicePanel = undefined;
|
|
3043
|
-
}
|
|
3044
|
-
this.statusContainer.clear();
|
|
3045
|
-
this.ui.requestRender();
|
|
3046
|
-
}
|
|
3047
1747
|
showStatus(message) {
|
|
3048
1748
|
const children = this.chatContainer.children;
|
|
3049
1749
|
const last = children.length > 0 ? children[children.length - 1] : undefined;
|
|
@@ -3261,7 +1961,7 @@ export class InteractiveMode {
|
|
|
3261
1961
|
// This prevents escape sequences from leaking to the parent shell over slow SSH.
|
|
3262
1962
|
await this.ui.terminal.drainInput(1000);
|
|
3263
1963
|
this.taskStoreUnsubscribe?.();
|
|
3264
|
-
this.
|
|
1964
|
+
this.teamFocus.closeAttach();
|
|
3265
1965
|
this.taskPanel.dispose();
|
|
3266
1966
|
this.stop();
|
|
3267
1967
|
await this.runtimeHost.dispose();
|
|
@@ -3445,7 +2145,7 @@ export class InteractiveMode {
|
|
|
3445
2145
|
}
|
|
3446
2146
|
setToolsExpanded(expanded) {
|
|
3447
2147
|
this.toolOutputExpanded = expanded;
|
|
3448
|
-
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
2148
|
+
const activeHeader = this.chrome.customHeader ?? this.builtInHeader;
|
|
3449
2149
|
if (isExpandable(activeHeader)) {
|
|
3450
2150
|
activeHeader.setExpanded(expanded);
|
|
3451
2151
|
}
|
|
@@ -3946,7 +2646,6 @@ export class InteractiveMode {
|
|
|
3946
2646
|
done();
|
|
3947
2647
|
this.showStatus(`Model: ${model.id}`);
|
|
3948
2648
|
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
|
|
3949
|
-
this.checkDaxnutsEasterEgg(model);
|
|
3950
2649
|
}
|
|
3951
2650
|
catch (error) {
|
|
3952
2651
|
done();
|
|
@@ -4081,7 +2780,7 @@ export class InteractiveMode {
|
|
|
4081
2780
|
// Check if we should skip the prompt (user preference to always default to no summary)
|
|
4082
2781
|
if (!this.settingsManager.getBranchSummarySkipPrompt()) {
|
|
4083
2782
|
while (true) {
|
|
4084
|
-
const summaryChoice = await this.
|
|
2783
|
+
const summaryChoice = await this.dialogs.showSelector("Summarize branch?", [
|
|
4085
2784
|
"No summary",
|
|
4086
2785
|
"Summarize",
|
|
4087
2786
|
"Summarize with custom prompt",
|
|
@@ -4093,7 +2792,7 @@ export class InteractiveMode {
|
|
|
4093
2792
|
}
|
|
4094
2793
|
wantsSummary = summaryChoice !== "No summary";
|
|
4095
2794
|
if (summaryChoice === "Summarize with custom prompt") {
|
|
4096
|
-
customInstructions = await this.
|
|
2795
|
+
customInstructions = await this.dialogs.showEditor("Custom summarization instructions");
|
|
4097
2796
|
if (customInstructions === undefined) {
|
|
4098
2797
|
// User cancelled - loop back to summary selector
|
|
4099
2798
|
continue;
|
|
@@ -4378,7 +3077,6 @@ export class InteractiveMode {
|
|
|
4378
3077
|
if (selectedModel) {
|
|
4379
3078
|
this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
|
|
4380
3079
|
void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
|
|
4381
|
-
this.checkDaxnutsEasterEgg(selectedModel);
|
|
4382
3080
|
}
|
|
4383
3081
|
else {
|
|
4384
3082
|
this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
|
|
@@ -4577,7 +3275,7 @@ export class InteractiveMode {
|
|
|
4577
3275
|
try {
|
|
4578
3276
|
await this.session.reload();
|
|
4579
3277
|
this.keybindings.reload();
|
|
4580
|
-
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
3278
|
+
const activeHeader = this.chrome.customHeader ?? this.builtInHeader;
|
|
4581
3279
|
if (isExpandable(activeHeader)) {
|
|
4582
3280
|
activeHeader.setExpanded(this.toolOutputExpanded);
|
|
4583
3281
|
}
|
|
@@ -4624,16 +3322,6 @@ export class InteractiveMode {
|
|
|
4624
3322
|
getAppKeyDisplay(action) {
|
|
4625
3323
|
return keyDisplayText(action);
|
|
4626
3324
|
}
|
|
4627
|
-
handleDaxnuts() {
|
|
4628
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4629
|
-
this.chatContainer.addChild(new DaxnutsComponent(this.ui));
|
|
4630
|
-
this.ui.requestRender();
|
|
4631
|
-
}
|
|
4632
|
-
checkDaxnutsEasterEgg(model) {
|
|
4633
|
-
if (model.provider === "opencode" && model.id.toLowerCase().includes("kimi-k2.5")) {
|
|
4634
|
-
this.handleDaxnuts();
|
|
4635
|
-
}
|
|
4636
|
-
}
|
|
4637
3325
|
async handleBashCommand(command, excludeFromContext = false) {
|
|
4638
3326
|
const extensionRunner = this.session.extensionRunner;
|
|
4639
3327
|
// Emit user_bash event to let extensions intercept
|
|
@@ -4718,18 +3406,7 @@ export class InteractiveMode {
|
|
|
4718
3406
|
}
|
|
4719
3407
|
stop() {
|
|
4720
3408
|
this.unregisterSignalHandlers();
|
|
4721
|
-
|
|
4722
|
-
this.voiceSession.stop();
|
|
4723
|
-
this.voiceSession = undefined;
|
|
4724
|
-
}
|
|
4725
|
-
this.voiceDaemon?.shutdown();
|
|
4726
|
-
this.voiceDaemon = undefined;
|
|
4727
|
-
if (this.voicePanel) {
|
|
4728
|
-
this.voicePanel.dispose();
|
|
4729
|
-
this.voicePanel = undefined;
|
|
4730
|
-
}
|
|
4731
|
-
this.voiceActive = false;
|
|
4732
|
-
this.voiceStarting = false;
|
|
3409
|
+
this.voice.dispose();
|
|
4733
3410
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
4734
3411
|
this.ui.terminal.setProgress(false);
|
|
4735
3412
|
}
|