@mariozechner/pi-coding-agent 0.49.2 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +126 -1
- package/README.md +310 -1229
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +57 -22
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +14 -0
- package/dist/cli/config-selector.d.ts.map +1 -0
- package/dist/cli/config-selector.js +31 -0
- package/dist/cli/config-selector.js.map +1 -0
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +53 -34
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +262 -67
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +8 -18
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +39 -55
- 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 +2 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/diagnostics.d.ts +15 -0
- package/dist/core/diagnostics.d.ts.map +1 -0
- package/dist/core/diagnostics.js +2 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/export-html/template.css +9 -0
- package/dist/core/export-html/template.js +6 -4
- 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 +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +10 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +9 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +39 -12
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +112 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +9 -2
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +13 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/model-registry.d.ts +42 -2
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +154 -44
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +3 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +129 -0
- package/dist/core/package-manager.d.ts.map +1 -0
- package/dist/core/package-manager.js +1148 -0
- package/dist/core/package-manager.js.map +1 -0
- package/dist/core/prompt-templates.d.ts +6 -0
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +114 -54
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resource-loader.d.ts +160 -0
- package/dist/core/resource-loader.d.ts.map +1 -0
- package/dist/core/resource-loader.js +604 -0
- package/dist/core/resource-loader.js.map +1 -0
- package/dist/core/sdk.d.ts +14 -105
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +52 -304
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +45 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +39 -16
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +107 -25
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +18 -10
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +126 -93
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts +3 -27
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +16 -103
- 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 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -4
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +12 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +209 -97
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -2
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +5 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +5 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +29 -9
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +3 -2
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +4 -2
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +3 -2
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +4 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +71 -0
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/config-selector.js +468 -0
- package/dist/modes/interactive/components/config-selector.js.map +1 -0
- package/dist/modes/interactive/components/custom-message.d.ts +3 -2
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +4 -2
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +9 -0
- 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/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +3 -4
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +18 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +195 -87
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -5
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +2 -2
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +2 -2
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +47 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +566 -211
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +8 -1
- package/dist/modes/interactive/theme/theme.d.ts +8 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +79 -28
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +25 -89
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +32 -92
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/git.d.ts +2 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +6 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/shell.d.ts +1 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +16 -2
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/sleep.d.ts +5 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +17 -0
- package/dist/utils/sleep.js.map +1 -0
- package/docs/compaction.md +23 -21
- package/docs/custom-provider.md +538 -0
- package/docs/development.md +69 -0
- package/docs/extensions.md +180 -118
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/json.md +79 -0
- package/docs/keybindings.md +162 -0
- package/docs/models.md +193 -0
- package/docs/packages.md +163 -0
- package/docs/prompt-templates.md +67 -0
- package/docs/providers.md +147 -0
- package/docs/sdk.md +111 -178
- package/docs/session.md +167 -16
- package/docs/settings.md +216 -0
- package/docs/shell-aliases.md +13 -0
- package/docs/skills.md +111 -202
- package/docs/terminal-setup.md +65 -0
- package/docs/themes.md +295 -0
- package/docs/tui.md +36 -5
- package/docs/windows.md +17 -0
- package/examples/README.md +1 -0
- package/examples/extensions/README.md +24 -2
- package/examples/extensions/antigravity-image-gen.ts +413 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
- package/examples/extensions/doom-overlay/doom/build.sh +1 -1
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/message-renderer.ts +59 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/space-invaders.ts +560 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/02-custom-model.ts +3 -3
- package/examples/sdk/03-custom-prompt.ts +20 -9
- package/examples/sdk/04-skills.ts +26 -27
- package/examples/sdk/06-extensions.ts +15 -6
- package/examples/sdk/07-context-files.ts +22 -18
- package/examples/sdk/08-prompt-templates.ts +19 -14
- package/examples/sdk/09-api-keys-and-oauth.ts +5 -12
- package/examples/sdk/10-settings.ts +3 -3
- package/examples/sdk/12-full-control.ts +16 -7
- package/examples/sdk/README.md +24 -30
- package/package.json +4 -4
- package/docs/theme.md +0 -617
- package/examples/extensions/chalk-logger.ts +0 -26
|
@@ -9,13 +9,13 @@ import * as path from "node:path";
|
|
|
9
9
|
import { getOAuthProviders, } from "@mariozechner/pi-ai";
|
|
10
10
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
|
|
11
11
|
import { spawn, spawnSync } from "child_process";
|
|
12
|
-
import { APP_NAME, getAuthPath, getDebugLogPath, isBunBinary, isBunRuntime, VERSION } from "../../config.js";
|
|
12
|
+
import { APP_NAME, getAuthPath, getDebugLogPath, getShareViewerUrl, isBunBinary, isBunRuntime, VERSION, } from "../../config.js";
|
|
13
|
+
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
13
14
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
14
15
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
15
16
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
16
17
|
import { resolveModelScope } from "../../core/model-resolver.js";
|
|
17
18
|
import { SessionManager } from "../../core/session-manager.js";
|
|
18
|
-
import { loadProjectContextFiles } from "../../core/system-prompt.js";
|
|
19
19
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
20
20
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
21
21
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
@@ -40,11 +40,12 @@ import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
|
40
40
|
import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
|
|
41
41
|
import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
42
42
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
43
|
+
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
43
44
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
44
45
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
45
46
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
46
47
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
47
|
-
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setTheme, setThemeInstance, Theme, theme, } from "./theme/theme.js";
|
|
48
|
+
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, Theme, theme, } from "./theme/theme.js";
|
|
48
49
|
function isExpandable(obj) {
|
|
49
50
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
50
51
|
}
|
|
@@ -65,9 +66,9 @@ export class InteractiveMode {
|
|
|
65
66
|
keybindings;
|
|
66
67
|
version;
|
|
67
68
|
isInitialized = false;
|
|
68
|
-
hasRenderedInitialMessages = false;
|
|
69
69
|
onInputCallback;
|
|
70
70
|
loadingAnimation = undefined;
|
|
71
|
+
pendingWorkingMessage = undefined;
|
|
71
72
|
defaultWorkingMessage = "Working...";
|
|
72
73
|
lastSigintTime = 0;
|
|
73
74
|
lastEscapeTime = 0;
|
|
@@ -150,7 +151,8 @@ export class InteractiveMode {
|
|
|
150
151
|
this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
151
152
|
// Load hide thinking block setting
|
|
152
153
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
153
|
-
//
|
|
154
|
+
// Register themes from resource loader and initialize
|
|
155
|
+
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
154
156
|
initTheme(this.settingsManager.getTheme(), true);
|
|
155
157
|
}
|
|
156
158
|
setupAutocomplete(fdPath) {
|
|
@@ -199,6 +201,7 @@ export class InteractiveMode {
|
|
|
199
201
|
{ name: "new", description: "Start a new session" },
|
|
200
202
|
{ name: "compact", description: "Manually compact the session context" },
|
|
201
203
|
{ name: "resume", description: "Resume a different session" },
|
|
204
|
+
{ name: "reload", description: "Reload extensions, skills, prompts, and themes" },
|
|
202
205
|
];
|
|
203
206
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
204
207
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
@@ -215,7 +218,7 @@ export class InteractiveMode {
|
|
|
215
218
|
this.skillCommands.clear();
|
|
216
219
|
const skillCommandList = [];
|
|
217
220
|
if (this.settingsManager.getEnableSkillCommands()) {
|
|
218
|
-
for (const skill of this.session.skills) {
|
|
221
|
+
for (const skill of this.session.resourceLoader.getSkills().skills) {
|
|
219
222
|
const commandName = `skill:${skill.name}`;
|
|
220
223
|
this.skillCommands.set(commandName, skill.filePath);
|
|
221
224
|
skillCommandList.push({ name: commandName, description: skill.description });
|
|
@@ -237,7 +240,7 @@ export class InteractiveMode {
|
|
|
237
240
|
this.fdPath = await ensureTool("fd");
|
|
238
241
|
this.setupAutocomplete(this.fdPath);
|
|
239
242
|
// Add header with keybindings from config (unless silenced)
|
|
240
|
-
if (!this.settingsManager.getQuietStartup()) {
|
|
243
|
+
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
|
|
241
244
|
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
|
|
242
245
|
// Build startup instructions using keybinding hint helpers
|
|
243
246
|
const kb = this.keybindings;
|
|
@@ -249,11 +252,11 @@ export class InteractiveMode {
|
|
|
249
252
|
hint("exit", "to exit (empty)"),
|
|
250
253
|
hint("suspend", "to suspend"),
|
|
251
254
|
keyHint("deleteToLineEnd", "to delete to end"),
|
|
252
|
-
hint("cycleThinkingLevel", "to cycle thinking"),
|
|
255
|
+
hint("cycleThinkingLevel", "to cycle thinking level"),
|
|
253
256
|
rawKeyHint(`${appKey(kb, "cycleModelForward")}/${appKey(kb, "cycleModelBackward")}`, "to cycle models"),
|
|
254
257
|
hint("selectModel", "to select model"),
|
|
255
258
|
hint("expandTools", "to expand tools"),
|
|
256
|
-
hint("toggleThinking", "to
|
|
259
|
+
hint("toggleThinking", "to expand thinking"),
|
|
257
260
|
hint("externalEditor", "for external editor"),
|
|
258
261
|
rawKeyHint("/", "for commands"),
|
|
259
262
|
rawKeyHint("!", "to run bash"),
|
|
@@ -280,7 +283,7 @@ export class InteractiveMode {
|
|
|
280
283
|
else {
|
|
281
284
|
this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
282
285
|
this.ui.addChild(new Spacer(1));
|
|
283
|
-
this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0,
|
|
286
|
+
this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, this.getMarkdownThemeWithSettings()));
|
|
284
287
|
this.ui.addChild(new Spacer(1));
|
|
285
288
|
}
|
|
286
289
|
this.ui.addChild(new DynamicBorder());
|
|
@@ -313,8 +316,7 @@ export class InteractiveMode {
|
|
|
313
316
|
this.ui.start();
|
|
314
317
|
this.isInitialized = true;
|
|
315
318
|
// Set terminal title
|
|
316
|
-
|
|
317
|
-
this.ui.terminal.setTitle(`pi - ${cwdBasename}`);
|
|
319
|
+
this.updateTerminalTitle();
|
|
318
320
|
// Initialize extensions with TUI-based UI context
|
|
319
321
|
await this.initExtensions();
|
|
320
322
|
// Subscribe to agent events
|
|
@@ -329,6 +331,21 @@ export class InteractiveMode {
|
|
|
329
331
|
this.footerDataProvider.onBranchChange(() => {
|
|
330
332
|
this.ui.requestRender();
|
|
331
333
|
});
|
|
334
|
+
// Initialize available provider count for footer display
|
|
335
|
+
await this.updateAvailableProviderCount();
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Update terminal title with session name and cwd.
|
|
339
|
+
*/
|
|
340
|
+
updateTerminalTitle() {
|
|
341
|
+
const cwdBasename = path.basename(process.cwd());
|
|
342
|
+
const sessionName = this.sessionManager.getSessionName();
|
|
343
|
+
if (sessionName) {
|
|
344
|
+
this.ui.terminal.setTitle(`π - ${sessionName} - ${cwdBasename}`);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
this.ui.terminal.setTitle(`π - ${cwdBasename}`);
|
|
348
|
+
}
|
|
332
349
|
}
|
|
333
350
|
/**
|
|
334
351
|
* Run the interactive mode. This is the main entry point.
|
|
@@ -435,201 +452,369 @@ export class InteractiveMode {
|
|
|
435
452
|
}
|
|
436
453
|
return undefined;
|
|
437
454
|
}
|
|
455
|
+
getMarkdownThemeWithSettings() {
|
|
456
|
+
return {
|
|
457
|
+
...getMarkdownTheme(),
|
|
458
|
+
codeBlockIndent: this.settingsManager.getCodeBlockIndent(),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
438
461
|
// =========================================================================
|
|
439
462
|
// Extension System
|
|
440
463
|
// =========================================================================
|
|
464
|
+
formatDisplayPath(p) {
|
|
465
|
+
const home = os.homedir();
|
|
466
|
+
let result = p;
|
|
467
|
+
// Replace home directory with ~
|
|
468
|
+
if (result.startsWith(home)) {
|
|
469
|
+
result = `~${result.slice(home.length)}`;
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
441
473
|
/**
|
|
442
|
-
*
|
|
474
|
+
* Get a short path relative to the package root for display.
|
|
443
475
|
*/
|
|
444
|
-
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
476
|
+
getShortPath(fullPath, source) {
|
|
477
|
+
// For npm packages, show path relative to node_modules/pkg/
|
|
478
|
+
const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
|
|
479
|
+
if (npmMatch && source.startsWith("npm:")) {
|
|
480
|
+
return npmMatch[2];
|
|
481
|
+
}
|
|
482
|
+
// For git packages, show path relative to repo root
|
|
483
|
+
const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
|
|
484
|
+
if (gitMatch && source.startsWith("git:")) {
|
|
485
|
+
return gitMatch[1];
|
|
486
|
+
}
|
|
487
|
+
// For local/auto, just use formatDisplayPath
|
|
488
|
+
return this.formatDisplayPath(fullPath);
|
|
489
|
+
}
|
|
490
|
+
getDisplaySourceInfo(source, scope) {
|
|
491
|
+
if (source === "local") {
|
|
492
|
+
if (scope === "user") {
|
|
493
|
+
return { label: "user", color: "muted" };
|
|
494
|
+
}
|
|
495
|
+
if (scope === "project") {
|
|
496
|
+
return { label: "project", color: "muted" };
|
|
497
|
+
}
|
|
498
|
+
if (scope === "temporary") {
|
|
499
|
+
return { label: "path", scopeLabel: "temp", color: "muted" };
|
|
500
|
+
}
|
|
501
|
+
return { label: "path", color: "muted" };
|
|
502
|
+
}
|
|
503
|
+
if (source === "cli") {
|
|
504
|
+
return { label: "path", scopeLabel: scope === "temporary" ? "temp" : undefined, color: "muted" };
|
|
505
|
+
}
|
|
506
|
+
const scopeLabel = scope === "user" ? "user" : scope === "project" ? "project" : scope === "temporary" ? "temp" : undefined;
|
|
507
|
+
return { label: source, scopeLabel, color: "accent" };
|
|
508
|
+
}
|
|
509
|
+
getScopeGroup(source, scope) {
|
|
510
|
+
if (source === "cli" || scope === "temporary")
|
|
511
|
+
return "path";
|
|
512
|
+
if (scope === "user")
|
|
513
|
+
return "user";
|
|
514
|
+
if (scope === "project")
|
|
515
|
+
return "project";
|
|
516
|
+
return "path";
|
|
517
|
+
}
|
|
518
|
+
isPackageSource(source) {
|
|
519
|
+
return source.startsWith("npm:") || source.startsWith("git:");
|
|
520
|
+
}
|
|
521
|
+
buildScopeGroups(paths, metadata) {
|
|
522
|
+
const groups = {
|
|
523
|
+
user: { scope: "user", paths: [], packages: new Map() },
|
|
524
|
+
project: { scope: "project", paths: [], packages: new Map() },
|
|
525
|
+
path: { scope: "path", paths: [], packages: new Map() },
|
|
526
|
+
};
|
|
527
|
+
for (const p of paths) {
|
|
528
|
+
const meta = this.findMetadata(p, metadata);
|
|
529
|
+
const source = meta?.source ?? "local";
|
|
530
|
+
const scope = meta?.scope ?? "project";
|
|
531
|
+
const groupKey = this.getScopeGroup(source, scope);
|
|
532
|
+
const group = groups[groupKey];
|
|
533
|
+
if (this.isPackageSource(source)) {
|
|
534
|
+
const list = group.packages.get(source) ?? [];
|
|
535
|
+
list.push(p);
|
|
536
|
+
group.packages.set(source, list);
|
|
453
537
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (skills.length > 0) {
|
|
457
|
-
const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
|
|
458
|
-
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
|
|
459
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
538
|
+
else {
|
|
539
|
+
group.paths.push(p);
|
|
460
540
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
541
|
+
}
|
|
542
|
+
return [groups.user, groups.project, groups.path].filter((group) => group.paths.length > 0 || group.packages.size > 0);
|
|
543
|
+
}
|
|
544
|
+
formatScopeGroups(groups, options) {
|
|
545
|
+
const lines = [];
|
|
546
|
+
for (const group of groups) {
|
|
547
|
+
lines.push(` ${theme.fg("accent", group.scope)}`);
|
|
548
|
+
const sortedPaths = [...group.paths].sort((a, b) => a.localeCompare(b));
|
|
549
|
+
for (const p of sortedPaths) {
|
|
550
|
+
lines.push(theme.fg("dim", ` ${options.formatPath(p)}`));
|
|
469
551
|
}
|
|
470
|
-
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
552
|
+
const sortedPackages = Array.from(group.packages.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
553
|
+
for (const [source, paths] of sortedPackages) {
|
|
554
|
+
lines.push(` ${theme.fg("mdLink", source)}`);
|
|
555
|
+
const sortedPackagePaths = [...paths].sort((a, b) => a.localeCompare(b));
|
|
556
|
+
for (const p of sortedPackagePaths) {
|
|
557
|
+
lines.push(theme.fg("dim", ` ${options.formatPackagePath(p, source)}`));
|
|
558
|
+
}
|
|
476
559
|
}
|
|
477
560
|
}
|
|
561
|
+
return lines.join("\n");
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Find metadata for a path, checking parent directories if exact match fails.
|
|
565
|
+
* Package manager stores metadata for directories, but we display file paths.
|
|
566
|
+
*/
|
|
567
|
+
findMetadata(p, metadata) {
|
|
568
|
+
// Try exact match first
|
|
569
|
+
const exact = metadata.get(p);
|
|
570
|
+
if (exact)
|
|
571
|
+
return exact;
|
|
572
|
+
// Try parent directories (package manager stores directory paths)
|
|
573
|
+
let current = p;
|
|
574
|
+
while (current.includes("/")) {
|
|
575
|
+
current = current.substring(0, current.lastIndexOf("/"));
|
|
576
|
+
const parent = metadata.get(current);
|
|
577
|
+
if (parent)
|
|
578
|
+
return parent;
|
|
579
|
+
}
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Format a path with its source/scope info from metadata.
|
|
584
|
+
*/
|
|
585
|
+
formatPathWithSource(p, metadata) {
|
|
586
|
+
const meta = this.findMetadata(p, metadata);
|
|
587
|
+
if (meta) {
|
|
588
|
+
const shortPath = this.getShortPath(p, meta.source);
|
|
589
|
+
const { label, scopeLabel } = this.getDisplaySourceInfo(meta.source, meta.scope);
|
|
590
|
+
const labelText = scopeLabel ? `${label} (${scopeLabel})` : label;
|
|
591
|
+
return `${labelText} ${shortPath}`;
|
|
592
|
+
}
|
|
593
|
+
return this.formatDisplayPath(p);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Format resource diagnostics with nice collision display using metadata.
|
|
597
|
+
*/
|
|
598
|
+
formatDiagnostics(diagnostics, metadata) {
|
|
599
|
+
const lines = [];
|
|
600
|
+
// Group collision diagnostics by name
|
|
601
|
+
const collisions = new Map();
|
|
602
|
+
const otherDiagnostics = [];
|
|
603
|
+
for (const d of diagnostics) {
|
|
604
|
+
if (d.type === "collision" && d.collision) {
|
|
605
|
+
const list = collisions.get(d.collision.name) ?? [];
|
|
606
|
+
list.push(d);
|
|
607
|
+
collisions.set(d.collision.name, list);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
otherDiagnostics.push(d);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
// Format collision diagnostics grouped by name
|
|
614
|
+
for (const [name, collisionList] of collisions) {
|
|
615
|
+
const first = collisionList[0]?.collision;
|
|
616
|
+
if (!first)
|
|
617
|
+
continue;
|
|
618
|
+
lines.push(theme.fg("warning", ` "${name}" collision:`));
|
|
619
|
+
// Show winner
|
|
620
|
+
lines.push(theme.fg("dim", ` ${theme.fg("success", "✓")} ${this.formatPathWithSource(first.winnerPath, metadata)}`));
|
|
621
|
+
// Show all losers
|
|
622
|
+
for (const d of collisionList) {
|
|
623
|
+
if (d.collision) {
|
|
624
|
+
lines.push(theme.fg("dim", ` ${theme.fg("warning", "✗")} ${this.formatPathWithSource(d.collision.loserPath, metadata)} (skipped)`));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
// Format other diagnostics (skill name collisions, parse errors, etc.)
|
|
629
|
+
for (const d of otherDiagnostics) {
|
|
630
|
+
if (d.path) {
|
|
631
|
+
// Use metadata-aware formatting for paths
|
|
632
|
+
const sourceInfo = this.formatPathWithSource(d.path, metadata);
|
|
633
|
+
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${sourceInfo}`));
|
|
634
|
+
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
lines.push(theme.fg(d.type === "error" ? "error" : "warning", ` ${d.message}`));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return lines.join("\n");
|
|
641
|
+
}
|
|
642
|
+
showLoadedResources(options) {
|
|
643
|
+
const shouldShow = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
|
|
644
|
+
if (!shouldShow) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const metadata = this.session.resourceLoader.getPathMetadata();
|
|
648
|
+
const sectionHeader = (name, color = "mdHeading") => theme.fg(color, `[${name}]`);
|
|
649
|
+
const contextFiles = this.session.resourceLoader.getAgentsFiles().agentsFiles;
|
|
650
|
+
if (contextFiles.length > 0) {
|
|
651
|
+
const contextList = contextFiles.map((f) => theme.fg("dim", ` ${this.formatDisplayPath(f.path)}`)).join("\n");
|
|
652
|
+
this.chatContainer.addChild(new Text(`${sectionHeader("Context")}\n${contextList}`, 0, 0));
|
|
653
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
654
|
+
}
|
|
655
|
+
const skills = this.session.resourceLoader.getSkills().skills;
|
|
656
|
+
if (skills.length > 0) {
|
|
657
|
+
const skillPaths = skills.map((s) => s.filePath);
|
|
658
|
+
const groups = this.buildScopeGroups(skillPaths, metadata);
|
|
659
|
+
const skillList = this.formatScopeGroups(groups, {
|
|
660
|
+
formatPath: (p) => this.formatDisplayPath(p),
|
|
661
|
+
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
|
662
|
+
});
|
|
663
|
+
this.chatContainer.addChild(new Text(`${sectionHeader("Skills")}\n${skillList}`, 0, 0));
|
|
664
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
665
|
+
}
|
|
666
|
+
const skillDiagnostics = this.session.resourceLoader.getSkills().diagnostics;
|
|
667
|
+
if (skillDiagnostics.length > 0) {
|
|
668
|
+
const warningLines = this.formatDiagnostics(skillDiagnostics, metadata);
|
|
669
|
+
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Skill conflicts]")}\n${warningLines}`, 0, 0));
|
|
670
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
671
|
+
}
|
|
672
|
+
const templates = this.session.promptTemplates;
|
|
673
|
+
if (templates.length > 0) {
|
|
674
|
+
const templatePaths = templates.map((t) => t.filePath);
|
|
675
|
+
const groups = this.buildScopeGroups(templatePaths, metadata);
|
|
676
|
+
const templateByPath = new Map(templates.map((t) => [t.filePath, t]));
|
|
677
|
+
const templateList = this.formatScopeGroups(groups, {
|
|
678
|
+
formatPath: (p) => {
|
|
679
|
+
const template = templateByPath.get(p);
|
|
680
|
+
return template ? `/${template.name}` : this.formatDisplayPath(p);
|
|
681
|
+
},
|
|
682
|
+
formatPackagePath: (p) => {
|
|
683
|
+
const template = templateByPath.get(p);
|
|
684
|
+
return template ? `/${template.name}` : this.formatDisplayPath(p);
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
this.chatContainer.addChild(new Text(`${sectionHeader("Prompts")}\n${templateList}`, 0, 0));
|
|
688
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
689
|
+
}
|
|
690
|
+
const promptDiagnostics = this.session.resourceLoader.getPrompts().diagnostics;
|
|
691
|
+
if (promptDiagnostics.length > 0) {
|
|
692
|
+
const warningLines = this.formatDiagnostics(promptDiagnostics, metadata);
|
|
693
|
+
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Prompt conflicts]")}\n${warningLines}`, 0, 0));
|
|
694
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
695
|
+
}
|
|
696
|
+
const extensionPaths = options?.extensionPaths ?? [];
|
|
697
|
+
if (extensionPaths.length > 0) {
|
|
698
|
+
const groups = this.buildScopeGroups(extensionPaths, metadata);
|
|
699
|
+
const extList = this.formatScopeGroups(groups, {
|
|
700
|
+
formatPath: (p) => this.formatDisplayPath(p),
|
|
701
|
+
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
|
702
|
+
});
|
|
703
|
+
this.chatContainer.addChild(new Text(`${sectionHeader("Extensions", "mdHeading")}\n${extList}`, 0, 0));
|
|
704
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
705
|
+
}
|
|
706
|
+
const extensionDiagnostics = [];
|
|
707
|
+
const extensionErrors = this.session.resourceLoader.getExtensions().errors;
|
|
708
|
+
if (extensionErrors.length > 0) {
|
|
709
|
+
for (const error of extensionErrors) {
|
|
710
|
+
extensionDiagnostics.push({ type: "error", message: error.error, path: error.path });
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const shortcutDiagnostics = this.session.extensionRunner?.getShortcutDiagnostics() ?? [];
|
|
714
|
+
extensionDiagnostics.push(...shortcutDiagnostics);
|
|
715
|
+
if (extensionDiagnostics.length > 0) {
|
|
716
|
+
const warningLines = this.formatDiagnostics(extensionDiagnostics, metadata);
|
|
717
|
+
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0));
|
|
718
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
719
|
+
}
|
|
720
|
+
// Show loaded themes (excluding built-in)
|
|
721
|
+
const loadedThemes = this.session.resourceLoader.getThemes().themes;
|
|
722
|
+
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
|
723
|
+
if (customThemes.length > 0) {
|
|
724
|
+
const themePaths = customThemes.map((t) => t.sourcePath);
|
|
725
|
+
const groups = this.buildScopeGroups(themePaths, metadata);
|
|
726
|
+
const themeList = this.formatScopeGroups(groups, {
|
|
727
|
+
formatPath: (p) => this.formatDisplayPath(p),
|
|
728
|
+
formatPackagePath: (p, source) => this.getShortPath(p, source),
|
|
729
|
+
});
|
|
730
|
+
this.chatContainer.addChild(new Text(`${sectionHeader("Themes")}\n${themeList}`, 0, 0));
|
|
731
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
732
|
+
}
|
|
733
|
+
const themeDiagnostics = this.session.resourceLoader.getThemes().diagnostics;
|
|
734
|
+
if (themeDiagnostics.length > 0) {
|
|
735
|
+
const warningLines = this.formatDiagnostics(themeDiagnostics, metadata);
|
|
736
|
+
this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Theme conflicts]")}\n${warningLines}`, 0, 0));
|
|
737
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Initialize the extension system with TUI-based UI context.
|
|
742
|
+
*/
|
|
743
|
+
async initExtensions() {
|
|
478
744
|
const extensionRunner = this.session.extensionRunner;
|
|
479
745
|
if (!extensionRunner) {
|
|
480
|
-
|
|
746
|
+
this.showLoadedResources({ extensionPaths: [], force: false });
|
|
747
|
+
return;
|
|
481
748
|
}
|
|
482
749
|
// Create extension UI context
|
|
483
750
|
const uiContext = this.createExtensionUIContext();
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// Don't rebuild if initial render hasn't happened yet
|
|
493
|
-
// (renderInitialMessages will handle it)
|
|
494
|
-
if (!wasStreaming && message.display && this.hasRenderedInitialMessages) {
|
|
495
|
-
this.rebuildChatFromMessages();
|
|
751
|
+
await this.session.bindExtensions({
|
|
752
|
+
uiContext,
|
|
753
|
+
commandContextActions: {
|
|
754
|
+
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
755
|
+
newSession: async (options) => {
|
|
756
|
+
if (this.loadingAnimation) {
|
|
757
|
+
this.loadingAnimation.stop();
|
|
758
|
+
this.loadingAnimation = undefined;
|
|
496
759
|
}
|
|
497
|
-
|
|
498
|
-
.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
},
|
|
502
|
-
sendUserMessage: (content, options) => {
|
|
503
|
-
this.session.sendUserMessage(content, options).catch((err) => {
|
|
504
|
-
this.showError(`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
505
|
-
});
|
|
506
|
-
},
|
|
507
|
-
appendEntry: (customType, data) => {
|
|
508
|
-
this.sessionManager.appendCustomEntry(customType, data);
|
|
509
|
-
},
|
|
510
|
-
setSessionName: (name) => {
|
|
511
|
-
this.sessionManager.appendSessionInfo(name);
|
|
512
|
-
},
|
|
513
|
-
getSessionName: () => {
|
|
514
|
-
return this.sessionManager.getSessionName();
|
|
515
|
-
},
|
|
516
|
-
setLabel: (entryId, label) => {
|
|
517
|
-
this.sessionManager.appendLabelChange(entryId, label);
|
|
518
|
-
},
|
|
519
|
-
getActiveTools: () => this.session.getActiveToolNames(),
|
|
520
|
-
getAllTools: () => this.session.getAllTools(),
|
|
521
|
-
setActiveTools: (toolNames) => this.session.setActiveToolsByName(toolNames),
|
|
522
|
-
setModel: async (model) => {
|
|
523
|
-
const key = await this.session.modelRegistry.getApiKey(model);
|
|
524
|
-
if (!key)
|
|
525
|
-
return false;
|
|
526
|
-
await this.session.setModel(model);
|
|
527
|
-
return true;
|
|
528
|
-
},
|
|
529
|
-
getThinkingLevel: () => this.session.thinkingLevel,
|
|
530
|
-
setThinkingLevel: (level) => this.session.setThinkingLevel(level),
|
|
531
|
-
},
|
|
532
|
-
// ExtensionContextActions - for ctx.* in event handlers
|
|
533
|
-
{
|
|
534
|
-
getModel: () => this.session.model,
|
|
535
|
-
isIdle: () => !this.session.isStreaming,
|
|
536
|
-
abort: () => this.session.abort(),
|
|
537
|
-
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
538
|
-
shutdown: () => {
|
|
539
|
-
this.shutdownRequested = true;
|
|
540
|
-
},
|
|
541
|
-
getContextUsage: () => this.session.getContextUsage(),
|
|
542
|
-
compact: (options) => {
|
|
543
|
-
void (async () => {
|
|
544
|
-
try {
|
|
545
|
-
const result = await this.executeCompaction(options?.customInstructions, false);
|
|
546
|
-
if (result) {
|
|
547
|
-
options?.onComplete?.(result);
|
|
548
|
-
}
|
|
760
|
+
this.statusContainer.clear();
|
|
761
|
+
const success = await this.session.newSession({ parentSession: options?.parentSession });
|
|
762
|
+
if (!success) {
|
|
763
|
+
return { cancelled: true };
|
|
549
764
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
options?.onError?.(err);
|
|
765
|
+
if (options?.setup) {
|
|
766
|
+
await options.setup(this.sessionManager);
|
|
553
767
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
this.
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
768
|
+
this.chatContainer.clear();
|
|
769
|
+
this.pendingMessagesContainer.clear();
|
|
770
|
+
this.compactionQueuedMessages = [];
|
|
771
|
+
this.streamingComponent = undefined;
|
|
772
|
+
this.streamingMessage = undefined;
|
|
773
|
+
this.pendingTools.clear();
|
|
774
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
775
|
+
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
|
|
776
|
+
this.ui.requestRender();
|
|
777
|
+
return { cancelled: false };
|
|
778
|
+
},
|
|
779
|
+
fork: async (entryId) => {
|
|
780
|
+
const result = await this.session.fork(entryId);
|
|
781
|
+
if (result.cancelled) {
|
|
782
|
+
return { cancelled: true };
|
|
783
|
+
}
|
|
784
|
+
this.chatContainer.clear();
|
|
785
|
+
this.renderInitialMessages();
|
|
786
|
+
this.editor.setText(result.selectedText);
|
|
787
|
+
this.showStatus("Forked to new session");
|
|
788
|
+
return { cancelled: false };
|
|
789
|
+
},
|
|
790
|
+
navigateTree: async (targetId, options) => {
|
|
791
|
+
const result = await this.session.navigateTree(targetId, {
|
|
792
|
+
summarize: options?.summarize,
|
|
793
|
+
customInstructions: options?.customInstructions,
|
|
794
|
+
replaceInstructions: options?.replaceInstructions,
|
|
795
|
+
label: options?.label,
|
|
796
|
+
});
|
|
797
|
+
if (result.cancelled) {
|
|
798
|
+
return { cancelled: true };
|
|
799
|
+
}
|
|
800
|
+
this.chatContainer.clear();
|
|
801
|
+
this.renderInitialMessages();
|
|
802
|
+
if (result.editorText) {
|
|
803
|
+
this.editor.setText(result.editorText);
|
|
804
|
+
}
|
|
805
|
+
this.showStatus("Navigated to selected point");
|
|
806
|
+
return { cancelled: false };
|
|
807
|
+
},
|
|
583
808
|
},
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
if (result.cancelled) {
|
|
587
|
-
return { cancelled: true };
|
|
588
|
-
}
|
|
589
|
-
this.chatContainer.clear();
|
|
590
|
-
this.renderInitialMessages();
|
|
591
|
-
this.editor.setText(result.selectedText);
|
|
592
|
-
this.showStatus("Forked to new session");
|
|
593
|
-
return { cancelled: false };
|
|
809
|
+
shutdownHandler: () => {
|
|
810
|
+
this.shutdownRequested = true;
|
|
594
811
|
},
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
summarize: options?.summarize,
|
|
598
|
-
customInstructions: options?.customInstructions,
|
|
599
|
-
replaceInstructions: options?.replaceInstructions,
|
|
600
|
-
label: options?.label,
|
|
601
|
-
});
|
|
602
|
-
if (result.cancelled) {
|
|
603
|
-
return { cancelled: true };
|
|
604
|
-
}
|
|
605
|
-
this.chatContainer.clear();
|
|
606
|
-
this.renderInitialMessages();
|
|
607
|
-
if (result.editorText) {
|
|
608
|
-
this.editor.setText(result.editorText);
|
|
609
|
-
}
|
|
610
|
-
this.showStatus("Navigated to selected point");
|
|
611
|
-
return { cancelled: false };
|
|
812
|
+
onError: (error) => {
|
|
813
|
+
this.showExtensionError(error.extensionPath, error.error, error.stack);
|
|
612
814
|
},
|
|
613
|
-
}, uiContext);
|
|
614
|
-
// Subscribe to extension errors
|
|
615
|
-
extensionRunner.onError((error) => {
|
|
616
|
-
this.showExtensionError(error.extensionPath, error.error, error.stack);
|
|
617
815
|
});
|
|
618
|
-
// Set up extension-registered shortcuts
|
|
619
816
|
this.setupExtensionShortcuts(extensionRunner);
|
|
620
|
-
|
|
621
|
-
if (!this.settingsManager.getQuietStartup()) {
|
|
622
|
-
const extensionPaths = extensionRunner.getExtensionPaths();
|
|
623
|
-
if (extensionPaths.length > 0) {
|
|
624
|
-
const extList = extensionPaths.map((p) => theme.fg("dim", ` ${p}`)).join("\n");
|
|
625
|
-
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded extensions:\n") + extList, 0, 0));
|
|
626
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
// Emit session_start event
|
|
630
|
-
await extensionRunner.emit({
|
|
631
|
-
type: "session_start",
|
|
632
|
-
});
|
|
817
|
+
this.showLoadedResources({ extensionPaths: extensionRunner.getExtensionPaths(), force: false });
|
|
633
818
|
}
|
|
634
819
|
/**
|
|
635
820
|
* Get a registered tool definition by name (for custom rendering).
|
|
@@ -735,6 +920,40 @@ export class InteractiveMode {
|
|
|
735
920
|
targetMap.set(key, component);
|
|
736
921
|
this.renderWidgets();
|
|
737
922
|
}
|
|
923
|
+
clearExtensionWidgets() {
|
|
924
|
+
for (const widget of this.extensionWidgetsAbove.values()) {
|
|
925
|
+
widget.dispose?.();
|
|
926
|
+
}
|
|
927
|
+
for (const widget of this.extensionWidgetsBelow.values()) {
|
|
928
|
+
widget.dispose?.();
|
|
929
|
+
}
|
|
930
|
+
this.extensionWidgetsAbove.clear();
|
|
931
|
+
this.extensionWidgetsBelow.clear();
|
|
932
|
+
this.renderWidgets();
|
|
933
|
+
}
|
|
934
|
+
resetExtensionUI() {
|
|
935
|
+
if (this.extensionSelector) {
|
|
936
|
+
this.hideExtensionSelector();
|
|
937
|
+
}
|
|
938
|
+
if (this.extensionInput) {
|
|
939
|
+
this.hideExtensionInput();
|
|
940
|
+
}
|
|
941
|
+
if (this.extensionEditor) {
|
|
942
|
+
this.hideExtensionEditor();
|
|
943
|
+
}
|
|
944
|
+
this.ui.hideOverlay();
|
|
945
|
+
this.setExtensionFooter(undefined);
|
|
946
|
+
this.setExtensionHeader(undefined);
|
|
947
|
+
this.clearExtensionWidgets();
|
|
948
|
+
this.footerDataProvider.clearExtensionStatuses();
|
|
949
|
+
this.footer.invalidate();
|
|
950
|
+
this.setCustomEditorComponent(undefined);
|
|
951
|
+
this.defaultEditor.onExtensionShortcut = undefined;
|
|
952
|
+
this.updateTerminalTitle();
|
|
953
|
+
if (this.loadingAnimation) {
|
|
954
|
+
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
738
957
|
// Maximum total widget lines to prevent viewport overflow
|
|
739
958
|
static MAX_WIDGET_LINES = 10;
|
|
740
959
|
/**
|
|
@@ -839,6 +1058,10 @@ export class InteractiveMode {
|
|
|
839
1058
|
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
|
|
840
1059
|
}
|
|
841
1060
|
}
|
|
1061
|
+
else {
|
|
1062
|
+
// Queue message for when loadingAnimation is created (handles agent_start race)
|
|
1063
|
+
this.pendingWorkingMessage = message;
|
|
1064
|
+
}
|
|
842
1065
|
},
|
|
843
1066
|
setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
|
|
844
1067
|
setFooter: (factory) => this.setExtensionFooter(factory),
|
|
@@ -1003,6 +1226,9 @@ export class InteractiveMode {
|
|
|
1003
1226
|
if (newEditor.borderColor !== undefined) {
|
|
1004
1227
|
newEditor.borderColor = this.defaultEditor.borderColor;
|
|
1005
1228
|
}
|
|
1229
|
+
if (newEditor.setPaddingX !== undefined) {
|
|
1230
|
+
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1231
|
+
}
|
|
1006
1232
|
// Set autocomplete if supported
|
|
1007
1233
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1008
1234
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1014,7 +1240,7 @@ export class InteractiveMode {
|
|
|
1014
1240
|
customEditor.onEscape = this.defaultEditor.onEscape;
|
|
1015
1241
|
customEditor.onCtrlD = this.defaultEditor.onCtrlD;
|
|
1016
1242
|
customEditor.onPasteImage = this.defaultEditor.onPasteImage;
|
|
1017
|
-
customEditor.onExtensionShortcut = this.defaultEditor.onExtensionShortcut;
|
|
1243
|
+
customEditor.onExtensionShortcut = (data) => this.defaultEditor.onExtensionShortcut?.(data);
|
|
1018
1244
|
// Copy action handlers (clear, suspend, model switching, etc.)
|
|
1019
1245
|
for (const [action, handler] of this.defaultEditor.actionHandlers) {
|
|
1020
1246
|
customEditor.actionHandlers.set(action, handler);
|
|
@@ -1304,6 +1530,11 @@ export class InteractiveMode {
|
|
|
1304
1530
|
await this.handleCompactCommand(customInstructions);
|
|
1305
1531
|
return;
|
|
1306
1532
|
}
|
|
1533
|
+
if (text === "/reload") {
|
|
1534
|
+
this.editor.setText("");
|
|
1535
|
+
await this.handleReloadCommand();
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1307
1538
|
if (text === "/debug") {
|
|
1308
1539
|
this.handleDebugCommand();
|
|
1309
1540
|
this.editor.setText("");
|
|
@@ -1400,6 +1631,13 @@ export class InteractiveMode {
|
|
|
1400
1631
|
this.statusContainer.clear();
|
|
1401
1632
|
this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.defaultWorkingMessage);
|
|
1402
1633
|
this.statusContainer.addChild(this.loadingAnimation);
|
|
1634
|
+
// Apply any pending working message queued before loader existed
|
|
1635
|
+
if (this.pendingWorkingMessage !== undefined) {
|
|
1636
|
+
if (this.pendingWorkingMessage) {
|
|
1637
|
+
this.loadingAnimation.setMessage(this.pendingWorkingMessage);
|
|
1638
|
+
}
|
|
1639
|
+
this.pendingWorkingMessage = undefined;
|
|
1640
|
+
}
|
|
1403
1641
|
this.ui.requestRender();
|
|
1404
1642
|
break;
|
|
1405
1643
|
case "message_start":
|
|
@@ -1413,7 +1651,7 @@ export class InteractiveMode {
|
|
|
1413
1651
|
this.ui.requestRender();
|
|
1414
1652
|
}
|
|
1415
1653
|
else if (event.message.role === "assistant") {
|
|
1416
|
-
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock);
|
|
1654
|
+
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
|
|
1417
1655
|
this.streamingMessage = event.message;
|
|
1418
1656
|
this.chatContainer.addChild(this.streamingComponent);
|
|
1419
1657
|
this.streamingComponent.updateContent(this.streamingMessage);
|
|
@@ -1663,20 +1901,20 @@ export class InteractiveMode {
|
|
|
1663
1901
|
case "custom": {
|
|
1664
1902
|
if (message.display) {
|
|
1665
1903
|
const renderer = this.session.extensionRunner?.getMessageRenderer(message.customType);
|
|
1666
|
-
this.chatContainer.addChild(new CustomMessageComponent(message, renderer));
|
|
1904
|
+
this.chatContainer.addChild(new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings()));
|
|
1667
1905
|
}
|
|
1668
1906
|
break;
|
|
1669
1907
|
}
|
|
1670
1908
|
case "compactionSummary": {
|
|
1671
1909
|
this.chatContainer.addChild(new Spacer(1));
|
|
1672
|
-
const component = new CompactionSummaryMessageComponent(message);
|
|
1910
|
+
const component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
|
1673
1911
|
component.setExpanded(this.toolOutputExpanded);
|
|
1674
1912
|
this.chatContainer.addChild(component);
|
|
1675
1913
|
break;
|
|
1676
1914
|
}
|
|
1677
1915
|
case "branchSummary": {
|
|
1678
1916
|
this.chatContainer.addChild(new Spacer(1));
|
|
1679
|
-
const component = new BranchSummaryMessageComponent(message);
|
|
1917
|
+
const component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
|
|
1680
1918
|
component.setExpanded(this.toolOutputExpanded);
|
|
1681
1919
|
this.chatContainer.addChild(component);
|
|
1682
1920
|
break;
|
|
@@ -1684,8 +1922,23 @@ export class InteractiveMode {
|
|
|
1684
1922
|
case "user": {
|
|
1685
1923
|
const textContent = this.getUserMessageText(message);
|
|
1686
1924
|
if (textContent) {
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1925
|
+
const skillBlock = parseSkillBlock(textContent);
|
|
1926
|
+
if (skillBlock) {
|
|
1927
|
+
// Render skill block (collapsible)
|
|
1928
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
1929
|
+
const component = new SkillInvocationMessageComponent(skillBlock, this.getMarkdownThemeWithSettings());
|
|
1930
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
1931
|
+
this.chatContainer.addChild(component);
|
|
1932
|
+
// Render user message separately if present
|
|
1933
|
+
if (skillBlock.userMessage) {
|
|
1934
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings());
|
|
1935
|
+
this.chatContainer.addChild(userComponent);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
else {
|
|
1939
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
|
|
1940
|
+
this.chatContainer.addChild(userComponent);
|
|
1941
|
+
}
|
|
1689
1942
|
if (options?.populateHistory) {
|
|
1690
1943
|
this.editor.addToHistory?.(textContent);
|
|
1691
1944
|
}
|
|
@@ -1693,7 +1946,7 @@ export class InteractiveMode {
|
|
|
1693
1946
|
break;
|
|
1694
1947
|
}
|
|
1695
1948
|
case "assistant": {
|
|
1696
|
-
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock);
|
|
1949
|
+
const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
|
|
1697
1950
|
this.chatContainer.addChild(assistantComponent);
|
|
1698
1951
|
break;
|
|
1699
1952
|
}
|
|
@@ -1765,7 +2018,6 @@ export class InteractiveMode {
|
|
|
1765
2018
|
this.ui.requestRender();
|
|
1766
2019
|
}
|
|
1767
2020
|
renderInitialMessages() {
|
|
1768
|
-
this.hasRenderedInitialMessages = true;
|
|
1769
2021
|
// Get aligned messages and entries from session context
|
|
1770
2022
|
const context = this.sessionManager.buildSessionContext();
|
|
1771
2023
|
this.renderSessionContext(context, {
|
|
@@ -1852,7 +2104,7 @@ export class InteractiveMode {
|
|
|
1852
2104
|
process.kill(0, "SIGTSTP");
|
|
1853
2105
|
}
|
|
1854
2106
|
async handleFollowUp() {
|
|
1855
|
-
const text = this.editor.getText().trim();
|
|
2107
|
+
const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
|
|
1856
2108
|
if (!text)
|
|
1857
2109
|
return;
|
|
1858
2110
|
// Queue input during compaction (extension commands execute immediately)
|
|
@@ -2015,22 +2267,51 @@ export class InteractiveMode {
|
|
|
2015
2267
|
? `Download from: ${theme.fg("accent", "https://github.com/badlogic/pi-mono/releases/latest")}`
|
|
2016
2268
|
: `Run: ${theme.fg("accent", `${isBunRuntime ? "bun" : "npm"} install -g @mariozechner/pi-coding-agent`)}`;
|
|
2017
2269
|
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
|
|
2270
|
+
const changelogUrl = theme.fg("accent", "https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md");
|
|
2271
|
+
const changelogLine = theme.fg("muted", "Changelog: ") + changelogUrl;
|
|
2018
2272
|
this.chatContainer.addChild(new Spacer(1));
|
|
2019
2273
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2020
|
-
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`, 1, 0));
|
|
2274
|
+
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}\n${changelogLine}`, 1, 0));
|
|
2021
2275
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2022
2276
|
this.ui.requestRender();
|
|
2023
2277
|
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Get all queued messages (read-only).
|
|
2280
|
+
* Combines session queue and compaction queue.
|
|
2281
|
+
*/
|
|
2282
|
+
getAllQueuedMessages() {
|
|
2283
|
+
return {
|
|
2284
|
+
steering: [
|
|
2285
|
+
...this.session.getSteeringMessages(),
|
|
2286
|
+
...this.compactionQueuedMessages.filter((msg) => msg.mode === "steer").map((msg) => msg.text),
|
|
2287
|
+
],
|
|
2288
|
+
followUp: [
|
|
2289
|
+
...this.session.getFollowUpMessages(),
|
|
2290
|
+
...this.compactionQueuedMessages.filter((msg) => msg.mode === "followUp").map((msg) => msg.text),
|
|
2291
|
+
],
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Clear all queued messages and return their contents.
|
|
2296
|
+
* Clears both session queue and compaction queue.
|
|
2297
|
+
*/
|
|
2298
|
+
clearAllQueues() {
|
|
2299
|
+
const { steering, followUp } = this.session.clearQueue();
|
|
2300
|
+
const compactionSteering = this.compactionQueuedMessages
|
|
2301
|
+
.filter((msg) => msg.mode === "steer")
|
|
2302
|
+
.map((msg) => msg.text);
|
|
2303
|
+
const compactionFollowUp = this.compactionQueuedMessages
|
|
2304
|
+
.filter((msg) => msg.mode === "followUp")
|
|
2305
|
+
.map((msg) => msg.text);
|
|
2306
|
+
this.compactionQueuedMessages = [];
|
|
2307
|
+
return {
|
|
2308
|
+
steering: [...steering, ...compactionSteering],
|
|
2309
|
+
followUp: [...followUp, ...compactionFollowUp],
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2024
2312
|
updatePendingMessagesDisplay() {
|
|
2025
2313
|
this.pendingMessagesContainer.clear();
|
|
2026
|
-
const steeringMessages =
|
|
2027
|
-
...this.session.getSteeringMessages(),
|
|
2028
|
-
...this.compactionQueuedMessages.filter((msg) => msg.mode === "steer").map((msg) => msg.text),
|
|
2029
|
-
];
|
|
2030
|
-
const followUpMessages = [
|
|
2031
|
-
...this.session.getFollowUpMessages(),
|
|
2032
|
-
...this.compactionQueuedMessages.filter((msg) => msg.mode === "followUp").map((msg) => msg.text),
|
|
2033
|
-
];
|
|
2314
|
+
const { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();
|
|
2034
2315
|
if (steeringMessages.length > 0 || followUpMessages.length > 0) {
|
|
2035
2316
|
this.pendingMessagesContainer.addChild(new Spacer(1));
|
|
2036
2317
|
for (const message of steeringMessages) {
|
|
@@ -2047,7 +2328,7 @@ export class InteractiveMode {
|
|
|
2047
2328
|
}
|
|
2048
2329
|
}
|
|
2049
2330
|
restoreQueuedMessagesToEditor(options) {
|
|
2050
|
-
const { steering, followUp } = this.
|
|
2331
|
+
const { steering, followUp } = this.clearAllQueues();
|
|
2051
2332
|
const allQueued = [...steering, ...followUp];
|
|
2052
2333
|
if (allQueued.length === 0) {
|
|
2053
2334
|
this.updatePendingMessagesDisplay();
|
|
@@ -2275,6 +2556,9 @@ export class InteractiveMode {
|
|
|
2275
2556
|
onEditorPaddingXChange: (padding) => {
|
|
2276
2557
|
this.settingsManager.setEditorPaddingX(padding);
|
|
2277
2558
|
this.defaultEditor.setPaddingX(padding);
|
|
2559
|
+
if (this.editor !== this.defaultEditor && this.editor.setPaddingX !== undefined) {
|
|
2560
|
+
this.editor.setPaddingX(padding);
|
|
2561
|
+
}
|
|
2278
2562
|
},
|
|
2279
2563
|
onCancel: () => {
|
|
2280
2564
|
done();
|
|
@@ -2340,6 +2624,12 @@ export class InteractiveMode {
|
|
|
2340
2624
|
return [];
|
|
2341
2625
|
}
|
|
2342
2626
|
}
|
|
2627
|
+
/** Update the footer's available provider count from current model candidates */
|
|
2628
|
+
async updateAvailableProviderCount() {
|
|
2629
|
+
const models = await this.getModelCandidates();
|
|
2630
|
+
const uniqueProviders = new Set(models.map((m) => m.provider));
|
|
2631
|
+
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
|
|
2632
|
+
}
|
|
2343
2633
|
showModelSelector(initialSearchInput) {
|
|
2344
2634
|
this.showSelector((done) => {
|
|
2345
2635
|
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
|
|
@@ -2613,8 +2903,17 @@ export class InteractiveMode {
|
|
|
2613
2903
|
this.ui.requestRender();
|
|
2614
2904
|
}, () => {
|
|
2615
2905
|
void this.shutdown();
|
|
2616
|
-
}, () => this.ui.requestRender(),
|
|
2617
|
-
|
|
2906
|
+
}, () => this.ui.requestRender(), {
|
|
2907
|
+
renameSession: async (sessionFilePath, nextName) => {
|
|
2908
|
+
const next = (nextName ?? "").trim();
|
|
2909
|
+
if (!next)
|
|
2910
|
+
return;
|
|
2911
|
+
const mgr = SessionManager.open(sessionFilePath);
|
|
2912
|
+
mgr.appendSessionInfo(next);
|
|
2913
|
+
},
|
|
2914
|
+
showRenameHint: true,
|
|
2915
|
+
}, this.sessionManager.getSessionFile());
|
|
2916
|
+
return { component: selector, focus: selector };
|
|
2618
2917
|
});
|
|
2619
2918
|
}
|
|
2620
2919
|
async handleResumeSession(sessionPath) {
|
|
@@ -2659,6 +2958,7 @@ export class InteractiveMode {
|
|
|
2659
2958
|
try {
|
|
2660
2959
|
this.session.modelRegistry.authStorage.logout(providerId);
|
|
2661
2960
|
this.session.modelRegistry.refresh();
|
|
2961
|
+
await this.updateAvailableProviderCount();
|
|
2662
2962
|
this.showStatus(`Logged out of ${providerName}`);
|
|
2663
2963
|
}
|
|
2664
2964
|
catch (error) {
|
|
@@ -2676,7 +2976,7 @@ export class InteractiveMode {
|
|
|
2676
2976
|
const providerInfo = getOAuthProviders().find((p) => p.id === providerId);
|
|
2677
2977
|
const providerName = providerInfo?.name || providerId;
|
|
2678
2978
|
// Providers that use callback servers (can paste redirect URL)
|
|
2679
|
-
const usesCallbackServer =
|
|
2979
|
+
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
2680
2980
|
// Create login dialog component
|
|
2681
2981
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
2682
2982
|
// Completion handled below
|
|
@@ -2739,6 +3039,7 @@ export class InteractiveMode {
|
|
|
2739
3039
|
// Success
|
|
2740
3040
|
restoreEditor();
|
|
2741
3041
|
this.session.modelRegistry.refresh();
|
|
3042
|
+
await this.updateAvailableProviderCount();
|
|
2742
3043
|
this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`);
|
|
2743
3044
|
}
|
|
2744
3045
|
catch (error) {
|
|
@@ -2752,6 +3053,53 @@ export class InteractiveMode {
|
|
|
2752
3053
|
// =========================================================================
|
|
2753
3054
|
// Command handlers
|
|
2754
3055
|
// =========================================================================
|
|
3056
|
+
async handleReloadCommand() {
|
|
3057
|
+
if (this.session.isStreaming) {
|
|
3058
|
+
this.showWarning("Wait for the current response to finish before reloading.");
|
|
3059
|
+
return;
|
|
3060
|
+
}
|
|
3061
|
+
if (this.session.isCompacting) {
|
|
3062
|
+
this.showWarning("Wait for compaction to finish before reloading.");
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
this.resetExtensionUI();
|
|
3066
|
+
const loader = new BorderedLoader(this.ui, theme, "Reloading extensions, skills, prompts, themes...", {
|
|
3067
|
+
cancellable: false,
|
|
3068
|
+
});
|
|
3069
|
+
const previousEditor = this.editor;
|
|
3070
|
+
this.editorContainer.clear();
|
|
3071
|
+
this.editorContainer.addChild(loader);
|
|
3072
|
+
this.ui.setFocus(loader);
|
|
3073
|
+
this.ui.requestRender();
|
|
3074
|
+
const dismissLoader = (editor) => {
|
|
3075
|
+
loader.dispose();
|
|
3076
|
+
this.editorContainer.clear();
|
|
3077
|
+
this.editorContainer.addChild(editor);
|
|
3078
|
+
this.ui.setFocus(editor);
|
|
3079
|
+
this.ui.requestRender();
|
|
3080
|
+
};
|
|
3081
|
+
try {
|
|
3082
|
+
await this.session.reload();
|
|
3083
|
+
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
3084
|
+
this.rebuildAutocomplete();
|
|
3085
|
+
const runner = this.session.extensionRunner;
|
|
3086
|
+
if (runner) {
|
|
3087
|
+
this.setupExtensionShortcuts(runner);
|
|
3088
|
+
}
|
|
3089
|
+
this.rebuildChatFromMessages();
|
|
3090
|
+
dismissLoader(this.editor);
|
|
3091
|
+
this.showLoadedResources({ extensionPaths: runner?.getExtensionPaths() ?? [], force: true });
|
|
3092
|
+
const modelsJsonError = this.session.modelRegistry.getError();
|
|
3093
|
+
if (modelsJsonError) {
|
|
3094
|
+
this.showError(`models.json error: ${modelsJsonError}`);
|
|
3095
|
+
}
|
|
3096
|
+
this.showStatus("Reloaded extensions, skills, prompts, themes");
|
|
3097
|
+
}
|
|
3098
|
+
catch (error) {
|
|
3099
|
+
dismissLoader(previousEditor);
|
|
3100
|
+
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
2755
3103
|
async handleExportCommand(text) {
|
|
2756
3104
|
const parts = text.split(/\s+/);
|
|
2757
3105
|
const outputPath = parts.length > 1 ? parts[1] : undefined;
|
|
@@ -2840,7 +3188,7 @@ export class InteractiveMode {
|
|
|
2840
3188
|
return;
|
|
2841
3189
|
}
|
|
2842
3190
|
// Create the preview URL
|
|
2843
|
-
const previewUrl =
|
|
3191
|
+
const previewUrl = getShareViewerUrl(gistId);
|
|
2844
3192
|
this.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
|
|
2845
3193
|
}
|
|
2846
3194
|
catch (error) {
|
|
@@ -2879,6 +3227,7 @@ export class InteractiveMode {
|
|
|
2879
3227
|
return;
|
|
2880
3228
|
}
|
|
2881
3229
|
this.sessionManager.appendSessionInfo(name);
|
|
3230
|
+
this.updateTerminalTitle();
|
|
2882
3231
|
this.chatContainer.addChild(new Spacer(1));
|
|
2883
3232
|
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
|
|
2884
3233
|
this.ui.requestRender();
|
|
@@ -2929,7 +3278,7 @@ export class InteractiveMode {
|
|
|
2929
3278
|
this.chatContainer.addChild(new DynamicBorder());
|
|
2930
3279
|
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
2931
3280
|
this.chatContainer.addChild(new Spacer(1));
|
|
2932
|
-
this.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1,
|
|
3281
|
+
this.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, this.getMarkdownThemeWithSettings()));
|
|
2933
3282
|
this.chatContainer.addChild(new DynamicBorder());
|
|
2934
3283
|
this.ui.requestRender();
|
|
2935
3284
|
}
|
|
@@ -2963,6 +3312,8 @@ export class InteractiveMode {
|
|
|
2963
3312
|
const cursorWordRight = this.getEditorKeyDisplay("cursorWordRight");
|
|
2964
3313
|
const cursorLineStart = this.getEditorKeyDisplay("cursorLineStart");
|
|
2965
3314
|
const cursorLineEnd = this.getEditorKeyDisplay("cursorLineEnd");
|
|
3315
|
+
const pageUp = this.getEditorKeyDisplay("pageUp");
|
|
3316
|
+
const pageDown = this.getEditorKeyDisplay("pageDown");
|
|
2966
3317
|
// Editing keybindings
|
|
2967
3318
|
const submit = this.getEditorKeyDisplay("submit");
|
|
2968
3319
|
const newLine = this.getEditorKeyDisplay("newLine");
|
|
@@ -2981,6 +3332,7 @@ export class InteractiveMode {
|
|
|
2981
3332
|
const suspend = this.getAppKeyDisplay("suspend");
|
|
2982
3333
|
const cycleThinkingLevel = this.getAppKeyDisplay("cycleThinkingLevel");
|
|
2983
3334
|
const cycleModelForward = this.getAppKeyDisplay("cycleModelForward");
|
|
3335
|
+
const selectModel = this.getAppKeyDisplay("selectModel");
|
|
2984
3336
|
const expandTools = this.getAppKeyDisplay("expandTools");
|
|
2985
3337
|
const toggleThinking = this.getAppKeyDisplay("toggleThinking");
|
|
2986
3338
|
const externalEditor = this.getAppKeyDisplay("externalEditor");
|
|
@@ -2994,6 +3346,7 @@ export class InteractiveMode {
|
|
|
2994
3346
|
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
|
2995
3347
|
| \`${cursorLineStart}\` | Start of line |
|
|
2996
3348
|
| \`${cursorLineEnd}\` | End of line |
|
|
3349
|
+
| \`${pageUp}\` / \`${pageDown}\` | Scroll by page |
|
|
2997
3350
|
|
|
2998
3351
|
**Editing**
|
|
2999
3352
|
| Key | Action |
|
|
@@ -3018,6 +3371,7 @@ export class InteractiveMode {
|
|
|
3018
3371
|
| \`${suspend}\` | Suspend to background |
|
|
3019
3372
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
3020
3373
|
| \`${cycleModelForward}\` | Cycle models |
|
|
3374
|
+
| \`${selectModel}\` | Open model selector |
|
|
3021
3375
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
3022
3376
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
3023
3377
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
@@ -3048,7 +3402,7 @@ export class InteractiveMode {
|
|
|
3048
3402
|
this.chatContainer.addChild(new DynamicBorder());
|
|
3049
3403
|
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Keyboard Shortcuts")), 1, 0));
|
|
3050
3404
|
this.chatContainer.addChild(new Spacer(1));
|
|
3051
|
-
this.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1,
|
|
3405
|
+
this.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, this.getMarkdownThemeWithSettings()));
|
|
3052
3406
|
this.chatContainer.addChild(new DynamicBorder());
|
|
3053
3407
|
this.ui.requestRender();
|
|
3054
3408
|
}
|
|
@@ -3074,11 +3428,12 @@ export class InteractiveMode {
|
|
|
3074
3428
|
}
|
|
3075
3429
|
handleDebugCommand() {
|
|
3076
3430
|
const width = this.ui.terminal.columns;
|
|
3431
|
+
const height = this.ui.terminal.rows;
|
|
3077
3432
|
const allLines = this.ui.render(width);
|
|
3078
3433
|
const debugLogPath = getDebugLogPath();
|
|
3079
3434
|
const debugData = [
|
|
3080
3435
|
`Debug output at ${new Date().toISOString()}`,
|
|
3081
|
-
`Terminal
|
|
3436
|
+
`Terminal: ${width}x${height}`,
|
|
3082
3437
|
`Total lines: ${allLines.length}`,
|
|
3083
3438
|
"",
|
|
3084
3439
|
"=== All rendered lines with visible widths ===",
|