apex-code 0.0.1-alpha.6 → 0.0.1-alpha.8
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 +162 -0
- package/README.md +5 -0
- package/dist/cli/args.d.ts +3 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +15 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/concurrent-session.d.ts +3 -0
- package/dist/cli/concurrent-session.d.ts.map +1 -0
- package/dist/cli/concurrent-session.js +13 -0
- package/dist/cli/concurrent-session.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -2
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +20 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +32 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +37 -6
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +4 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +6 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +10 -9
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +4 -0
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/types.d.ts +4 -0
- 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 +1 -2
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -17
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/http-idle-timeout.d.ts +12 -0
- package/dist/core/http-idle-timeout.d.ts.map +1 -0
- package/dist/core/http-idle-timeout.js +27 -0
- package/dist/core/http-idle-timeout.js.map +1 -0
- package/dist/core/keybindings.d.ts +24 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/lsp/client.d.ts +2 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +27 -2
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +1 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/permissions/gate.d.ts +7 -0
- package/dist/core/permissions/gate.d.ts.map +1 -1
- package/dist/core/permissions/gate.js.map +1 -1
- package/dist/core/permissions/startup.d.ts +12 -0
- package/dist/core/permissions/startup.d.ts.map +1 -1
- package/dist/core/permissions/startup.js +12 -4
- package/dist/core/permissions/startup.js.map +1 -1
- package/dist/core/sandbox/child-entry.d.ts.map +1 -1
- package/dist/core/sandbox/child-entry.js +8 -1
- package/dist/core/sandbox/child-entry.js.map +1 -1
- package/dist/core/sandbox/cli-launch.d.ts +3 -0
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
- package/dist/core/sandbox/cli-launch.js +25 -4
- package/dist/core/sandbox/cli-launch.js.map +1 -1
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/cli-supervisor.js +53 -13
- package/dist/core/sandbox/cli-supervisor.js.map +1 -1
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
- package/dist/core/sandbox/linux-backend.js +15 -0
- package/dist/core/sandbox/linux-backend.js.map +1 -1
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
- package/dist/core/sandbox/macos-backend.js +24 -1
- package/dist/core/sandbox/macos-backend.js.map +1 -1
- package/dist/core/sandbox/rpc/credential-client.d.ts +20 -0
- package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-client.js +170 -0
- package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.js +381 -0
- package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
- package/dist/core/sandbox/supervisor.d.ts +11 -0
- package/dist/core/sandbox/supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/supervisor.js.map +1 -1
- package/dist/core/sandbox/terminal-size.d.ts +40 -0
- package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-size.js +105 -0
- package/dist/core/sandbox/terminal-size.js.map +1 -0
- package/dist/core/sdk.d.ts +6 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +16 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-lease.d.ts +31 -0
- package/dist/core/session-lease.d.ts.map +1 -0
- package/dist/core/session-lease.js +138 -0
- package/dist/core/session-lease.js.map +1 -0
- package/dist/core/session-manager.d.ts +5 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +3 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +26 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +42 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -2
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +47 -62
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +2 -0
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +24 -0
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +2 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +2 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +35 -10
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts +2 -0
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/web-search-exa.d.ts +33 -0
- package/dist/core/tools/web-search-exa.d.ts.map +1 -0
- package/dist/core/tools/web-search-exa.js +128 -0
- package/dist/core/tools/web-search-exa.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -0
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/web-search-provider.d.ts +64 -0
- package/dist/core/web-search-provider.d.ts.map +1 -0
- package/dist/core/web-search-provider.js +149 -0
- package/dist/core/web-search-provider.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +21 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +23 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +105 -2
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +11 -2
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +24 -6
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +6 -3
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
- package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
- package/dist/modes/interactive/components/first-use-hints.js +22 -0
- package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +15 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +133 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +2 -0
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +20 -5
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +16 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +106 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/splash-header.d.ts +29 -4
- package/dist/modes/interactive/components/splash-header.d.ts.map +1 -1
- package/dist/modes/interactive/components/splash-header.js +58 -23
- package/dist/modes/interactive/components/splash-header.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +14 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +113 -35
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
- package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-panel.js +130 -0
- package/dist/modes/interactive/components/tool-panel.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +48 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +339 -68
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
- package/dist/modes/interactive/model-catalog-refresh.js +34 -0
- package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
- package/dist/modes/interactive/theme/apex.json +62 -63
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +2 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +25 -11
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +14 -3
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +55 -6
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +4 -1
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +5 -2
- package/dist/modes/json-event.js.map +1 -1
- package/dist/server/create-harness.d.ts.map +1 -1
- package/dist/server/create-harness.js +2 -0
- package/dist/server/create-harness.js.map +1 -1
- package/dist/themes/apex-logo.d.ts +36 -16
- package/dist/themes/apex-logo.d.ts.map +1 -1
- package/dist/themes/apex-logo.js +32 -28
- package/dist/themes/apex-logo.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +10 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +15 -20
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/compaction.md +3 -3
- package/docs/custom-provider.md +1 -1
- package/docs/environment-variables.md +1 -0
- package/docs/extensions.md +4 -0
- package/docs/json.md +6 -4
- package/docs/keybindings.md +8 -2
- package/docs/rpc.md +15 -4
- package/docs/sdk.md +9 -0
- package/docs/settings.md +63 -0
- package/docs/terminal-setup.md +23 -2
- package/docs/themes.md +24 -3
- package/docs/tui.md +2 -2
- package/docs/usage.md +3 -2
- 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/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +2 -0
- package/examples/extensions/subagent/agents.ts +40 -9
- package/examples/extensions/subagent/index.ts +21 -3
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +27 -78
- package/package.json +6 -6
|
@@ -54,6 +54,7 @@ import { EarendilAnnouncementComponent } from "./components/earendil-announcemen
|
|
|
54
54
|
import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
55
55
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
56
56
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
57
|
+
import { FirstUseHints } from "./components/first-use-hints.js";
|
|
57
58
|
import { FooterComponent, formatTokens } from "./components/footer.js";
|
|
58
59
|
import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
59
60
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
@@ -62,7 +63,7 @@ import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
|
62
63
|
import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
|
|
63
64
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
64
65
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
65
|
-
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
66
|
+
import { PERMISSION_MODE_OVERRIDE_HINTS, SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
66
67
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
67
68
|
import { ApexSplashHeader } from "./components/splash-header.js";
|
|
68
69
|
import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
|
|
@@ -72,6 +73,7 @@ import { TrustSelectorComponent } from "./components/trust-selector.js";
|
|
|
72
73
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
73
74
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
74
75
|
import { editInExternalEditor } from "./external-editor.js";
|
|
76
|
+
import { refreshModelCatalogs } from "./model-catalog-refresh.js";
|
|
75
77
|
import { getModelSearchText } from "./model-search.js";
|
|
76
78
|
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
77
79
|
import { InteractiveThemeController } from "./theme/theme-controller.js";
|
|
@@ -172,9 +174,21 @@ function formatLoginProviderCompletionDescription(provider) {
|
|
|
172
174
|
export function createInteractiveTui(options) {
|
|
173
175
|
const terminal = options.terminal ?? new ProcessTerminal();
|
|
174
176
|
if (options.tuiMode === "fullscreen") {
|
|
177
|
+
const styleSearchMatch = (text) => theme.bg("searchMatchBg", theme.fg("searchMatchText", text));
|
|
175
178
|
return new TuiAltScreen(terminal, options.showHardwareCursor, options.logDirectory, {
|
|
179
|
+
searchMatchStyle: (text) => theme.underline(styleSearchMatch(text)),
|
|
180
|
+
searchCurrentMatchStyle: (text) => theme.bold(theme.inverse(styleSearchMatch(text))),
|
|
176
181
|
openUrl: openBrowser,
|
|
177
182
|
onRightClickPaste: options.onRightClickPaste,
|
|
183
|
+
copySelection: async (text) => {
|
|
184
|
+
try {
|
|
185
|
+
await copyToClipboard(text);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
},
|
|
178
192
|
});
|
|
179
193
|
}
|
|
180
194
|
return new TuiMainScreen(terminal, options.showHardwareCursor, options.logDirectory);
|
|
@@ -212,6 +226,8 @@ export function createInteractiveTuiReference(getTui) {
|
|
|
212
226
|
}
|
|
213
227
|
export class InteractiveMode {
|
|
214
228
|
runtimeHost;
|
|
229
|
+
/** Settled once the most recent /settings permission-mode write has hit disk. */
|
|
230
|
+
pendingPermissionModeWrite;
|
|
215
231
|
renderer;
|
|
216
232
|
ui;
|
|
217
233
|
mainScreenRenderState;
|
|
@@ -234,6 +250,14 @@ export class InteractiveMode {
|
|
|
234
250
|
footer;
|
|
235
251
|
footerContainer;
|
|
236
252
|
footerDataProvider;
|
|
253
|
+
/**
|
|
254
|
+
* Whether the startup screen is showing its counted inventory line.
|
|
255
|
+
*
|
|
256
|
+
* Warnings are only allowed to collapse into that count when the count is
|
|
257
|
+
* actually on screen. Under a quiet startup there is no splash header, so
|
|
258
|
+
* collapsing them would hide conflicts entirely rather than summarise them.
|
|
259
|
+
*/
|
|
260
|
+
startupInventoryVisible = false;
|
|
237
261
|
// Stored so the same manager can be injected into custom editors, selectors, and extension UI.
|
|
238
262
|
keybindings;
|
|
239
263
|
version;
|
|
@@ -256,6 +280,7 @@ export class InteractiveMode {
|
|
|
256
280
|
// Status line tracking (for mutating immediately-sequential status updates)
|
|
257
281
|
lastStatusSpacer = undefined;
|
|
258
282
|
lastStatusText = undefined;
|
|
283
|
+
managedToolStatusStarted = false;
|
|
259
284
|
// Streaming message tracking
|
|
260
285
|
streamingComponent = undefined;
|
|
261
286
|
streamingMessage = undefined;
|
|
@@ -277,6 +302,7 @@ export class InteractiveMode {
|
|
|
277
302
|
signalCleanupHandlers = [];
|
|
278
303
|
// Track if editor is in bash mode (text starts with !)
|
|
279
304
|
isBashMode = false;
|
|
305
|
+
firstUseHints;
|
|
280
306
|
// Track current bash execution component
|
|
281
307
|
bashComponent = undefined;
|
|
282
308
|
// Track pending bash components (shown in pending area, moved to chat on submit)
|
|
@@ -326,6 +352,14 @@ export class InteractiveMode {
|
|
|
326
352
|
get settingsManager() {
|
|
327
353
|
return this.session.settingsManager;
|
|
328
354
|
}
|
|
355
|
+
offerFirstUseHint(id) {
|
|
356
|
+
this.firstUseHints ??= new FirstUseHints(this.settingsManager.getFirstUseHints());
|
|
357
|
+
const hint = this.firstUseHints.offer(id);
|
|
358
|
+
if (!hint)
|
|
359
|
+
return;
|
|
360
|
+
this.settingsManager.setFirstUseHints(this.firstUseHints.getSeen());
|
|
361
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", hint), this.outputPad, 0));
|
|
362
|
+
}
|
|
329
363
|
constructor(runtimeHost, options = {}) {
|
|
330
364
|
this.runtimeHost = runtimeHost;
|
|
331
365
|
const tuiMode = options.tuiMode ?? this.settingsManager.getTuiMode();
|
|
@@ -336,6 +370,7 @@ export class InteractiveMode {
|
|
|
336
370
|
});
|
|
337
371
|
this.runtimeHost.setRebindSession(async () => {
|
|
338
372
|
await this.rebindCurrentSession({ renderBeforeBind: true });
|
|
373
|
+
await this.themeController.applyFromSettings();
|
|
339
374
|
});
|
|
340
375
|
this.version = VERSION;
|
|
341
376
|
this.renderer = createInteractiveTui({
|
|
@@ -364,11 +399,14 @@ export class InteractiveMode {
|
|
|
364
399
|
this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
|
|
365
400
|
paddingX: editorPaddingX,
|
|
366
401
|
autocompleteMaxVisible,
|
|
367
|
-
promptPrefix: "
|
|
402
|
+
promptPrefix: "› ",
|
|
368
403
|
promptColor: (text) => theme.fg("accent", text),
|
|
369
|
-
placeholder: "Ask anything
|
|
404
|
+
placeholder: "Ask anything",
|
|
370
405
|
placeholderColor: (text) => theme.fg("dim", text),
|
|
371
406
|
commandColor: (text) => theme.fg("accent", text),
|
|
407
|
+
surfaceColor: (text) => theme.bg("userMessageBg", text),
|
|
408
|
+
autocompleteRule: (width) => theme.fg("borderMuted", (this.settingsManager.getSymbolPreset() === "ascii" ? "-" : "┄").repeat(Math.max(1, width))),
|
|
409
|
+
autocompleteFooter: () => this.buildAutocompleteFooter(),
|
|
372
410
|
});
|
|
373
411
|
this.editor = this.defaultEditor;
|
|
374
412
|
this.editorContainer = new Container();
|
|
@@ -383,7 +421,12 @@ export class InteractiveMode {
|
|
|
383
421
|
this.outputPad = this.settingsManager.getOutputPad();
|
|
384
422
|
// Register themes from resource loader and initialize
|
|
385
423
|
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
386
|
-
this.themeController = new InteractiveThemeController(this.ui,
|
|
424
|
+
this.themeController = new InteractiveThemeController(this.ui, {
|
|
425
|
+
getSettingsManager: () => this.settingsManager,
|
|
426
|
+
showError: (message) => this.showError(message),
|
|
427
|
+
onChanged: () => this.updateEditorBorderColor(),
|
|
428
|
+
initialThemeSetting: options.initialThemeSetting,
|
|
429
|
+
});
|
|
387
430
|
}
|
|
388
431
|
getAutocompleteSourceTag(sourceInfo) {
|
|
389
432
|
if (!sourceInfo) {
|
|
@@ -546,14 +589,14 @@ export class InteractiveMode {
|
|
|
546
589
|
tui.setLayoutRoot(this.fullscreenLayoutRoot);
|
|
547
590
|
}
|
|
548
591
|
}
|
|
549
|
-
stopInteractiveTui() {
|
|
550
|
-
if (this.renderer.mode === "fullscreen") {
|
|
592
|
+
stopInteractiveTui(fullscreenExitOutput) {
|
|
593
|
+
if (this.renderer.mode === "fullscreen" && fullscreenExitOutput === "transcript") {
|
|
551
594
|
while (this.renderer.hasOverlayEntries)
|
|
552
595
|
this.renderer.hideOverlay();
|
|
553
596
|
this.switchTuiMode("regular", false, false);
|
|
554
597
|
this.renderer.renderNow();
|
|
555
598
|
}
|
|
556
|
-
this.ui.stop();
|
|
599
|
+
this.ui.stop({ preserveScreen: this.renderer.mode === "fullscreen" });
|
|
557
600
|
}
|
|
558
601
|
switchTuiMode(mode, restoreProgress = true, startRenderer = true) {
|
|
559
602
|
const previousUi = this.renderer;
|
|
@@ -610,10 +653,9 @@ export class InteractiveMode {
|
|
|
610
653
|
this.registerSignalHandlers();
|
|
611
654
|
// Load changelog (only show new entries, skip for resumed sessions)
|
|
612
655
|
this.changelogMarkdown = this.getChangelogForDisplay();
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
|
|
616
|
-
this.fdPath = fdPath;
|
|
656
|
+
// Resolve the effective mode before mounting or focusing components. Both
|
|
657
|
+
// operations can schedule a frame even before the renderer is started.
|
|
658
|
+
await this.refreshFooterPermissionMode();
|
|
617
659
|
if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
|
|
618
660
|
const modelList = this.session.scopedModels
|
|
619
661
|
.map((sm) => {
|
|
@@ -657,9 +699,11 @@ export class InteractiveMode {
|
|
|
657
699
|
this.widgetContainerBelow,
|
|
658
700
|
this.footerContainer,
|
|
659
701
|
]);
|
|
702
|
+
// Accept text while startup completes, but only enable interrupt, exit, and submission feedback.
|
|
703
|
+
this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
|
|
704
|
+
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
|
705
|
+
this.defaultEditor.onSubmit = (text) => this.handleStartupSubmit(text);
|
|
660
706
|
this.ui.setFocus(this.editor);
|
|
661
|
-
this.setupKeyHandlers();
|
|
662
|
-
this.setupEditorSubmitHandler();
|
|
663
707
|
// Start the UI before initializing extensions so session_start handlers can use interactive dialogs
|
|
664
708
|
this.ui.start();
|
|
665
709
|
this.isInitialized = true;
|
|
@@ -696,18 +740,42 @@ export class InteractiveMode {
|
|
|
696
740
|
this.builtInHeader = new ApexSplashHeader(this.version, () => this.session.state.model?.id, () => this.session.sessionManager.getCwd(), this.options.verbose ? expandedInstructions : undefined, {
|
|
697
741
|
topPadding: true,
|
|
698
742
|
getSymbolPreset: () => this.settingsManager.getSymbolPreset(),
|
|
743
|
+
getExtraMetadata: () => {
|
|
744
|
+
// Which branch the session is rooted on differs between launches
|
|
745
|
+
// and is not derivable from the prompt, so it belongs here. The
|
|
746
|
+
// tray carries it too, but drops it first when space is tight.
|
|
747
|
+
const branch = this.footerDataProvider.getGitBranch();
|
|
748
|
+
return branch ? [{ label: "branch", value: branch }] : [];
|
|
749
|
+
},
|
|
750
|
+
getInventory: () => this.buildStartupInventory(),
|
|
751
|
+
inventoryHint: "/resources",
|
|
752
|
+
getShortcuts: () => this.buildStartupShortcuts(),
|
|
699
753
|
getHint: () => "Apex Code can explain its own features and look up its docs.",
|
|
700
754
|
});
|
|
701
755
|
// Setup UI layout
|
|
756
|
+
this.startupInventoryVisible = true;
|
|
702
757
|
this.headerContainer.addChild(this.builtInHeader);
|
|
703
758
|
this.headerContainer.addChild(new Spacer(1));
|
|
704
759
|
}
|
|
705
760
|
else {
|
|
706
761
|
// Minimal header when silenced
|
|
762
|
+
this.startupInventoryVisible = false;
|
|
707
763
|
this.builtInHeader = new Text("", 0, 0);
|
|
708
764
|
this.headerContainer.addChild(this.builtInHeader);
|
|
709
765
|
}
|
|
710
766
|
this.ui.requestRender();
|
|
767
|
+
// Ensure fd and rg are available after mounting the TUI (downloads if missing, adds to PATH via getBinDir)
|
|
768
|
+
// so slow downloads do not make startup appear frozen.
|
|
769
|
+
// Both are needed: fd for autocomplete, rg for grep tool and bash commands.
|
|
770
|
+
const [fdPath] = await Promise.all([
|
|
771
|
+
ensureTool("fd", (status) => this.showManagedToolStatus(status)),
|
|
772
|
+
ensureTool("rg", (status) => this.showManagedToolStatus(status)),
|
|
773
|
+
]);
|
|
774
|
+
this.fdPath = fdPath;
|
|
775
|
+
// Enable the remaining input handlers only after managed-tool setup completes.
|
|
776
|
+
this.setupKeyHandlers();
|
|
777
|
+
this.setupEditorSubmitHandler();
|
|
778
|
+
this.ui.requestRender();
|
|
711
779
|
// Initialize extensions first so resources are shown before messages
|
|
712
780
|
await this.rebindCurrentSession();
|
|
713
781
|
// Render initial messages AFTER showing loaded resources
|
|
@@ -747,8 +815,7 @@ export class InteractiveMode {
|
|
|
747
815
|
if (!getApexEnvironment("APEX_CODE_OFFLINE")) {
|
|
748
816
|
const controller = new AbortController();
|
|
749
817
|
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
750
|
-
void this.session.modelRuntime
|
|
751
|
-
.refresh({ signal: controller.signal })
|
|
818
|
+
void refreshModelCatalogs(this.session.modelRuntime, controller.signal)
|
|
752
819
|
.then(() => this.updateAvailableProviderCount())
|
|
753
820
|
.catch(() => { })
|
|
754
821
|
.finally(() => clearTimeout(timeout));
|
|
@@ -1203,7 +1270,7 @@ export class InteractiveMode {
|
|
|
1203
1270
|
showLoadedResources(options) {
|
|
1204
1271
|
// Resource rendering is idempotent; chat clears no longer clear this separate container.
|
|
1205
1272
|
this.loadedResourcesContainer.clear();
|
|
1206
|
-
const showListing = options?.force || this.options.verbose
|
|
1273
|
+
const showListing = options?.force || this.options.verbose;
|
|
1207
1274
|
const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
|
|
1208
1275
|
if (!showListing && !showDiagnostics) {
|
|
1209
1276
|
return;
|
|
@@ -1320,43 +1387,105 @@ export class InteractiveMode {
|
|
|
1320
1387
|
addLoadedSection("Themes", themeCompactList, themeList);
|
|
1321
1388
|
}
|
|
1322
1389
|
}
|
|
1390
|
+
// Warnings collapse into the counted line when that line is on screen; the
|
|
1391
|
+
// full trace lives behind /resources. Errors always surface inline,
|
|
1392
|
+
// because an error means a resource did not load at all.
|
|
1323
1393
|
if (showDiagnostics) {
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
const
|
|
1333
|
-
this.loadedResourcesContainer.addChild(new Text(`${theme.fg(
|
|
1334
|
-
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1335
|
-
}
|
|
1336
|
-
const extensionDiagnostics = [];
|
|
1337
|
-
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
1338
|
-
if (extensionErrors.length > 0) {
|
|
1339
|
-
for (const error of extensionErrors) {
|
|
1340
|
-
extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1343
|
-
const commandDiagnostics = this.session.extensionRunner.getCommandDiagnostics();
|
|
1344
|
-
extensionDiagnostics.push(...commandDiagnostics);
|
|
1345
|
-
extensionDiagnostics.push(...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner));
|
|
1346
|
-
const shortcutDiagnostics = this.session.extensionRunner.getShortcutDiagnostics();
|
|
1347
|
-
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
1348
|
-
if (extensionDiagnostics.length > 0) {
|
|
1349
|
-
const warningLines = this.formatDiagnostics(extensionDiagnostics, sourceInfos);
|
|
1350
|
-
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
1351
|
-
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1352
|
-
}
|
|
1353
|
-
const themeDiagnostics = themesResult.diagnostics;
|
|
1354
|
-
if (themeDiagnostics.length > 0) {
|
|
1355
|
-
const warningLines = this.formatDiagnostics(themeDiagnostics, sourceInfos);
|
|
1356
|
-
this.loadedResourcesContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
1394
|
+
const collapseWarnings = !showListing && this.startupInventoryVisible;
|
|
1395
|
+
for (const group of this.collectDiagnosticGroups()) {
|
|
1396
|
+
const visible = collapseWarnings
|
|
1397
|
+
? group.diagnostics.filter((diagnostic) => diagnostic.type === "error")
|
|
1398
|
+
: group.diagnostics;
|
|
1399
|
+
if (visible.length === 0)
|
|
1400
|
+
continue;
|
|
1401
|
+
const severity = visible.some((diagnostic) => diagnostic.type === "error") ? "error" : "warning";
|
|
1402
|
+
const body = this.formatDiagnostics(visible, sourceInfos);
|
|
1403
|
+
this.loadedResourcesContainer.addChild(new Text(`${theme.fg(severity, `[${group.label}]`)}\n${body}`, 0, 0));
|
|
1357
1404
|
this.loadedResourcesContainer.addChild(new Spacer(1));
|
|
1358
1405
|
}
|
|
1359
1406
|
}
|
|
1407
|
+
// Counts on the startup screen are read at render time, so they fill in
|
|
1408
|
+
// once resources finish loading without an explicit refresh here.
|
|
1409
|
+
this.builtInHeader?.invalidate?.();
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Every startup diagnostic, grouped by the resource kind that produced it.
|
|
1413
|
+
*
|
|
1414
|
+
* One list feeds both the counted line in the header and the detailed
|
|
1415
|
+
* listing, so the two can never disagree about how many issues exist.
|
|
1416
|
+
*/
|
|
1417
|
+
collectDiagnosticGroups() {
|
|
1418
|
+
const extensionDiagnostics = [
|
|
1419
|
+
...this.session.resourceLoader
|
|
1420
|
+
.getExtensions()
|
|
1421
|
+
.errors.map((error) => ({ type: "error", message: error.error, path: error.path })),
|
|
1422
|
+
...this.session.extensionRunner.getCommandDiagnostics(),
|
|
1423
|
+
...this.getBuiltInCommandConflictDiagnostics(this.session.extensionRunner),
|
|
1424
|
+
...this.session.extensionRunner.getShortcutDiagnostics(),
|
|
1425
|
+
];
|
|
1426
|
+
return [
|
|
1427
|
+
{ label: "Skill conflicts", diagnostics: this.session.resourceLoader.getSkills().diagnostics },
|
|
1428
|
+
{ label: "Prompt conflicts", diagnostics: this.session.resourceLoader.getPrompts().diagnostics },
|
|
1429
|
+
{ label: "Extension issues", diagnostics: extensionDiagnostics },
|
|
1430
|
+
{ label: "Theme conflicts", diagnostics: this.session.resourceLoader.getThemes().diagnostics },
|
|
1431
|
+
].filter((group) => group.diagnostics.length > 0);
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* The counted line in the ruled band under the mark.
|
|
1435
|
+
*
|
|
1436
|
+
* The startup screen used to print all 152 skill names across fifteen rows.
|
|
1437
|
+
* A count answers the only question that screen can usefully answer, which
|
|
1438
|
+
* is what got loaded; the names live behind /resources now.
|
|
1439
|
+
*/
|
|
1440
|
+
/**
|
|
1441
|
+
* The sigil row under the hint: how to reach commands, bash, and files.
|
|
1442
|
+
*
|
|
1443
|
+
* These are the only three characters that change what the composer does, so
|
|
1444
|
+
* they take the accent and everything around them stays quiet.
|
|
1445
|
+
*/
|
|
1446
|
+
/** Live keybindings under the autocomplete dropdown. */
|
|
1447
|
+
buildAutocompleteFooter() {
|
|
1448
|
+
const separator = theme.fg("borderMuted", this.settingsManager.getSymbolPreset() === "ascii" ? " - " : " · ");
|
|
1449
|
+
return [
|
|
1450
|
+
rawKeyHint(`${keyText("tui.select.up")}/${keyText("tui.select.down")}`, "move"),
|
|
1451
|
+
keyHint("tui.select.confirm", "select"),
|
|
1452
|
+
keyHint("tui.input.tab", "complete"),
|
|
1453
|
+
keyHint("tui.select.cancel", "dismiss"),
|
|
1454
|
+
].join(separator);
|
|
1455
|
+
}
|
|
1456
|
+
buildStartupShortcuts() {
|
|
1457
|
+
const entry = (sigil, label) => `${theme.fg("accent", sigil)} ${theme.fg("muted", label)}`;
|
|
1458
|
+
return [
|
|
1459
|
+
entry("/", "commands"),
|
|
1460
|
+
entry("!", "bash"),
|
|
1461
|
+
entry("@", "files"),
|
|
1462
|
+
`${theme.fg("dim", keyText("app.interrupt"))} ${theme.fg("muted", "interrupt")}`,
|
|
1463
|
+
].join(" ");
|
|
1464
|
+
}
|
|
1465
|
+
buildStartupInventory() {
|
|
1466
|
+
const counted = (total, singular) => `${total} ${singular}${total === 1 ? "" : "s"}`;
|
|
1467
|
+
const parts = [];
|
|
1468
|
+
const skills = this.session.resourceLoader.getSkills().skills.length;
|
|
1469
|
+
const extensions = this.session.resourceLoader.getExtensions().extensions.filter((e) => !e.hidden).length;
|
|
1470
|
+
const prompts = this.session.resourceLoader.getPrompts().prompts.length;
|
|
1471
|
+
if (skills > 0)
|
|
1472
|
+
parts.push(theme.fg("muted", counted(skills, "skill")));
|
|
1473
|
+
if (extensions > 0)
|
|
1474
|
+
parts.push(theme.fg("muted", counted(extensions, "extension")));
|
|
1475
|
+
if (prompts > 0)
|
|
1476
|
+
parts.push(theme.fg("muted", counted(prompts, "prompt")));
|
|
1477
|
+
const diagnostics = this.collectDiagnosticGroups().flatMap((group) => group.diagnostics);
|
|
1478
|
+
const errors = diagnostics.filter((diagnostic) => diagnostic.type === "error").length;
|
|
1479
|
+
const conflicts = diagnostics.length - errors;
|
|
1480
|
+
if (errors > 0)
|
|
1481
|
+
parts.push(theme.fg("error", counted(errors, "error")));
|
|
1482
|
+
if (conflicts > 0)
|
|
1483
|
+
parts.push(theme.fg("warning", counted(conflicts, "conflict")));
|
|
1484
|
+
if (parts.length === 0)
|
|
1485
|
+
return undefined;
|
|
1486
|
+
const ascii = this.settingsManager.getSymbolPreset() === "ascii";
|
|
1487
|
+
const separator = theme.fg("borderMuted", ascii ? " - " : " · ");
|
|
1488
|
+
return parts.join(separator);
|
|
1360
1489
|
}
|
|
1361
1490
|
/**
|
|
1362
1491
|
* Initialize the extension system with TUI-based UI context.
|
|
@@ -1444,6 +1573,7 @@ export class InteractiveMode {
|
|
|
1444
1573
|
this.applyFullscreenScrollbarSetting();
|
|
1445
1574
|
this.footer.setSession(this.session);
|
|
1446
1575
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1576
|
+
void this.refreshFooterPermissionMode();
|
|
1447
1577
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
1448
1578
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
1449
1579
|
this.outputPad = this.settingsManager.getOutputPad();
|
|
@@ -1486,7 +1616,7 @@ export class InteractiveMode {
|
|
|
1486
1616
|
const message = error instanceof Error ? error.message : String(error);
|
|
1487
1617
|
this.showError(`${prefix}: ${message}`);
|
|
1488
1618
|
stopThemeWatcher();
|
|
1489
|
-
this.stop();
|
|
1619
|
+
this.stop("transcript");
|
|
1490
1620
|
process.exit(1);
|
|
1491
1621
|
}
|
|
1492
1622
|
renderCurrentSessionState() {
|
|
@@ -1496,9 +1626,14 @@ export class InteractiveMode {
|
|
|
1496
1626
|
this.compactionQueuedMessages = [];
|
|
1497
1627
|
this.streamingComponent = undefined;
|
|
1498
1628
|
this.streamingMessage = undefined;
|
|
1499
|
-
this.
|
|
1629
|
+
this.clearPendingTools();
|
|
1500
1630
|
this.renderInitialMessages();
|
|
1501
1631
|
}
|
|
1632
|
+
clearPendingTools() {
|
|
1633
|
+
for (const component of this.pendingTools.values())
|
|
1634
|
+
component.dispose();
|
|
1635
|
+
this.pendingTools.clear();
|
|
1636
|
+
}
|
|
1502
1637
|
/**
|
|
1503
1638
|
* Get a registered tool definition by name (for custom rendering).
|
|
1504
1639
|
*/
|
|
@@ -1881,7 +2016,7 @@ export class InteractiveMode {
|
|
|
1881
2016
|
/**
|
|
1882
2017
|
* Show a selector for extensions.
|
|
1883
2018
|
*/
|
|
1884
|
-
showExtensionSelector(title, options, opts) {
|
|
2019
|
+
showExtensionSelector(title, options, opts, enableSearch = false) {
|
|
1885
2020
|
return new Promise((resolve) => {
|
|
1886
2021
|
if (opts?.signal?.aborted) {
|
|
1887
2022
|
resolve(undefined);
|
|
@@ -1900,7 +2035,12 @@ export class InteractiveMode {
|
|
|
1900
2035
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1901
2036
|
this.hideExtensionSelector();
|
|
1902
2037
|
resolve(undefined);
|
|
1903
|
-
}, {
|
|
2038
|
+
}, {
|
|
2039
|
+
tui: this.ui,
|
|
2040
|
+
timeout: opts?.timeout,
|
|
2041
|
+
onToggleToolsExpanded: () => this.toggleToolOutputExpansion(),
|
|
2042
|
+
enableSearch,
|
|
2043
|
+
});
|
|
1904
2044
|
this.disposeActiveSelector();
|
|
1905
2045
|
this.editorContainer.clear();
|
|
1906
2046
|
this.editorContainer.addChild(this.extensionSelector);
|
|
@@ -1919,6 +2059,40 @@ export class InteractiveMode {
|
|
|
1919
2059
|
this.ui.setFocus(this.editor);
|
|
1920
2060
|
this.ui.requestRender();
|
|
1921
2061
|
}
|
|
2062
|
+
async showConfigurationIndex() {
|
|
2063
|
+
const choice = await this.showExtensionSelector("Configuration", [
|
|
2064
|
+
"Settings",
|
|
2065
|
+
"Model",
|
|
2066
|
+
"Model cycling",
|
|
2067
|
+
"Provider login",
|
|
2068
|
+
"Provider logout",
|
|
2069
|
+
"Project trust",
|
|
2070
|
+
"Resources, extensions, and MCP adapters",
|
|
2071
|
+
], undefined, true);
|
|
2072
|
+
switch (choice) {
|
|
2073
|
+
case "Settings":
|
|
2074
|
+
await this.showSettingsSelector();
|
|
2075
|
+
break;
|
|
2076
|
+
case "Model":
|
|
2077
|
+
this.showModelSelector();
|
|
2078
|
+
break;
|
|
2079
|
+
case "Model cycling":
|
|
2080
|
+
await this.showModelsSelector();
|
|
2081
|
+
break;
|
|
2082
|
+
case "Provider login":
|
|
2083
|
+
await this.handleLoginCommand();
|
|
2084
|
+
break;
|
|
2085
|
+
case "Provider logout":
|
|
2086
|
+
this.showOAuthSelector("logout");
|
|
2087
|
+
break;
|
|
2088
|
+
case "Project trust":
|
|
2089
|
+
this.showTrustSelector();
|
|
2090
|
+
break;
|
|
2091
|
+
case "Resources, extensions, and MCP adapters":
|
|
2092
|
+
this.showStatus("Run apex-code config to manage resources, extensions, and MCP adapters.");
|
|
2093
|
+
break;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
1922
2096
|
/**
|
|
1923
2097
|
* Show a confirmation dialog for extensions.
|
|
1924
2098
|
*/
|
|
@@ -2230,6 +2404,8 @@ export class InteractiveMode {
|
|
|
2230
2404
|
const wasBashMode = this.isBashMode;
|
|
2231
2405
|
this.isBashMode = text.trimStart().startsWith("!");
|
|
2232
2406
|
if (wasBashMode !== this.isBashMode) {
|
|
2407
|
+
if (this.isBashMode)
|
|
2408
|
+
this.offerFirstUseHint("bash");
|
|
2233
2409
|
this.updateEditorBorderColor();
|
|
2234
2410
|
}
|
|
2235
2411
|
};
|
|
@@ -2278,15 +2454,31 @@ export class InteractiveMode {
|
|
|
2278
2454
|
// Silently ignore clipboard errors (may not have permission, etc.)
|
|
2279
2455
|
}
|
|
2280
2456
|
}
|
|
2457
|
+
handleStartupSubmit(text) {
|
|
2458
|
+
this.editor.setText(text);
|
|
2459
|
+
this.showStatus("Startup is still in progress");
|
|
2460
|
+
}
|
|
2281
2461
|
setupEditorSubmitHandler() {
|
|
2282
2462
|
this.defaultEditor.onSubmit = async (text) => {
|
|
2283
2463
|
text = text.trim();
|
|
2284
2464
|
if (!text)
|
|
2285
2465
|
return;
|
|
2466
|
+
// A mode chosen in /settings is written asynchronously. Awaiting it here
|
|
2467
|
+
// keeps the store authoritative before any tool call can read it, so a
|
|
2468
|
+
// switch *out* of bypassPermissions can never be raced by the next turn.
|
|
2469
|
+
if (this.pendingPermissionModeWrite) {
|
|
2470
|
+
await this.pendingPermissionModeWrite;
|
|
2471
|
+
this.pendingPermissionModeWrite = undefined;
|
|
2472
|
+
}
|
|
2286
2473
|
// Handle commands
|
|
2474
|
+
if (text === "/config") {
|
|
2475
|
+
this.editor.setText("");
|
|
2476
|
+
await this.showConfigurationIndex();
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2287
2479
|
if (text === "/settings") {
|
|
2288
|
-
this.showSettingsSelector();
|
|
2289
2480
|
this.editor.setText("");
|
|
2481
|
+
await this.showSettingsSelector();
|
|
2290
2482
|
return;
|
|
2291
2483
|
}
|
|
2292
2484
|
if (text === "/scoped-models") {
|
|
@@ -2335,6 +2527,11 @@ export class InteractiveMode {
|
|
|
2335
2527
|
this.editor.setText("");
|
|
2336
2528
|
return;
|
|
2337
2529
|
}
|
|
2530
|
+
if (text === "/resources") {
|
|
2531
|
+
this.showLoadedResources({ force: true });
|
|
2532
|
+
this.editor.setText("");
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2338
2535
|
if (text === "/hotkeys") {
|
|
2339
2536
|
this.handleHotkeysCommand();
|
|
2340
2537
|
this.editor.setText("");
|
|
@@ -2475,7 +2672,7 @@ export class InteractiveMode {
|
|
|
2475
2672
|
this.footer.invalidate();
|
|
2476
2673
|
switch (event.type) {
|
|
2477
2674
|
case "agent_start":
|
|
2478
|
-
this.
|
|
2675
|
+
this.clearPendingTools();
|
|
2479
2676
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2480
2677
|
this.ui.terminal.setProgress(true);
|
|
2481
2678
|
}
|
|
@@ -2527,6 +2724,7 @@ export class InteractiveMode {
|
|
|
2527
2724
|
this.streamingMessage = event.message;
|
|
2528
2725
|
this.chatContainer.addChild(this.streamingComponent);
|
|
2529
2726
|
this.streamingComponent.updateContent(this.streamingMessage, true);
|
|
2727
|
+
this.updateEditorBorderColor();
|
|
2530
2728
|
this.ui.requestRender();
|
|
2531
2729
|
}
|
|
2532
2730
|
break;
|
|
@@ -2544,6 +2742,7 @@ export class InteractiveMode {
|
|
|
2544
2742
|
component.setExpanded(this.toolOutputExpanded);
|
|
2545
2743
|
this.chatContainer.addChild(component);
|
|
2546
2744
|
this.pendingTools.set(content.id, component);
|
|
2745
|
+
this.offerFirstUseHint("tool-expand");
|
|
2547
2746
|
}
|
|
2548
2747
|
else {
|
|
2549
2748
|
const component = this.pendingTools.get(content.id);
|
|
@@ -2581,7 +2780,7 @@ export class InteractiveMode {
|
|
|
2581
2780
|
isError: true,
|
|
2582
2781
|
});
|
|
2583
2782
|
}
|
|
2584
|
-
this.
|
|
2783
|
+
this.clearPendingTools();
|
|
2585
2784
|
}
|
|
2586
2785
|
else {
|
|
2587
2786
|
// Args are now complete - trigger diff computation for edit tools
|
|
@@ -2593,6 +2792,7 @@ export class InteractiveMode {
|
|
|
2593
2792
|
this.streamingComponent = undefined;
|
|
2594
2793
|
this.streamingMessage = undefined;
|
|
2595
2794
|
this.footer.invalidate();
|
|
2795
|
+
this.updateEditorBorderColor();
|
|
2596
2796
|
}
|
|
2597
2797
|
this.ui.requestRender();
|
|
2598
2798
|
break;
|
|
@@ -2641,7 +2841,7 @@ export class InteractiveMode {
|
|
|
2641
2841
|
this.streamingComponent = undefined;
|
|
2642
2842
|
this.streamingMessage = undefined;
|
|
2643
2843
|
}
|
|
2644
|
-
this.
|
|
2844
|
+
this.clearPendingTools();
|
|
2645
2845
|
this.ui.requestRender();
|
|
2646
2846
|
break;
|
|
2647
2847
|
case "agent_settled":
|
|
@@ -2753,6 +2953,19 @@ export class InteractiveMode {
|
|
|
2753
2953
|
: message.content.filter((c) => c.type === "text");
|
|
2754
2954
|
return textBlocks.map((c) => c.text).join("");
|
|
2755
2955
|
}
|
|
2956
|
+
/** Show a managed-tool status update in the chat. */
|
|
2957
|
+
showManagedToolStatus(status) {
|
|
2958
|
+
if (!this.managedToolStatusStarted) {
|
|
2959
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
2960
|
+
this.managedToolStatusStarted = true;
|
|
2961
|
+
}
|
|
2962
|
+
const message = status.type === "warning" ? `Warning: ${status.message}` : status.message;
|
|
2963
|
+
const color = status.type === "warning" ? "warning" : "dim";
|
|
2964
|
+
this.chatContainer.addChild(new Text(theme.fg(color, message), 1, 0));
|
|
2965
|
+
this.lastStatusSpacer = undefined;
|
|
2966
|
+
this.lastStatusText = undefined;
|
|
2967
|
+
this.ui.requestRender();
|
|
2968
|
+
}
|
|
2756
2969
|
/**
|
|
2757
2970
|
* Show a status message in the chat.
|
|
2758
2971
|
*
|
|
@@ -2873,7 +3086,7 @@ export class InteractiveMode {
|
|
|
2873
3086
|
}
|
|
2874
3087
|
}
|
|
2875
3088
|
renderSessionItems(items, options = {}) {
|
|
2876
|
-
this.
|
|
3089
|
+
this.clearPendingTools();
|
|
2877
3090
|
const renderedPendingTools = new Map();
|
|
2878
3091
|
// Cache-miss notices are not persisted; re-derive them from the full entry
|
|
2879
3092
|
// list and re-inject them after the assistant messages that paid for them.
|
|
@@ -3231,6 +3444,7 @@ export class InteractiveMode {
|
|
|
3231
3444
|
this.editor.addToHistory?.(text);
|
|
3232
3445
|
this.editor.setText("");
|
|
3233
3446
|
await this.session.prompt(text, { streamingBehavior: "followUp" });
|
|
3447
|
+
this.offerFirstUseHint("queue");
|
|
3234
3448
|
this.updatePendingMessagesDisplay();
|
|
3235
3449
|
this.ui.requestRender();
|
|
3236
3450
|
}
|
|
@@ -3250,6 +3464,7 @@ export class InteractiveMode {
|
|
|
3250
3464
|
}
|
|
3251
3465
|
}
|
|
3252
3466
|
updateEditorBorderColor() {
|
|
3467
|
+
this.defaultEditor.setModeLabel(this.isBashMode ? "bash" : this.session.isStreaming ? "busy" : undefined);
|
|
3253
3468
|
if (this.isBashMode) {
|
|
3254
3469
|
this.editor.borderColor = theme.getBashModeBorderColor();
|
|
3255
3470
|
}
|
|
@@ -3310,6 +3525,7 @@ export class InteractiveMode {
|
|
|
3310
3525
|
this.showStatus(`Tool output: ${expanded ? "expanded" : "collapsed"}`);
|
|
3311
3526
|
}
|
|
3312
3527
|
toggleThinkingBlockVisibility() {
|
|
3528
|
+
this.offerFirstUseHint("thinking");
|
|
3313
3529
|
this.hideThinkingBlock = !this.hideThinkingBlock;
|
|
3314
3530
|
this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
|
|
3315
3531
|
// Rebuild chat from session messages
|
|
@@ -3593,7 +3809,53 @@ export class InteractiveMode {
|
|
|
3593
3809
|
this.ui.setFocus(created.focus);
|
|
3594
3810
|
this.ui.requestRender();
|
|
3595
3811
|
}
|
|
3596
|
-
|
|
3812
|
+
/**
|
|
3813
|
+
* Resyncs the footer from the store. Called on every session swap, not just at
|
|
3814
|
+
* startup: `applyRuntimeSettings` also repoints the cwd, and cwd selects which
|
|
3815
|
+
* project-scope permission file applies, so the mode genuinely can differ
|
|
3816
|
+
* between two sessions in one process. A footer left showing the old value
|
|
3817
|
+
* would under-report bypassPermissions, which is the one direction that matters.
|
|
3818
|
+
*/
|
|
3819
|
+
async refreshFooterPermissionMode() {
|
|
3820
|
+
const resolution = await this.session.getPermissionMode();
|
|
3821
|
+
this.footer.setPermissionMode(resolution?.mode ?? "default");
|
|
3822
|
+
if (this.isInitialized) {
|
|
3823
|
+
this.ui.requestRender();
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
/**
|
|
3827
|
+
* Writes the mode to user scope, then reports what is actually in force. The
|
|
3828
|
+
* write can be outranked by `--permission-mode` or a project file, and a
|
|
3829
|
+
* settings row that silently does nothing is worse than no row at all.
|
|
3830
|
+
*/
|
|
3831
|
+
async applyPermissionMode(mode) {
|
|
3832
|
+
let resolution;
|
|
3833
|
+
try {
|
|
3834
|
+
const write = this.session.setPermissionMode(mode);
|
|
3835
|
+
this.pendingPermissionModeWrite = write.then(() => { }, () => { });
|
|
3836
|
+
resolution = await write;
|
|
3837
|
+
}
|
|
3838
|
+
catch (error) {
|
|
3839
|
+
// Never swallow this. A permission mode the user believes they set, and
|
|
3840
|
+
// did not, is the one outcome worse than the prompt they were escaping.
|
|
3841
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3842
|
+
this.chatContainer.addChild(new Text(theme.fg("error", `Permission mode could not be saved: ${detail}`), 1, 0));
|
|
3843
|
+
this.ui.requestRender();
|
|
3844
|
+
return;
|
|
3845
|
+
}
|
|
3846
|
+
if (!resolution)
|
|
3847
|
+
return;
|
|
3848
|
+
const outranked = resolution.mode !== mode;
|
|
3849
|
+
const source = PERMISSION_MODE_OVERRIDE_HINTS[resolution.origin] ?? resolution.origin;
|
|
3850
|
+
const line = outranked
|
|
3851
|
+
? `Permission mode saved as ${mode}, but ${source} sets ${resolution.mode} for this session.`
|
|
3852
|
+
: `Permission mode set to ${mode}.`;
|
|
3853
|
+
this.chatContainer.addChild(new Text(theme.fg(outranked ? "warning" : "muted", line), 1, 0));
|
|
3854
|
+
this.footer.setPermissionMode(resolution.mode);
|
|
3855
|
+
this.ui.requestRender();
|
|
3856
|
+
}
|
|
3857
|
+
async showSettingsSelector() {
|
|
3858
|
+
const permissionMode = await this.session.getPermissionMode();
|
|
3597
3859
|
this.showSelector((done) => {
|
|
3598
3860
|
let selector;
|
|
3599
3861
|
selector = new SettingsSelectorComponent({
|
|
@@ -3609,7 +3871,7 @@ export class InteractiveMode {
|
|
|
3609
3871
|
httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
|
|
3610
3872
|
thinkingLevel: this.session.thinkingLevel,
|
|
3611
3873
|
availableThinkingLevels: this.session.getAvailableThinkingLevels(),
|
|
3612
|
-
currentTheme: this.
|
|
3874
|
+
currentTheme: this.themeController.getThemeSelection() || "dark",
|
|
3613
3875
|
terminalTheme: this.themeController.getTerminalTheme(),
|
|
3614
3876
|
availableThemes: getAvailableThemes(),
|
|
3615
3877
|
hideThinkingBlock: this.hideThinkingBlock,
|
|
@@ -3621,6 +3883,7 @@ export class InteractiveMode {
|
|
|
3621
3883
|
showHardwareCursor: this.settingsManager.getShowHardwareCursor(),
|
|
3622
3884
|
showCacheMissNotices: this.settingsManager.getShowCacheMissNotices(),
|
|
3623
3885
|
defaultProjectTrust: this.settingsManager.getDefaultProjectTrust(),
|
|
3886
|
+
permissionMode,
|
|
3624
3887
|
editorPaddingX: this.settingsManager.getEditorPaddingX(),
|
|
3625
3888
|
outputPad: this.settingsManager.getOutputPad(),
|
|
3626
3889
|
autocompleteMaxVisible: this.settingsManager.getAutocompleteMaxVisible(),
|
|
@@ -3628,6 +3891,7 @@ export class InteractiveMode {
|
|
|
3628
3891
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3629
3892
|
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3630
3893
|
tuiMode: this.ui.mode,
|
|
3894
|
+
fullscreenExitOutput: this.settingsManager.getFullscreenExitOutput(),
|
|
3631
3895
|
fullscreenScrollbar: this.settingsManager.getFullscreenScrollbar(),
|
|
3632
3896
|
warnings: this.settingsManager.getWarnings(),
|
|
3633
3897
|
}, {
|
|
@@ -3683,7 +3947,7 @@ export class InteractiveMode {
|
|
|
3683
3947
|
},
|
|
3684
3948
|
onThemeChange: (themeSetting) => {
|
|
3685
3949
|
this.settingsManager.setTheme(themeSetting);
|
|
3686
|
-
void this.themeController.
|
|
3950
|
+
void this.themeController.setThemeSetting(themeSetting);
|
|
3687
3951
|
},
|
|
3688
3952
|
onThemePreview: (themeName) => this.themeController.preview(themeName),
|
|
3689
3953
|
onHideThinkingBlockChange: (hidden) => {
|
|
@@ -3718,6 +3982,9 @@ export class InteractiveMode {
|
|
|
3718
3982
|
onDefaultProjectTrustChange: (defaultProjectTrust) => {
|
|
3719
3983
|
this.settingsManager.setDefaultProjectTrust(defaultProjectTrust);
|
|
3720
3984
|
},
|
|
3985
|
+
onPermissionModeChange: (mode) => {
|
|
3986
|
+
void this.applyPermissionMode(mode);
|
|
3987
|
+
},
|
|
3721
3988
|
onDoubleEscapeActionChange: (action) => {
|
|
3722
3989
|
this.settingsManager.setDoubleEscapeAction(action);
|
|
3723
3990
|
},
|
|
@@ -3782,6 +4049,9 @@ export class InteractiveMode {
|
|
|
3782
4049
|
this.statusContainer.clear();
|
|
3783
4050
|
this.showStatus(`TUI mode: ${mode}`);
|
|
3784
4051
|
},
|
|
4052
|
+
onFullscreenExitOutputChange: (output) => {
|
|
4053
|
+
this.settingsManager.setFullscreenExitOutput(output);
|
|
4054
|
+
},
|
|
3785
4055
|
onFullscreenScrollbarChange: (mode) => {
|
|
3786
4056
|
this.settingsManager.setFullscreenScrollbar(mode);
|
|
3787
4057
|
this.applyFullscreenScrollbarSetting();
|
|
@@ -3834,7 +4104,7 @@ export class InteractiveMode {
|
|
|
3834
4104
|
controller.abort();
|
|
3835
4105
|
}, 15_000);
|
|
3836
4106
|
try {
|
|
3837
|
-
const result = await this.session.modelRuntime
|
|
4107
|
+
const result = await refreshModelCatalogs(this.session.modelRuntime, controller.signal);
|
|
3838
4108
|
if (result.aborted && timedOut) {
|
|
3839
4109
|
this.showWarning("Model refresh timed out; searching cached models.");
|
|
3840
4110
|
}
|
|
@@ -3922,7 +4192,7 @@ export class InteractiveMode {
|
|
|
3922
4192
|
onSelect: (selection) => {
|
|
3923
4193
|
trustStore.setMany(selection.updates);
|
|
3924
4194
|
done();
|
|
3925
|
-
this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart
|
|
4195
|
+
this.showStatus(`Saved trust decision: ${selection.trusted ? "trusted" : "untrusted"}. Restart ${APP_NAME} for this to take effect.`);
|
|
3926
4196
|
},
|
|
3927
4197
|
onCancel: () => {
|
|
3928
4198
|
done();
|
|
@@ -4022,8 +4292,7 @@ export class InteractiveMode {
|
|
|
4022
4292
|
this.ui.requestRender();
|
|
4023
4293
|
},
|
|
4024
4294
|
});
|
|
4025
|
-
void this.session.modelRuntime
|
|
4026
|
-
.refresh({ signal: controller.signal })
|
|
4295
|
+
void refreshModelCatalogs(this.session.modelRuntime, controller.signal)
|
|
4027
4296
|
.then((result) => {
|
|
4028
4297
|
if (disposed)
|
|
4029
4298
|
return;
|
|
@@ -4814,7 +5083,9 @@ export class InteractiveMode {
|
|
|
4814
5083
|
this.showStatus(`Session exported to: ${filePath}`);
|
|
4815
5084
|
}
|
|
4816
5085
|
else {
|
|
4817
|
-
const filePath = await this.session.exportToHtml(outputPath
|
|
5086
|
+
const filePath = await this.session.exportToHtml(outputPath, {
|
|
5087
|
+
themeName: theme.name,
|
|
5088
|
+
});
|
|
4818
5089
|
this.showStatus(`Session exported to: ${filePath}`);
|
|
4819
5090
|
}
|
|
4820
5091
|
}
|
|
@@ -5356,7 +5627,7 @@ export class InteractiveMode {
|
|
|
5356
5627
|
// Ignore, will be emitted as an event
|
|
5357
5628
|
}
|
|
5358
5629
|
}
|
|
5359
|
-
stop() {
|
|
5630
|
+
stop(fullscreenExitOutput = this.settingsManager.getFullscreenExitOutput()) {
|
|
5360
5631
|
this.disposeActiveSelector();
|
|
5361
5632
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
5362
5633
|
this.ui.terminal.setProgress(false);
|
|
@@ -5370,7 +5641,7 @@ export class InteractiveMode {
|
|
|
5370
5641
|
this.unsubscribe();
|
|
5371
5642
|
}
|
|
5372
5643
|
if (this.isInitialized) {
|
|
5373
|
-
this.stopInteractiveTui();
|
|
5644
|
+
this.stopInteractiveTui(fullscreenExitOutput);
|
|
5374
5645
|
this.isInitialized = false;
|
|
5375
5646
|
}
|
|
5376
5647
|
this.unregisterSignalHandlers();
|