@earendil-works/pi-coding-agent 0.80.2 → 0.80.5
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 +115 -1
- package/README.md +3 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +4 -2
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +13 -25
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/core/agent-session.d.ts +22 -5
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +133 -50
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +13 -5
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/cache-stats.d.ts +49 -0
- package/dist/core/cache-stats.d.ts.map +1 -0
- package/dist/core/cache-stats.js +101 -0
- package/dist/core/cache-stats.js.map +1 -0
- package/dist/core/compaction/compaction.d.ts +1 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +56 -81
- package/dist/core/compaction/compaction.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 +10 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +38 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +39 -5
- 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 +28 -1
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +3 -6
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-registry.d.ts +3 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +16 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +10 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +15 -18
- package/dist/core/model-resolver.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 +68 -26
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/provider-attribution.d.ts.map +1 -1
- package/dist/core/provider-attribution.js +0 -10
- package/dist/core/provider-attribution.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +8 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +8 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +21 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +104 -72
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +10 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +27 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts +1 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +3 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/timings.d.ts +4 -2
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +24 -14
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +20 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +2 -2
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +12 -25
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +17 -4
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +23 -15
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +34 -3
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +280 -25
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
- package/dist/modes/interactive/components/custom-entry.js +52 -0
- package/dist/modes/interactive/components/custom-entry.js.map +1 -0
- package/dist/modes/interactive/components/extension-editor.d.ts +3 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +12 -3
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- 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/oauth-selector.d.ts +3 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +17 -6
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +23 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts +28 -0
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
- package/dist/modes/interactive/components/status-indicator.js +60 -0
- package/dist/modes/interactive/components/status-indicator.js.map +1 -0
- package/dist/modes/interactive/components/user-message.d.ts +6 -2
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +19 -6
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +21 -10
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +417 -196
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +21 -6
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +17 -3
- 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 +20 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +26 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts +2 -2
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +71 -17
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/rpc-entry.d.ts +3 -0
- package/dist/rpc-entry.d.ts.map +1 -0
- package/dist/rpc-entry.js +10 -0
- package/dist/rpc-entry.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/image-convert.d.ts +1 -0
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js +21 -15
- package/dist/utils/image-convert.js.map +1 -1
- package/dist/utils/image-process.d.ts +18 -0
- package/dist/utils/image-process.d.ts.map +1 -0
- package/dist/utils/image-process.js +83 -0
- package/dist/utils/image-process.js.map +1 -0
- package/dist/utils/mime.d.ts.map +1 -1
- package/dist/utils/mime.js +41 -0
- package/dist/utils/mime.js.map +1 -1
- package/docs/extensions.md +88 -17
- package/docs/keybindings.md +1 -1
- package/docs/models.md +4 -4
- package/docs/packages.md +2 -2
- package/docs/providers.md +2 -1
- package/docs/rpc.md +71 -3
- package/docs/sdk.md +50 -0
- package/docs/session-format.md +18 -8
- package/docs/settings.md +11 -0
- package/docs/usage.md +3 -3
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/custom-compaction.ts +1 -0
- 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/package.json +1 -1
- package/examples/extensions/entry-renderer.ts +41 -0
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/handoff.ts +1 -1
- package/examples/extensions/qna.ts +1 -1
- package/examples/extensions/question.ts +1 -0
- package/examples/extensions/reload-runtime.ts +2 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/summarize.ts +1 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +8 -5
|
@@ -7,12 +7,13 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { getProviders, } from "@earendil-works/pi-ai/compat";
|
|
10
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink,
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import { spawn, spawnSync } from "child_process";
|
|
13
13
|
import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
14
14
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
15
15
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
16
|
+
import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
|
|
16
17
|
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";
|
|
@@ -21,7 +22,7 @@ import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScop
|
|
|
21
22
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
22
23
|
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
23
24
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
24
|
-
import { SessionManager } from "../../core/session-manager.js";
|
|
25
|
+
import { SessionManager, sessionEntryToContextMessages } from "../../core/session-manager.js";
|
|
25
26
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
26
27
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
27
28
|
import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
|
|
@@ -40,8 +41,8 @@ import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
|
40
41
|
import { BorderedLoader } from "./components/bordered-loader.js";
|
|
41
42
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
42
43
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
43
|
-
import { CountdownTimer } from "./components/countdown-timer.js";
|
|
44
44
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
45
|
+
import { CustomEntryComponent } from "./components/custom-entry.js";
|
|
45
46
|
import { CustomMessageComponent } from "./components/custom-message.js";
|
|
46
47
|
import { DaxnutsComponent } from "./components/daxnuts.js";
|
|
47
48
|
import { DynamicBorder } from "./components/dynamic-border.js";
|
|
@@ -49,15 +50,16 @@ import { EarendilAnnouncementComponent } from "./components/earendil-announcemen
|
|
|
49
50
|
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
50
51
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
51
52
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
52
|
-
import { FooterComponent } from "./components/footer.js";
|
|
53
|
+
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
53
54
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
54
55
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
55
56
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
56
|
-
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
57
|
+
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
57
58
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
58
59
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
59
60
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
60
61
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
62
|
+
import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
|
|
61
63
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
62
64
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
63
65
|
import { TrustSelectorComponent } from "./components/trust-selector.js";
|
|
@@ -81,6 +83,9 @@ class ExpandableText extends Text {
|
|
|
81
83
|
this.setText(expanded ? this.getExpandedText() : this.getCollapsedText());
|
|
82
84
|
}
|
|
83
85
|
}
|
|
86
|
+
function isCustomSessionEntry(item) {
|
|
87
|
+
return "type" in item && item.type === "custom";
|
|
88
|
+
}
|
|
84
89
|
const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
|
|
85
90
|
function isDeadTerminalError(error) {
|
|
86
91
|
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
@@ -131,9 +136,46 @@ export function isApiKeyLoginProvider(providerId, oauthProviderIds, builtInProvi
|
|
|
131
136
|
}
|
|
132
137
|
return !oauthProviderIds.has(providerId);
|
|
133
138
|
}
|
|
139
|
+
const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
|
|
140
|
+
function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
|
|
141
|
+
const filtered = fuzzyFilter(items, prefix, getSearchText);
|
|
142
|
+
if (filtered.length === 0)
|
|
143
|
+
return null;
|
|
144
|
+
return filtered.map(toAutocompleteItem);
|
|
145
|
+
}
|
|
146
|
+
function getLoginProviderCompletionOptions(providerOptions) {
|
|
147
|
+
const byId = new Map();
|
|
148
|
+
for (const provider of providerOptions) {
|
|
149
|
+
const existing = byId.get(provider.id);
|
|
150
|
+
if (existing) {
|
|
151
|
+
if (!existing.authTypes.includes(provider.authType)) {
|
|
152
|
+
existing.authTypes.push(provider.authType);
|
|
153
|
+
existing.authTypes.sort((a, b) => AUTH_TYPE_ORDER[a] - AUTH_TYPE_ORDER[b]);
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
byId.set(provider.id, {
|
|
158
|
+
id: provider.id,
|
|
159
|
+
name: provider.name,
|
|
160
|
+
authTypes: [provider.authType],
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
164
|
+
}
|
|
165
|
+
function getLoginProviderSearchText(provider) {
|
|
166
|
+
const authTypes = provider.authTypes
|
|
167
|
+
.map((authType) => `${authType} ${formatAuthSelectorProviderType(authType)}`)
|
|
168
|
+
.join(" ");
|
|
169
|
+
return `${provider.id} ${provider.name} ${authTypes}`;
|
|
170
|
+
}
|
|
171
|
+
function formatLoginProviderCompletionDescription(provider) {
|
|
172
|
+
const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
|
|
173
|
+
return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
|
|
174
|
+
}
|
|
134
175
|
export class InteractiveMode {
|
|
135
176
|
runtimeHost;
|
|
136
177
|
ui;
|
|
178
|
+
loadedResourcesContainer;
|
|
137
179
|
chatContainer;
|
|
138
180
|
pendingMessagesContainer;
|
|
139
181
|
statusContainer;
|
|
@@ -152,7 +194,8 @@ export class InteractiveMode {
|
|
|
152
194
|
isInitialized = false;
|
|
153
195
|
onInputCallback;
|
|
154
196
|
pendingUserInputs = [];
|
|
155
|
-
|
|
197
|
+
activeStatusIndicator = undefined;
|
|
198
|
+
idleStatus = new IdleStatus();
|
|
156
199
|
workingMessage = undefined;
|
|
157
200
|
workingVisible = true;
|
|
158
201
|
workingIndicatorOptions = undefined;
|
|
@@ -176,6 +219,7 @@ export class InteractiveMode {
|
|
|
176
219
|
toolOutputExpanded = false;
|
|
177
220
|
// Thinking block visibility state
|
|
178
221
|
hideThinkingBlock = false;
|
|
222
|
+
outputPad = 1;
|
|
179
223
|
// Skill commands: command name -> skill file path
|
|
180
224
|
skillCommands = new Map();
|
|
181
225
|
// Agent subscription unsubscribe function
|
|
@@ -188,11 +232,8 @@ export class InteractiveMode {
|
|
|
188
232
|
// Track pending bash components (shown in pending area, moved to chat on submit)
|
|
189
233
|
pendingBashComponents = [];
|
|
190
234
|
// Auto-compaction state
|
|
191
|
-
autoCompactionLoader = undefined;
|
|
192
235
|
autoCompactionEscapeHandler;
|
|
193
236
|
// Auto-retry state
|
|
194
|
-
retryLoader = undefined;
|
|
195
|
-
retryCountdown = undefined;
|
|
196
237
|
retryEscapeHandler;
|
|
197
238
|
// Messages queued while compaction is running
|
|
198
239
|
compactionQueuedMessages = [];
|
|
@@ -246,6 +287,7 @@ export class InteractiveMode {
|
|
|
246
287
|
this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
|
|
247
288
|
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
|
248
289
|
this.headerContainer = new Container();
|
|
290
|
+
this.loadedResourcesContainer = new Container();
|
|
249
291
|
this.chatContainer = new Container();
|
|
250
292
|
this.pendingMessagesContainer = new Container();
|
|
251
293
|
this.statusContainer = new Container();
|
|
@@ -267,6 +309,7 @@ export class InteractiveMode {
|
|
|
267
309
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
268
310
|
// Load hide thinking block setting
|
|
269
311
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
312
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
270
313
|
// Register themes from resource loader and initialize
|
|
271
314
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
272
315
|
this.themeController = new InteractiveThemeController(this.ui, this.settingsManager, (message) => this.showError(message), () => this.updateEditorBorderColor());
|
|
@@ -315,6 +358,7 @@ export class InteractiveMode {
|
|
|
315
358
|
const slashCommands = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
|
316
359
|
name: command.name,
|
|
317
360
|
description: command.description,
|
|
361
|
+
...(command.argumentHint && { argumentHint: command.argumentHint }),
|
|
318
362
|
}));
|
|
319
363
|
const modelCommand = slashCommands.find((command) => command.name === "model");
|
|
320
364
|
if (modelCommand) {
|
|
@@ -332,17 +376,24 @@ export class InteractiveMode {
|
|
|
332
376
|
name: m.name,
|
|
333
377
|
label: `${m.provider}/${m.id}`,
|
|
334
378
|
}));
|
|
335
|
-
|
|
336
|
-
const filtered = fuzzyFilter(items, prefix, getModelSearchText);
|
|
337
|
-
if (filtered.length === 0)
|
|
338
|
-
return null;
|
|
339
|
-
return filtered.map((item) => ({
|
|
379
|
+
return createFuzzyAutocompleteItems(items, prefix, getModelSearchText, (item) => ({
|
|
340
380
|
value: item.label,
|
|
341
381
|
label: item.id,
|
|
342
382
|
description: item.provider,
|
|
343
383
|
}));
|
|
344
384
|
};
|
|
345
385
|
}
|
|
386
|
+
const loginCommand = slashCommands.find((command) => command.name === "login");
|
|
387
|
+
if (loginCommand) {
|
|
388
|
+
loginCommand.getArgumentCompletions = (prefix) => {
|
|
389
|
+
const providers = getLoginProviderCompletionOptions(this.getLoginProviderOptions());
|
|
390
|
+
return createFuzzyAutocompleteItems(providers, prefix, getLoginProviderSearchText, (provider) => ({
|
|
391
|
+
value: provider.id,
|
|
392
|
+
label: provider.id,
|
|
393
|
+
description: formatLoginProviderCompletionDescription(provider),
|
|
394
|
+
}));
|
|
395
|
+
};
|
|
396
|
+
}
|
|
346
397
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
347
398
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
348
399
|
name: cmd.name,
|
|
@@ -439,8 +490,10 @@ export class InteractiveMode {
|
|
|
439
490
|
: "";
|
|
440
491
|
console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
|
|
441
492
|
}
|
|
442
|
-
// Add header container as first child. Populate it after
|
|
493
|
+
// Add header container as first child. Populate it after applying theme settings.
|
|
494
|
+
// Keep loaded resources before chat so restored session messages never precede them.
|
|
443
495
|
this.ui.addChild(this.headerContainer);
|
|
496
|
+
this.ui.addChild(this.loadedResourcesContainer);
|
|
444
497
|
this.ui.addChild(this.chatContainer);
|
|
445
498
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
446
499
|
this.ui.addChild(this.statusContainer);
|
|
@@ -990,6 +1043,8 @@ export class InteractiveMode {
|
|
|
990
1043
|
return lines.join("\n");
|
|
991
1044
|
}
|
|
992
1045
|
showLoadedResources(options) {
|
|
1046
|
+
// Resource rendering is idempotent; chat clears no longer clear this separate container.
|
|
1047
|
+
this.loadedResourcesContainer.clear();
|
|
993
1048
|
const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
|
994
1049
|
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
|
995
1050
|
if (!showListing && !showDiagnostics) {
|
|
@@ -1005,8 +1060,8 @@ export class InteractiveMode {
|
|
|
1005
1060
|
};
|
|
1006
1061
|
const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
|
|
1007
1062
|
const section = new ExpandableText(() => `${sectionHeader(name, color)}\n${collapsedBody}`, () => `${sectionHeader(name, color)}\n${expandedBody}`, this.getStartupExpansionState(), 0, 0);
|
|
1008
|
-
this.
|
|
1009
|
-
this.
|
|
1063
|
+
this.loadedResourcesContainer.addChild(section);
|
|
1064
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1010
1065
|
};
|
|
1011
1066
|
const skillsResult = this.session.resourceLoader.getSkills();
|
|
1012
1067
|
const promptsResult = this.session.resourceLoader.getPrompts();
|
|
@@ -1040,7 +1095,7 @@ export class InteractiveMode {
|
|
|
1040
1095
|
if (showListing) {
|
|
1041
1096
|
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
|
1042
1097
|
if (contextFiles.length > 0) {
|
|
1043
|
-
this.
|
|
1098
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1044
1099
|
const contextList = contextFiles
|
|
1045
1100
|
.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`))
|
|
1046
1101
|
.join("\n");
|
|
@@ -1103,14 +1158,14 @@ export class InteractiveMode {
|
|
|
1103
1158
|
const skillDiagnostics = skillsResult.diagnostics;
|
|
1104
1159
|
if (skillDiagnostics.length > 0) {
|
|
1105
1160
|
const warningLines = this.formatDiagnostics(skillDiagnostics, sourceInfos);
|
|
1106
|
-
this.
|
|
1107
|
-
this.
|
|
1161
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
1162
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1108
1163
|
}
|
|
1109
1164
|
const promptDiagnostics = promptsResult.diagnostics;
|
|
1110
1165
|
if (promptDiagnostics.length > 0) {
|
|
1111
1166
|
const warningLines = this.formatDiagnostics(promptDiagnostics, sourceInfos);
|
|
1112
|
-
this.
|
|
1113
|
-
this.
|
|
1167
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
1168
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1114
1169
|
}
|
|
1115
1170
|
const extensionDiagnostics = [];
|
|
1116
1171
|
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
@@ -1126,14 +1181,14 @@ export class InteractiveMode {
|
|
|
1126
1181
|
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
1127
1182
|
if (extensionDiagnostics.length > 0) {
|
|
1128
1183
|
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
1129
|
-
this.
|
|
1130
|
-
this.
|
|
1184
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
1185
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1131
1186
|
}
|
|
1132
1187
|
const themeDiagnostics = themesResult.diagnostics;
|
|
1133
1188
|
if (themeDiagnostics.length > 0) {
|
|
1134
1189
|
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
1135
|
-
this.
|
|
1136
|
-
this.
|
|
1190
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
1191
|
+
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1137
1192
|
}
|
|
1138
1193
|
}
|
|
1139
1194
|
}
|
|
@@ -1149,13 +1204,9 @@ export class InteractiveMode {
|
|
|
1149
1204
|
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1150
1205
|
},
|
|
1151
1206
|
commandContextActions: {
|
|
1152
|
-
waitForIdle: () => this.session.
|
|
1207
|
+
waitForIdle: () => this.session.waitForIdle(),
|
|
1153
1208
|
newSession: async (options) => {
|
|
1154
|
-
|
|
1155
|
-
this.loadingAnimation.stop();
|
|
1156
|
-
this.loadingAnimation = undefined;
|
|
1157
|
-
}
|
|
1158
|
-
this.statusContainer.clear();
|
|
1209
|
+
this.clearStatusIndicator();
|
|
1159
1210
|
try {
|
|
1160
1211
|
return await this.runtimeHost.newSession(options);
|
|
1161
1212
|
}
|
|
@@ -1204,7 +1255,7 @@ export class InteractiveMode {
|
|
|
1204
1255
|
},
|
|
1205
1256
|
shutdownHandler: () => {
|
|
1206
1257
|
this.shutdownRequested = true;
|
|
1207
|
-
if (
|
|
1258
|
+
if (this.session.isIdle) {
|
|
1208
1259
|
void this.shutdown();
|
|
1209
1260
|
}
|
|
1210
1261
|
},
|
|
@@ -1225,8 +1276,13 @@ export class InteractiveMode {
|
|
|
1225
1276
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1226
1277
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
1227
1278
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
1279
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
1228
1280
|
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
1229
|
-
this.
|
|
1281
|
+
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
1282
|
+
this.ui.setClearOnShrink(clearOnShrink);
|
|
1283
|
+
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
1284
|
+
this.statusContainer.clear();
|
|
1285
|
+
}
|
|
1230
1286
|
const editorPaddingX = this.settingsManager.getEditorPaddingX();
|
|
1231
1287
|
const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
|
|
1232
1288
|
this.defaultEditor.setPaddingX(editorPaddingX);
|
|
@@ -1261,6 +1317,7 @@ export class InteractiveMode {
|
|
|
1261
1317
|
process.exit(1);
|
|
1262
1318
|
}
|
|
1263
1319
|
renderCurrentSessionState() {
|
|
1320
|
+
this.loadedResourcesContainer.clear();
|
|
1264
1321
|
this.chatContainer.clear();
|
|
1265
1322
|
this.pendingMessagesContainer.clear();
|
|
1266
1323
|
this.compactionQueuedMessages = [];
|
|
@@ -1291,7 +1348,7 @@ export class InteractiveMode {
|
|
|
1291
1348
|
sessionManager: this.sessionManager,
|
|
1292
1349
|
modelRegistry: this.session.modelRegistry,
|
|
1293
1350
|
model: this.session.model,
|
|
1294
|
-
isIdle: () =>
|
|
1351
|
+
isIdle: () => this.session.isIdle,
|
|
1295
1352
|
isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
|
|
1296
1353
|
signal: this.session.agent.signal,
|
|
1297
1354
|
abort: () => {
|
|
@@ -1338,36 +1395,41 @@ export class InteractiveMode {
|
|
|
1338
1395
|
this.footerDataProvider.setExtensionStatus(key, text);
|
|
1339
1396
|
this.ui.requestRender();
|
|
1340
1397
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1398
|
+
showStatusIndicator(indicator) {
|
|
1399
|
+
this.activeStatusIndicator?.dispose();
|
|
1400
|
+
this.activeStatusIndicator = indicator;
|
|
1401
|
+
this.statusContainer.clear();
|
|
1402
|
+
this.statusContainer.addChild(indicator);
|
|
1346
1403
|
}
|
|
1347
|
-
|
|
1348
|
-
if (this.
|
|
1349
|
-
|
|
1350
|
-
this.loadingAnimation = undefined;
|
|
1404
|
+
clearStatusIndicator(kind) {
|
|
1405
|
+
if (kind && this.activeStatusIndicator?.kind !== kind) {
|
|
1406
|
+
return;
|
|
1351
1407
|
}
|
|
1408
|
+
const hadActiveStatusIndicator = this.activeStatusIndicator !== undefined;
|
|
1409
|
+
this.activeStatusIndicator?.dispose();
|
|
1410
|
+
this.activeStatusIndicator = undefined;
|
|
1352
1411
|
this.statusContainer.clear();
|
|
1412
|
+
if (hadActiveStatusIndicator && this.ui.getClearOnShrink()) {
|
|
1413
|
+
this.statusContainer.addChild(this.idleStatus);
|
|
1414
|
+
}
|
|
1353
1415
|
}
|
|
1354
1416
|
setWorkingVisible(visible) {
|
|
1355
1417
|
this.workingVisible = visible;
|
|
1356
1418
|
if (!visible) {
|
|
1357
|
-
this.
|
|
1419
|
+
this.clearStatusIndicator("working");
|
|
1358
1420
|
this.ui.requestRender();
|
|
1359
1421
|
return;
|
|
1360
1422
|
}
|
|
1361
|
-
if (this.session.isStreaming &&
|
|
1362
|
-
this.
|
|
1363
|
-
this.loadingAnimation = this.createWorkingLoader();
|
|
1364
|
-
this.statusContainer.addChild(this.loadingAnimation);
|
|
1423
|
+
if (this.session.isStreaming && this.activeStatusIndicator?.kind !== "working") {
|
|
1424
|
+
this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions));
|
|
1365
1425
|
}
|
|
1366
1426
|
this.ui.requestRender();
|
|
1367
1427
|
}
|
|
1368
1428
|
setWorkingIndicator(options) {
|
|
1369
1429
|
this.workingIndicatorOptions = options;
|
|
1370
|
-
this.
|
|
1430
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1431
|
+
this.activeStatusIndicator.setIndicator(options);
|
|
1432
|
+
}
|
|
1371
1433
|
this.ui.requestRender();
|
|
1372
1434
|
}
|
|
1373
1435
|
setHiddenThinkingLabel(label) {
|
|
@@ -1455,8 +1517,8 @@ export class InteractiveMode {
|
|
|
1455
1517
|
this.workingMessage = undefined;
|
|
1456
1518
|
this.workingVisible = true;
|
|
1457
1519
|
this.setWorkingIndicator();
|
|
1458
|
-
if (this.
|
|
1459
|
-
this.
|
|
1520
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1521
|
+
this.activeStatusIndicator.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
|
|
1460
1522
|
}
|
|
1461
1523
|
this.setHiddenThinkingLabel();
|
|
1462
1524
|
}
|
|
@@ -1596,8 +1658,8 @@ export class InteractiveMode {
|
|
|
1596
1658
|
setStatus: (key, text) => this.setExtensionStatus(key, text),
|
|
1597
1659
|
setWorkingMessage: (message) => {
|
|
1598
1660
|
this.workingMessage = message;
|
|
1599
|
-
if (this.
|
|
1600
|
-
this.
|
|
1661
|
+
if (this.activeStatusIndicator?.kind === "working") {
|
|
1662
|
+
this.activeStatusIndicator.setMessage(message ?? this.defaultWorkingMessage);
|
|
1601
1663
|
}
|
|
1602
1664
|
},
|
|
1603
1665
|
setWorkingVisible: (visible) => this.setWorkingVisible(visible),
|
|
@@ -1741,7 +1803,7 @@ export class InteractiveMode {
|
|
|
1741
1803
|
}, () => {
|
|
1742
1804
|
this.hideExtensionEditor();
|
|
1743
1805
|
resolve(undefined);
|
|
1744
|
-
});
|
|
1806
|
+
}, undefined, this.settingsManager.getExternalEditorCommand());
|
|
1745
1807
|
this.editorContainer.clear();
|
|
1746
1808
|
this.editorContainer.addChild(this.extensionEditor);
|
|
1747
1809
|
this.ui.setFocus(this.extensionEditor);
|
|
@@ -2092,9 +2154,10 @@ export class InteractiveMode {
|
|
|
2092
2154
|
this.editor.setText("");
|
|
2093
2155
|
return;
|
|
2094
2156
|
}
|
|
2095
|
-
if (text === "/login") {
|
|
2096
|
-
|
|
2157
|
+
if (text === "/login" || text.startsWith("/login ")) {
|
|
2158
|
+
const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
|
|
2097
2159
|
this.editor.setText("");
|
|
2160
|
+
await this.handleLoginCommand(providerRef);
|
|
2098
2161
|
return;
|
|
2099
2162
|
}
|
|
2100
2163
|
if (text === "/logout") {
|
|
@@ -2216,18 +2279,11 @@ export class InteractiveMode {
|
|
|
2216
2279
|
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2217
2280
|
this.retryEscapeHandler = undefined;
|
|
2218
2281
|
}
|
|
2219
|
-
if (this.retryCountdown) {
|
|
2220
|
-
this.retryCountdown.dispose();
|
|
2221
|
-
this.retryCountdown = undefined;
|
|
2222
|
-
}
|
|
2223
|
-
if (this.retryLoader) {
|
|
2224
|
-
this.retryLoader.stop();
|
|
2225
|
-
this.retryLoader = undefined;
|
|
2226
|
-
}
|
|
2227
|
-
this.stopWorkingLoader();
|
|
2228
2282
|
if (this.workingVisible) {
|
|
2229
|
-
this.
|
|
2230
|
-
|
|
2283
|
+
this.showStatusIndicator(new WorkingStatusIndicator(this.ui, this.workingMessage ?? this.defaultWorkingMessage, this.workingIndicatorOptions));
|
|
2284
|
+
}
|
|
2285
|
+
else {
|
|
2286
|
+
this.clearStatusIndicator();
|
|
2231
2287
|
}
|
|
2232
2288
|
this.ui.requestRender();
|
|
2233
2289
|
break;
|
|
@@ -2235,6 +2291,12 @@ export class InteractiveMode {
|
|
|
2235
2291
|
this.updatePendingMessagesDisplay();
|
|
2236
2292
|
this.ui.requestRender();
|
|
2237
2293
|
break;
|
|
2294
|
+
case "entry_appended":
|
|
2295
|
+
if (event.entry.type === "custom") {
|
|
2296
|
+
this.addCustomEntryToChat(event.entry);
|
|
2297
|
+
this.ui.requestRender();
|
|
2298
|
+
}
|
|
2299
|
+
break;
|
|
2238
2300
|
case "session_info_changed":
|
|
2239
2301
|
this.updateTerminalTitle();
|
|
2240
2302
|
this.footer.invalidate();
|
|
@@ -2255,7 +2317,7 @@ export class InteractiveMode {
|
|
|
2255
2317
|
this.ui.requestRender();
|
|
2256
2318
|
}
|
|
2257
2319
|
else if (event.message.role === "assistant") {
|
|
2258
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2320
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2259
2321
|
this.streamingMessage = event.message;
|
|
2260
2322
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2261
2323
|
this.streamingComponent.updateContent(this.streamingMessage);
|
|
@@ -2320,6 +2382,7 @@ export class InteractiveMode {
|
|
|
2320
2382
|
for (const [, component] of this.pendingTools.entries()) {
|
|
2321
2383
|
component.setArgsComplete();
|
|
2322
2384
|
}
|
|
2385
|
+
this.maybeShowCacheMissNotice(this.streamingMessage);
|
|
2323
2386
|
}
|
|
2324
2387
|
this.streamingComponent = undefined;
|
|
2325
2388
|
this.streamingMessage = undefined;
|
|
@@ -2363,20 +2426,18 @@ export class InteractiveMode {
|
|
|
2363
2426
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2364
2427
|
this.ui.terminal.setProgress(false);
|
|
2365
2428
|
}
|
|
2366
|
-
|
|
2367
|
-
this.loadingAnimation.stop();
|
|
2368
|
-
this.loadingAnimation = undefined;
|
|
2369
|
-
this.statusContainer.clear();
|
|
2370
|
-
}
|
|
2429
|
+
this.clearStatusIndicator("working");
|
|
2371
2430
|
if (this.streamingComponent) {
|
|
2372
2431
|
this.chatContainer.removeChild(this.streamingComponent);
|
|
2373
2432
|
this.streamingComponent = undefined;
|
|
2374
2433
|
this.streamingMessage = undefined;
|
|
2375
2434
|
}
|
|
2376
2435
|
this.pendingTools.clear();
|
|
2377
|
-
await this.checkShutdownRequested();
|
|
2378
2436
|
this.ui.requestRender();
|
|
2379
2437
|
break;
|
|
2438
|
+
case "agent_settled":
|
|
2439
|
+
await this.checkShutdownRequested();
|
|
2440
|
+
break;
|
|
2380
2441
|
case "compaction_start": {
|
|
2381
2442
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2382
2443
|
this.ui.terminal.setProgress(true);
|
|
@@ -2386,13 +2447,7 @@ export class InteractiveMode {
|
|
|
2386
2447
|
this.defaultEditor.onEscape = () => {
|
|
2387
2448
|
this.session.abortCompaction();
|
|
2388
2449
|
};
|
|
2389
|
-
this.
|
|
2390
|
-
const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
|
|
2391
|
-
const label = event.reason === "manual"
|
|
2392
|
-
? `Compacting context... ${cancelHint}`
|
|
2393
|
-
: `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
|
|
2394
|
-
this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
|
|
2395
|
-
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
2450
|
+
this.showStatusIndicator(new CompactionStatusIndicator(this.ui, event.reason));
|
|
2396
2451
|
this.ui.requestRender();
|
|
2397
2452
|
break;
|
|
2398
2453
|
}
|
|
@@ -2404,11 +2459,7 @@ export class InteractiveMode {
|
|
|
2404
2459
|
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
2405
2460
|
this.autoCompactionEscapeHandler = undefined;
|
|
2406
2461
|
}
|
|
2407
|
-
|
|
2408
|
-
this.autoCompactionLoader.stop();
|
|
2409
|
-
this.autoCompactionLoader = undefined;
|
|
2410
|
-
this.statusContainer.clear();
|
|
2411
|
-
}
|
|
2462
|
+
this.clearStatusIndicator("compaction");
|
|
2412
2463
|
if (event.aborted) {
|
|
2413
2464
|
if (event.reason === "manual") {
|
|
2414
2465
|
this.showError("Compaction cancelled");
|
|
@@ -2442,17 +2493,7 @@ export class InteractiveMode {
|
|
|
2442
2493
|
this.defaultEditor.onEscape = () => {
|
|
2443
2494
|
this.session.abortRetry();
|
|
2444
2495
|
};
|
|
2445
|
-
|
|
2446
|
-
this.statusContainer.clear();
|
|
2447
|
-
this.retryCountdown?.dispose();
|
|
2448
|
-
const retryMessage = (seconds) => `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`;
|
|
2449
|
-
this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), retryMessage(Math.ceil(event.delayMs / 1000)));
|
|
2450
|
-
this.retryCountdown = new CountdownTimer(event.delayMs, this.ui, (seconds) => {
|
|
2451
|
-
this.retryLoader?.setMessage(retryMessage(seconds));
|
|
2452
|
-
}, () => {
|
|
2453
|
-
this.retryCountdown = undefined;
|
|
2454
|
-
});
|
|
2455
|
-
this.statusContainer.addChild(this.retryLoader);
|
|
2496
|
+
this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
|
|
2456
2497
|
this.ui.requestRender();
|
|
2457
2498
|
break;
|
|
2458
2499
|
}
|
|
@@ -2462,16 +2503,7 @@ export class InteractiveMode {
|
|
|
2462
2503
|
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2463
2504
|
this.retryEscapeHandler = undefined;
|
|
2464
2505
|
}
|
|
2465
|
-
|
|
2466
|
-
this.retryCountdown.dispose();
|
|
2467
|
-
this.retryCountdown = undefined;
|
|
2468
|
-
}
|
|
2469
|
-
// Stop loader
|
|
2470
|
-
if (this.retryLoader) {
|
|
2471
|
-
this.retryLoader.stop();
|
|
2472
|
-
this.retryLoader = undefined;
|
|
2473
|
-
this.statusContainer.clear();
|
|
2474
|
-
}
|
|
2506
|
+
this.clearStatusIndicator("retry");
|
|
2475
2507
|
// Show error only on final failure (success shows normal response)
|
|
2476
2508
|
if (!event.success) {
|
|
2477
2509
|
this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
@@ -2513,6 +2545,25 @@ export class InteractiveMode {
|
|
|
2513
2545
|
this.lastStatusText = text;
|
|
2514
2546
|
this.ui.requestRender();
|
|
2515
2547
|
}
|
|
2548
|
+
addCustomEntryToChat(entry) {
|
|
2549
|
+
const renderer = this.session.extensionRunner.getEntryRenderer(entry.customType);
|
|
2550
|
+
if (!renderer) {
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
const component = new CustomEntryComponent(entry, renderer);
|
|
2554
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
2555
|
+
if (!component.hasContent()) {
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (this.streamingComponent) {
|
|
2559
|
+
const streamingIndex = this.chatContainer.children.indexOf(this.streamingComponent);
|
|
2560
|
+
if (streamingIndex >= 0) {
|
|
2561
|
+
this.chatContainer.children.splice(streamingIndex, 0, component);
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
this.chatContainer.addChild(component);
|
|
2566
|
+
}
|
|
2516
2567
|
addMessageToChat(message, options) {
|
|
2517
2568
|
switch (message.role) {
|
|
2518
2569
|
case "bashExecution": {
|
|
@@ -2562,12 +2613,12 @@ export class InteractiveMode {
|
|
|
2562
2613
|
// Render user message separately if present
|
|
2563
2614
|
if (skillBlock.userMessage) {
|
|
2564
2615
|
this.chatContainer.addChild(new Spacer(1));
|
|
2565
|
-
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings());
|
|
2616
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2566
2617
|
this.chatContainer.addChild(userComponent);
|
|
2567
2618
|
}
|
|
2568
2619
|
}
|
|
2569
2620
|
else {
|
|
2570
|
-
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
|
|
2621
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings(), this.outputPad);
|
|
2571
2622
|
this.chatContainer.addChild(userComponent);
|
|
2572
2623
|
}
|
|
2573
2624
|
if (options?.populateHistory) {
|
|
@@ -2577,7 +2628,7 @@ export class InteractiveMode {
|
|
|
2577
2628
|
break;
|
|
2578
2629
|
}
|
|
2579
2630
|
case "assistant": {
|
|
2580
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
2631
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad);
|
|
2581
2632
|
this.chatContainer.addChild(assistantComponent);
|
|
2582
2633
|
break;
|
|
2583
2634
|
}
|
|
@@ -2590,20 +2641,24 @@ export class InteractiveMode {
|
|
|
2590
2641
|
}
|
|
2591
2642
|
}
|
|
2592
2643
|
}
|
|
2593
|
-
|
|
2594
|
-
* Render session context to chat. Used for initial load and rebuild after compaction.
|
|
2595
|
-
* @param sessionContext Session context to render
|
|
2596
|
-
* @param options.updateFooter Update footer state
|
|
2597
|
-
* @param options.populateHistory Add user messages to editor history
|
|
2598
|
-
*/
|
|
2599
|
-
renderSessionContext(sessionContext, options = {}) {
|
|
2644
|
+
renderSessionItems(items, options = {}) {
|
|
2600
2645
|
this.pendingTools.clear();
|
|
2601
2646
|
const renderedPendingTools = new Map();
|
|
2647
|
+
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2648
|
+
// list and re-inject them after the assistant messages that paid for them.
|
|
2649
|
+
const cacheMisses = this.settingsManager.getShowCacheMissNotices()
|
|
2650
|
+
? collectCacheMisses(this.sessionManager.getEntries(), this.session.modelRegistry)
|
|
2651
|
+
: new Map();
|
|
2602
2652
|
if (options.updateFooter) {
|
|
2603
2653
|
this.footer.invalidate();
|
|
2604
2654
|
this.updateEditorBorderColor();
|
|
2605
2655
|
}
|
|
2606
|
-
for (const
|
|
2656
|
+
for (const item of items) {
|
|
2657
|
+
if (isCustomSessionEntry(item)) {
|
|
2658
|
+
this.addCustomEntryToChat(item);
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
const message = item;
|
|
2607
2662
|
// Assistant messages need special handling for tool calls
|
|
2608
2663
|
if (message.role === "assistant") {
|
|
2609
2664
|
this.addMessageToChat(message);
|
|
@@ -2635,6 +2690,11 @@ export class InteractiveMode {
|
|
|
2635
2690
|
}
|
|
2636
2691
|
}
|
|
2637
2692
|
}
|
|
2693
|
+
if (message.stopReason !== "aborted" && message.stopReason !== "error") {
|
|
2694
|
+
const miss = cacheMisses.get(message);
|
|
2695
|
+
if (miss)
|
|
2696
|
+
this.addCacheMissNotice(miss);
|
|
2697
|
+
}
|
|
2638
2698
|
}
|
|
2639
2699
|
else if (message.role === "toolResult") {
|
|
2640
2700
|
// Match tool results to pending tool components
|
|
@@ -2654,10 +2714,53 @@ export class InteractiveMode {
|
|
|
2654
2714
|
}
|
|
2655
2715
|
this.ui.requestRender();
|
|
2656
2716
|
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Render session entries to chat. Used for initial load and rebuild after compaction.
|
|
2719
|
+
* @param entries Compaction-aware session entries to render
|
|
2720
|
+
* @param options.updateFooter Update footer state
|
|
2721
|
+
* @param options.populateHistory Add user messages to editor history
|
|
2722
|
+
*/
|
|
2723
|
+
renderSessionEntries(entries, options = {}) {
|
|
2724
|
+
const items = entries.flatMap((entry) => {
|
|
2725
|
+
if (entry.type === "custom") {
|
|
2726
|
+
return [entry];
|
|
2727
|
+
}
|
|
2728
|
+
return sessionEntryToContextMessages(entry);
|
|
2729
|
+
});
|
|
2730
|
+
this.renderSessionItems(items, options);
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2733
|
+
* Show a transcript notice when a completed assistant message paid for a
|
|
2734
|
+
* significant cache miss. Only states observable facts: the miss itself,
|
|
2735
|
+
* a model switch, or an idle gap past the cache TTL.
|
|
2736
|
+
*/
|
|
2737
|
+
maybeShowCacheMissNotice(message) {
|
|
2738
|
+
if (!this.settingsManager.getShowCacheMissNotices())
|
|
2739
|
+
return;
|
|
2740
|
+
// Entries don't contain `message` yet: message_end fires before persistence.
|
|
2741
|
+
const miss = detectCacheMiss(this.sessionManager.getEntries(), message, this.session.modelRegistry);
|
|
2742
|
+
if (miss)
|
|
2743
|
+
this.addCacheMissNotice(miss);
|
|
2744
|
+
}
|
|
2745
|
+
addCacheMissNotice(miss) {
|
|
2746
|
+
if (miss.missedTokens < 20_000 && miss.missedCost < 0.1)
|
|
2747
|
+
return;
|
|
2748
|
+
const cost = miss.missedCost >= 0.01 ? ` (~$${miss.missedCost.toFixed(2)})` : "";
|
|
2749
|
+
const reBilled = `${formatTokens(miss.missedTokens)} tokens re-billed${cost}`;
|
|
2750
|
+
let label = "Cache miss";
|
|
2751
|
+
if (miss.modelChanged) {
|
|
2752
|
+
label = "Cache miss after model switch";
|
|
2753
|
+
}
|
|
2754
|
+
else if (miss.idleMs >= CACHE_TTL_MS) {
|
|
2755
|
+
label = `Cache miss after ${Math.round(miss.idleMs / 60_000)}m idle`;
|
|
2756
|
+
}
|
|
2757
|
+
const text = theme.fg("warning", `${label}: ${reBilled}`);
|
|
2758
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2759
|
+
this.chatContainer.addChild(new Text(text, 1, 0));
|
|
2760
|
+
}
|
|
2657
2761
|
renderInitialMessages() {
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
this.renderSessionContext(context, {
|
|
2762
|
+
const entries = this.sessionManager.buildContextEntries();
|
|
2763
|
+
this.renderSessionEntries(entries, {
|
|
2661
2764
|
updateFooter: true,
|
|
2662
2765
|
populateHistory: true,
|
|
2663
2766
|
});
|
|
@@ -2693,8 +2796,7 @@ export class InteractiveMode {
|
|
|
2693
2796
|
}
|
|
2694
2797
|
rebuildChatFromMessages() {
|
|
2695
2798
|
this.chatContainer.clear();
|
|
2696
|
-
|
|
2697
|
-
this.renderSessionContext(context);
|
|
2799
|
+
this.renderSessionEntries(this.sessionManager.buildContextEntries());
|
|
2698
2800
|
}
|
|
2699
2801
|
// =========================================================================
|
|
2700
2802
|
// Key handlers
|
|
@@ -2965,9 +3067,11 @@ export class InteractiveMode {
|
|
|
2965
3067
|
if (isExpandable(activeHeader)) {
|
|
2966
3068
|
activeHeader.setExpanded(expanded);
|
|
2967
3069
|
}
|
|
2968
|
-
for (const
|
|
2969
|
-
|
|
2970
|
-
child
|
|
3070
|
+
for (const container of [this.loadedResourcesContainer, this.chatContainer]) {
|
|
3071
|
+
for (const child of container.children) {
|
|
3072
|
+
if (isExpandable(child)) {
|
|
3073
|
+
child.setExpanded(expanded);
|
|
3074
|
+
}
|
|
2971
3075
|
}
|
|
2972
3076
|
}
|
|
2973
3077
|
this.ui.requestRender();
|
|
@@ -2987,10 +3091,9 @@ export class InteractiveMode {
|
|
|
2987
3091
|
this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
2988
3092
|
}
|
|
2989
3093
|
async openExternalEditor() {
|
|
2990
|
-
|
|
2991
|
-
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
3094
|
+
const editorCmd = this.settingsManager.getExternalEditorCommand();
|
|
2992
3095
|
if (!editorCmd) {
|
|
2993
|
-
this.showWarning("No editor configured. Set
|
|
3096
|
+
this.showWarning("No editor configured. Set externalEditor in settings.json or $VISUAL/$EDITOR.");
|
|
2994
3097
|
return;
|
|
2995
3098
|
}
|
|
2996
3099
|
const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
|
|
@@ -3292,8 +3395,10 @@ export class InteractiveMode {
|
|
|
3292
3395
|
doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(),
|
|
3293
3396
|
treeFilterMode: this.settingsManager.getTreeFilterMode(),
|
|
3294
3397
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
3398
|
+
showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
|
|
3295
3399
|
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
|
3296
3400
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
3401
|
+
outputPad: this.settingsManager.getOutputPad(),
|
|
3297
3402
|
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
|
3298
3403
|
quietStartup: this.settingsManager.getQuietStartup(),
|
|
3299
3404
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
@@ -3366,6 +3471,10 @@ export class InteractiveMode {
|
|
|
3366
3471
|
this.chatContainer.clear();
|
|
3367
3472
|
this.rebuildChatFromMessages();
|
|
3368
3473
|
},
|
|
3474
|
+
onShowCacheMissNoticesChange: (shown) => {
|
|
3475
|
+
this.settingsManager.setShowCacheMissNotices(shown);
|
|
3476
|
+
this.rebuildChatFromMessages();
|
|
3477
|
+
},
|
|
3369
3478
|
onCollapseChangelogChange: (collapsed) => {
|
|
3370
3479
|
this.settingsManager.setCollapseChangelog(collapsed);
|
|
3371
3480
|
},
|
|
@@ -3395,6 +3504,23 @@ export class InteractiveMode {
|
|
|
3395
3504
|
this.editor.setPaddingX(padding);
|
|
3396
3505
|
}
|
|
3397
3506
|
},
|
|
3507
|
+
onOutputPadChange: (padding) => {
|
|
3508
|
+
this.settingsManager.setOutputPad(padding);
|
|
3509
|
+
this.outputPad = padding;
|
|
3510
|
+
if (this.streamingComponent || this.session.isStreaming) {
|
|
3511
|
+
for (const child of this.chatContainer.children) {
|
|
3512
|
+
if (child instanceof AssistantMessageComponent || child instanceof UserMessageComponent) {
|
|
3513
|
+
child.setOutputPad(padding);
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
if (this.streamingComponent) {
|
|
3517
|
+
this.streamingComponent.setOutputPad(padding);
|
|
3518
|
+
}
|
|
3519
|
+
this.ui.requestRender();
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
this.rebuildChatFromMessages();
|
|
3523
|
+
},
|
|
3398
3524
|
onAutocompleteMaxVisibleChange: (maxVisible) => {
|
|
3399
3525
|
this.settingsManager.setAutocompleteMaxVisible(maxVisible);
|
|
3400
3526
|
this.defaultEditor.setAutocompleteMaxVisible(maxVisible);
|
|
@@ -3405,6 +3531,9 @@ export class InteractiveMode {
|
|
|
3405
3531
|
onClearOnShrinkChange: (enabled) => {
|
|
3406
3532
|
this.settingsManager.setClearOnShrink(enabled);
|
|
3407
3533
|
this.ui.setClearOnShrink(enabled);
|
|
3534
|
+
if (!enabled && !this.activeStatusIndicator) {
|
|
3535
|
+
this.statusContainer.clear();
|
|
3536
|
+
}
|
|
3408
3537
|
},
|
|
3409
3538
|
onShowTerminalProgressChange: (enabled) => {
|
|
3410
3539
|
this.settingsManager.setShowTerminalProgress(enabled);
|
|
@@ -3635,19 +3764,17 @@ export class InteractiveMode {
|
|
|
3635
3764
|
const initialSelectedId = userMessages[userMessages.length - 1]?.entryId;
|
|
3636
3765
|
this.showSelector((done) => {
|
|
3637
3766
|
const selector = new UserMessageSelectorComponent(userMessages.map((m) => ({ id: m.entryId, text: m.text })), async (entryId) => {
|
|
3767
|
+
done();
|
|
3638
3768
|
try {
|
|
3639
3769
|
const result = await this.runtimeHost.fork(entryId);
|
|
3640
3770
|
if (result.cancelled) {
|
|
3641
|
-
done();
|
|
3642
3771
|
this.ui.requestRender();
|
|
3643
3772
|
return;
|
|
3644
3773
|
}
|
|
3645
3774
|
this.editor.setText(result.selectedText ?? "");
|
|
3646
|
-
done();
|
|
3647
3775
|
this.showStatus("Forked to new session");
|
|
3648
3776
|
}
|
|
3649
3777
|
catch (error) {
|
|
3650
|
-
done();
|
|
3651
3778
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3652
3779
|
}
|
|
3653
3780
|
}, () => {
|
|
@@ -3722,16 +3849,16 @@ export class InteractiveMode {
|
|
|
3722
3849
|
break;
|
|
3723
3850
|
}
|
|
3724
3851
|
}
|
|
3725
|
-
// Set up escape handler and
|
|
3726
|
-
let
|
|
3852
|
+
// Set up escape handler and status indicator if summarizing
|
|
3853
|
+
let showingSummaryIndicator = false;
|
|
3727
3854
|
const originalOnEscape = this.defaultEditor.onEscape;
|
|
3728
3855
|
if (wantsSummary) {
|
|
3729
3856
|
this.defaultEditor.onEscape = () => {
|
|
3730
3857
|
this.session.abortBranchSummary();
|
|
3731
3858
|
};
|
|
3732
3859
|
this.chatContainer.addChild(new Spacer(1));
|
|
3733
|
-
|
|
3734
|
-
|
|
3860
|
+
this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
|
|
3861
|
+
showingSummaryIndicator = true;
|
|
3735
3862
|
this.ui.requestRender();
|
|
3736
3863
|
}
|
|
3737
3864
|
try {
|
|
@@ -3762,9 +3889,8 @@ export class InteractiveMode {
|
|
|
3762
3889
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
3763
3890
|
}
|
|
3764
3891
|
finally {
|
|
3765
|
-
if (
|
|
3766
|
-
|
|
3767
|
-
this.statusContainer.clear();
|
|
3892
|
+
if (showingSummaryIndicator) {
|
|
3893
|
+
this.clearStatusIndicator("branchSummary");
|
|
3768
3894
|
}
|
|
3769
3895
|
this.defaultEditor.onEscape = originalOnEscape;
|
|
3770
3896
|
}
|
|
@@ -3805,11 +3931,7 @@ export class InteractiveMode {
|
|
|
3805
3931
|
});
|
|
3806
3932
|
}
|
|
3807
3933
|
async handleResumeSession(sessionPath, options) {
|
|
3808
|
-
|
|
3809
|
-
this.loadingAnimation.stop();
|
|
3810
|
-
this.loadingAnimation = undefined;
|
|
3811
|
-
}
|
|
3812
|
-
this.statusContainer.clear();
|
|
3934
|
+
this.clearStatusIndicator();
|
|
3813
3935
|
try {
|
|
3814
3936
|
const result = await this.runtimeHost.switchSession(sessionPath, {
|
|
3815
3937
|
withSession: options?.withSession,
|
|
@@ -3881,13 +4003,82 @@ export class InteractiveMode {
|
|
|
3881
4003
|
}
|
|
3882
4004
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
3883
4005
|
}
|
|
3884
|
-
|
|
4006
|
+
findLoginProviderOptions(providerRef) {
|
|
4007
|
+
const normalizedProviderRef = providerRef.trim().toLowerCase();
|
|
4008
|
+
if (!normalizedProviderRef) {
|
|
4009
|
+
return [];
|
|
4010
|
+
}
|
|
4011
|
+
return this.getLoginProviderOptions().filter((provider) => provider.id.toLowerCase() === normalizedProviderRef ||
|
|
4012
|
+
provider.name.toLowerCase() === normalizedProviderRef);
|
|
4013
|
+
}
|
|
4014
|
+
async handleLoginCommand(providerRef) {
|
|
4015
|
+
if (!providerRef) {
|
|
4016
|
+
this.showLoginAuthTypeSelector();
|
|
4017
|
+
return;
|
|
4018
|
+
}
|
|
4019
|
+
const providerOptions = this.findLoginProviderOptions(providerRef);
|
|
4020
|
+
if (providerOptions.length === 1) {
|
|
4021
|
+
await this.startProviderLogin(providerOptions[0]);
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
if (providerOptions.length > 1) {
|
|
4025
|
+
const providerIds = new Set(providerOptions.map((provider) => provider.id));
|
|
4026
|
+
if (providerIds.size === 1) {
|
|
4027
|
+
this.showLoginAuthTypeSelector(providerOptions);
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
this.showLoginProviderSelector(undefined, providerRef);
|
|
4032
|
+
}
|
|
4033
|
+
async startProviderLogin(providerOption) {
|
|
4034
|
+
if (providerOption.authType === "oauth") {
|
|
4035
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4036
|
+
}
|
|
4037
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
4038
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
4039
|
+
}
|
|
4040
|
+
else {
|
|
4041
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
showLoginAuthTypeSelector(providerOptions) {
|
|
3885
4045
|
const subscriptionLabel = "Use a subscription";
|
|
3886
4046
|
const apiKeyLabel = "Use an API key";
|
|
4047
|
+
const availableAuthTypes = providerOptions
|
|
4048
|
+
? new Set(providerOptions.map((provider) => provider.authType))
|
|
4049
|
+
: new Set(["oauth", "api_key"]);
|
|
4050
|
+
const options = [];
|
|
4051
|
+
if (availableAuthTypes.has("oauth")) {
|
|
4052
|
+
options.push(subscriptionLabel);
|
|
4053
|
+
}
|
|
4054
|
+
if (availableAuthTypes.has("api_key")) {
|
|
4055
|
+
options.push(apiKeyLabel);
|
|
4056
|
+
}
|
|
4057
|
+
if (options.length === 0) {
|
|
4058
|
+
this.showStatus("No login methods available.");
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
if (providerOptions && options.length === 1) {
|
|
4062
|
+
const providerOption = providerOptions[0];
|
|
4063
|
+
if (providerOption) {
|
|
4064
|
+
void this.startProviderLogin(providerOption);
|
|
4065
|
+
}
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
const title = providerOptions?.[0]
|
|
4069
|
+
? `Select authentication method for ${providerOptions[0].name}:`
|
|
4070
|
+
: "Select authentication method:";
|
|
3887
4071
|
this.showSelector((done) => {
|
|
3888
|
-
const selector = new ExtensionSelectorComponent(
|
|
4072
|
+
const selector = new ExtensionSelectorComponent(title, options, (option) => {
|
|
3889
4073
|
done();
|
|
3890
4074
|
const authType = option === subscriptionLabel ? "oauth" : "api_key";
|
|
4075
|
+
if (providerOptions) {
|
|
4076
|
+
const providerOption = providerOptions.find((provider) => provider.authType === authType);
|
|
4077
|
+
if (providerOption) {
|
|
4078
|
+
void this.startProviderLogin(providerOption);
|
|
4079
|
+
}
|
|
4080
|
+
return;
|
|
4081
|
+
}
|
|
3891
4082
|
this.showLoginProviderSelector(authType);
|
|
3892
4083
|
}, () => {
|
|
3893
4084
|
done();
|
|
@@ -3896,32 +4087,34 @@ export class InteractiveMode {
|
|
|
3896
4087
|
return { component: selector, focus: selector };
|
|
3897
4088
|
});
|
|
3898
4089
|
}
|
|
3899
|
-
showLoginProviderSelector(authType) {
|
|
4090
|
+
showLoginProviderSelector(authType, initialSearchInput) {
|
|
3900
4091
|
const providerOptions = this.getLoginProviderOptions(authType);
|
|
3901
4092
|
if (providerOptions.length === 0) {
|
|
3902
|
-
|
|
4093
|
+
const message = authType === "oauth"
|
|
4094
|
+
? "No subscription providers available."
|
|
4095
|
+
: authType === "api_key"
|
|
4096
|
+
? "No API key providers available."
|
|
4097
|
+
: "No login providers available.";
|
|
4098
|
+
this.showStatus(message);
|
|
3903
4099
|
return;
|
|
3904
4100
|
}
|
|
3905
4101
|
this.showSelector((done) => {
|
|
3906
|
-
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
4102
|
+
const selector = new OAuthSelectorComponent("login", this.session.modelRegistry.authStorage, providerOptions, async (providerId, selectedAuthType) => {
|
|
3907
4103
|
done();
|
|
3908
|
-
const providerOption = providerOptions.find((provider) => provider.id === providerId);
|
|
4104
|
+
const providerOption = providerOptions.find((provider) => provider.id === providerId && provider.authType === selectedAuthType);
|
|
3909
4105
|
if (!providerOption) {
|
|
3910
4106
|
return;
|
|
3911
4107
|
}
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
this.
|
|
4108
|
+
await this.startProviderLogin(providerOption);
|
|
4109
|
+
}, () => {
|
|
4110
|
+
done();
|
|
4111
|
+
if (authType) {
|
|
4112
|
+
this.showLoginAuthTypeSelector();
|
|
3917
4113
|
}
|
|
3918
4114
|
else {
|
|
3919
|
-
|
|
4115
|
+
this.ui.requestRender();
|
|
3920
4116
|
}
|
|
3921
|
-
}, () =>
|
|
3922
|
-
done();
|
|
3923
|
-
this.showLoginAuthTypeSelector();
|
|
3924
|
-
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId));
|
|
4117
|
+
}, (providerId) => this.session.modelRegistry.getProviderAuthStatus(providerId), initialSearchInput);
|
|
3925
4118
|
return { component: selector, focus: selector };
|
|
3926
4119
|
});
|
|
3927
4120
|
}
|
|
@@ -4180,7 +4373,7 @@ export class InteractiveMode {
|
|
|
4180
4373
|
const borderColor = (s) => theme.fg("border", s);
|
|
4181
4374
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4182
4375
|
reloadBox.addChild(new Spacer(1));
|
|
4183
|
-
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes..."), 1, 0));
|
|
4376
|
+
reloadBox.addChild(new Text(theme.fg("muted", "Reloading keybindings, extensions, skills, prompts, themes, and context files..."), 1, 0));
|
|
4184
4377
|
reloadBox.addChild(new Spacer(1));
|
|
4185
4378
|
reloadBox.addChild(new DynamicBorder(borderColor));
|
|
4186
4379
|
const previousEditor = this.editor;
|
|
@@ -4202,6 +4395,7 @@ export class InteractiveMode {
|
|
|
4202
4395
|
return;
|
|
4203
4396
|
}
|
|
4204
4397
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
4398
|
+
this.outputPad = this.settingsManager.getOutputPad();
|
|
4205
4399
|
this.rebuildChatFromMessages();
|
|
4206
4400
|
chatRestoredBeforeSessionStart = true;
|
|
4207
4401
|
};
|
|
@@ -4225,7 +4419,11 @@ export class InteractiveMode {
|
|
|
4225
4419
|
this.editor.setAutocompleteMaxVisible?.(autocompleteMaxVisible);
|
|
4226
4420
|
}
|
|
4227
4421
|
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
4228
|
-
this.
|
|
4422
|
+
const clearOnShrink = this.settingsManager.getClearOnShrink();
|
|
4423
|
+
this.ui.setClearOnShrink(clearOnShrink);
|
|
4424
|
+
if (!clearOnShrink && !this.activeStatusIndicator) {
|
|
4425
|
+
this.statusContainer.clear();
|
|
4426
|
+
}
|
|
4229
4427
|
this.setupAutocompleteProvider();
|
|
4230
4428
|
const runner = this.session.extensionRunner;
|
|
4231
4429
|
this.setupExtensionShortcuts(runner);
|
|
@@ -4239,8 +4437,8 @@ export class InteractiveMode {
|
|
|
4239
4437
|
this.showError(`models.json error: ${modelsJsonError}`);
|
|
4240
4438
|
}
|
|
4241
4439
|
this.showStatus(savedImplicitProjectTrust
|
|
4242
|
-
? "Reloaded keybindings, extensions, skills, prompts, themes; saved project trust"
|
|
4243
|
-
: "Reloaded keybindings, extensions, skills, prompts, themes");
|
|
4440
|
+
? "Reloaded keybindings, extensions, skills, prompts, themes, and context files; saved project trust"
|
|
4441
|
+
: "Reloaded keybindings, extensions, skills, prompts, themes, and context files");
|
|
4244
4442
|
dismissReloadBox(this.editor);
|
|
4245
4443
|
reloadBoxDismissed = true;
|
|
4246
4444
|
}
|
|
@@ -4304,11 +4502,7 @@ export class InteractiveMode {
|
|
|
4304
4502
|
return;
|
|
4305
4503
|
}
|
|
4306
4504
|
try {
|
|
4307
|
-
|
|
4308
|
-
this.loadingAnimation.stop();
|
|
4309
|
-
this.loadingAnimation = undefined;
|
|
4310
|
-
}
|
|
4311
|
-
this.statusContainer.clear();
|
|
4505
|
+
this.clearStatusIndicator();
|
|
4312
4506
|
const result = await this.runtimeHost.importFromJsonl(inputPath);
|
|
4313
4507
|
if (result.cancelled) {
|
|
4314
4508
|
this.showStatus("Import cancelled");
|
|
@@ -4465,6 +4659,26 @@ export class InteractiveMode {
|
|
|
4465
4659
|
handleSessionCommand() {
|
|
4466
4660
|
const stats = this.session.getSessionStats();
|
|
4467
4661
|
const sessionName = this.sessionManager.getSessionName();
|
|
4662
|
+
const entries = this.sessionManager.getEntries();
|
|
4663
|
+
const cacheWaste = computeCacheWaste(entries, this.session.modelRegistry);
|
|
4664
|
+
// Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
|
|
4665
|
+
// resolves to a concrete responseModel), sorted by cost descending.
|
|
4666
|
+
const perModelMap = new Map();
|
|
4667
|
+
for (const entry of entries) {
|
|
4668
|
+
if (entry.type !== "message" || entry.message.role !== "assistant")
|
|
4669
|
+
continue;
|
|
4670
|
+
const message = entry.message;
|
|
4671
|
+
const usage = message.usage;
|
|
4672
|
+
const key = `${message.provider}/${message.responseModel ?? message.model}`;
|
|
4673
|
+
let bucket = perModelMap.get(key);
|
|
4674
|
+
if (!bucket) {
|
|
4675
|
+
bucket = { key, cost: 0, tokens: 0 };
|
|
4676
|
+
perModelMap.set(key, bucket);
|
|
4677
|
+
}
|
|
4678
|
+
bucket.cost += usage.cost.total;
|
|
4679
|
+
bucket.tokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
4680
|
+
}
|
|
4681
|
+
const perModel = Array.from(perModelMap.values()).sort((a, b) => b.cost - a.cost);
|
|
4468
4682
|
let info = `${theme.bold("Session Info")}\n\n`;
|
|
4469
4683
|
if (sessionName) {
|
|
4470
4684
|
info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
|
|
@@ -4472,24 +4686,42 @@ export class InteractiveMode {
|
|
|
4472
4686
|
info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
|
|
4473
4687
|
info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
|
|
4474
4688
|
info += `${theme.bold("Messages")}\n`;
|
|
4689
|
+
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n`;
|
|
4475
4690
|
info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
|
|
4476
4691
|
info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
|
|
4477
|
-
info += `${theme.fg("dim", "
|
|
4478
|
-
info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
|
|
4479
|
-
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
|
|
4692
|
+
info += `${theme.fg("dim", "Tools:")} ${stats.toolCalls} calls, ${stats.toolResults} results\n\n`;
|
|
4480
4693
|
info += `${theme.bold("Tokens")}\n`;
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
}
|
|
4486
|
-
|
|
4487
|
-
|
|
4694
|
+
// "Input" is the full prompt volume. With cache activity, split it into
|
|
4695
|
+
// cached (served from cache) vs uncached (everything else) - the only
|
|
4696
|
+
// provider-independent split. Cache writes, where reported, are a detail
|
|
4697
|
+
// of the uncached portion.
|
|
4698
|
+
const { input, cacheRead, cacheWrite } = stats.tokens;
|
|
4699
|
+
const promptTokens = input + cacheRead + cacheWrite;
|
|
4700
|
+
info += `${theme.fg("dim", "Input:")} ${promptTokens.toLocaleString()}\n`;
|
|
4701
|
+
if (promptTokens > 0 && (cacheRead > 0 || cacheWrite > 0)) {
|
|
4702
|
+
const hitRate = theme.fg("dim", `(${((cacheRead / promptTokens) * 100).toFixed(1)}%)`);
|
|
4703
|
+
info += ` ${theme.fg("dim", "Cached:")} ${cacheRead.toLocaleString()} ${hitRate}\n`;
|
|
4704
|
+
const written = cacheWrite > 0 ? ` ${theme.fg("dim", `(${cacheWrite.toLocaleString()} written to cache)`)}` : "";
|
|
4705
|
+
info += ` ${theme.fg("dim", "Uncached:")} ${(input + cacheWrite).toLocaleString()}${written}\n`;
|
|
4488
4706
|
}
|
|
4707
|
+
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
4489
4708
|
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
4490
|
-
if (stats.cost > 0) {
|
|
4709
|
+
if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
|
|
4491
4710
|
info += `\n${theme.bold("Cost")}\n`;
|
|
4492
|
-
info += `${theme.fg("dim", "Total:")}
|
|
4711
|
+
info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
|
|
4712
|
+
if (perModel.length > 1) {
|
|
4713
|
+
for (const entry of perModel) {
|
|
4714
|
+
info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
if (cacheWaste.missedTokens > 0) {
|
|
4718
|
+
const missLabel = cacheWaste.missCount === 1 ? "1 miss" : `${cacheWaste.missCount} misses`;
|
|
4719
|
+
const detail = `${cacheWaste.missedTokens.toLocaleString()} tokens, ${missLabel}`;
|
|
4720
|
+
info +=
|
|
4721
|
+
cacheWaste.missedCost >= 0.0001
|
|
4722
|
+
? `\n${theme.fg("dim", "Cache Re-billed:")} $${cacheWaste.missedCost.toFixed(3)} ${theme.fg("dim", `(${detail})`)}`
|
|
4723
|
+
: `\n${theme.fg("dim", "Cache Re-billed:")} ${detail}`;
|
|
4724
|
+
}
|
|
4493
4725
|
}
|
|
4494
4726
|
this.chatContainer.addChild(new Spacer(1));
|
|
4495
4727
|
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
@@ -4634,11 +4866,7 @@ export class InteractiveMode {
|
|
|
4634
4866
|
this.ui.requestRender();
|
|
4635
4867
|
}
|
|
4636
4868
|
async handleClearCommand() {
|
|
4637
|
-
|
|
4638
|
-
this.loadingAnimation.stop();
|
|
4639
|
-
this.loadingAnimation = undefined;
|
|
4640
|
-
}
|
|
4641
|
-
this.statusContainer.clear();
|
|
4869
|
+
this.clearStatusIndicator();
|
|
4642
4870
|
try {
|
|
4643
4871
|
const result = await this.runtimeHost.newSession();
|
|
4644
4872
|
if (result.cancelled) {
|
|
@@ -4765,11 +4993,7 @@ export class InteractiveMode {
|
|
|
4765
4993
|
this.ui.requestRender();
|
|
4766
4994
|
}
|
|
4767
4995
|
async handleCompactCommand(customInstructions) {
|
|
4768
|
-
|
|
4769
|
-
this.loadingAnimation.stop();
|
|
4770
|
-
this.loadingAnimation = undefined;
|
|
4771
|
-
}
|
|
4772
|
-
this.statusContainer.clear();
|
|
4996
|
+
this.clearStatusIndicator();
|
|
4773
4997
|
try {
|
|
4774
4998
|
await this.session.compact(customInstructions);
|
|
4775
4999
|
}
|
|
@@ -4781,10 +5005,7 @@ export class InteractiveMode {
|
|
|
4781
5005
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
4782
5006
|
this.ui.terminal.setProgress(false);
|
|
4783
5007
|
}
|
|
4784
|
-
|
|
4785
|
-
this.loadingAnimation.stop();
|
|
4786
|
-
this.loadingAnimation = undefined;
|
|
4787
|
-
}
|
|
5008
|
+
this.clearStatusIndicator();
|
|
4788
5009
|
this.themeController.disableAutoSync();
|
|
4789
5010
|
this.clearExtensionTerminalInputListeners();
|
|
4790
5011
|
this.footer.dispose();
|