@earendil-works/pi-coding-agent 0.82.1 → 0.84.0
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 +243 -0
- package/README.md +6 -1
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +27 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +2 -2
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/credential-print.d.ts +23 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +117 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/experimental/auth.d.ts +16 -0
- package/dist/cli/experimental/auth.d.ts.map +1 -0
- package/dist/cli/experimental/auth.js +13 -0
- package/dist/cli/experimental/auth.js.map +1 -0
- package/dist/cli/experimental/cli.d.ts +6 -0
- package/dist/cli/experimental/cli.d.ts.map +1 -0
- package/dist/cli/experimental/cli.js +5 -0
- package/dist/cli/experimental/cli.js.map +1 -0
- package/dist/cli/experimental/command-options.d.ts +17 -0
- package/dist/cli/experimental/command-options.d.ts.map +1 -0
- package/dist/cli/experimental/command-options.js +35 -0
- package/dist/cli/experimental/command-options.js.map +1 -0
- package/dist/cli/experimental/command.d.ts +63 -0
- package/dist/cli/experimental/command.d.ts.map +1 -0
- package/dist/cli/experimental/command.js +130 -0
- package/dist/cli/experimental/command.js.map +1 -0
- package/dist/cli/experimental/commands/client.d.ts +13 -0
- package/dist/cli/experimental/commands/client.d.ts.map +1 -0
- package/dist/cli/experimental/commands/client.js +25 -0
- package/dist/cli/experimental/commands/client.js.map +1 -0
- package/dist/cli/experimental/commands/pi.d.ts +15 -0
- package/dist/cli/experimental/commands/pi.d.ts.map +1 -0
- package/dist/cli/experimental/commands/pi.js +28 -0
- package/dist/cli/experimental/commands/pi.js.map +1 -0
- package/dist/cli/experimental/commands/server.d.ts +13 -0
- package/dist/cli/experimental/commands/server.d.ts.map +1 -0
- package/dist/cli/experimental/commands/server.js +25 -0
- package/dist/cli/experimental/commands/server.js.map +1 -0
- package/dist/cli/experimental/transport-address.d.ts +10 -0
- package/dist/cli/experimental/transport-address.d.ts.map +1 -0
- package/dist/cli/experimental/transport-address.js +38 -0
- package/dist/cli/experimental/transport-address.js.map +1 -0
- package/dist/cli/list-models.d.ts +1 -1
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +2 -2
- package/dist/cli/list-models.js.map +1 -1
- package/dist/cli/startup-ui.d.ts +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +2 -2
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/remote-session.d.ts +53 -0
- package/dist/client/remote-session.d.ts.map +1 -0
- package/dist/client/remote-session.js +340 -0
- package/dist/client/remote-session.js.map +1 -0
- package/dist/client/transcript.d.ts +12 -0
- package/dist/client/transcript.d.ts.map +1 -0
- package/dist/client/transcript.js +98 -0
- package/dist/client/transcript.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -11
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +85 -76
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +15 -9
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +159 -38
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +43 -20
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +10 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +28 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +5 -0
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/keybindings.d.ts +36 -4
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/model-config.d.ts +29 -5
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +4 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -3
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +13 -10
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +4 -3
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +41 -10
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +30 -13
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +231 -73
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/models-store.d.ts +12 -7
- package/dist/core/models-store.d.ts.map +1 -1
- package/dist/core/models-store.js +83 -14
- package/dist/core/models-store.js.map +1 -1
- package/dist/core/package-manager.d.ts +4 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +76 -42
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/pi-manifest.d.ts +8 -0
- package/dist/core/pi-manifest.d.ts.map +1 -0
- package/dist/core/pi-manifest.js +25 -0
- package/dist/core/pi-manifest.js.map +1 -0
- package/dist/core/provider-composer.d.ts +4 -1
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +36 -15
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +73 -67
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts +15 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +64 -10
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-credentials.d.ts +5 -5
- package/dist/core/runtime-credentials.d.ts.map +1 -1
- package/dist/core/runtime-credentials.js +11 -8
- package/dist/core/runtime-credentials.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +3 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +12 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +40 -16
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/find.d.ts +3 -0
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +11 -18
- package/dist/core/tools/find.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/llama/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +11 -2
- package/dist/extensions/llama/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +22 -8
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +57 -5
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +5 -2
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +13 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +7 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts +3 -0
- package/dist/modes/interactive/components/markdown-transform.d.ts.map +1 -0
- package/dist/modes/interactive/components/markdown-transform.js +19 -0
- package/dist/modes/interactive/components/markdown-transform.js.map +1 -0
- package/dist/modes/interactive/components/mermaid.d.ts +11 -0
- package/dist/modes/interactive/components/mermaid.d.ts.map +1 -0
- package/dist/modes/interactive/components/mermaid.js +75 -0
- package/dist/modes/interactive/components/mermaid.js.map +1 -0
- package/dist/modes/interactive/components/model-selector.d.ts +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +20 -5
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +4 -0
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +26 -0
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +8 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +30 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +3 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +9 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +33 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +443 -144
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -0
- package/dist/modes/interactive/theme/light.json +1 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +3 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +10 -1
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +5 -1
- package/dist/modes/interactive/theme/theme.d.ts +2 -2
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +13 -3
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +28 -0
- package/dist/modes/json-event.d.ts.map +1 -0
- package/dist/modes/json-event.js +12 -0
- package/dist/modes/json-event.js.map +1 -0
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +12 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +5 -4
- 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-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +17 -3
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +11 -9
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/rpc-entry.d.ts.map +1 -1
- package/dist/rpc-entry.js +1 -0
- package/dist/rpc-entry.js.map +1 -1
- package/dist/utils/abort.d.ts +5 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +48 -0
- package/dist/utils/abort.js.map +1 -0
- package/dist/utils/clipboard.d.ts +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +22 -2
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/management-http.d.ts +23 -0
- package/dist/utils/management-http.d.ts.map +1 -0
- package/dist/utils/management-http.js +50 -0
- package/dist/utils/management-http.js.map +1 -0
- package/dist/utils/paths.d.ts +3 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +23 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/tool-result-images.d.ts +19 -0
- package/dist/utils/tool-result-images.d.ts.map +1 -0
- package/dist/utils/tool-result-images.js +45 -0
- package/dist/utils/tool-result-images.js.map +1 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +4 -6
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +4 -0
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +20 -2
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +2 -2
- package/docs/custom-provider.md +16 -8
- package/docs/extensions.md +30 -4
- package/docs/json.md +19 -14
- package/docs/keybindings.md +38 -10
- package/docs/models.md +24 -3
- package/docs/providers.md +2 -0
- package/docs/quickstart.md +2 -0
- package/docs/rpc.md +13 -11
- package/docs/sdk.md +21 -2
- package/docs/security.md +1 -1
- package/docs/session-format.md +2 -0
- package/docs/settings.md +4 -1
- package/docs/themes.md +5 -3
- package/docs/usage.md +7 -0
- package/examples/extensions/custom-compaction.ts +1 -16
- package/examples/extensions/custom-provider-anthropic/index.ts +10 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/doom-overlay/wad-finder.ts +7 -3
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/handoff.ts +2 -11
- package/examples/extensions/qna.ts +3 -7
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/summarize.ts +7 -18
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/rpc-extension-ui.ts +11 -2
- package/examples/sdk/02-custom-model.ts +1 -2
- package/examples/sdk/09-api-keys-and-oauth.ts +1 -1
- package/examples/sdk/12-full-control.ts +3 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +70 -26
- package/package.json +14 -7
|
@@ -6,7 +6,8 @@ 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
|
|
9
|
+
import * as TuiLayouts from "@earendil-works/pi-tui";
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TuiAltScreen, TuiMainScreen, visibleWidth, } from "@earendil-works/pi-tui";
|
|
10
11
|
import chalk from "chalk";
|
|
11
12
|
import { spawn, spawnSync } from "child_process";
|
|
12
13
|
import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
@@ -17,7 +18,8 @@ import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
|
17
18
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
18
19
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
19
20
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
20
|
-
import { defaultModelPerProvider, findExactModelReferenceMatch,
|
|
21
|
+
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScopeFromModels, } from "../../core/model-resolver.js";
|
|
22
|
+
import { CredentialSynchronizationError } from "../../core/model-runtime.js";
|
|
21
23
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
22
24
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
23
25
|
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
@@ -29,6 +31,7 @@ import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelo
|
|
|
29
31
|
import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
|
|
30
32
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
31
33
|
import { parseGitUrl } from "../../utils/git.js";
|
|
34
|
+
import { openBrowser } from "../../utils/open-browser.js";
|
|
32
35
|
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
33
36
|
import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
34
37
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
@@ -52,6 +55,7 @@ import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
|
52
55
|
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
53
56
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
54
57
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
58
|
+
import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
|
|
55
59
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
56
60
|
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
57
61
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
@@ -161,11 +165,48 @@ function formatLoginProviderCompletionDescription(provider) {
|
|
|
161
165
|
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
162
166
|
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
163
167
|
}
|
|
168
|
+
/** Composition root for selecting the interactive terminal renderer. */
|
|
169
|
+
export function createInteractiveTui(options) {
|
|
170
|
+
const terminal = options.terminal ?? new ProcessTerminal();
|
|
171
|
+
if (options.tuiMode === "fullscreen") {
|
|
172
|
+
return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, { openUrl: openBrowser });
|
|
173
|
+
}
|
|
174
|
+
return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
|
|
175
|
+
}
|
|
176
|
+
/** Stable reference for components while InteractiveMode replaces the active renderer. */
|
|
177
|
+
export function createInteractiveTuiReference(getTui) {
|
|
178
|
+
return new Proxy({}, {
|
|
179
|
+
get: (_target, property) => {
|
|
180
|
+
const tui = getTui();
|
|
181
|
+
const value = Reflect.get(tui, property, tui);
|
|
182
|
+
if (typeof value !== "function")
|
|
183
|
+
return value;
|
|
184
|
+
return (...args) => {
|
|
185
|
+
const tui = getTui();
|
|
186
|
+
const method = Reflect.get(tui, property, tui);
|
|
187
|
+
if (typeof method !== "function")
|
|
188
|
+
throw new TypeError(`TUI property ${String(property)} is not callable`);
|
|
189
|
+
return Reflect.apply(method, tui, args);
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
set: (_target, property, value) => {
|
|
193
|
+
const tui = getTui();
|
|
194
|
+
return Reflect.set(tui, property, value, tui);
|
|
195
|
+
},
|
|
196
|
+
has: (_target, property) => Reflect.has(getTui(), property),
|
|
197
|
+
getPrototypeOf: () => Reflect.getPrototypeOf(getTui()),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
164
200
|
export class InteractiveMode {
|
|
165
201
|
runtimeHost;
|
|
202
|
+
renderer;
|
|
166
203
|
ui;
|
|
204
|
+
mainScreenRenderState;
|
|
167
205
|
loadedResourcesContainer;
|
|
168
206
|
chatContainer;
|
|
207
|
+
documentContainer;
|
|
208
|
+
transcriptScrollView;
|
|
209
|
+
fullscreenLayoutRoot;
|
|
169
210
|
pendingMessagesContainer;
|
|
170
211
|
statusContainer;
|
|
171
212
|
defaultEditor;
|
|
@@ -175,7 +216,10 @@ export class InteractiveMode {
|
|
|
175
216
|
autocompleteProviderWrappers = [];
|
|
176
217
|
fdPath;
|
|
177
218
|
editorContainer;
|
|
219
|
+
activeSelectorToken;
|
|
220
|
+
activeSelectorDispose;
|
|
178
221
|
footer;
|
|
222
|
+
footerContainer;
|
|
179
223
|
footerDataProvider;
|
|
180
224
|
// Stored so the same manager can be injected into custom editors, selectors, and extension UI.
|
|
181
225
|
keybindings;
|
|
@@ -209,6 +253,10 @@ export class InteractiveMode {
|
|
|
209
253
|
// Thinking block visibility state
|
|
210
254
|
hideThinkingBlock = false;
|
|
211
255
|
outputPad = 1;
|
|
256
|
+
mermaidMarkdownTransformer = createMermaidMarkdownTransformer({
|
|
257
|
+
getMode: () => this.settingsManager.getMermaidRenderingMode(),
|
|
258
|
+
theme,
|
|
259
|
+
});
|
|
212
260
|
// Skill commands: command name -> skill file path
|
|
213
261
|
skillCommands = new Map();
|
|
214
262
|
// Agent subscription unsubscribe function
|
|
@@ -232,7 +280,7 @@ export class InteractiveMode {
|
|
|
232
280
|
extensionSelector = undefined;
|
|
233
281
|
extensionInput = undefined;
|
|
234
282
|
extensionEditor = undefined;
|
|
235
|
-
|
|
283
|
+
extensionTerminalInputSubscriptions = new Set();
|
|
236
284
|
// Extension widgets (components rendered above/below the editor)
|
|
237
285
|
extensionWidgetsAbove = new Map();
|
|
238
286
|
extensionWidgetsBelow = new Map();
|
|
@@ -264,7 +312,8 @@ export class InteractiveMode {
|
|
|
264
312
|
}
|
|
265
313
|
constructor(runtimeHost, options = {}) {
|
|
266
314
|
this.runtimeHost = runtimeHost;
|
|
267
|
-
|
|
315
|
+
const tuiMode = options.tuiMode ?? this.settingsManager.getTuiMode();
|
|
316
|
+
this.options = { ...options, tuiMode };
|
|
268
317
|
this.autoTrustOnReloadCwd = options.autoTrustOnReloadCwd;
|
|
269
318
|
this.runtimeHost.setBeforeSessionInvalidate(() => {
|
|
270
319
|
this.resetExtensionUI();
|
|
@@ -273,11 +322,20 @@ export class InteractiveMode {
|
|
|
273
322
|
await this.rebindCurrentSession({ renderBeforeBind: true });
|
|
274
323
|
});
|
|
275
324
|
this.version = VERSION;
|
|
276
|
-
this.
|
|
325
|
+
this.renderer = createInteractiveTui({
|
|
326
|
+
tuiMode,
|
|
327
|
+
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
328
|
+
logDirectory: getAgentDir(),
|
|
329
|
+
});
|
|
330
|
+
this.ui = createInteractiveTuiReference(() => this.renderer);
|
|
277
331
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
278
332
|
this.headerContainer = new Container();
|
|
279
333
|
this.loadedResourcesContainer = new Container();
|
|
280
334
|
this.chatContainer = new Container();
|
|
335
|
+
this.documentContainer = new Container();
|
|
336
|
+
this.documentContainer.addChild(this.headerContainer);
|
|
337
|
+
this.documentContainer.addChild(this.loadedResourcesContainer);
|
|
338
|
+
this.documentContainer.addChild(this.chatContainer);
|
|
281
339
|
this.pendingMessagesContainer = new Container();
|
|
282
340
|
this.statusContainer = new Container();
|
|
283
341
|
this.widgetContainerAbove = new Container();
|
|
@@ -296,6 +354,8 @@ export class InteractiveMode {
|
|
|
296
354
|
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
297
355
|
this.footer = new FooterComponent(this.session, this.footerDataProvider);
|
|
298
356
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
357
|
+
this.footerContainer = new Container();
|
|
358
|
+
this.footerContainer.addChild(this.footer);
|
|
299
359
|
// Load hide thinking block setting
|
|
300
360
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
301
361
|
this.outputPad = this.settingsManager.getOutputPad();
|
|
@@ -351,11 +411,10 @@ export class InteractiveMode {
|
|
|
351
411
|
}));
|
|
352
412
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
353
413
|
if (modelCommand) {
|
|
354
|
-
modelCommand.getArgumentCompletions =
|
|
355
|
-
// Get available models (scoped or from registry)
|
|
414
|
+
modelCommand.getArgumentCompletions = (prefix) => {
|
|
356
415
|
const models = this.session.scopedModels.length > 0
|
|
357
416
|
? this.session.scopedModels.map((s) => s.model)
|
|
358
|
-
:
|
|
417
|
+
: this.session.modelRuntime.getAvailableSnapshot();
|
|
359
418
|
if (models.length === 0)
|
|
360
419
|
return null;
|
|
361
420
|
// Create items with provider/id format
|
|
@@ -456,6 +515,72 @@ export class InteractiveMode {
|
|
|
456
515
|
}
|
|
457
516
|
this.chatContainer.addChild(new DynamicBorder());
|
|
458
517
|
}
|
|
518
|
+
mountInteractiveTui(tui, components) {
|
|
519
|
+
for (const component of components)
|
|
520
|
+
tui.addChild(component);
|
|
521
|
+
if (TuiLayouts.isViewportTUI(tui)) {
|
|
522
|
+
if (!this.fullscreenLayoutRoot)
|
|
523
|
+
throw new Error("Fullscreen layout is not initialized");
|
|
524
|
+
tui.setLayoutRoot(this.fullscreenLayoutRoot);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
stopInteractiveTui() {
|
|
528
|
+
if (this.renderer.mode === "fullscreen") {
|
|
529
|
+
while (this.renderer.hasOverlayEntries)
|
|
530
|
+
this.renderer.hideOverlay();
|
|
531
|
+
this.switchTuiMode("regular", false, false);
|
|
532
|
+
this.renderer.renderNow();
|
|
533
|
+
}
|
|
534
|
+
this.ui.stop();
|
|
535
|
+
}
|
|
536
|
+
switchTuiMode(mode, restoreProgress = true, startRenderer = true) {
|
|
537
|
+
const previousUi = this.renderer;
|
|
538
|
+
if (mode === previousUi.mode)
|
|
539
|
+
return true;
|
|
540
|
+
if (previousUi.hasOverlayEntries)
|
|
541
|
+
return false;
|
|
542
|
+
const components = [...previousUi.children];
|
|
543
|
+
const focus = previousUi.getFocusedComponent();
|
|
544
|
+
const terminal = previousUi.terminal;
|
|
545
|
+
const showHardwareCursor = previousUi.getShowHardwareCursor();
|
|
546
|
+
const clearOnShrink = previousUi.getClearOnShrink();
|
|
547
|
+
const onDebug = previousUi.onDebug;
|
|
548
|
+
if (previousUi instanceof TuiMainScreen) {
|
|
549
|
+
this.mainScreenRenderState = previousUi.captureRenderState();
|
|
550
|
+
}
|
|
551
|
+
previousUi.stop({ preserveScreen: true });
|
|
552
|
+
previousUi.setFocus(null);
|
|
553
|
+
previousUi.clear();
|
|
554
|
+
if (TuiLayouts.isViewportTUI(previousUi))
|
|
555
|
+
previousUi.setLayoutRoot(undefined);
|
|
556
|
+
const nextUi = createInteractiveTui({
|
|
557
|
+
tuiMode: mode,
|
|
558
|
+
showHardwareCursor,
|
|
559
|
+
logDirectory: getAgentDir(),
|
|
560
|
+
terminal,
|
|
561
|
+
});
|
|
562
|
+
nextUi.setClearOnShrink(clearOnShrink);
|
|
563
|
+
nextUi.onDebug = onDebug;
|
|
564
|
+
if (nextUi instanceof TuiMainScreen && this.mainScreenRenderState) {
|
|
565
|
+
nextUi.restoreRenderState(this.mainScreenRenderState);
|
|
566
|
+
}
|
|
567
|
+
this.renderer = nextUi;
|
|
568
|
+
this.options.tuiMode = mode;
|
|
569
|
+
this.mountInteractiveTui(nextUi, components);
|
|
570
|
+
nextUi.invalidate();
|
|
571
|
+
nextUi.setFocus(focus);
|
|
572
|
+
if (!startRenderer)
|
|
573
|
+
return true;
|
|
574
|
+
nextUi.start();
|
|
575
|
+
this.themeController.rebindTui();
|
|
576
|
+
this.rebindExtensionTerminalInputListeners();
|
|
577
|
+
if (restoreProgress &&
|
|
578
|
+
this.settingsManager.getShowTerminalProgress() &&
|
|
579
|
+
(this.session.isStreaming || this.session.isCompacting)) {
|
|
580
|
+
terminal.setProgress(true);
|
|
581
|
+
}
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
459
584
|
async init() {
|
|
460
585
|
if (this.isInitialized)
|
|
461
586
|
return;
|
|
@@ -479,18 +604,36 @@ export class InteractiveMode {
|
|
|
479
604
|
: "";
|
|
480
605
|
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
|
481
606
|
}
|
|
482
|
-
//
|
|
483
|
-
// Keep loaded resources before chat so restored session messages never precede them.
|
|
484
|
-
this.ui.addChild(this.headerContainer);
|
|
485
|
-
this.ui.addChild(this.loadedResourcesContainer);
|
|
486
|
-
this.ui.addChild(this.chatContainer);
|
|
487
|
-
this.ui.addChild(this.pendingMessagesContainer);
|
|
488
|
-
this.ui.addChild(this.statusContainer);
|
|
607
|
+
// Keep one component tree and remount it when changing renderers.
|
|
489
608
|
this.renderWidgets(); // Initialize with default spacer
|
|
490
|
-
this.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
609
|
+
this.transcriptScrollView = new TuiLayouts.ScrollView(this.documentContainer, {
|
|
610
|
+
follow: "end",
|
|
611
|
+
primary: true,
|
|
612
|
+
overscroll: "chain",
|
|
613
|
+
scrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
614
|
+
scrollbarStyle: (text) => theme.bg("scrollbarThumb", text),
|
|
615
|
+
});
|
|
616
|
+
const dock = new TuiLayouts.VStack([
|
|
617
|
+
{ component: this.pendingMessagesContainer, shrink: 1, minSize: 0 },
|
|
618
|
+
{ component: this.statusContainer, shrink: 1, minSize: 0 },
|
|
619
|
+
{ component: this.widgetContainerAbove, shrink: 1, minSize: 0 },
|
|
620
|
+
{ component: this.editorContainer, shrink: 1, minSize: 3 },
|
|
621
|
+
{ component: this.widgetContainerBelow, shrink: 1, minSize: 0 },
|
|
622
|
+
{ component: this.footerContainer, shrink: 1, minSize: 1 },
|
|
623
|
+
]);
|
|
624
|
+
this.fullscreenLayoutRoot = new TuiLayouts.VStack([
|
|
625
|
+
{ component: this.transcriptScrollView, basis: 0, grow: 1, shrink: 1, minSize: 1 },
|
|
626
|
+
{ component: dock, basis: "auto", grow: 0, shrink: 1, minSize: 1 },
|
|
627
|
+
]);
|
|
628
|
+
this.mountInteractiveTui(this.renderer, [
|
|
629
|
+
this.documentContainer,
|
|
630
|
+
this.pendingMessagesContainer,
|
|
631
|
+
this.statusContainer,
|
|
632
|
+
this.widgetContainerAbove,
|
|
633
|
+
this.editorContainer,
|
|
634
|
+
this.widgetContainerBelow,
|
|
635
|
+
this.footerContainer,
|
|
636
|
+
]);
|
|
494
637
|
this.ui.setFocus(this.editor);
|
|
495
638
|
this.setupKeyHandlers();
|
|
496
639
|
this.setupEditorSubmitHandler();
|
|
@@ -582,10 +725,13 @@ export class InteractiveMode {
|
|
|
582
725
|
async run() {
|
|
583
726
|
await this.init();
|
|
584
727
|
if (!process.env.PI_OFFLINE) {
|
|
728
|
+
const controller = new AbortController();
|
|
729
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
585
730
|
void this.session.modelRuntime
|
|
586
|
-
.refresh()
|
|
731
|
+
.refresh({ signal: controller.signal })
|
|
587
732
|
.then(() => this.updateAvailableProviderCount())
|
|
588
|
-
.catch(() => { })
|
|
733
|
+
.catch(() => { })
|
|
734
|
+
.finally(() => clearTimeout(timeout));
|
|
589
735
|
}
|
|
590
736
|
// Start version check asynchronously
|
|
591
737
|
checkForNewPiVersion(this.version).then((newRelease) => {
|
|
@@ -1106,7 +1252,12 @@ export class InteractiveMode {
|
|
|
1106
1252
|
}
|
|
1107
1253
|
}
|
|
1108
1254
|
if (showListing) {
|
|
1109
|
-
const
|
|
1255
|
+
const systemPromptSource = this.session.resourceLoader.getSystemPromptSource();
|
|
1256
|
+
const contextFiles = [
|
|
1257
|
+
...(systemPromptSource ? [systemPromptSource] : []),
|
|
1258
|
+
...this.session.resourceLoader.getAppendSystemPromptSources(),
|
|
1259
|
+
...this.session.resourceLoader.getAgentsFiles().agentsFiles,
|
|
1260
|
+
];
|
|
1110
1261
|
if (contextFiles.length > 0) {
|
|
1111
1262
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1112
1263
|
const contextList = contextFiles
|
|
@@ -1283,8 +1434,12 @@ export class InteractiveMode {
|
|
|
1283
1434
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1284
1435
|
this.showStartupNoticesIfNeeded();
|
|
1285
1436
|
}
|
|
1437
|
+
applyFullscreenScrollbarSetting() {
|
|
1438
|
+
this.transcriptScrollView?.setScrollbar(this.settingsManager.getFullscreenScrollbar());
|
|
1439
|
+
}
|
|
1286
1440
|
applyRuntimeSettings() {
|
|
1287
1441
|
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
1442
|
+
this.applyFullscreenScrollbarSetting();
|
|
1288
1443
|
this.footer.setSession(this.session);
|
|
1289
1444
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1290
1445
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
@@ -1306,16 +1461,19 @@ export class InteractiveMode {
|
|
|
1306
1461
|
}
|
|
1307
1462
|
}
|
|
1308
1463
|
async rebindCurrentSession(options = {}) {
|
|
1464
|
+
const session = this.session;
|
|
1309
1465
|
this.unsubscribe?.();
|
|
1310
1466
|
this.unsubscribe = undefined;
|
|
1311
1467
|
this.applyRuntimeSettings();
|
|
1312
1468
|
if (options.renderBeforeBind) {
|
|
1313
1469
|
this.renderCurrentSessionState();
|
|
1314
1470
|
this.subscribeToAgent();
|
|
1315
|
-
await this.bindCurrentSessionExtensions();
|
|
1316
1471
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1472
|
+
await this.bindCurrentSessionExtensions();
|
|
1473
|
+
if (this.session !== session) {
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
if (!options.renderBeforeBind) {
|
|
1319
1477
|
this.subscribeToAgent();
|
|
1320
1478
|
}
|
|
1321
1479
|
await this.updateAvailableProviderCount();
|
|
@@ -1345,6 +1503,9 @@ export class InteractiveMode {
|
|
|
1345
1503
|
getRegisteredToolDefinition(toolName) {
|
|
1346
1504
|
return this.session.getToolDefinition(toolName);
|
|
1347
1505
|
}
|
|
1506
|
+
getMarkdownTransformers() {
|
|
1507
|
+
return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
|
|
1508
|
+
}
|
|
1348
1509
|
/**
|
|
1349
1510
|
* Set up keyboard shortcuts registered by extensions.
|
|
1350
1511
|
*/
|
|
@@ -1361,6 +1522,7 @@ export class InteractiveMode {
|
|
|
1361
1522
|
sessionManager: this.sessionManager,
|
|
1362
1523
|
modelRegistry: extensionRunner.getModelRegistry(),
|
|
1363
1524
|
model: this.session.model,
|
|
1525
|
+
scopedModels: this.session.scopedModels,
|
|
1364
1526
|
thinkingLevel: this.session.thinkingLevel,
|
|
1365
1527
|
isIdle: () => this.session.isIdle,
|
|
1366
1528
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
@@ -1423,7 +1585,7 @@ export class InteractiveMode {
|
|
|
1423
1585
|
this.activeStatusIndicator?.dispose();
|
|
1424
1586
|
this.activeStatusIndicator = undefined;
|
|
1425
1587
|
this.statusContainer.clear();
|
|
1426
|
-
if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
|
|
1588
|
+
if (hadActiveStatusIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
|
|
1427
1589
|
this.statusContainer.addChild(this.idleStatus);
|
|
1428
1590
|
}
|
|
1429
1591
|
}
|
|
@@ -1571,22 +1733,16 @@ export class InteractiveMode {
|
|
|
1571
1733
|
if (this.customFooter?.dispose) {
|
|
1572
1734
|
this.customFooter.dispose();
|
|
1573
1735
|
}
|
|
1574
|
-
|
|
1575
|
-
if (this.customFooter) {
|
|
1576
|
-
this.ui.removeChild(this.customFooter);
|
|
1577
|
-
}
|
|
1578
|
-
else {
|
|
1579
|
-
this.ui.removeChild(this.footer);
|
|
1580
|
-
}
|
|
1736
|
+
this.footerContainer.clear();
|
|
1581
1737
|
if (factory) {
|
|
1582
1738
|
// Create and add custom footer, passing the data provider
|
|
1583
1739
|
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
1584
|
-
this.
|
|
1740
|
+
this.footerContainer.addChild(this.customFooter);
|
|
1585
1741
|
}
|
|
1586
1742
|
else {
|
|
1587
1743
|
// Restore built-in footer
|
|
1588
1744
|
this.customFooter = undefined;
|
|
1589
|
-
this.
|
|
1745
|
+
this.footerContainer.addChild(this.footer);
|
|
1590
1746
|
}
|
|
1591
1747
|
this.ui.requestRender();
|
|
1592
1748
|
}
|
|
@@ -1632,18 +1788,23 @@ export class InteractiveMode {
|
|
|
1632
1788
|
this.ui.requestRender();
|
|
1633
1789
|
}
|
|
1634
1790
|
addExtensionTerminalInputListener(handler) {
|
|
1635
|
-
const
|
|
1636
|
-
this.
|
|
1791
|
+
const subscription = { handler, unsubscribe: this.ui.addInputListener(handler) };
|
|
1792
|
+
this.extensionTerminalInputSubscriptions.add(subscription);
|
|
1637
1793
|
return () => {
|
|
1638
|
-
unsubscribe();
|
|
1639
|
-
this.
|
|
1794
|
+
subscription.unsubscribe();
|
|
1795
|
+
this.extensionTerminalInputSubscriptions.delete(subscription);
|
|
1640
1796
|
};
|
|
1641
1797
|
}
|
|
1642
|
-
|
|
1643
|
-
for (const
|
|
1644
|
-
unsubscribe();
|
|
1798
|
+
rebindExtensionTerminalInputListeners() {
|
|
1799
|
+
for (const subscription of this.extensionTerminalInputSubscriptions) {
|
|
1800
|
+
subscription.unsubscribe();
|
|
1801
|
+
subscription.unsubscribe = this.ui.addInputListener(subscription.handler);
|
|
1645
1802
|
}
|
|
1646
|
-
|
|
1803
|
+
}
|
|
1804
|
+
clearExtensionTerminalInputListeners() {
|
|
1805
|
+
for (const subscription of this.extensionTerminalInputSubscriptions)
|
|
1806
|
+
subscription.unsubscribe();
|
|
1807
|
+
this.extensionTerminalInputSubscriptions.clear();
|
|
1647
1808
|
}
|
|
1648
1809
|
/**
|
|
1649
1810
|
* Create the ExtensionUIContext for extensions.
|
|
@@ -1738,6 +1899,7 @@ export class InteractiveMode {
|
|
|
1738
1899
|
this.hideExtensionSelector();
|
|
1739
1900
|
resolve(undefined);
|
|
1740
1901
|
}, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
|
|
1902
|
+
this.disposeActiveSelector();
|
|
1741
1903
|
this.editorContainer.clear();
|
|
1742
1904
|
this.editorContainer.addChild(this.extensionSelector);
|
|
1743
1905
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -1789,6 +1951,7 @@ export class InteractiveMode {
|
|
|
1789
1951
|
this.hideExtensionInput();
|
|
1790
1952
|
resolve(undefined);
|
|
1791
1953
|
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1954
|
+
this.disposeActiveSelector();
|
|
1792
1955
|
this.editorContainer.clear();
|
|
1793
1956
|
this.editorContainer.addChild(this.extensionInput);
|
|
1794
1957
|
this.ui.setFocus(this.extensionInput);
|
|
@@ -1818,6 +1981,7 @@ export class InteractiveMode {
|
|
|
1818
1981
|
this.hideExtensionEditor();
|
|
1819
1982
|
resolve(undefined);
|
|
1820
1983
|
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
1984
|
+
this.disposeActiveSelector();
|
|
1821
1985
|
this.editorContainer.clear();
|
|
1822
1986
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1823
1987
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -1842,6 +2006,7 @@ export class InteractiveMode {
|
|
|
1842
2006
|
this.editorComponentFactory = factory;
|
|
1843
2007
|
// Save text from current editor before switching
|
|
1844
2008
|
const currentText = this.editor.getText();
|
|
2009
|
+
this.disposeActiveSelector();
|
|
1845
2010
|
this.editorContainer.clear();
|
|
1846
2011
|
if (factory) {
|
|
1847
2012
|
// Create the custom editor with tui, theme, and keybindings
|
|
@@ -1858,6 +2023,9 @@ export class InteractiveMode {
|
|
|
1858
2023
|
if (newEditor.setPaddingX !== undefined) {
|
|
1859
2024
|
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1860
2025
|
}
|
|
2026
|
+
if (newEditor.setAutocompleteMaxVisible !== undefined) {
|
|
2027
|
+
newEditor.setAutocompleteMaxVisible(this.defaultEditor.getAutocompleteMaxVisible());
|
|
2028
|
+
}
|
|
1861
2029
|
// Set autocomplete if supported
|
|
1862
2030
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1863
2031
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1962,6 +2130,7 @@ export class InteractiveMode {
|
|
|
1962
2130
|
options?.onHandle?.(handle);
|
|
1963
2131
|
}
|
|
1964
2132
|
else {
|
|
2133
|
+
this.disposeActiveSelector();
|
|
1965
2134
|
this.editorContainer.clear();
|
|
1966
2135
|
this.editorContainer.addChild(component);
|
|
1967
2136
|
this.ui.setFocus(component);
|
|
@@ -2048,7 +2217,7 @@ export class InteractiveMode {
|
|
|
2048
2217
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2049
2218
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2050
2219
|
this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
|
|
2051
|
-
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
|
|
2220
|
+
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true }));
|
|
2052
2221
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2053
2222
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2054
2223
|
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
@@ -2336,17 +2505,17 @@ export class InteractiveMode {
|
|
|
2336
2505
|
this.ui.requestRender();
|
|
2337
2506
|
}
|
|
2338
2507
|
else if (event.message.role === "assistant") {
|
|
2339
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2508
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2340
2509
|
this.streamingMessage = event.message;
|
|
2341
2510
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2342
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2511
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2343
2512
|
this.ui.requestRender();
|
|
2344
2513
|
}
|
|
2345
2514
|
break;
|
|
2346
2515
|
case "message_update":
|
|
2347
2516
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2348
2517
|
this.streamingMessage = event.message;
|
|
2349
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2518
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2350
2519
|
for (const content of this.streamingMessage.content) {
|
|
2351
2520
|
if (content.type === "toolCall") {
|
|
2352
2521
|
if (!this.pendingTools.has(content.id)) {
|
|
@@ -2383,7 +2552,7 @@ export class InteractiveMode {
|
|
|
2383
2552
|
: "Operation aborted";
|
|
2384
2553
|
this.streamingMessage.errorMessage = errorMessage;
|
|
2385
2554
|
}
|
|
2386
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2555
|
+
this.streamingComponent.updateContent(this.streamingMessage, false);
|
|
2387
2556
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2388
2557
|
if (!errorMessage) {
|
|
2389
2558
|
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
@@ -2657,12 +2826,12 @@ export class InteractiveMode {
|
|
|
2657
2826
|
// Render user message separately if present
|
|
2658
2827
|
if (skillBlock.userMessage) {
|
|
2659
2828
|
this.chatContainer.addChild(new Spacer(1));
|
|
2660
|
-
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2829
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2661
2830
|
this.chatContainer.addChild(userComponent);
|
|
2662
2831
|
}
|
|
2663
2832
|
}
|
|
2664
2833
|
else {
|
|
2665
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2834
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2666
2835
|
this.chatContainer.addChild(userComponent);
|
|
2667
2836
|
}
|
|
2668
2837
|
if (options?.populateHistory) {
|
|
@@ -2672,7 +2841,7 @@ export class InteractiveMode {
|
|
|
2672
2841
|
break;
|
|
2673
2842
|
}
|
|
2674
2843
|
case "assistant": {
|
|
2675
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2844
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
|
|
2676
2845
|
this.chatContainer.addChild(assistantComponent);
|
|
2677
2846
|
break;
|
|
2678
2847
|
}
|
|
@@ -3106,6 +3275,8 @@ export class InteractiveMode {
|
|
|
3106
3275
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
3107
3276
|
}
|
|
3108
3277
|
setToolsExpanded(expanded) {
|
|
3278
|
+
if (expanded === this.toolOutputExpanded)
|
|
3279
|
+
return;
|
|
3109
3280
|
this.toolOutputExpanded = expanded;
|
|
3110
3281
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
3111
3282
|
if (isExpandable(activeHeader)) {
|
|
@@ -3118,7 +3289,7 @@ export class InteractiveMode {
|
|
|
3118
3289
|
}
|
|
3119
3290
|
}
|
|
3120
3291
|
}
|
|
3121
|
-
this.
|
|
3292
|
+
this.showStatus(`Tool output: ${expanded ? "expanded" : "collapsed"}`);
|
|
3122
3293
|
}
|
|
3123
3294
|
toggleThinkingBlockVisibility() {
|
|
3124
3295
|
this.hideThinkingBlock = !this.hideThinkingBlock;
|
|
@@ -3161,7 +3332,7 @@ export class InteractiveMode {
|
|
|
3161
3332
|
}
|
|
3162
3333
|
showError(errorMessage) {
|
|
3163
3334
|
this.chatContainer.addChild(new Spacer(1));
|
|
3164
|
-
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`),
|
|
3335
|
+
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
|
|
3165
3336
|
this.ui.requestRender();
|
|
3166
3337
|
}
|
|
3167
3338
|
showWarning(warningMessage) {
|
|
@@ -3371,25 +3542,43 @@ export class InteractiveMode {
|
|
|
3371
3542
|
// =========================================================================
|
|
3372
3543
|
// Selectors
|
|
3373
3544
|
// =========================================================================
|
|
3545
|
+
disposeActiveSelector() {
|
|
3546
|
+
const dispose = this.activeSelectorDispose;
|
|
3547
|
+
this.activeSelectorToken = undefined;
|
|
3548
|
+
this.activeSelectorDispose = undefined;
|
|
3549
|
+
dispose?.();
|
|
3550
|
+
}
|
|
3374
3551
|
/**
|
|
3375
3552
|
* Shows a selector component in place of the editor.
|
|
3376
3553
|
* @param create Factory that receives a `done` callback and returns the component and focus target
|
|
3377
3554
|
*/
|
|
3378
3555
|
showSelector(create) {
|
|
3556
|
+
const token = {};
|
|
3557
|
+
let dispose;
|
|
3379
3558
|
const done = () => {
|
|
3559
|
+
dispose?.();
|
|
3560
|
+
if (this.activeSelectorToken !== token)
|
|
3561
|
+
return;
|
|
3562
|
+
this.activeSelectorToken = undefined;
|
|
3563
|
+
this.activeSelectorDispose = undefined;
|
|
3380
3564
|
this.editorContainer.clear();
|
|
3381
3565
|
this.editorContainer.addChild(this.editor);
|
|
3382
3566
|
this.ui.setFocus(this.editor);
|
|
3383
3567
|
};
|
|
3384
|
-
const
|
|
3568
|
+
const created = create(done);
|
|
3569
|
+
dispose = created.dispose;
|
|
3570
|
+
this.disposeActiveSelector();
|
|
3571
|
+
this.activeSelectorToken = token;
|
|
3572
|
+
this.activeSelectorDispose = dispose;
|
|
3385
3573
|
this.editorContainer.clear();
|
|
3386
|
-
this.editorContainer.addChild(component);
|
|
3387
|
-
this.ui.setFocus(focus);
|
|
3574
|
+
this.editorContainer.addChild(created.component);
|
|
3575
|
+
this.ui.setFocus(created.focus);
|
|
3388
3576
|
this.ui.requestRender();
|
|
3389
3577
|
}
|
|
3390
3578
|
showSettingsSelector() {
|
|
3391
3579
|
this.showSelector((done) => {
|
|
3392
|
-
|
|
3580
|
+
let selector;
|
|
3581
|
+
selector = new SettingsSelectorComponent({
|
|
3393
3582
|
autoCompact: this.session.autoCompactionEnabled,
|
|
3394
3583
|
showImages: this.settingsManager.getShowImages(),
|
|
3395
3584
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
@@ -3406,6 +3595,7 @@ export class InteractiveMode {
|
|
|
3406
3595
|
terminalTheme: this.themeController.getTerminalTheme(),
|
|
3407
3596
|
availableThemes: getAvailableThemes(),
|
|
3408
3597
|
hideThinkingBlock: this.hideThinkingBlock,
|
|
3598
|
+
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
|
|
3409
3599
|
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
|
3410
3600
|
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
|
3411
3601
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
@@ -3419,6 +3609,8 @@ export class InteractiveMode {
|
|
|
3419
3609
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3420
3610
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3421
3611
|
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3612
|
+
tuiMode: this.ui.mode,
|
|
3613
|
+
fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
3422
3614
|
warnings: this.settingsManager.getWarnings(),
|
|
3423
3615
|
}, {
|
|
3424
3616
|
onAutoCompactChange: (enabled) => {
|
|
@@ -3487,6 +3679,11 @@ export class InteractiveMode {
|
|
|
3487
3679
|
this.chatContainer.clear();
|
|
3488
3680
|
this.rebuildChatFromMessages();
|
|
3489
3681
|
},
|
|
3682
|
+
onMermaidRenderingModeChange: (mode) => {
|
|
3683
|
+
this.settingsManager.setMermaidRenderingMode(mode);
|
|
3684
|
+
this.chatContainer.invalidate();
|
|
3685
|
+
this.ui.requestRender();
|
|
3686
|
+
},
|
|
3490
3687
|
onShowCacheMissNoticesChange: (shown) => {
|
|
3491
3688
|
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3492
3689
|
this.rebuildChatFromMessages();
|
|
@@ -3556,6 +3753,21 @@ export class InteractiveMode {
|
|
|
3556
3753
|
onShowTerminalProgressChange: (enabled) => {
|
|
3557
3754
|
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3558
3755
|
},
|
|
3756
|
+
onTuiModeChange: (mode) => {
|
|
3757
|
+
if (!this.switchTuiMode(mode)) {
|
|
3758
|
+
selector?.getSettingsList().updateValue("tui-mode", this.ui.mode);
|
|
3759
|
+
this.showStatus("Close active overlays before changing TUI mode");
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
this.settingsManager.setTuiMode(mode);
|
|
3763
|
+
if (!this.activeStatusIndicator)
|
|
3764
|
+
this.statusContainer.clear();
|
|
3765
|
+
this.showStatus(`TUI mode: ${mode}`);
|
|
3766
|
+
},
|
|
3767
|
+
onFullscreenScrollbarChange: (mode) => {
|
|
3768
|
+
this.settingsManager.setFullscreenScrollbar(mode);
|
|
3769
|
+
this.applyFullscreenScrollbarSetting();
|
|
3770
|
+
},
|
|
3559
3771
|
onWarningsChange: (warnings) => {
|
|
3560
3772
|
this.settingsManager.setWarnings(warnings);
|
|
3561
3773
|
},
|
|
@@ -3590,20 +3802,37 @@ export class InteractiveMode {
|
|
|
3590
3802
|
this.showModelSelector(searchTerm);
|
|
3591
3803
|
}
|
|
3592
3804
|
async findExactModelMatch(searchTerm) {
|
|
3593
|
-
const
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
if (this.session.scopedModels.length > 0)
|
|
3598
|
-
return
|
|
3599
|
-
|
|
3805
|
+
const cachedModels = this.session.scopedModels.length > 0
|
|
3806
|
+
? this.session.scopedModels.map((scoped) => scoped.model)
|
|
3807
|
+
: [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
3808
|
+
const cachedMatch = findExactModelReferenceMatch(searchTerm, cachedModels);
|
|
3809
|
+
if (cachedMatch || this.session.scopedModels.length > 0)
|
|
3810
|
+
return cachedMatch;
|
|
3811
|
+
this.showStatus("Refreshing model catalogs…");
|
|
3812
|
+
const controller = new AbortController();
|
|
3813
|
+
let timedOut = false;
|
|
3814
|
+
const timeout = setTimeout(() => {
|
|
3815
|
+
timedOut = true;
|
|
3816
|
+
controller.abort();
|
|
3817
|
+
}, 15_000);
|
|
3600
3818
|
try {
|
|
3601
|
-
await this.session.modelRuntime.refresh();
|
|
3602
|
-
|
|
3819
|
+
const result = await this.session.modelRuntime.refresh({ signal: controller.signal });
|
|
3820
|
+
if (result.aborted && timedOut) {
|
|
3821
|
+
this.showWarning("Model refresh timed out; searching cached models.");
|
|
3822
|
+
}
|
|
3823
|
+
else if (result.errors.size > 0) {
|
|
3824
|
+
this.showWarning(`Could not refresh ${[...result.errors.keys()].join(", ")}; searching cached models.`);
|
|
3825
|
+
}
|
|
3603
3826
|
}
|
|
3604
|
-
catch {
|
|
3605
|
-
|
|
3827
|
+
catch (error) {
|
|
3828
|
+
this.showWarning(timedOut
|
|
3829
|
+
? "Model refresh timed out; searching cached models."
|
|
3830
|
+
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`);
|
|
3831
|
+
}
|
|
3832
|
+
finally {
|
|
3833
|
+
clearTimeout(timeout);
|
|
3606
3834
|
}
|
|
3835
|
+
return findExactModelReferenceMatch(searchTerm, [...this.session.modelRuntime.getAvailableSnapshot()]);
|
|
3607
3836
|
}
|
|
3608
3837
|
/** Update the footer's available provider count from the current snapshot without refreshing catalogs. */
|
|
3609
3838
|
updateAvailableProviderCount() {
|
|
@@ -3705,73 +3934,67 @@ export class InteractiveMode {
|
|
|
3705
3934
|
done();
|
|
3706
3935
|
this.ui.requestRender();
|
|
3707
3936
|
}, initialSearchInput);
|
|
3708
|
-
return { component: selector, focus: selector };
|
|
3937
|
+
return { component: selector, focus: selector, dispose: () => selector.dispose() };
|
|
3709
3938
|
});
|
|
3710
3939
|
}
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3715
|
-
const allModelIds = new Set(allModels.map((model) => `${model.provider}/${model.id}`));
|
|
3940
|
+
showModelsSelector() {
|
|
3941
|
+
let availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
3942
|
+
let availableModelIds = new Set(availableModels.map((model) => `${model.provider}/${model.id}`));
|
|
3716
3943
|
const configuredPatterns = this.settingsManager.getEnabledModels();
|
|
3717
3944
|
const sessionScopedModels = this.session.scopedModels;
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
currentEnabledIds = configuredScope.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3735
|
-
}
|
|
3736
|
-
for (const diagnostic of configuredScope?.diagnostics ?? []) {
|
|
3737
|
-
if (diagnostic.code !== "no-match")
|
|
3738
|
-
continue;
|
|
3739
|
-
currentEnabledIds ??= [];
|
|
3740
|
-
if (!currentEnabledIds.includes(diagnostic.pattern))
|
|
3741
|
-
currentEnabledIds.push(diagnostic.pattern);
|
|
3742
|
-
}
|
|
3743
|
-
// Helper to update session's scoped models (session-only, no persist)
|
|
3744
|
-
const updateSessionModels = async (enabledIds) => {
|
|
3945
|
+
const configuredEnabledIds = (models) => {
|
|
3946
|
+
if (!configuredPatterns?.length)
|
|
3947
|
+
return null;
|
|
3948
|
+
const resolved = resolveModelScopeFromModels(configuredPatterns, models);
|
|
3949
|
+
const ids = resolved.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3950
|
+
for (const diagnostic of resolved.diagnostics) {
|
|
3951
|
+
if (diagnostic.code === "no-match" && !ids.includes(diagnostic.pattern))
|
|
3952
|
+
ids.push(diagnostic.pattern);
|
|
3953
|
+
}
|
|
3954
|
+
return ids;
|
|
3955
|
+
};
|
|
3956
|
+
let currentEnabledIds = sessionScopedModels.length > 0
|
|
3957
|
+
? sessionScopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`)
|
|
3958
|
+
: configuredEnabledIds(availableModels);
|
|
3959
|
+
let selectionChanged = false;
|
|
3960
|
+
const updateSessionModels = (enabledIds) => {
|
|
3745
3961
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3746
|
-
const hasEnabledAvailableModel = enabledIds?.some((id) =>
|
|
3747
|
-
const allAvailableModelsEnabled = enabledIds !== null && [...
|
|
3962
|
+
const hasEnabledAvailableModel = enabledIds?.some((id) => availableModelIds.has(id)) ?? false;
|
|
3963
|
+
const allAvailableModelsEnabled = enabledIds !== null && [...availableModelIds].every((id) => enabledIds.includes(id));
|
|
3748
3964
|
if (enabledIds && hasEnabledAvailableModel && !allAvailableModelsEnabled) {
|
|
3749
|
-
const newScopedModels =
|
|
3750
|
-
this.session.setScopedModels(newScopedModels.map((
|
|
3751
|
-
model:
|
|
3752
|
-
thinkingLevel:
|
|
3965
|
+
const newScopedModels = resolveModelScopeFromModels(enabledIds, availableModels).scopedModels;
|
|
3966
|
+
this.session.setScopedModels(newScopedModels.map((scoped) => ({
|
|
3967
|
+
model: scoped.model,
|
|
3968
|
+
thinkingLevel: scoped.thinkingLevel,
|
|
3753
3969
|
})));
|
|
3754
3970
|
}
|
|
3755
3971
|
else {
|
|
3756
|
-
// All enabled or none enabled = no filter
|
|
3757
3972
|
this.session.setScopedModels([]);
|
|
3758
3973
|
}
|
|
3759
|
-
|
|
3974
|
+
this.updateAvailableProviderCount();
|
|
3760
3975
|
this.ui.requestRender();
|
|
3761
3976
|
};
|
|
3762
3977
|
this.showSelector((done) => {
|
|
3978
|
+
let disposed = false;
|
|
3979
|
+
let timedOut = false;
|
|
3980
|
+
const controller = new AbortController();
|
|
3981
|
+
const timeout = setTimeout(() => {
|
|
3982
|
+
timedOut = true;
|
|
3983
|
+
controller.abort();
|
|
3984
|
+
}, 15_000);
|
|
3763
3985
|
const selector = new ScopedModelsSelectorComponent({
|
|
3764
|
-
allModels,
|
|
3986
|
+
allModels: availableModels,
|
|
3765
3987
|
enabledModelIds: currentEnabledIds,
|
|
3988
|
+
refreshStatus: "Refreshing model catalogs…",
|
|
3766
3989
|
}, {
|
|
3767
|
-
onChange:
|
|
3768
|
-
|
|
3990
|
+
onChange: (enabledIds) => {
|
|
3991
|
+
selectionChanged = true;
|
|
3992
|
+
updateSessionModels(enabledIds);
|
|
3769
3993
|
},
|
|
3770
3994
|
onPersist: (enabledIds) => {
|
|
3771
|
-
// Persist to settings
|
|
3772
3995
|
const allEnabled = enabledIds !== null &&
|
|
3773
|
-
enabledIds.length ===
|
|
3774
|
-
enabledIds.every((id) =>
|
|
3996
|
+
enabledIds.length === availableModels.length &&
|
|
3997
|
+
enabledIds.every((id) => availableModelIds.has(id));
|
|
3775
3998
|
const newPatterns = enabledIds === null || allEnabled ? undefined : enabledIds;
|
|
3776
3999
|
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
3777
4000
|
this.showStatus("Model selection saved to settings");
|
|
@@ -3781,7 +4004,51 @@ export class InteractiveMode {
|
|
|
3781
4004
|
this.ui.requestRender();
|
|
3782
4005
|
},
|
|
3783
4006
|
});
|
|
3784
|
-
|
|
4007
|
+
void this.session.modelRuntime
|
|
4008
|
+
.refresh({ signal: controller.signal })
|
|
4009
|
+
.then((result) => {
|
|
4010
|
+
if (disposed)
|
|
4011
|
+
return;
|
|
4012
|
+
availableModels = [...this.session.modelRuntime.getAvailableSnapshot()];
|
|
4013
|
+
availableModelIds = new Set(availableModels.map((model) => `${model.provider}/${model.id}`));
|
|
4014
|
+
if (!selectionChanged && sessionScopedModels.length === 0) {
|
|
4015
|
+
currentEnabledIds = configuredEnabledIds(availableModels);
|
|
4016
|
+
selector.updateModels(availableModels, currentEnabledIds);
|
|
4017
|
+
}
|
|
4018
|
+
else {
|
|
4019
|
+
selector.updateModels(availableModels);
|
|
4020
|
+
}
|
|
4021
|
+
if (currentEnabledIds !== null)
|
|
4022
|
+
updateSessionModels(currentEnabledIds);
|
|
4023
|
+
if (result.aborted && timedOut) {
|
|
4024
|
+
selector.setRefreshStatus("Model refresh timed out; showing cached models.", "warning");
|
|
4025
|
+
}
|
|
4026
|
+
else if (result.errors.size > 0) {
|
|
4027
|
+
selector.setRefreshStatus(`Could not refresh ${[...result.errors.keys()].join(", ")}; showing cached models.`, "warning");
|
|
4028
|
+
}
|
|
4029
|
+
else {
|
|
4030
|
+
selector.setRefreshStatus("Model catalogs refreshed.", "success");
|
|
4031
|
+
}
|
|
4032
|
+
this.ui.requestRender();
|
|
4033
|
+
})
|
|
4034
|
+
.catch((error) => {
|
|
4035
|
+
if (disposed)
|
|
4036
|
+
return;
|
|
4037
|
+
selector.setRefreshStatus(timedOut
|
|
4038
|
+
? "Model refresh timed out; showing cached models."
|
|
4039
|
+
: `Could not refresh model catalogs: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
4040
|
+
this.ui.requestRender();
|
|
4041
|
+
})
|
|
4042
|
+
.finally(() => clearTimeout(timeout));
|
|
4043
|
+
return {
|
|
4044
|
+
component: selector,
|
|
4045
|
+
focus: selector,
|
|
4046
|
+
dispose: () => {
|
|
4047
|
+
disposed = true;
|
|
4048
|
+
clearTimeout(timeout);
|
|
4049
|
+
controller.abort();
|
|
4050
|
+
},
|
|
4051
|
+
};
|
|
3785
4052
|
});
|
|
3786
4053
|
}
|
|
3787
4054
|
showUserMessageSelector() {
|
|
@@ -3843,7 +4110,7 @@ export class InteractiveMode {
|
|
|
3843
4110
|
this.showSelector((done) => {
|
|
3844
4111
|
const selector = new TreeSelectorComponent(tree, realLeafId, this.ui.terminal.rows, async (entryId) => {
|
|
3845
4112
|
// Selecting the current leaf is a no-op (already there)
|
|
3846
|
-
if (entryId ===
|
|
4113
|
+
if (entryId === this.sessionManager.getLeafId()) {
|
|
3847
4114
|
done();
|
|
3848
4115
|
this.showStatus("Already at this point");
|
|
3849
4116
|
return;
|
|
@@ -3878,6 +4145,11 @@ export class InteractiveMode {
|
|
|
3878
4145
|
break;
|
|
3879
4146
|
}
|
|
3880
4147
|
}
|
|
4148
|
+
// The user committed to navigating: stop the active response first.
|
|
4149
|
+
if (this.session.isStreaming) {
|
|
4150
|
+
this.restoreQueuedMessagesToEditor();
|
|
4151
|
+
await this.session.abort();
|
|
4152
|
+
}
|
|
3881
4153
|
// Set up escape handler and status indicator if summarizing
|
|
3882
4154
|
let showingSummaryIndicator = false;
|
|
3883
4155
|
const originalOnEscape = this.defaultEditor.onEscape;
|
|
@@ -4038,7 +4310,7 @@ export class InteractiveMode {
|
|
|
4038
4310
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4039
4311
|
}
|
|
4040
4312
|
async getLogoutProviderOptions() {
|
|
4041
|
-
return (await this.session.modelRuntime.listCredentials())
|
|
4313
|
+
return (await this.session.modelRuntime.listCredentials({ signal: AbortSignal.timeout(15_000) }))
|
|
4042
4314
|
.map(({ providerId, type }) => ({
|
|
4043
4315
|
id: providerId,
|
|
4044
4316
|
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
@@ -4056,7 +4328,6 @@ export class InteractiveMode {
|
|
|
4056
4328
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4057
4329
|
}
|
|
4058
4330
|
async handleLoginCommand(providerRef) {
|
|
4059
|
-
await this.session.modelRuntime.getAvailable();
|
|
4060
4331
|
if (!providerRef) {
|
|
4061
4332
|
this.showLoginAuthTypeSelector();
|
|
4062
4333
|
return;
|
|
@@ -4170,7 +4441,14 @@ export class InteractiveMode {
|
|
|
4170
4441
|
this.showLoginAuthTypeSelector();
|
|
4171
4442
|
return;
|
|
4172
4443
|
}
|
|
4173
|
-
|
|
4444
|
+
let providerOptions;
|
|
4445
|
+
try {
|
|
4446
|
+
providerOptions = await this.getLogoutProviderOptions();
|
|
4447
|
+
}
|
|
4448
|
+
catch (error) {
|
|
4449
|
+
this.showError(`Could not read stored credentials: ${error instanceof Error ? error.message : String(error)}`);
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4174
4452
|
if (providerOptions.length === 0) {
|
|
4175
4453
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4176
4454
|
return;
|
|
@@ -4183,7 +4461,9 @@ export class InteractiveMode {
|
|
|
4183
4461
|
return;
|
|
4184
4462
|
}
|
|
4185
4463
|
try {
|
|
4186
|
-
await this.session.modelRuntime.logout(providerOption.id
|
|
4464
|
+
await this.session.modelRuntime.logout(providerOption.id, {
|
|
4465
|
+
signal: AbortSignal.timeout(15_000),
|
|
4466
|
+
});
|
|
4187
4467
|
await this.updateAvailableProviderCount();
|
|
4188
4468
|
const message = providerOption.authType === "oauth"
|
|
4189
4469
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4191,7 +4471,10 @@ export class InteractiveMode {
|
|
|
4191
4471
|
this.showStatus(message);
|
|
4192
4472
|
}
|
|
4193
4473
|
catch (error) {
|
|
4194
|
-
|
|
4474
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4475
|
+
this.showError(error instanceof CredentialSynchronizationError
|
|
4476
|
+
? `Credentials removed for ${providerOption.name}, but local model state could not be synchronized: ${message}`
|
|
4477
|
+
: `Logout failed: ${message}`);
|
|
4195
4478
|
}
|
|
4196
4479
|
}, () => {
|
|
4197
4480
|
done();
|
|
@@ -4201,12 +4484,11 @@ export class InteractiveMode {
|
|
|
4201
4484
|
});
|
|
4202
4485
|
}
|
|
4203
4486
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4204
|
-
await this.session.modelRuntime.getAvailable();
|
|
4205
4487
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4206
4488
|
let selectedModel;
|
|
4207
4489
|
let selectionError;
|
|
4208
4490
|
if (isUnknownModel(previousModel)) {
|
|
4209
|
-
const availableModels =
|
|
4491
|
+
const availableModels = this.session.modelRuntime.getAvailableSnapshot();
|
|
4210
4492
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4211
4493
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4212
4494
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4249,6 +4531,25 @@ export class InteractiveMode {
|
|
|
4249
4531
|
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
4250
4532
|
}
|
|
4251
4533
|
}
|
|
4534
|
+
const controller = new AbortController();
|
|
4535
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
4536
|
+
void this.session.modelRuntime
|
|
4537
|
+
.refresh({ providers: [providerId], signal: controller.signal })
|
|
4538
|
+
.then((result) => {
|
|
4539
|
+
if (result.aborted) {
|
|
4540
|
+
this.showWarning(`${actionLabel}, but its model catalog refresh timed out; using cached models.`);
|
|
4541
|
+
}
|
|
4542
|
+
else if (result.errors.size > 0) {
|
|
4543
|
+
this.showWarning(`${actionLabel}, but its model catalog could not be refreshed; using cached models.`);
|
|
4544
|
+
}
|
|
4545
|
+
this.updateAvailableProviderCount();
|
|
4546
|
+
this.footer.invalidate();
|
|
4547
|
+
this.ui.requestRender();
|
|
4548
|
+
})
|
|
4549
|
+
.catch((error) => {
|
|
4550
|
+
this.showWarning(`${actionLabel}, but its model catalog could not be refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4551
|
+
})
|
|
4552
|
+
.finally(() => clearTimeout(timeout));
|
|
4252
4553
|
}
|
|
4253
4554
|
showAmbientAuthDialog(providerOption) {
|
|
4254
4555
|
const restoreEditor = () => {
|
|
@@ -4294,7 +4595,10 @@ export class InteractiveMode {
|
|
|
4294
4595
|
catch (error) {
|
|
4295
4596
|
restoreEditor();
|
|
4296
4597
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4297
|
-
if (
|
|
4598
|
+
if (error instanceof CredentialSynchronizationError) {
|
|
4599
|
+
this.showError(`Saved API key for ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
|
|
4600
|
+
}
|
|
4601
|
+
else if (errorMsg !== "Login cancelled") {
|
|
4298
4602
|
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
4299
4603
|
}
|
|
4300
4604
|
}
|
|
@@ -4397,7 +4701,10 @@ export class InteractiveMode {
|
|
|
4397
4701
|
catch (error) {
|
|
4398
4702
|
restoreEditor();
|
|
4399
4703
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4400
|
-
if (
|
|
4704
|
+
if (error instanceof CredentialSynchronizationError) {
|
|
4705
|
+
this.showError(`Logged in to ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
|
|
4706
|
+
}
|
|
4707
|
+
else if (errorMsg !== "Login cancelled") {
|
|
4401
4708
|
this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
|
|
4402
4709
|
}
|
|
4403
4710
|
}
|
|
@@ -4448,7 +4755,6 @@ export class InteractiveMode {
|
|
|
4448
4755
|
try {
|
|
4449
4756
|
await this.session.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
|
|
4450
4757
|
restoreChatBeforeSessionStart();
|
|
4451
|
-
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
4452
4758
|
this.keybindings.reload();
|
|
4453
4759
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
4454
4760
|
if (isExpandable(activeHeader)) {
|
|
@@ -4456,20 +4762,7 @@ export class InteractiveMode {
|
|
|
4456
4762
|
}
|
|
4457
4763
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
4458
4764
|
await this.themeController.applyFromSettings();
|
|
4459
|
-
|
|
4460
|
-
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
4461
|
-
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
4462
|
-
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
4463
|
-
if (this.editor !== this.defaultEditor) {
|
|
4464
|
-
this.editor.setPaddingX?.(editorPaddingX);
|
|
4465
|
-
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
4466
|
-
}
|
|
4467
|
-
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
4468
|
-
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
4469
|
-
this.ui.setClearOnShrink(clearOnShrink);
|
|
4470
|
-
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
4471
|
-
this.statusContainer.clear();
|
|
4472
|
-
}
|
|
4765
|
+
this.applyRuntimeSettings();
|
|
4473
4766
|
this.setupAutocompleteProvider();
|
|
4474
4767
|
const runner = this.session.extensionRunner;
|
|
4475
4768
|
this.setupExtensionShortcuts(runner);
|
|
@@ -4665,7 +4958,7 @@ export class InteractiveMode {
|
|
|
4665
4958
|
}
|
|
4666
4959
|
}
|
|
4667
4960
|
}
|
|
4668
|
-
async handleCopyCommand() {
|
|
4961
|
+
async handleCopyCommand(options = {}) {
|
|
4669
4962
|
const text = this.session.getLastAssistantText();
|
|
4670
4963
|
if (!text) {
|
|
4671
4964
|
this.showError("No agent messages to copy yet.");
|
|
@@ -4673,7 +4966,12 @@ export class InteractiveMode {
|
|
|
4673
4966
|
}
|
|
4674
4967
|
try {
|
|
4675
4968
|
await copyToClipboard(text);
|
|
4676
|
-
|
|
4969
|
+
if (options.flashConfirmation && this.ui instanceof TuiAltScreen) {
|
|
4970
|
+
this.ui.flash("Copied!");
|
|
4971
|
+
}
|
|
4972
|
+
else {
|
|
4973
|
+
this.showStatus("Copied last agent message to clipboard");
|
|
4974
|
+
}
|
|
4677
4975
|
}
|
|
4678
4976
|
catch (error) {
|
|
4679
4977
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -5036,6 +5334,7 @@ export class InteractiveMode {
|
|
|
5036
5334
|
}
|
|
5037
5335
|
}
|
|
5038
5336
|
stop() {
|
|
5337
|
+
this.disposeActiveSelector();
|
|
5039
5338
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
5040
5339
|
this.ui.terminal.setProgress(false);
|
|
5041
5340
|
}
|
|
@@ -5048,7 +5347,7 @@ export class InteractiveMode {
|
|
|
5048
5347
|
this.unsubscribe();
|
|
5049
5348
|
}
|
|
5050
5349
|
if (this.isInitialized) {
|
|
5051
|
-
this.
|
|
5350
|
+
this.stopInteractiveTui();
|
|
5052
5351
|
this.isInitialized = false;
|
|
5053
5352
|
}
|
|
5054
5353
|
this.unregisterSignalHandlers();
|