@mariozechner/pi-coding-agent 0.49.3 → 0.50.1
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 +110 -1
- package/README.md +310 -1230
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +57 -23
- 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/core/agent-session.d.ts +60 -37
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +272 -69
- 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 +130 -0
- package/dist/core/package-manager.d.ts.map +1 -0
- package/dist/core/package-manager.js +1177 -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 +34 -16
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +104 -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/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/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/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -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 +5 -5
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +42 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +538 -204
- 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 +72 -25
- 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 +7 -74
- 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 +17 -82
- 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 +14 -1
- 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 +182 -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 +168 -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 +22 -2
- 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/message-renderer.ts +59 -0
- package/examples/extensions/session-name.ts +27 -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
|
@@ -10,12 +10,12 @@ 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
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"),
|
|
@@ -328,6 +331,8 @@ export class InteractiveMode {
|
|
|
328
331
|
this.footerDataProvider.onBranchChange(() => {
|
|
329
332
|
this.ui.requestRender();
|
|
330
333
|
});
|
|
334
|
+
// Initialize available provider count for footer display
|
|
335
|
+
await this.updateAvailableProviderCount();
|
|
331
336
|
}
|
|
332
337
|
/**
|
|
333
338
|
* Update terminal title with session name and cwd.
|
|
@@ -456,199 +461,358 @@ export class InteractiveMode {
|
|
|
456
461
|
// =========================================================================
|
|
457
462
|
// Extension System
|
|
458
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
|
+
}
|
|
459
473
|
/**
|
|
460
|
-
*
|
|
474
|
+
* Get a short path relative to the package root for display.
|
|
461
475
|
*/
|
|
462
|
-
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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);
|
|
471
537
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (skills.length > 0) {
|
|
475
|
-
const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
|
|
476
|
-
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
|
|
477
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
538
|
+
else {
|
|
539
|
+
group.paths.push(p);
|
|
478
540
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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)}`));
|
|
487
551
|
}
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
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
|
+
}
|
|
494
559
|
}
|
|
495
560
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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;
|
|
499
646
|
}
|
|
500
|
-
|
|
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() {
|
|
501
744
|
const uiContext = this.createExtensionUIContext();
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
// Don't rebuild if initial render hasn't happened yet
|
|
511
|
-
// (renderInitialMessages will handle it)
|
|
512
|
-
if (!wasStreaming && message.display && this.hasRenderedInitialMessages) {
|
|
513
|
-
this.rebuildChatFromMessages();
|
|
745
|
+
await this.session.bindExtensions({
|
|
746
|
+
uiContext,
|
|
747
|
+
commandContextActions: {
|
|
748
|
+
waitForIdle: () => this.session.agent.waitForIdle(),
|
|
749
|
+
newSession: async (options) => {
|
|
750
|
+
if (this.loadingAnimation) {
|
|
751
|
+
this.loadingAnimation.stop();
|
|
752
|
+
this.loadingAnimation = undefined;
|
|
514
753
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
sendUserMessage: (content, options) => {
|
|
521
|
-
this.session.sendUserMessage(content, options).catch((err) => {
|
|
522
|
-
this.showError(`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
523
|
-
});
|
|
524
|
-
},
|
|
525
|
-
appendEntry: (customType, data) => {
|
|
526
|
-
this.sessionManager.appendCustomEntry(customType, data);
|
|
527
|
-
},
|
|
528
|
-
setSessionName: (name) => {
|
|
529
|
-
this.sessionManager.appendSessionInfo(name);
|
|
530
|
-
this.updateTerminalTitle();
|
|
531
|
-
},
|
|
532
|
-
getSessionName: () => {
|
|
533
|
-
return this.sessionManager.getSessionName();
|
|
534
|
-
},
|
|
535
|
-
setLabel: (entryId, label) => {
|
|
536
|
-
this.sessionManager.appendLabelChange(entryId, label);
|
|
537
|
-
},
|
|
538
|
-
getActiveTools: () => this.session.getActiveToolNames(),
|
|
539
|
-
getAllTools: () => this.session.getAllTools(),
|
|
540
|
-
setActiveTools: (toolNames) => this.session.setActiveToolsByName(toolNames),
|
|
541
|
-
setModel: async (model) => {
|
|
542
|
-
const key = await this.session.modelRegistry.getApiKey(model);
|
|
543
|
-
if (!key)
|
|
544
|
-
return false;
|
|
545
|
-
await this.session.setModel(model);
|
|
546
|
-
return true;
|
|
547
|
-
},
|
|
548
|
-
getThinkingLevel: () => this.session.thinkingLevel,
|
|
549
|
-
setThinkingLevel: (level) => this.session.setThinkingLevel(level),
|
|
550
|
-
},
|
|
551
|
-
// ExtensionContextActions - for ctx.* in event handlers
|
|
552
|
-
{
|
|
553
|
-
getModel: () => this.session.model,
|
|
554
|
-
isIdle: () => !this.session.isStreaming,
|
|
555
|
-
abort: () => this.session.abort(),
|
|
556
|
-
hasPendingMessages: () => this.session.pendingMessageCount > 0,
|
|
557
|
-
shutdown: () => {
|
|
558
|
-
this.shutdownRequested = true;
|
|
559
|
-
},
|
|
560
|
-
getContextUsage: () => this.session.getContextUsage(),
|
|
561
|
-
compact: (options) => {
|
|
562
|
-
void (async () => {
|
|
563
|
-
try {
|
|
564
|
-
const result = await this.executeCompaction(options?.customInstructions, false);
|
|
565
|
-
if (result) {
|
|
566
|
-
options?.onComplete?.(result);
|
|
567
|
-
}
|
|
754
|
+
this.statusContainer.clear();
|
|
755
|
+
// Delegate to AgentSession (handles setup + agent state sync)
|
|
756
|
+
const success = await this.session.newSession(options);
|
|
757
|
+
if (!success) {
|
|
758
|
+
return { cancelled: true };
|
|
568
759
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
760
|
+
// Clear UI state
|
|
761
|
+
this.chatContainer.clear();
|
|
762
|
+
this.pendingMessagesContainer.clear();
|
|
763
|
+
this.compactionQueuedMessages = [];
|
|
764
|
+
this.streamingComponent = undefined;
|
|
765
|
+
this.streamingMessage = undefined;
|
|
766
|
+
this.pendingTools.clear();
|
|
767
|
+
// Render any messages added via setup, or show empty session
|
|
768
|
+
this.renderInitialMessages();
|
|
769
|
+
this.ui.requestRender();
|
|
770
|
+
return { cancelled: false };
|
|
771
|
+
},
|
|
772
|
+
fork: async (entryId) => {
|
|
773
|
+
const result = await this.session.fork(entryId);
|
|
774
|
+
if (result.cancelled) {
|
|
775
|
+
return { cancelled: true };
|
|
572
776
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
this.pendingTools.clear();
|
|
598
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
599
|
-
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
|
|
600
|
-
this.ui.requestRender();
|
|
601
|
-
return { cancelled: false };
|
|
777
|
+
this.chatContainer.clear();
|
|
778
|
+
this.renderInitialMessages();
|
|
779
|
+
this.editor.setText(result.selectedText);
|
|
780
|
+
this.showStatus("Forked to new session");
|
|
781
|
+
return { cancelled: false };
|
|
782
|
+
},
|
|
783
|
+
navigateTree: async (targetId, options) => {
|
|
784
|
+
const result = await this.session.navigateTree(targetId, {
|
|
785
|
+
summarize: options?.summarize,
|
|
786
|
+
customInstructions: options?.customInstructions,
|
|
787
|
+
replaceInstructions: options?.replaceInstructions,
|
|
788
|
+
label: options?.label,
|
|
789
|
+
});
|
|
790
|
+
if (result.cancelled) {
|
|
791
|
+
return { cancelled: true };
|
|
792
|
+
}
|
|
793
|
+
this.chatContainer.clear();
|
|
794
|
+
this.renderInitialMessages();
|
|
795
|
+
if (result.editorText) {
|
|
796
|
+
this.editor.setText(result.editorText);
|
|
797
|
+
}
|
|
798
|
+
this.showStatus("Navigated to selected point");
|
|
799
|
+
return { cancelled: false };
|
|
800
|
+
},
|
|
602
801
|
},
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (result.cancelled) {
|
|
606
|
-
return { cancelled: true };
|
|
607
|
-
}
|
|
608
|
-
this.chatContainer.clear();
|
|
609
|
-
this.renderInitialMessages();
|
|
610
|
-
this.editor.setText(result.selectedText);
|
|
611
|
-
this.showStatus("Forked to new session");
|
|
612
|
-
return { cancelled: false };
|
|
802
|
+
shutdownHandler: () => {
|
|
803
|
+
this.shutdownRequested = true;
|
|
613
804
|
},
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
summarize: options?.summarize,
|
|
617
|
-
customInstructions: options?.customInstructions,
|
|
618
|
-
replaceInstructions: options?.replaceInstructions,
|
|
619
|
-
label: options?.label,
|
|
620
|
-
});
|
|
621
|
-
if (result.cancelled) {
|
|
622
|
-
return { cancelled: true };
|
|
623
|
-
}
|
|
624
|
-
this.chatContainer.clear();
|
|
625
|
-
this.renderInitialMessages();
|
|
626
|
-
if (result.editorText) {
|
|
627
|
-
this.editor.setText(result.editorText);
|
|
628
|
-
}
|
|
629
|
-
this.showStatus("Navigated to selected point");
|
|
630
|
-
return { cancelled: false };
|
|
805
|
+
onError: (error) => {
|
|
806
|
+
this.showExtensionError(error.extensionPath, error.error, error.stack);
|
|
631
807
|
},
|
|
632
|
-
}, uiContext);
|
|
633
|
-
// Subscribe to extension errors
|
|
634
|
-
extensionRunner.onError((error) => {
|
|
635
|
-
this.showExtensionError(error.extensionPath, error.error, error.stack);
|
|
636
808
|
});
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const extensionPaths = extensionRunner.getExtensionPaths();
|
|
642
|
-
if (extensionPaths.length > 0) {
|
|
643
|
-
const extList = extensionPaths.map((p) => theme.fg("dim", ` ${p}`)).join("\n");
|
|
644
|
-
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded extensions:\n") + extList, 0, 0));
|
|
645
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
646
|
-
}
|
|
809
|
+
const extensionRunner = this.session.extensionRunner;
|
|
810
|
+
if (!extensionRunner) {
|
|
811
|
+
this.showLoadedResources({ extensionPaths: [], force: false });
|
|
812
|
+
return;
|
|
647
813
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
type: "session_start",
|
|
651
|
-
});
|
|
814
|
+
this.setupExtensionShortcuts(extensionRunner);
|
|
815
|
+
this.showLoadedResources({ extensionPaths: extensionRunner.getExtensionPaths(), force: false });
|
|
652
816
|
}
|
|
653
817
|
/**
|
|
654
818
|
* Get a registered tool definition by name (for custom rendering).
|
|
@@ -754,6 +918,40 @@ export class InteractiveMode {
|
|
|
754
918
|
targetMap.set(key, component);
|
|
755
919
|
this.renderWidgets();
|
|
756
920
|
}
|
|
921
|
+
clearExtensionWidgets() {
|
|
922
|
+
for (const widget of this.extensionWidgetsAbove.values()) {
|
|
923
|
+
widget.dispose?.();
|
|
924
|
+
}
|
|
925
|
+
for (const widget of this.extensionWidgetsBelow.values()) {
|
|
926
|
+
widget.dispose?.();
|
|
927
|
+
}
|
|
928
|
+
this.extensionWidgetsAbove.clear();
|
|
929
|
+
this.extensionWidgetsBelow.clear();
|
|
930
|
+
this.renderWidgets();
|
|
931
|
+
}
|
|
932
|
+
resetExtensionUI() {
|
|
933
|
+
if (this.extensionSelector) {
|
|
934
|
+
this.hideExtensionSelector();
|
|
935
|
+
}
|
|
936
|
+
if (this.extensionInput) {
|
|
937
|
+
this.hideExtensionInput();
|
|
938
|
+
}
|
|
939
|
+
if (this.extensionEditor) {
|
|
940
|
+
this.hideExtensionEditor();
|
|
941
|
+
}
|
|
942
|
+
this.ui.hideOverlay();
|
|
943
|
+
this.setExtensionFooter(undefined);
|
|
944
|
+
this.setExtensionHeader(undefined);
|
|
945
|
+
this.clearExtensionWidgets();
|
|
946
|
+
this.footerDataProvider.clearExtensionStatuses();
|
|
947
|
+
this.footer.invalidate();
|
|
948
|
+
this.setCustomEditorComponent(undefined);
|
|
949
|
+
this.defaultEditor.onExtensionShortcut = undefined;
|
|
950
|
+
this.updateTerminalTitle();
|
|
951
|
+
if (this.loadingAnimation) {
|
|
952
|
+
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
757
955
|
// Maximum total widget lines to prevent viewport overflow
|
|
758
956
|
static MAX_WIDGET_LINES = 10;
|
|
759
957
|
/**
|
|
@@ -858,6 +1056,10 @@ export class InteractiveMode {
|
|
|
858
1056
|
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
|
|
859
1057
|
}
|
|
860
1058
|
}
|
|
1059
|
+
else {
|
|
1060
|
+
// Queue message for when loadingAnimation is created (handles agent_start race)
|
|
1061
|
+
this.pendingWorkingMessage = message;
|
|
1062
|
+
}
|
|
861
1063
|
},
|
|
862
1064
|
setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
|
|
863
1065
|
setFooter: (factory) => this.setExtensionFooter(factory),
|
|
@@ -1022,6 +1224,9 @@ export class InteractiveMode {
|
|
|
1022
1224
|
if (newEditor.borderColor !== undefined) {
|
|
1023
1225
|
newEditor.borderColor = this.defaultEditor.borderColor;
|
|
1024
1226
|
}
|
|
1227
|
+
if (newEditor.setPaddingX !== undefined) {
|
|
1228
|
+
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1229
|
+
}
|
|
1025
1230
|
// Set autocomplete if supported
|
|
1026
1231
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1027
1232
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1033,7 +1238,7 @@ export class InteractiveMode {
|
|
|
1033
1238
|
customEditor.onEscape = this.defaultEditor.onEscape;
|
|
1034
1239
|
customEditor.onCtrlD = this.defaultEditor.onCtrlD;
|
|
1035
1240
|
customEditor.onPasteImage = this.defaultEditor.onPasteImage;
|
|
1036
|
-
customEditor.onExtensionShortcut = this.defaultEditor.onExtensionShortcut;
|
|
1241
|
+
customEditor.onExtensionShortcut = (data) => this.defaultEditor.onExtensionShortcut?.(data);
|
|
1037
1242
|
// Copy action handlers (clear, suspend, model switching, etc.)
|
|
1038
1243
|
for (const [action, handler] of this.defaultEditor.actionHandlers) {
|
|
1039
1244
|
customEditor.actionHandlers.set(action, handler);
|
|
@@ -1323,6 +1528,11 @@ export class InteractiveMode {
|
|
|
1323
1528
|
await this.handleCompactCommand(customInstructions);
|
|
1324
1529
|
return;
|
|
1325
1530
|
}
|
|
1531
|
+
if (text === "/reload") {
|
|
1532
|
+
this.editor.setText("");
|
|
1533
|
+
await this.handleReloadCommand();
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1326
1536
|
if (text === "/debug") {
|
|
1327
1537
|
this.handleDebugCommand();
|
|
1328
1538
|
this.editor.setText("");
|
|
@@ -1419,6 +1629,13 @@ export class InteractiveMode {
|
|
|
1419
1629
|
this.statusContainer.clear();
|
|
1420
1630
|
this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.defaultWorkingMessage);
|
|
1421
1631
|
this.statusContainer.addChild(this.loadingAnimation);
|
|
1632
|
+
// Apply any pending working message queued before loader existed
|
|
1633
|
+
if (this.pendingWorkingMessage !== undefined) {
|
|
1634
|
+
if (this.pendingWorkingMessage) {
|
|
1635
|
+
this.loadingAnimation.setMessage(this.pendingWorkingMessage);
|
|
1636
|
+
}
|
|
1637
|
+
this.pendingWorkingMessage = undefined;
|
|
1638
|
+
}
|
|
1422
1639
|
this.ui.requestRender();
|
|
1423
1640
|
break;
|
|
1424
1641
|
case "message_start":
|
|
@@ -1703,8 +1920,23 @@ export class InteractiveMode {
|
|
|
1703
1920
|
case "user": {
|
|
1704
1921
|
const textContent = this.getUserMessageText(message);
|
|
1705
1922
|
if (textContent) {
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1923
|
+
const skillBlock = parseSkillBlock(textContent);
|
|
1924
|
+
if (skillBlock) {
|
|
1925
|
+
// Render skill block (collapsible)
|
|
1926
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
1927
|
+
const component = new SkillInvocationMessageComponent(skillBlock, this.getMarkdownThemeWithSettings());
|
|
1928
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
1929
|
+
this.chatContainer.addChild(component);
|
|
1930
|
+
// Render user message separately if present
|
|
1931
|
+
if (skillBlock.userMessage) {
|
|
1932
|
+
const userComponent = new UserMessageComponent(skillBlock.userMessage, this.getMarkdownThemeWithSettings());
|
|
1933
|
+
this.chatContainer.addChild(userComponent);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
else {
|
|
1937
|
+
const userComponent = new UserMessageComponent(textContent, this.getMarkdownThemeWithSettings());
|
|
1938
|
+
this.chatContainer.addChild(userComponent);
|
|
1939
|
+
}
|
|
1708
1940
|
if (options?.populateHistory) {
|
|
1709
1941
|
this.editor.addToHistory?.(textContent);
|
|
1710
1942
|
}
|
|
@@ -1784,7 +2016,6 @@ export class InteractiveMode {
|
|
|
1784
2016
|
this.ui.requestRender();
|
|
1785
2017
|
}
|
|
1786
2018
|
renderInitialMessages() {
|
|
1787
|
-
this.hasRenderedInitialMessages = true;
|
|
1788
2019
|
// Get aligned messages and entries from session context
|
|
1789
2020
|
const context = this.sessionManager.buildSessionContext();
|
|
1790
2021
|
this.renderSessionContext(context, {
|
|
@@ -1871,7 +2102,7 @@ export class InteractiveMode {
|
|
|
1871
2102
|
process.kill(0, "SIGTSTP");
|
|
1872
2103
|
}
|
|
1873
2104
|
async handleFollowUp() {
|
|
1874
|
-
const text = this.editor.getText().trim();
|
|
2105
|
+
const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
|
|
1875
2106
|
if (!text)
|
|
1876
2107
|
return;
|
|
1877
2108
|
// Queue input during compaction (extension commands execute immediately)
|
|
@@ -2034,22 +2265,51 @@ export class InteractiveMode {
|
|
|
2034
2265
|
? `Download from: ${theme.fg("accent", "https://github.com/badlogic/pi-mono/releases/latest")}`
|
|
2035
2266
|
: `Run: ${theme.fg("accent", `${isBunRuntime ? "bun" : "npm"} install -g @mariozechner/pi-coding-agent`)}`;
|
|
2036
2267
|
const updateInstruction = theme.fg("muted", `New version ${newVersion} is available. `) + action;
|
|
2268
|
+
const changelogUrl = theme.fg("accent", "https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md");
|
|
2269
|
+
const changelogLine = theme.fg("muted", "Changelog: ") + changelogUrl;
|
|
2037
2270
|
this.chatContainer.addChild(new Spacer(1));
|
|
2038
2271
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2039
|
-
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}`, 1, 0));
|
|
2272
|
+
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}\n${changelogLine}`, 1, 0));
|
|
2040
2273
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2041
2274
|
this.ui.requestRender();
|
|
2042
2275
|
}
|
|
2276
|
+
/**
|
|
2277
|
+
* Get all queued messages (read-only).
|
|
2278
|
+
* Combines session queue and compaction queue.
|
|
2279
|
+
*/
|
|
2280
|
+
getAllQueuedMessages() {
|
|
2281
|
+
return {
|
|
2282
|
+
steering: [
|
|
2283
|
+
...this.session.getSteeringMessages(),
|
|
2284
|
+
...this.compactionQueuedMessages.filter((msg) => msg.mode === "steer").map((msg) => msg.text),
|
|
2285
|
+
],
|
|
2286
|
+
followUp: [
|
|
2287
|
+
...this.session.getFollowUpMessages(),
|
|
2288
|
+
...this.compactionQueuedMessages.filter((msg) => msg.mode === "followUp").map((msg) => msg.text),
|
|
2289
|
+
],
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Clear all queued messages and return their contents.
|
|
2294
|
+
* Clears both session queue and compaction queue.
|
|
2295
|
+
*/
|
|
2296
|
+
clearAllQueues() {
|
|
2297
|
+
const { steering, followUp } = this.session.clearQueue();
|
|
2298
|
+
const compactionSteering = this.compactionQueuedMessages
|
|
2299
|
+
.filter((msg) => msg.mode === "steer")
|
|
2300
|
+
.map((msg) => msg.text);
|
|
2301
|
+
const compactionFollowUp = this.compactionQueuedMessages
|
|
2302
|
+
.filter((msg) => msg.mode === "followUp")
|
|
2303
|
+
.map((msg) => msg.text);
|
|
2304
|
+
this.compactionQueuedMessages = [];
|
|
2305
|
+
return {
|
|
2306
|
+
steering: [...steering, ...compactionSteering],
|
|
2307
|
+
followUp: [...followUp, ...compactionFollowUp],
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2043
2310
|
updatePendingMessagesDisplay() {
|
|
2044
2311
|
this.pendingMessagesContainer.clear();
|
|
2045
|
-
const steeringMessages =
|
|
2046
|
-
...this.session.getSteeringMessages(),
|
|
2047
|
-
...this.compactionQueuedMessages.filter((msg) => msg.mode === "steer").map((msg) => msg.text),
|
|
2048
|
-
];
|
|
2049
|
-
const followUpMessages = [
|
|
2050
|
-
...this.session.getFollowUpMessages(),
|
|
2051
|
-
...this.compactionQueuedMessages.filter((msg) => msg.mode === "followUp").map((msg) => msg.text),
|
|
2052
|
-
];
|
|
2312
|
+
const { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();
|
|
2053
2313
|
if (steeringMessages.length > 0 || followUpMessages.length > 0) {
|
|
2054
2314
|
this.pendingMessagesContainer.addChild(new Spacer(1));
|
|
2055
2315
|
for (const message of steeringMessages) {
|
|
@@ -2066,7 +2326,7 @@ export class InteractiveMode {
|
|
|
2066
2326
|
}
|
|
2067
2327
|
}
|
|
2068
2328
|
restoreQueuedMessagesToEditor(options) {
|
|
2069
|
-
const { steering, followUp } = this.
|
|
2329
|
+
const { steering, followUp } = this.clearAllQueues();
|
|
2070
2330
|
const allQueued = [...steering, ...followUp];
|
|
2071
2331
|
if (allQueued.length === 0) {
|
|
2072
2332
|
this.updatePendingMessagesDisplay();
|
|
@@ -2294,6 +2554,9 @@ export class InteractiveMode {
|
|
|
2294
2554
|
onEditorPaddingXChange: (padding) => {
|
|
2295
2555
|
this.settingsManager.setEditorPaddingX(padding);
|
|
2296
2556
|
this.defaultEditor.setPaddingX(padding);
|
|
2557
|
+
if (this.editor !== this.defaultEditor && this.editor.setPaddingX !== undefined) {
|
|
2558
|
+
this.editor.setPaddingX(padding);
|
|
2559
|
+
}
|
|
2297
2560
|
},
|
|
2298
2561
|
onCancel: () => {
|
|
2299
2562
|
done();
|
|
@@ -2359,6 +2622,12 @@ export class InteractiveMode {
|
|
|
2359
2622
|
return [];
|
|
2360
2623
|
}
|
|
2361
2624
|
}
|
|
2625
|
+
/** Update the footer's available provider count from current model candidates */
|
|
2626
|
+
async updateAvailableProviderCount() {
|
|
2627
|
+
const models = await this.getModelCandidates();
|
|
2628
|
+
const uniqueProviders = new Set(models.map((m) => m.provider));
|
|
2629
|
+
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
|
|
2630
|
+
}
|
|
2362
2631
|
showModelSelector(initialSearchInput) {
|
|
2363
2632
|
this.showSelector((done) => {
|
|
2364
2633
|
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
|
|
@@ -2632,8 +2901,17 @@ export class InteractiveMode {
|
|
|
2632
2901
|
this.ui.requestRender();
|
|
2633
2902
|
}, () => {
|
|
2634
2903
|
void this.shutdown();
|
|
2635
|
-
}, () => this.ui.requestRender(),
|
|
2636
|
-
|
|
2904
|
+
}, () => this.ui.requestRender(), {
|
|
2905
|
+
renameSession: async (sessionFilePath, nextName) => {
|
|
2906
|
+
const next = (nextName ?? "").trim();
|
|
2907
|
+
if (!next)
|
|
2908
|
+
return;
|
|
2909
|
+
const mgr = SessionManager.open(sessionFilePath);
|
|
2910
|
+
mgr.appendSessionInfo(next);
|
|
2911
|
+
},
|
|
2912
|
+
showRenameHint: true,
|
|
2913
|
+
}, this.sessionManager.getSessionFile());
|
|
2914
|
+
return { component: selector, focus: selector };
|
|
2637
2915
|
});
|
|
2638
2916
|
}
|
|
2639
2917
|
async handleResumeSession(sessionPath) {
|
|
@@ -2678,6 +2956,7 @@ export class InteractiveMode {
|
|
|
2678
2956
|
try {
|
|
2679
2957
|
this.session.modelRegistry.authStorage.logout(providerId);
|
|
2680
2958
|
this.session.modelRegistry.refresh();
|
|
2959
|
+
await this.updateAvailableProviderCount();
|
|
2681
2960
|
this.showStatus(`Logged out of ${providerName}`);
|
|
2682
2961
|
}
|
|
2683
2962
|
catch (error) {
|
|
@@ -2695,7 +2974,7 @@ export class InteractiveMode {
|
|
|
2695
2974
|
const providerInfo = getOAuthProviders().find((p) => p.id === providerId);
|
|
2696
2975
|
const providerName = providerInfo?.name || providerId;
|
|
2697
2976
|
// Providers that use callback servers (can paste redirect URL)
|
|
2698
|
-
const usesCallbackServer =
|
|
2977
|
+
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
2699
2978
|
// Create login dialog component
|
|
2700
2979
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
2701
2980
|
// Completion handled below
|
|
@@ -2758,6 +3037,7 @@ export class InteractiveMode {
|
|
|
2758
3037
|
// Success
|
|
2759
3038
|
restoreEditor();
|
|
2760
3039
|
this.session.modelRegistry.refresh();
|
|
3040
|
+
await this.updateAvailableProviderCount();
|
|
2761
3041
|
this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`);
|
|
2762
3042
|
}
|
|
2763
3043
|
catch (error) {
|
|
@@ -2771,6 +3051,53 @@ export class InteractiveMode {
|
|
|
2771
3051
|
// =========================================================================
|
|
2772
3052
|
// Command handlers
|
|
2773
3053
|
// =========================================================================
|
|
3054
|
+
async handleReloadCommand() {
|
|
3055
|
+
if (this.session.isStreaming) {
|
|
3056
|
+
this.showWarning("Wait for the current response to finish before reloading.");
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
if (this.session.isCompacting) {
|
|
3060
|
+
this.showWarning("Wait for compaction to finish before reloading.");
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
3063
|
+
this.resetExtensionUI();
|
|
3064
|
+
const loader = new BorderedLoader(this.ui, theme, "Reloading extensions, skills, prompts, themes...", {
|
|
3065
|
+
cancellable: false,
|
|
3066
|
+
});
|
|
3067
|
+
const previousEditor = this.editor;
|
|
3068
|
+
this.editorContainer.clear();
|
|
3069
|
+
this.editorContainer.addChild(loader);
|
|
3070
|
+
this.ui.setFocus(loader);
|
|
3071
|
+
this.ui.requestRender();
|
|
3072
|
+
const dismissLoader = (editor) => {
|
|
3073
|
+
loader.dispose();
|
|
3074
|
+
this.editorContainer.clear();
|
|
3075
|
+
this.editorContainer.addChild(editor);
|
|
3076
|
+
this.ui.setFocus(editor);
|
|
3077
|
+
this.ui.requestRender();
|
|
3078
|
+
};
|
|
3079
|
+
try {
|
|
3080
|
+
await this.session.reload();
|
|
3081
|
+
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
|
3082
|
+
this.rebuildAutocomplete();
|
|
3083
|
+
const runner = this.session.extensionRunner;
|
|
3084
|
+
if (runner) {
|
|
3085
|
+
this.setupExtensionShortcuts(runner);
|
|
3086
|
+
}
|
|
3087
|
+
this.rebuildChatFromMessages();
|
|
3088
|
+
dismissLoader(this.editor);
|
|
3089
|
+
this.showLoadedResources({ extensionPaths: runner?.getExtensionPaths() ?? [], force: true });
|
|
3090
|
+
const modelsJsonError = this.session.modelRegistry.getError();
|
|
3091
|
+
if (modelsJsonError) {
|
|
3092
|
+
this.showError(`models.json error: ${modelsJsonError}`);
|
|
3093
|
+
}
|
|
3094
|
+
this.showStatus("Reloaded extensions, skills, prompts, themes");
|
|
3095
|
+
}
|
|
3096
|
+
catch (error) {
|
|
3097
|
+
dismissLoader(previousEditor);
|
|
3098
|
+
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
2774
3101
|
async handleExportCommand(text) {
|
|
2775
3102
|
const parts = text.split(/\s+/);
|
|
2776
3103
|
const outputPath = parts.length > 1 ? parts[1] : undefined;
|
|
@@ -2983,6 +3310,8 @@ export class InteractiveMode {
|
|
|
2983
3310
|
const cursorWordRight = this.getEditorKeyDisplay("cursorWordRight");
|
|
2984
3311
|
const cursorLineStart = this.getEditorKeyDisplay("cursorLineStart");
|
|
2985
3312
|
const cursorLineEnd = this.getEditorKeyDisplay("cursorLineEnd");
|
|
3313
|
+
const pageUp = this.getEditorKeyDisplay("pageUp");
|
|
3314
|
+
const pageDown = this.getEditorKeyDisplay("pageDown");
|
|
2986
3315
|
// Editing keybindings
|
|
2987
3316
|
const submit = this.getEditorKeyDisplay("submit");
|
|
2988
3317
|
const newLine = this.getEditorKeyDisplay("newLine");
|
|
@@ -3001,6 +3330,7 @@ export class InteractiveMode {
|
|
|
3001
3330
|
const suspend = this.getAppKeyDisplay("suspend");
|
|
3002
3331
|
const cycleThinkingLevel = this.getAppKeyDisplay("cycleThinkingLevel");
|
|
3003
3332
|
const cycleModelForward = this.getAppKeyDisplay("cycleModelForward");
|
|
3333
|
+
const selectModel = this.getAppKeyDisplay("selectModel");
|
|
3004
3334
|
const expandTools = this.getAppKeyDisplay("expandTools");
|
|
3005
3335
|
const toggleThinking = this.getAppKeyDisplay("toggleThinking");
|
|
3006
3336
|
const externalEditor = this.getAppKeyDisplay("externalEditor");
|
|
@@ -3014,6 +3344,7 @@ export class InteractiveMode {
|
|
|
3014
3344
|
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
|
3015
3345
|
| \`${cursorLineStart}\` | Start of line |
|
|
3016
3346
|
| \`${cursorLineEnd}\` | End of line |
|
|
3347
|
+
| \`${pageUp}\` / \`${pageDown}\` | Scroll by page |
|
|
3017
3348
|
|
|
3018
3349
|
**Editing**
|
|
3019
3350
|
| Key | Action |
|
|
@@ -3038,6 +3369,7 @@ export class InteractiveMode {
|
|
|
3038
3369
|
| \`${suspend}\` | Suspend to background |
|
|
3039
3370
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
3040
3371
|
| \`${cycleModelForward}\` | Cycle models |
|
|
3372
|
+
| \`${selectModel}\` | Open model selector |
|
|
3041
3373
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
3042
3374
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
3043
3375
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
@@ -3060,7 +3392,8 @@ export class InteractiveMode {
|
|
|
3060
3392
|
`;
|
|
3061
3393
|
for (const [key, shortcut] of shortcuts) {
|
|
3062
3394
|
const description = shortcut.description ?? shortcut.extensionPath;
|
|
3063
|
-
|
|
3395
|
+
const keyDisplay = key.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
3396
|
+
hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
|
|
3064
3397
|
}
|
|
3065
3398
|
}
|
|
3066
3399
|
}
|
|
@@ -3094,11 +3427,12 @@ export class InteractiveMode {
|
|
|
3094
3427
|
}
|
|
3095
3428
|
handleDebugCommand() {
|
|
3096
3429
|
const width = this.ui.terminal.columns;
|
|
3430
|
+
const height = this.ui.terminal.rows;
|
|
3097
3431
|
const allLines = this.ui.render(width);
|
|
3098
3432
|
const debugLogPath = getDebugLogPath();
|
|
3099
3433
|
const debugData = [
|
|
3100
3434
|
`Debug output at ${new Date().toISOString()}`,
|
|
3101
|
-
`Terminal
|
|
3435
|
+
`Terminal: ${width}x${height}`,
|
|
3102
3436
|
`Total lines: ${allLines.length}`,
|
|
3103
3437
|
"",
|
|
3104
3438
|
"=== All rendered lines with visible widths ===",
|