@mariozechner/pi-coding-agent 0.49.3 → 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 +99 -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 +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 +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 +535 -200
- 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 +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 +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 +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 +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,360 @@ 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
|
}
|
|
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() {
|
|
496
744
|
const extensionRunner = this.session.extensionRunner;
|
|
497
745
|
if (!extensionRunner) {
|
|
498
|
-
|
|
746
|
+
this.showLoadedResources({ extensionPaths: [], force: false });
|
|
747
|
+
return;
|
|
499
748
|
}
|
|
500
749
|
// Create extension UI context
|
|
501
750
|
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();
|
|
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;
|
|
514
759
|
}
|
|
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
|
-
}
|
|
760
|
+
this.statusContainer.clear();
|
|
761
|
+
const success = await this.session.newSession({ parentSession: options?.parentSession });
|
|
762
|
+
if (!success) {
|
|
763
|
+
return { cancelled: true };
|
|
568
764
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
options?.onError?.(err);
|
|
765
|
+
if (options?.setup) {
|
|
766
|
+
await options.setup(this.sessionManager);
|
|
572
767
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
this.
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
+
},
|
|
602
808
|
},
|
|
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 };
|
|
809
|
+
shutdownHandler: () => {
|
|
810
|
+
this.shutdownRequested = true;
|
|
613
811
|
},
|
|
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 };
|
|
812
|
+
onError: (error) => {
|
|
813
|
+
this.showExtensionError(error.extensionPath, error.error, error.stack);
|
|
631
814
|
},
|
|
632
|
-
}, uiContext);
|
|
633
|
-
// Subscribe to extension errors
|
|
634
|
-
extensionRunner.onError((error) => {
|
|
635
|
-
this.showExtensionError(error.extensionPath, error.error, error.stack);
|
|
636
815
|
});
|
|
637
|
-
// Set up extension-registered shortcuts
|
|
638
816
|
this.setupExtensionShortcuts(extensionRunner);
|
|
639
|
-
|
|
640
|
-
if (!this.settingsManager.getQuietStartup()) {
|
|
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
|
-
}
|
|
647
|
-
}
|
|
648
|
-
// Emit session_start event
|
|
649
|
-
await extensionRunner.emit({
|
|
650
|
-
type: "session_start",
|
|
651
|
-
});
|
|
817
|
+
this.showLoadedResources({ extensionPaths: extensionRunner.getExtensionPaths(), force: false });
|
|
652
818
|
}
|
|
653
819
|
/**
|
|
654
820
|
* Get a registered tool definition by name (for custom rendering).
|
|
@@ -754,6 +920,40 @@ export class InteractiveMode {
|
|
|
754
920
|
targetMap.set(key, component);
|
|
755
921
|
this.renderWidgets();
|
|
756
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
|
+
}
|
|
757
957
|
// Maximum total widget lines to prevent viewport overflow
|
|
758
958
|
static MAX_WIDGET_LINES = 10;
|
|
759
959
|
/**
|
|
@@ -858,6 +1058,10 @@ export class InteractiveMode {
|
|
|
858
1058
|
this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
|
|
859
1059
|
}
|
|
860
1060
|
}
|
|
1061
|
+
else {
|
|
1062
|
+
// Queue message for when loadingAnimation is created (handles agent_start race)
|
|
1063
|
+
this.pendingWorkingMessage = message;
|
|
1064
|
+
}
|
|
861
1065
|
},
|
|
862
1066
|
setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
|
|
863
1067
|
setFooter: (factory) => this.setExtensionFooter(factory),
|
|
@@ -1022,6 +1226,9 @@ export class InteractiveMode {
|
|
|
1022
1226
|
if (newEditor.borderColor !== undefined) {
|
|
1023
1227
|
newEditor.borderColor = this.defaultEditor.borderColor;
|
|
1024
1228
|
}
|
|
1229
|
+
if (newEditor.setPaddingX !== undefined) {
|
|
1230
|
+
newEditor.setPaddingX(this.defaultEditor.getPaddingX());
|
|
1231
|
+
}
|
|
1025
1232
|
// Set autocomplete if supported
|
|
1026
1233
|
if (newEditor.setAutocompleteProvider && this.autocompleteProvider) {
|
|
1027
1234
|
newEditor.setAutocompleteProvider(this.autocompleteProvider);
|
|
@@ -1033,7 +1240,7 @@ export class InteractiveMode {
|
|
|
1033
1240
|
customEditor.onEscape = this.defaultEditor.onEscape;
|
|
1034
1241
|
customEditor.onCtrlD = this.defaultEditor.onCtrlD;
|
|
1035
1242
|
customEditor.onPasteImage = this.defaultEditor.onPasteImage;
|
|
1036
|
-
customEditor.onExtensionShortcut = this.defaultEditor.onExtensionShortcut;
|
|
1243
|
+
customEditor.onExtensionShortcut = (data) => this.defaultEditor.onExtensionShortcut?.(data);
|
|
1037
1244
|
// Copy action handlers (clear, suspend, model switching, etc.)
|
|
1038
1245
|
for (const [action, handler] of this.defaultEditor.actionHandlers) {
|
|
1039
1246
|
customEditor.actionHandlers.set(action, handler);
|
|
@@ -1323,6 +1530,11 @@ export class InteractiveMode {
|
|
|
1323
1530
|
await this.handleCompactCommand(customInstructions);
|
|
1324
1531
|
return;
|
|
1325
1532
|
}
|
|
1533
|
+
if (text === "/reload") {
|
|
1534
|
+
this.editor.setText("");
|
|
1535
|
+
await this.handleReloadCommand();
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1326
1538
|
if (text === "/debug") {
|
|
1327
1539
|
this.handleDebugCommand();
|
|
1328
1540
|
this.editor.setText("");
|
|
@@ -1419,6 +1631,13 @@ export class InteractiveMode {
|
|
|
1419
1631
|
this.statusContainer.clear();
|
|
1420
1632
|
this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.defaultWorkingMessage);
|
|
1421
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
|
+
}
|
|
1422
1641
|
this.ui.requestRender();
|
|
1423
1642
|
break;
|
|
1424
1643
|
case "message_start":
|
|
@@ -1703,8 +1922,23 @@ export class InteractiveMode {
|
|
|
1703
1922
|
case "user": {
|
|
1704
1923
|
const textContent = this.getUserMessageText(message);
|
|
1705
1924
|
if (textContent) {
|
|
1706
|
-
const
|
|
1707
|
-
|
|
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
|
+
}
|
|
1708
1942
|
if (options?.populateHistory) {
|
|
1709
1943
|
this.editor.addToHistory?.(textContent);
|
|
1710
1944
|
}
|
|
@@ -1784,7 +2018,6 @@ export class InteractiveMode {
|
|
|
1784
2018
|
this.ui.requestRender();
|
|
1785
2019
|
}
|
|
1786
2020
|
renderInitialMessages() {
|
|
1787
|
-
this.hasRenderedInitialMessages = true;
|
|
1788
2021
|
// Get aligned messages and entries from session context
|
|
1789
2022
|
const context = this.sessionManager.buildSessionContext();
|
|
1790
2023
|
this.renderSessionContext(context, {
|
|
@@ -1871,7 +2104,7 @@ export class InteractiveMode {
|
|
|
1871
2104
|
process.kill(0, "SIGTSTP");
|
|
1872
2105
|
}
|
|
1873
2106
|
async handleFollowUp() {
|
|
1874
|
-
const text = this.editor.getText().trim();
|
|
2107
|
+
const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
|
|
1875
2108
|
if (!text)
|
|
1876
2109
|
return;
|
|
1877
2110
|
// Queue input during compaction (extension commands execute immediately)
|
|
@@ -2034,22 +2267,51 @@ export class InteractiveMode {
|
|
|
2034
2267
|
? `Download from: ${theme.fg("accent", "https://github.com/badlogic/pi-mono/releases/latest")}`
|
|
2035
2268
|
: `Run: ${theme.fg("accent", `${isBunRuntime ? "bun" : "npm"} install -g @mariozechner/pi-coding-agent`)}`;
|
|
2036
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;
|
|
2037
2272
|
this.chatContainer.addChild(new Spacer(1));
|
|
2038
2273
|
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));
|
|
2274
|
+
this.chatContainer.addChild(new Text(`${theme.bold(theme.fg("warning", "Update Available"))}\n${updateInstruction}\n${changelogLine}`, 1, 0));
|
|
2040
2275
|
this.chatContainer.addChild(new DynamicBorder((text) => theme.fg("warning", text)));
|
|
2041
2276
|
this.ui.requestRender();
|
|
2042
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
|
+
}
|
|
2043
2312
|
updatePendingMessagesDisplay() {
|
|
2044
2313
|
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
|
-
];
|
|
2314
|
+
const { steering: steeringMessages, followUp: followUpMessages } = this.getAllQueuedMessages();
|
|
2053
2315
|
if (steeringMessages.length > 0 || followUpMessages.length > 0) {
|
|
2054
2316
|
this.pendingMessagesContainer.addChild(new Spacer(1));
|
|
2055
2317
|
for (const message of steeringMessages) {
|
|
@@ -2066,7 +2328,7 @@ export class InteractiveMode {
|
|
|
2066
2328
|
}
|
|
2067
2329
|
}
|
|
2068
2330
|
restoreQueuedMessagesToEditor(options) {
|
|
2069
|
-
const { steering, followUp } = this.
|
|
2331
|
+
const { steering, followUp } = this.clearAllQueues();
|
|
2070
2332
|
const allQueued = [...steering, ...followUp];
|
|
2071
2333
|
if (allQueued.length === 0) {
|
|
2072
2334
|
this.updatePendingMessagesDisplay();
|
|
@@ -2294,6 +2556,9 @@ export class InteractiveMode {
|
|
|
2294
2556
|
onEditorPaddingXChange: (padding) => {
|
|
2295
2557
|
this.settingsManager.setEditorPaddingX(padding);
|
|
2296
2558
|
this.defaultEditor.setPaddingX(padding);
|
|
2559
|
+
if (this.editor !== this.defaultEditor && this.editor.setPaddingX !== undefined) {
|
|
2560
|
+
this.editor.setPaddingX(padding);
|
|
2561
|
+
}
|
|
2297
2562
|
},
|
|
2298
2563
|
onCancel: () => {
|
|
2299
2564
|
done();
|
|
@@ -2359,6 +2624,12 @@ export class InteractiveMode {
|
|
|
2359
2624
|
return [];
|
|
2360
2625
|
}
|
|
2361
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
|
+
}
|
|
2362
2633
|
showModelSelector(initialSearchInput) {
|
|
2363
2634
|
this.showSelector((done) => {
|
|
2364
2635
|
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
|
|
@@ -2632,8 +2903,17 @@ export class InteractiveMode {
|
|
|
2632
2903
|
this.ui.requestRender();
|
|
2633
2904
|
}, () => {
|
|
2634
2905
|
void this.shutdown();
|
|
2635
|
-
}, () => this.ui.requestRender(),
|
|
2636
|
-
|
|
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 };
|
|
2637
2917
|
});
|
|
2638
2918
|
}
|
|
2639
2919
|
async handleResumeSession(sessionPath) {
|
|
@@ -2678,6 +2958,7 @@ export class InteractiveMode {
|
|
|
2678
2958
|
try {
|
|
2679
2959
|
this.session.modelRegistry.authStorage.logout(providerId);
|
|
2680
2960
|
this.session.modelRegistry.refresh();
|
|
2961
|
+
await this.updateAvailableProviderCount();
|
|
2681
2962
|
this.showStatus(`Logged out of ${providerName}`);
|
|
2682
2963
|
}
|
|
2683
2964
|
catch (error) {
|
|
@@ -2695,7 +2976,7 @@ export class InteractiveMode {
|
|
|
2695
2976
|
const providerInfo = getOAuthProviders().find((p) => p.id === providerId);
|
|
2696
2977
|
const providerName = providerInfo?.name || providerId;
|
|
2697
2978
|
// Providers that use callback servers (can paste redirect URL)
|
|
2698
|
-
const usesCallbackServer =
|
|
2979
|
+
const usesCallbackServer = providerInfo?.usesCallbackServer ?? false;
|
|
2699
2980
|
// Create login dialog component
|
|
2700
2981
|
const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
|
|
2701
2982
|
// Completion handled below
|
|
@@ -2758,6 +3039,7 @@ export class InteractiveMode {
|
|
|
2758
3039
|
// Success
|
|
2759
3040
|
restoreEditor();
|
|
2760
3041
|
this.session.modelRegistry.refresh();
|
|
3042
|
+
await this.updateAvailableProviderCount();
|
|
2761
3043
|
this.showStatus(`Logged in to ${providerName}. Credentials saved to ${getAuthPath()}`);
|
|
2762
3044
|
}
|
|
2763
3045
|
catch (error) {
|
|
@@ -2771,6 +3053,53 @@ export class InteractiveMode {
|
|
|
2771
3053
|
// =========================================================================
|
|
2772
3054
|
// Command handlers
|
|
2773
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
|
+
}
|
|
2774
3103
|
async handleExportCommand(text) {
|
|
2775
3104
|
const parts = text.split(/\s+/);
|
|
2776
3105
|
const outputPath = parts.length > 1 ? parts[1] : undefined;
|
|
@@ -2983,6 +3312,8 @@ export class InteractiveMode {
|
|
|
2983
3312
|
const cursorWordRight = this.getEditorKeyDisplay("cursorWordRight");
|
|
2984
3313
|
const cursorLineStart = this.getEditorKeyDisplay("cursorLineStart");
|
|
2985
3314
|
const cursorLineEnd = this.getEditorKeyDisplay("cursorLineEnd");
|
|
3315
|
+
const pageUp = this.getEditorKeyDisplay("pageUp");
|
|
3316
|
+
const pageDown = this.getEditorKeyDisplay("pageDown");
|
|
2986
3317
|
// Editing keybindings
|
|
2987
3318
|
const submit = this.getEditorKeyDisplay("submit");
|
|
2988
3319
|
const newLine = this.getEditorKeyDisplay("newLine");
|
|
@@ -3001,6 +3332,7 @@ export class InteractiveMode {
|
|
|
3001
3332
|
const suspend = this.getAppKeyDisplay("suspend");
|
|
3002
3333
|
const cycleThinkingLevel = this.getAppKeyDisplay("cycleThinkingLevel");
|
|
3003
3334
|
const cycleModelForward = this.getAppKeyDisplay("cycleModelForward");
|
|
3335
|
+
const selectModel = this.getAppKeyDisplay("selectModel");
|
|
3004
3336
|
const expandTools = this.getAppKeyDisplay("expandTools");
|
|
3005
3337
|
const toggleThinking = this.getAppKeyDisplay("toggleThinking");
|
|
3006
3338
|
const externalEditor = this.getAppKeyDisplay("externalEditor");
|
|
@@ -3014,6 +3346,7 @@ export class InteractiveMode {
|
|
|
3014
3346
|
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
|
3015
3347
|
| \`${cursorLineStart}\` | Start of line |
|
|
3016
3348
|
| \`${cursorLineEnd}\` | End of line |
|
|
3349
|
+
| \`${pageUp}\` / \`${pageDown}\` | Scroll by page |
|
|
3017
3350
|
|
|
3018
3351
|
**Editing**
|
|
3019
3352
|
| Key | Action |
|
|
@@ -3038,6 +3371,7 @@ export class InteractiveMode {
|
|
|
3038
3371
|
| \`${suspend}\` | Suspend to background |
|
|
3039
3372
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
3040
3373
|
| \`${cycleModelForward}\` | Cycle models |
|
|
3374
|
+
| \`${selectModel}\` | Open model selector |
|
|
3041
3375
|
| \`${expandTools}\` | Toggle tool output expansion |
|
|
3042
3376
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
3043
3377
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
@@ -3094,11 +3428,12 @@ export class InteractiveMode {
|
|
|
3094
3428
|
}
|
|
3095
3429
|
handleDebugCommand() {
|
|
3096
3430
|
const width = this.ui.terminal.columns;
|
|
3431
|
+
const height = this.ui.terminal.rows;
|
|
3097
3432
|
const allLines = this.ui.render(width);
|
|
3098
3433
|
const debugLogPath = getDebugLogPath();
|
|
3099
3434
|
const debugData = [
|
|
3100
3435
|
`Debug output at ${new Date().toISOString()}`,
|
|
3101
|
-
`Terminal
|
|
3436
|
+
`Terminal: ${width}x${height}`,
|
|
3102
3437
|
`Total lines: ${allLines.length}`,
|
|
3103
3438
|
"",
|
|
3104
3439
|
"=== All rendered lines with visible widths ===",
|