@earendil-works/pi-coding-agent 0.83.0 → 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 +198 -0
- package/README.md +5 -0
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +19 -0
- 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 +1 -1
- package/dist/cli/credential-print.d.ts.map +1 -1
- package/dist/cli/credential-print.js +6 -5
- package/dist/cli/credential-print.js.map +1 -1
- 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-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 +1 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +72 -70
- 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 +2 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +3 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +21 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.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 +28 -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 +63 -36
- 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.map +1 -1
- package/dist/core/resource-loader.js +2 -2
- 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 +21 -7
- 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 +16 -7
- 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 +16 -4
- 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 +423 -138
- 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 +4 -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 +7 -5
- package/docs/extensions.md +27 -3
- package/docs/json.md +19 -14
- package/docs/keybindings.md +38 -10
- package/docs/models.md +24 -3
- package/docs/providers.md +1 -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/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 +2 -1
- 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 +1 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +64 -20
- package/package.json +13 -6
|
@@ -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) => {
|
|
@@ -1288,8 +1434,12 @@ export class InteractiveMode {
|
|
|
1288
1434
|
this.showLoadedResources({ force: false, showDiagnosticsWhenQuiet: true });
|
|
1289
1435
|
this.showStartupNoticesIfNeeded();
|
|
1290
1436
|
}
|
|
1437
|
+
applyFullscreenScrollbarSetting() {
|
|
1438
|
+
this.transcriptScrollView?.setScrollbar(this.settingsManager.getFullscreenScrollbar());
|
|
1439
|
+
}
|
|
1291
1440
|
applyRuntimeSettings() {
|
|
1292
1441
|
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
1442
|
+
this.applyFullscreenScrollbarSetting();
|
|
1293
1443
|
this.footer.setSession(this.session);
|
|
1294
1444
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1295
1445
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
@@ -1353,6 +1503,9 @@ export class InteractiveMode {
|
|
|
1353
1503
|
getRegisteredToolDefinition(toolName) {
|
|
1354
1504
|
return this.session.getToolDefinition(toolName);
|
|
1355
1505
|
}
|
|
1506
|
+
getMarkdownTransformers() {
|
|
1507
|
+
return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
|
|
1508
|
+
}
|
|
1356
1509
|
/**
|
|
1357
1510
|
* Set up keyboard shortcuts registered by extensions.
|
|
1358
1511
|
*/
|
|
@@ -1432,7 +1585,7 @@ export class InteractiveMode {
|
|
|
1432
1585
|
this.activeStatusIndicator?.dispose();
|
|
1433
1586
|
this.activeStatusIndicator = undefined;
|
|
1434
1587
|
this.statusContainer.clear();
|
|
1435
|
-
if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
|
|
1588
|
+
if (hadActiveStatusIndicator && this.options.tuiMode === "regular" && this.ui.getClearOnShrink()) {
|
|
1436
1589
|
this.statusContainer.addChild(this.idleStatus);
|
|
1437
1590
|
}
|
|
1438
1591
|
}
|
|
@@ -1580,22 +1733,16 @@ export class InteractiveMode {
|
|
|
1580
1733
|
if (this.customFooter?.dispose) {
|
|
1581
1734
|
this.customFooter.dispose();
|
|
1582
1735
|
}
|
|
1583
|
-
|
|
1584
|
-
if (this.customFooter) {
|
|
1585
|
-
this.ui.removeChild(this.customFooter);
|
|
1586
|
-
}
|
|
1587
|
-
else {
|
|
1588
|
-
this.ui.removeChild(this.footer);
|
|
1589
|
-
}
|
|
1736
|
+
this.footerContainer.clear();
|
|
1590
1737
|
if (factory) {
|
|
1591
1738
|
// Create and add custom footer, passing the data provider
|
|
1592
1739
|
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
1593
|
-
this.
|
|
1740
|
+
this.footerContainer.addChild(this.customFooter);
|
|
1594
1741
|
}
|
|
1595
1742
|
else {
|
|
1596
1743
|
// Restore built-in footer
|
|
1597
1744
|
this.customFooter = undefined;
|
|
1598
|
-
this.
|
|
1745
|
+
this.footerContainer.addChild(this.footer);
|
|
1599
1746
|
}
|
|
1600
1747
|
this.ui.requestRender();
|
|
1601
1748
|
}
|
|
@@ -1641,18 +1788,23 @@ export class InteractiveMode {
|
|
|
1641
1788
|
this.ui.requestRender();
|
|
1642
1789
|
}
|
|
1643
1790
|
addExtensionTerminalInputListener(handler) {
|
|
1644
|
-
const
|
|
1645
|
-
this.
|
|
1791
|
+
const subscription = { handler, unsubscribe: this.ui.addInputListener(handler) };
|
|
1792
|
+
this.extensionTerminalInputSubscriptions.add(subscription);
|
|
1646
1793
|
return () => {
|
|
1647
|
-
unsubscribe();
|
|
1648
|
-
this.
|
|
1794
|
+
subscription.unsubscribe();
|
|
1795
|
+
this.extensionTerminalInputSubscriptions.delete(subscription);
|
|
1649
1796
|
};
|
|
1650
1797
|
}
|
|
1651
|
-
|
|
1652
|
-
for (const
|
|
1653
|
-
unsubscribe();
|
|
1798
|
+
rebindExtensionTerminalInputListeners() {
|
|
1799
|
+
for (const subscription of this.extensionTerminalInputSubscriptions) {
|
|
1800
|
+
subscription.unsubscribe();
|
|
1801
|
+
subscription.unsubscribe = this.ui.addInputListener(subscription.handler);
|
|
1654
1802
|
}
|
|
1655
|
-
|
|
1803
|
+
}
|
|
1804
|
+
clearExtensionTerminalInputListeners() {
|
|
1805
|
+
for (const subscription of this.extensionTerminalInputSubscriptions)
|
|
1806
|
+
subscription.unsubscribe();
|
|
1807
|
+
this.extensionTerminalInputSubscriptions.clear();
|
|
1656
1808
|
}
|
|
1657
1809
|
/**
|
|
1658
1810
|
* Create the ExtensionUIContext for extensions.
|
|
@@ -1747,6 +1899,7 @@ export class InteractiveMode {
|
|
|
1747
1899
|
this.hideExtensionSelector();
|
|
1748
1900
|
resolve(undefined);
|
|
1749
1901
|
}, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
|
|
1902
|
+
this.disposeActiveSelector();
|
|
1750
1903
|
this.editorContainer.clear();
|
|
1751
1904
|
this.editorContainer.addChild(this.extensionSelector);
|
|
1752
1905
|
this.ui.setFocus(this.extensionSelector);
|
|
@@ -1798,6 +1951,7 @@ export class InteractiveMode {
|
|
|
1798
1951
|
this.hideExtensionInput();
|
|
1799
1952
|
resolve(undefined);
|
|
1800
1953
|
}, { tui: this.ui, timeout: opts?.timeout });
|
|
1954
|
+
this.disposeActiveSelector();
|
|
1801
1955
|
this.editorContainer.clear();
|
|
1802
1956
|
this.editorContainer.addChild(this.extensionInput);
|
|
1803
1957
|
this.ui.setFocus(this.extensionInput);
|
|
@@ -1827,6 +1981,7 @@ export class InteractiveMode {
|
|
|
1827
1981
|
this.hideExtensionEditor();
|
|
1828
1982
|
resolve(undefined);
|
|
1829
1983
|
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
1984
|
+
this.disposeActiveSelector();
|
|
1830
1985
|
this.editorContainer.clear();
|
|
1831
1986
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1832
1987
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -1851,6 +2006,7 @@ export class InteractiveMode {
|
|
|
1851
2006
|
this.editorComponentFactory = factory;
|
|
1852
2007
|
// Save text from current editor before switching
|
|
1853
2008
|
const currentText = this.editor.getText();
|
|
2009
|
+
this.disposeActiveSelector();
|
|
1854
2010
|
this.editorContainer.clear();
|
|
1855
2011
|
if (factory) {
|
|
1856
2012
|
// Create the custom editor with tui, theme, and keybindings
|
|
@@ -1867,6 +2023,9 @@ export class InteractiveMode {
|
|
|
1867
2023
|
if (newEditor.setPaddingX !== undefined) {
|
|
1868
2024
|
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1869
2025
|
}
|
|
2026
|
+
if (newEditor.setAutocompleteMaxVisible !== undefined) {
|
|
2027
|
+
newEditor.setAutocompleteMaxVisible(this.defaultEditor.getAutocompleteMaxVisible());
|
|
2028
|
+
}
|
|
1870
2029
|
// Set autocomplete if supported
|
|
1871
2030
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1872
2031
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1971,6 +2130,7 @@ export class InteractiveMode {
|
|
|
1971
2130
|
options?.onHandle?.(handle);
|
|
1972
2131
|
}
|
|
1973
2132
|
else {
|
|
2133
|
+
this.disposeActiveSelector();
|
|
1974
2134
|
this.editorContainer.clear();
|
|
1975
2135
|
this.editorContainer.addChild(component);
|
|
1976
2136
|
this.ui.setFocus(component);
|
|
@@ -2057,7 +2217,7 @@ export class InteractiveMode {
|
|
|
2057
2217
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2058
2218
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2059
2219
|
this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
|
|
2060
|
-
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
|
|
2220
|
+
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true }));
|
|
2061
2221
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2062
2222
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2063
2223
|
this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
|
|
@@ -2345,17 +2505,17 @@ export class InteractiveMode {
|
|
|
2345
2505
|
this.ui.requestRender();
|
|
2346
2506
|
}
|
|
2347
2507
|
else if (event.message.role === "assistant") {
|
|
2348
|
-
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());
|
|
2349
2509
|
this.streamingMessage = event.message;
|
|
2350
2510
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2351
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2511
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2352
2512
|
this.ui.requestRender();
|
|
2353
2513
|
}
|
|
2354
2514
|
break;
|
|
2355
2515
|
case "message_update":
|
|
2356
2516
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2357
2517
|
this.streamingMessage = event.message;
|
|
2358
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2518
|
+
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2359
2519
|
for (const content of this.streamingMessage.content) {
|
|
2360
2520
|
if (content.type === "toolCall") {
|
|
2361
2521
|
if (!this.pendingTools.has(content.id)) {
|
|
@@ -2392,7 +2552,7 @@ export class InteractiveMode {
|
|
|
2392
2552
|
: "Operation aborted";
|
|
2393
2553
|
this.streamingMessage.errorMessage = errorMessage;
|
|
2394
2554
|
}
|
|
2395
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2555
|
+
this.streamingComponent.updateContent(this.streamingMessage, false);
|
|
2396
2556
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2397
2557
|
if (!errorMessage) {
|
|
2398
2558
|
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
@@ -2666,12 +2826,12 @@ export class InteractiveMode {
|
|
|
2666
2826
|
// Render user message separately if present
|
|
2667
2827
|
if (skillBlock.userMessage) {
|
|
2668
2828
|
this.chatContainer.addChild(new Spacer(1));
|
|
2669
|
-
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2829
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2670
2830
|
this.chatContainer.addChild(userComponent);
|
|
2671
2831
|
}
|
|
2672
2832
|
}
|
|
2673
2833
|
else {
|
|
2674
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2834
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad, this.getMarkdownTransformers());
|
|
2675
2835
|
this.chatContainer.addChild(userComponent);
|
|
2676
2836
|
}
|
|
2677
2837
|
if (options?.populateHistory) {
|
|
@@ -2681,7 +2841,7 @@ export class InteractiveMode {
|
|
|
2681
2841
|
break;
|
|
2682
2842
|
}
|
|
2683
2843
|
case "assistant": {
|
|
2684
|
-
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());
|
|
2685
2845
|
this.chatContainer.addChild(assistantComponent);
|
|
2686
2846
|
break;
|
|
2687
2847
|
}
|
|
@@ -3115,6 +3275,8 @@ export class InteractiveMode {
|
|
|
3115
3275
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
3116
3276
|
}
|
|
3117
3277
|
setToolsExpanded(expanded) {
|
|
3278
|
+
if (expanded === this.toolOutputExpanded)
|
|
3279
|
+
return;
|
|
3118
3280
|
this.toolOutputExpanded = expanded;
|
|
3119
3281
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
3120
3282
|
if (isExpandable(activeHeader)) {
|
|
@@ -3170,7 +3332,7 @@ export class InteractiveMode {
|
|
|
3170
3332
|
}
|
|
3171
3333
|
showError(errorMessage) {
|
|
3172
3334
|
this.chatContainer.addChild(new Spacer(1));
|
|
3173
|
-
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`),
|
|
3335
|
+
this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), this.outputPad, 0));
|
|
3174
3336
|
this.ui.requestRender();
|
|
3175
3337
|
}
|
|
3176
3338
|
showWarning(warningMessage) {
|
|
@@ -3380,25 +3542,43 @@ export class InteractiveMode {
|
|
|
3380
3542
|
// =========================================================================
|
|
3381
3543
|
// Selectors
|
|
3382
3544
|
// =========================================================================
|
|
3545
|
+
disposeActiveSelector() {
|
|
3546
|
+
const dispose = this.activeSelectorDispose;
|
|
3547
|
+
this.activeSelectorToken = undefined;
|
|
3548
|
+
this.activeSelectorDispose = undefined;
|
|
3549
|
+
dispose?.();
|
|
3550
|
+
}
|
|
3383
3551
|
/**
|
|
3384
3552
|
* Shows a selector component in place of the editor.
|
|
3385
3553
|
* @param create Factory that receives a `done` callback and returns the component and focus target
|
|
3386
3554
|
*/
|
|
3387
3555
|
showSelector(create) {
|
|
3556
|
+
const token = {};
|
|
3557
|
+
let dispose;
|
|
3388
3558
|
const done = () => {
|
|
3559
|
+
dispose?.();
|
|
3560
|
+
if (this.activeSelectorToken !== token)
|
|
3561
|
+
return;
|
|
3562
|
+
this.activeSelectorToken = undefined;
|
|
3563
|
+
this.activeSelectorDispose = undefined;
|
|
3389
3564
|
this.editorContainer.clear();
|
|
3390
3565
|
this.editorContainer.addChild(this.editor);
|
|
3391
3566
|
this.ui.setFocus(this.editor);
|
|
3392
3567
|
};
|
|
3393
|
-
const
|
|
3568
|
+
const created = create(done);
|
|
3569
|
+
dispose = created.dispose;
|
|
3570
|
+
this.disposeActiveSelector();
|
|
3571
|
+
this.activeSelectorToken = token;
|
|
3572
|
+
this.activeSelectorDispose = dispose;
|
|
3394
3573
|
this.editorContainer.clear();
|
|
3395
|
-
this.editorContainer.addChild(component);
|
|
3396
|
-
this.ui.setFocus(focus);
|
|
3574
|
+
this.editorContainer.addChild(created.component);
|
|
3575
|
+
this.ui.setFocus(created.focus);
|
|
3397
3576
|
this.ui.requestRender();
|
|
3398
3577
|
}
|
|
3399
3578
|
showSettingsSelector() {
|
|
3400
3579
|
this.showSelector((done) => {
|
|
3401
|
-
|
|
3580
|
+
let selector;
|
|
3581
|
+
selector = new SettingsSelectorComponent({
|
|
3402
3582
|
autoCompact: this.session.autoCompactionEnabled,
|
|
3403
3583
|
showImages: this.settingsManager.getShowImages(),
|
|
3404
3584
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
@@ -3415,6 +3595,7 @@ export class InteractiveMode {
|
|
|
3415
3595
|
terminalTheme: this.themeController.getTerminalTheme(),
|
|
3416
3596
|
availableThemes: getAvailableThemes(),
|
|
3417
3597
|
hideThinkingBlock: this.hideThinkingBlock,
|
|
3598
|
+
mermaidRenderingMode: this.settingsManager.getMermaidRenderingMode(),
|
|
3418
3599
|
collapseChangelog: this.settingsManager.getCollapseChangelog(),
|
|
3419
3600
|
enableInstallTelemetry: this.settingsManager.getEnableInstallTelemetry(),
|
|
3420
3601
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
@@ -3428,6 +3609,8 @@ export class InteractiveMode {
|
|
|
3428
3609
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3429
3610
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3430
3611
|
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3612
|
+
tuiMode: this.ui.mode,
|
|
3613
|
+
fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
3431
3614
|
warnings: this.settingsManager.getWarnings(),
|
|
3432
3615
|
}, {
|
|
3433
3616
|
onAutoCompactChange: (enabled) => {
|
|
@@ -3496,6 +3679,11 @@ export class InteractiveMode {
|
|
|
3496
3679
|
this.chatContainer.clear();
|
|
3497
3680
|
this.rebuildChatFromMessages();
|
|
3498
3681
|
},
|
|
3682
|
+
onMermaidRenderingModeChange: (mode) => {
|
|
3683
|
+
this.settingsManager.setMermaidRenderingMode(mode);
|
|
3684
|
+
this.chatContainer.invalidate();
|
|
3685
|
+
this.ui.requestRender();
|
|
3686
|
+
},
|
|
3499
3687
|
onShowCacheMissNoticesChange: (shown) => {
|
|
3500
3688
|
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3501
3689
|
this.rebuildChatFromMessages();
|
|
@@ -3565,6 +3753,21 @@ export class InteractiveMode {
|
|
|
3565
3753
|
onShowTerminalProgressChange: (enabled) => {
|
|
3566
3754
|
this.settingsManager.setShowTerminalProgress(enabled);
|
|
3567
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
|
+
},
|
|
3568
3771
|
onWarningsChange: (warnings) => {
|
|
3569
3772
|
this.settingsManager.setWarnings(warnings);
|
|
3570
3773
|
},
|
|
@@ -3599,20 +3802,37 @@ export class InteractiveMode {
|
|
|
3599
3802
|
this.showModelSelector(searchTerm);
|
|
3600
3803
|
}
|
|
3601
3804
|
async findExactModelMatch(searchTerm) {
|
|
3602
|
-
const
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
if (this.session.scopedModels.length > 0)
|
|
3607
|
-
return
|
|
3608
|
-
|
|
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);
|
|
3609
3818
|
try {
|
|
3610
|
-
await this.session.modelRuntime.refresh();
|
|
3611
|
-
|
|
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
|
+
}
|
|
3612
3826
|
}
|
|
3613
|
-
catch {
|
|
3614
|
-
|
|
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)}`);
|
|
3615
3831
|
}
|
|
3832
|
+
finally {
|
|
3833
|
+
clearTimeout(timeout);
|
|
3834
|
+
}
|
|
3835
|
+
return findExactModelReferenceMatch(searchTerm, [...this.session.modelRuntime.getAvailableSnapshot()]);
|
|
3616
3836
|
}
|
|
3617
3837
|
/** Update the footer's available provider count from the current snapshot without refreshing catalogs. */
|
|
3618
3838
|
updateAvailableProviderCount() {
|
|
@@ -3714,73 +3934,67 @@ export class InteractiveMode {
|
|
|
3714
3934
|
done();
|
|
3715
3935
|
this.ui.requestRender();
|
|
3716
3936
|
}, initialSearchInput);
|
|
3717
|
-
return { component: selector, focus: selector };
|
|
3937
|
+
return { component: selector, focus: selector, dispose: () => selector.dispose() };
|
|
3718
3938
|
});
|
|
3719
3939
|
}
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
const allModels = [...(await this.session.modelRuntime.getAvailable())];
|
|
3724
|
-
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}`));
|
|
3725
3943
|
const configuredPatterns = this.settingsManager.getEnabledModels();
|
|
3726
3944
|
const sessionScopedModels = this.session.scopedModels;
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
currentEnabledIds = configuredScope.scopedModels.map((scoped) => `${scoped.model.provider}/${scoped.model.id}`);
|
|
3744
|
-
}
|
|
3745
|
-
for (const diagnostic of configuredScope?.diagnostics ?? []) {
|
|
3746
|
-
if (diagnostic.code !== "no-match")
|
|
3747
|
-
continue;
|
|
3748
|
-
currentEnabledIds ??= [];
|
|
3749
|
-
if (!currentEnabledIds.includes(diagnostic.pattern))
|
|
3750
|
-
currentEnabledIds.push(diagnostic.pattern);
|
|
3751
|
-
}
|
|
3752
|
-
// Helper to update session's scoped models (session-only, no persist)
|
|
3753
|
-
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) => {
|
|
3754
3961
|
currentEnabledIds = enabledIds === null ? null : [...enabledIds];
|
|
3755
|
-
const hasEnabledAvailableModel = enabledIds?.some((id) =>
|
|
3756
|
-
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));
|
|
3757
3964
|
if (enabledIds && hasEnabledAvailableModel && !allAvailableModelsEnabled) {
|
|
3758
|
-
const newScopedModels =
|
|
3759
|
-
this.session.setScopedModels(newScopedModels.map((
|
|
3760
|
-
model:
|
|
3761
|
-
thinkingLevel:
|
|
3965
|
+
const newScopedModels = resolveModelScopeFromModels(enabledIds, availableModels).scopedModels;
|
|
3966
|
+
this.session.setScopedModels(newScopedModels.map((scoped) => ({
|
|
3967
|
+
model: scoped.model,
|
|
3968
|
+
thinkingLevel: scoped.thinkingLevel,
|
|
3762
3969
|
})));
|
|
3763
3970
|
}
|
|
3764
3971
|
else {
|
|
3765
|
-
// All enabled or none enabled = no filter
|
|
3766
3972
|
this.session.setScopedModels([]);
|
|
3767
3973
|
}
|
|
3768
|
-
|
|
3974
|
+
this.updateAvailableProviderCount();
|
|
3769
3975
|
this.ui.requestRender();
|
|
3770
3976
|
};
|
|
3771
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);
|
|
3772
3985
|
const selector = new ScopedModelsSelectorComponent({
|
|
3773
|
-
allModels,
|
|
3986
|
+
allModels: availableModels,
|
|
3774
3987
|
enabledModelIds: currentEnabledIds,
|
|
3988
|
+
refreshStatus: "Refreshing model catalogs…",
|
|
3775
3989
|
}, {
|
|
3776
|
-
onChange:
|
|
3777
|
-
|
|
3990
|
+
onChange: (enabledIds) => {
|
|
3991
|
+
selectionChanged = true;
|
|
3992
|
+
updateSessionModels(enabledIds);
|
|
3778
3993
|
},
|
|
3779
3994
|
onPersist: (enabledIds) => {
|
|
3780
|
-
// Persist to settings
|
|
3781
3995
|
const allEnabled = enabledIds !== null &&
|
|
3782
|
-
enabledIds.length ===
|
|
3783
|
-
enabledIds.every((id) =>
|
|
3996
|
+
enabledIds.length === availableModels.length &&
|
|
3997
|
+
enabledIds.every((id) => availableModelIds.has(id));
|
|
3784
3998
|
const newPatterns = enabledIds === null || allEnabled ? undefined : enabledIds;
|
|
3785
3999
|
this.settingsManager.setEnabledModels(newPatterns ? [...newPatterns] : undefined);
|
|
3786
4000
|
this.showStatus("Model selection saved to settings");
|
|
@@ -3790,7 +4004,51 @@ export class InteractiveMode {
|
|
|
3790
4004
|
this.ui.requestRender();
|
|
3791
4005
|
},
|
|
3792
4006
|
});
|
|
3793
|
-
|
|
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
|
+
};
|
|
3794
4052
|
});
|
|
3795
4053
|
}
|
|
3796
4054
|
showUserMessageSelector() {
|
|
@@ -4052,7 +4310,7 @@ export class InteractiveMode {
|
|
|
4052
4310
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4053
4311
|
}
|
|
4054
4312
|
async getLogoutProviderOptions() {
|
|
4055
|
-
return (await this.session.modelRuntime.listCredentials())
|
|
4313
|
+
return (await this.session.modelRuntime.listCredentials({ signal: AbortSignal.timeout(15_000) }))
|
|
4056
4314
|
.map(({ providerId, type }) => ({
|
|
4057
4315
|
id: providerId,
|
|
4058
4316
|
name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
|
|
@@ -4070,7 +4328,6 @@ export class InteractiveMode {
|
|
|
4070
4328
|
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4071
4329
|
}
|
|
4072
4330
|
async handleLoginCommand(providerRef) {
|
|
4073
|
-
await this.session.modelRuntime.getAvailable();
|
|
4074
4331
|
if (!providerRef) {
|
|
4075
4332
|
this.showLoginAuthTypeSelector();
|
|
4076
4333
|
return;
|
|
@@ -4184,7 +4441,14 @@ export class InteractiveMode {
|
|
|
4184
4441
|
this.showLoginAuthTypeSelector();
|
|
4185
4442
|
return;
|
|
4186
4443
|
}
|
|
4187
|
-
|
|
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
|
+
}
|
|
4188
4452
|
if (providerOptions.length === 0) {
|
|
4189
4453
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4190
4454
|
return;
|
|
@@ -4197,7 +4461,9 @@ export class InteractiveMode {
|
|
|
4197
4461
|
return;
|
|
4198
4462
|
}
|
|
4199
4463
|
try {
|
|
4200
|
-
await this.session.modelRuntime.logout(providerOption.id
|
|
4464
|
+
await this.session.modelRuntime.logout(providerOption.id, {
|
|
4465
|
+
signal: AbortSignal.timeout(15_000),
|
|
4466
|
+
});
|
|
4201
4467
|
await this.updateAvailableProviderCount();
|
|
4202
4468
|
const message = providerOption.authType === "oauth"
|
|
4203
4469
|
? `Logged out of ${providerOption.name}`
|
|
@@ -4205,7 +4471,10 @@ export class InteractiveMode {
|
|
|
4205
4471
|
this.showStatus(message);
|
|
4206
4472
|
}
|
|
4207
4473
|
catch (error) {
|
|
4208
|
-
|
|
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}`);
|
|
4209
4478
|
}
|
|
4210
4479
|
}, () => {
|
|
4211
4480
|
done();
|
|
@@ -4215,12 +4484,11 @@ export class InteractiveMode {
|
|
|
4215
4484
|
});
|
|
4216
4485
|
}
|
|
4217
4486
|
async completeProviderAuthentication(providerId, providerName, authType, previousModel) {
|
|
4218
|
-
await this.session.modelRuntime.getAvailable();
|
|
4219
4487
|
const actionLabel = authType === "oauth" ? `Logged in to ${providerName}` : `Saved API key for ${providerName}`;
|
|
4220
4488
|
let selectedModel;
|
|
4221
4489
|
let selectionError;
|
|
4222
4490
|
if (isUnknownModel(previousModel)) {
|
|
4223
|
-
const availableModels =
|
|
4491
|
+
const availableModels = this.session.modelRuntime.getAvailableSnapshot();
|
|
4224
4492
|
const providerModels = availableModels.filter((model) => model.provider === providerId);
|
|
4225
4493
|
if (!hasDefaultModelProvider(providerId)) {
|
|
4226
4494
|
selectionError = `${actionLabel}, but no default model is configured for provider "${providerId}". Use /model to select a model.`;
|
|
@@ -4263,6 +4531,25 @@ export class InteractiveMode {
|
|
|
4263
4531
|
void this.maybeWarnAboutAnthropicSubscriptionAuth();
|
|
4264
4532
|
}
|
|
4265
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));
|
|
4266
4553
|
}
|
|
4267
4554
|
showAmbientAuthDialog(providerOption) {
|
|
4268
4555
|
const restoreEditor = () => {
|
|
@@ -4308,7 +4595,10 @@ export class InteractiveMode {
|
|
|
4308
4595
|
catch (error) {
|
|
4309
4596
|
restoreEditor();
|
|
4310
4597
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4311
|
-
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") {
|
|
4312
4602
|
this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
|
|
4313
4603
|
}
|
|
4314
4604
|
}
|
|
@@ -4411,7 +4701,10 @@ export class InteractiveMode {
|
|
|
4411
4701
|
catch (error) {
|
|
4412
4702
|
restoreEditor();
|
|
4413
4703
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4414
|
-
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") {
|
|
4415
4708
|
this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
|
|
4416
4709
|
}
|
|
4417
4710
|
}
|
|
@@ -4462,7 +4755,6 @@ export class InteractiveMode {
|
|
|
4462
4755
|
try {
|
|
4463
4756
|
await this.session.reload({ beforeSessionStart: restoreChatBeforeSessionStart });
|
|
4464
4757
|
restoreChatBeforeSessionStart();
|
|
4465
|
-
configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
|
|
4466
4758
|
this.keybindings.reload();
|
|
4467
4759
|
const activeHeader = this.customHeader ?? this.builtInHeader;
|
|
4468
4760
|
if (isExpandable(activeHeader)) {
|
|
@@ -4470,20 +4762,7 @@ export class InteractiveMode {
|
|
|
4470
4762
|
}
|
|
4471
4763
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
4472
4764
|
await this.themeController.applyFromSettings();
|
|
4473
|
-
|
|
4474
|
-
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
4475
|
-
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
4476
|
-
this.defaultEditor.setAutocompleteMaxVisible(autocompleteMaxVisible);
|
|
4477
|
-
if (this.editor !== this.defaultEditor) {
|
|
4478
|
-
this.editor.setPaddingX?.(editorPaddingX);
|
|
4479
|
-
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
4480
|
-
}
|
|
4481
|
-
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
4482
|
-
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
4483
|
-
this.ui.setClearOnShrink(clearOnShrink);
|
|
4484
|
-
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
4485
|
-
this.statusContainer.clear();
|
|
4486
|
-
}
|
|
4765
|
+
this.applyRuntimeSettings();
|
|
4487
4766
|
this.setupAutocompleteProvider();
|
|
4488
4767
|
const runner = this.session.extensionRunner;
|
|
4489
4768
|
this.setupExtensionShortcuts(runner);
|
|
@@ -4679,7 +4958,7 @@ export class InteractiveMode {
|
|
|
4679
4958
|
}
|
|
4680
4959
|
}
|
|
4681
4960
|
}
|
|
4682
|
-
async handleCopyCommand() {
|
|
4961
|
+
async handleCopyCommand(options = {}) {
|
|
4683
4962
|
const text = this.session.getLastAssistantText();
|
|
4684
4963
|
if (!text) {
|
|
4685
4964
|
this.showError("No agent messages to copy yet.");
|
|
@@ -4687,7 +4966,12 @@ export class InteractiveMode {
|
|
|
4687
4966
|
}
|
|
4688
4967
|
try {
|
|
4689
4968
|
await copyToClipboard(text);
|
|
4690
|
-
|
|
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
|
+
}
|
|
4691
4975
|
}
|
|
4692
4976
|
catch (error) {
|
|
4693
4977
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -5050,6 +5334,7 @@ export class InteractiveMode {
|
|
|
5050
5334
|
}
|
|
5051
5335
|
}
|
|
5052
5336
|
stop() {
|
|
5337
|
+
this.disposeActiveSelector();
|
|
5053
5338
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
5054
5339
|
this.ui.terminal.setProgress(false);
|
|
5055
5340
|
}
|
|
@@ -5062,7 +5347,7 @@ export class InteractiveMode {
|
|
|
5062
5347
|
this.unsubscribe();
|
|
5063
5348
|
}
|
|
5064
5349
|
if (this.isInitialized) {
|
|
5065
|
-
this.
|
|
5350
|
+
this.stopInteractiveTui();
|
|
5066
5351
|
this.isInitialized = false;
|
|
5067
5352
|
}
|
|
5068
5353
|
this.unregisterSignalHandlers();
|