@kolisachint/hoocode-agent 0.4.24 → 0.4.25
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 +17 -0
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +13 -4
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +27 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +23 -0
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +48 -11
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resource-loader.d.ts +5 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +40 -5
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +1 -0
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +1 -0
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +14 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +1 -1
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +1 -1
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +1 -1
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/extensions/core/hoo-core.d.ts.map +1 -1
- package/dist/extensions/core/hoo-core.js +4 -1
- package/dist/extensions/core/hoo-core.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/command-executor.d.ts +64 -0
- package/dist/modes/interactive/command-executor.d.ts.map +1 -0
- package/dist/modes/interactive/command-executor.js +547 -0
- package/dist/modes/interactive/command-executor.js.map +1 -0
- package/dist/modes/interactive/components/ask-options.d.ts.map +1 -1
- package/dist/modes/interactive/components/ask-options.js +2 -0
- package/dist/modes/interactive/components/ask-options.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +9 -20
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +86 -552
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/prompt-templates.md +34 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/08-prompt-templates.ts +1 -0
- package/package.json +4 -4
|
@@ -7,12 +7,10 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { getProviders, } from "@kolisachint/hoocode-ai";
|
|
10
|
-
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI,
|
|
10
|
+
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, } from "@kolisachint/hoocode-tui";
|
|
11
11
|
import { spawn, spawnSync } from "child_process";
|
|
12
|
-
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath,
|
|
13
|
-
import { loadAgentRegistry } from "../../core/agent-registry.js";
|
|
12
|
+
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDocsPath, VERSION } from "../../config.js";
|
|
14
13
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
15
|
-
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
16
14
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
17
15
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
18
16
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
@@ -22,22 +20,19 @@ import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-nam
|
|
|
22
20
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
23
21
|
import { SessionManager } from "../../core/session-manager.js";
|
|
24
22
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
25
|
-
import { getSubagentPool } from "../../core/subagent-pool-instance.js";
|
|
26
23
|
import { taskStore } from "../../core/task-store.js";
|
|
27
24
|
import { buildCompactWordmark } from "../../core/wordmark.js";
|
|
28
25
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
29
|
-
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
30
26
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
31
27
|
import { parseGitUrl } from "../../utils/git.js";
|
|
32
28
|
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
33
29
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
34
30
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
35
31
|
import { checkForNewHooCodeVersion } from "../../utils/version-check.js";
|
|
36
|
-
import {
|
|
32
|
+
import { CommandExecutor } from "./command-executor.js";
|
|
37
33
|
import { AskOptionsComponent } from "./components/ask-options.js";
|
|
38
34
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
39
35
|
import { BashExecutionComponent } from "./components/bash-execution.js";
|
|
40
|
-
import { BorderedLoader } from "./components/bordered-loader.js";
|
|
41
36
|
import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js";
|
|
42
37
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js";
|
|
43
38
|
import { CountdownTimer } from "./components/countdown-timer.js";
|
|
@@ -49,7 +44,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
|
|
|
49
44
|
import { ExtensionInputComponent } from "./components/extension-input.js";
|
|
50
45
|
import { ExtensionSelectorComponent } from "./components/extension-selector.js";
|
|
51
46
|
import { FooterComponent } from "./components/footer.js";
|
|
52
|
-
import {
|
|
47
|
+
import { keyDisplayText, keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
|
|
53
48
|
import { LoginDialogComponent } from "./components/login-dialog.js";
|
|
54
49
|
import { ModelSelectorComponent } from "./components/model-selector.js";
|
|
55
50
|
import { OAuthSelectorComponent } from "./components/oauth-selector.js";
|
|
@@ -201,6 +196,72 @@ export class InteractiveMode {
|
|
|
201
196
|
get session() {
|
|
202
197
|
return this.runtimeHost.session;
|
|
203
198
|
}
|
|
199
|
+
_commandExecutor;
|
|
200
|
+
/**
|
|
201
|
+
* Lazily-built command executor. The context uses getters for mutable
|
|
202
|
+
* dependencies (e.g. the active session) so handlers always operate on the
|
|
203
|
+
* current state even after a session switch.
|
|
204
|
+
*/
|
|
205
|
+
get commandExecutor() {
|
|
206
|
+
if (!this._commandExecutor) {
|
|
207
|
+
const self = this;
|
|
208
|
+
const context = {
|
|
209
|
+
get session() {
|
|
210
|
+
return self.session;
|
|
211
|
+
},
|
|
212
|
+
get sessionManager() {
|
|
213
|
+
return self.sessionManager;
|
|
214
|
+
},
|
|
215
|
+
get runtimeHost() {
|
|
216
|
+
return self.runtimeHost;
|
|
217
|
+
},
|
|
218
|
+
get ui() {
|
|
219
|
+
return self.ui;
|
|
220
|
+
},
|
|
221
|
+
get editor() {
|
|
222
|
+
return self.editor;
|
|
223
|
+
},
|
|
224
|
+
get editorContainer() {
|
|
225
|
+
return self.editorContainer;
|
|
226
|
+
},
|
|
227
|
+
get chatContainer() {
|
|
228
|
+
return self.chatContainer;
|
|
229
|
+
},
|
|
230
|
+
get statusContainer() {
|
|
231
|
+
return self.statusContainer;
|
|
232
|
+
},
|
|
233
|
+
get footer() {
|
|
234
|
+
return self.footer;
|
|
235
|
+
},
|
|
236
|
+
get keybindings() {
|
|
237
|
+
return self.keybindings;
|
|
238
|
+
},
|
|
239
|
+
showStatus: (message) => self.showStatus(message),
|
|
240
|
+
showError: (message) => self.showError(message),
|
|
241
|
+
showWarning: (message) => self.showWarning(message),
|
|
242
|
+
updateEditorBorderColor: () => self.updateEditorBorderColor(),
|
|
243
|
+
renderCurrentSessionState: () => self.renderCurrentSessionState(),
|
|
244
|
+
rebuildChatFromMessages: () => self.rebuildChatFromMessages(),
|
|
245
|
+
getMarkdownThemeWithSettings: () => self.getMarkdownThemeWithSettings(),
|
|
246
|
+
stopLoadingAnimation: () => self.stopLoadingAnimation(),
|
|
247
|
+
findExactModelMatch: (searchTerm) => self.findExactModelMatch(searchTerm),
|
|
248
|
+
maybeWarnAboutAnthropicSubscriptionAuth: (model) => self.maybeWarnAboutAnthropicSubscriptionAuth(model),
|
|
249
|
+
checkDaxnutsEasterEgg: (model) => self.checkDaxnutsEasterEgg(model),
|
|
250
|
+
showModelSelector: (searchTerm) => self.showModelSelector(searchTerm),
|
|
251
|
+
showExtensionConfirm: (title, message) => self.showExtensionConfirm(title, message),
|
|
252
|
+
promptForMissingSessionCwd: (error) => self.promptForMissingSessionCwd(error),
|
|
253
|
+
handleFatalRuntimeError: (prefix, error) => self.handleFatalRuntimeError(prefix, error),
|
|
254
|
+
};
|
|
255
|
+
this._commandExecutor = new CommandExecutor(context);
|
|
256
|
+
}
|
|
257
|
+
return this._commandExecutor;
|
|
258
|
+
}
|
|
259
|
+
stopLoadingAnimation() {
|
|
260
|
+
if (this.loadingAnimation) {
|
|
261
|
+
this.loadingAnimation.stop();
|
|
262
|
+
this.loadingAnimation = undefined;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
204
265
|
get agent() {
|
|
205
266
|
return this.session.agent;
|
|
206
267
|
}
|
|
@@ -2000,14 +2061,14 @@ export class InteractiveMode {
|
|
|
2000
2061
|
this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
|
|
2001
2062
|
this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
|
|
2002
2063
|
// Global debug handler on TUI (works regardless of focus)
|
|
2003
|
-
this.ui.onDebug = () => this.
|
|
2064
|
+
this.ui.onDebug = () => this.commandExecutor.handleDebug();
|
|
2004
2065
|
this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
|
|
2005
2066
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
2006
2067
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2007
2068
|
this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
|
|
2008
2069
|
this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
|
|
2009
2070
|
this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
|
|
2010
|
-
this.defaultEditor.onAction("app.session.new", () => this.
|
|
2071
|
+
this.defaultEditor.onAction("app.session.new", () => this.commandExecutor.handleClear());
|
|
2011
2072
|
this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
|
|
2012
2073
|
this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
|
|
2013
2074
|
this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
|
|
@@ -2063,46 +2124,46 @@ export class InteractiveMode {
|
|
|
2063
2124
|
if (text === "/model" || text.startsWith("/model ")) {
|
|
2064
2125
|
const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
|
|
2065
2126
|
this.editor.setText("");
|
|
2066
|
-
await this.
|
|
2127
|
+
await this.commandExecutor.handleModel(searchTerm);
|
|
2067
2128
|
return;
|
|
2068
2129
|
}
|
|
2069
2130
|
if (text === "/export" || text.startsWith("/export ")) {
|
|
2070
|
-
await this.
|
|
2131
|
+
await this.commandExecutor.handleExport(text);
|
|
2071
2132
|
this.editor.setText("");
|
|
2072
2133
|
return;
|
|
2073
2134
|
}
|
|
2074
2135
|
if (text === "/import" || text.startsWith("/import ")) {
|
|
2075
|
-
await this.
|
|
2136
|
+
await this.commandExecutor.handleImport(text);
|
|
2076
2137
|
this.editor.setText("");
|
|
2077
2138
|
return;
|
|
2078
2139
|
}
|
|
2079
2140
|
if (text === "/share") {
|
|
2080
|
-
await this.
|
|
2141
|
+
await this.commandExecutor.handleShare();
|
|
2081
2142
|
this.editor.setText("");
|
|
2082
2143
|
return;
|
|
2083
2144
|
}
|
|
2084
2145
|
if (text === "/copy") {
|
|
2085
|
-
await this.
|
|
2146
|
+
await this.commandExecutor.handleCopy();
|
|
2086
2147
|
this.editor.setText("");
|
|
2087
2148
|
return;
|
|
2088
2149
|
}
|
|
2089
2150
|
if (text === "/name" || text.startsWith("/name ")) {
|
|
2090
|
-
this.
|
|
2151
|
+
this.commandExecutor.handleName(text);
|
|
2091
2152
|
this.editor.setText("");
|
|
2092
2153
|
return;
|
|
2093
2154
|
}
|
|
2094
2155
|
if (text === "/session") {
|
|
2095
|
-
this.
|
|
2156
|
+
this.commandExecutor.handleSession();
|
|
2096
2157
|
this.editor.setText("");
|
|
2097
2158
|
return;
|
|
2098
2159
|
}
|
|
2099
2160
|
if (text === "/changelog") {
|
|
2100
|
-
this.
|
|
2161
|
+
this.commandExecutor.handleChangelog();
|
|
2101
2162
|
this.editor.setText("");
|
|
2102
2163
|
return;
|
|
2103
2164
|
}
|
|
2104
2165
|
if (text === "/hotkeys") {
|
|
2105
|
-
this.
|
|
2166
|
+
this.commandExecutor.handleHotkeys();
|
|
2106
2167
|
this.editor.setText("");
|
|
2107
2168
|
return;
|
|
2108
2169
|
}
|
|
@@ -2113,7 +2174,7 @@ export class InteractiveMode {
|
|
|
2113
2174
|
}
|
|
2114
2175
|
if (text === "/clone") {
|
|
2115
2176
|
this.editor.setText("");
|
|
2116
|
-
await this.
|
|
2177
|
+
await this.commandExecutor.handleClone();
|
|
2117
2178
|
return;
|
|
2118
2179
|
}
|
|
2119
2180
|
if (text === "/tree") {
|
|
@@ -2133,7 +2194,7 @@ export class InteractiveMode {
|
|
|
2133
2194
|
}
|
|
2134
2195
|
if (text === "/new") {
|
|
2135
2196
|
this.editor.setText("");
|
|
2136
|
-
await this.
|
|
2197
|
+
await this.commandExecutor.handleClear();
|
|
2137
2198
|
return;
|
|
2138
2199
|
}
|
|
2139
2200
|
if (text === "/compact" || text.startsWith("/compact ")) {
|
|
@@ -2151,12 +2212,12 @@ export class InteractiveMode {
|
|
|
2151
2212
|
return;
|
|
2152
2213
|
}
|
|
2153
2214
|
if (text === "/debug") {
|
|
2154
|
-
this.
|
|
2215
|
+
this.commandExecutor.handleDebug();
|
|
2155
2216
|
this.editor.setText("");
|
|
2156
2217
|
return;
|
|
2157
2218
|
}
|
|
2158
2219
|
if (text === "/arminsayshi") {
|
|
2159
|
-
this.handleArminSaysHi();
|
|
2220
|
+
this.commandExecutor.handleArminSaysHi();
|
|
2160
2221
|
this.editor.setText("");
|
|
2161
2222
|
return;
|
|
2162
2223
|
}
|
|
@@ -2172,7 +2233,7 @@ export class InteractiveMode {
|
|
|
2172
2233
|
}
|
|
2173
2234
|
if (text === "/subagent" || text.startsWith("/subagent ")) {
|
|
2174
2235
|
this.editor.setText("");
|
|
2175
|
-
await this.
|
|
2236
|
+
await this.commandExecutor.handleSubagent(text);
|
|
2176
2237
|
return;
|
|
2177
2238
|
}
|
|
2178
2239
|
// Handle bash command (! for normal, !! for excluded from context)
|
|
@@ -3373,28 +3434,6 @@ export class InteractiveMode {
|
|
|
3373
3434
|
return { component: selector, focus: selector.getSettingsList() };
|
|
3374
3435
|
});
|
|
3375
3436
|
}
|
|
3376
|
-
async handleModelCommand(searchTerm) {
|
|
3377
|
-
if (!searchTerm) {
|
|
3378
|
-
this.showModelSelector();
|
|
3379
|
-
return;
|
|
3380
|
-
}
|
|
3381
|
-
const model = await this.findExactModelMatch(searchTerm);
|
|
3382
|
-
if (model) {
|
|
3383
|
-
try {
|
|
3384
|
-
await this.session.setModel(model);
|
|
3385
|
-
this.footer.invalidate();
|
|
3386
|
-
this.updateEditorBorderColor();
|
|
3387
|
-
this.showStatus(`Model: ${model.id}`);
|
|
3388
|
-
void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
|
|
3389
|
-
this.checkDaxnutsEasterEgg(model);
|
|
3390
|
-
}
|
|
3391
|
-
catch (error) {
|
|
3392
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3393
|
-
}
|
|
3394
|
-
return;
|
|
3395
|
-
}
|
|
3396
|
-
this.showModelSelector(searchTerm);
|
|
3397
|
-
}
|
|
3398
3437
|
async findExactModelMatch(searchTerm) {
|
|
3399
3438
|
const models = await this.getModelCandidates();
|
|
3400
3439
|
return findExactModelReferenceMatch(searchTerm, models);
|
|
@@ -3566,80 +3605,6 @@ export class InteractiveMode {
|
|
|
3566
3605
|
return { component: selector, focus: selector.getMessageList() };
|
|
3567
3606
|
});
|
|
3568
3607
|
}
|
|
3569
|
-
async handleCloneCommand() {
|
|
3570
|
-
const leafId = this.sessionManager.getLeafId();
|
|
3571
|
-
if (!leafId) {
|
|
3572
|
-
this.showStatus("Nothing to clone yet");
|
|
3573
|
-
return;
|
|
3574
|
-
}
|
|
3575
|
-
try {
|
|
3576
|
-
const result = await this.runtimeHost.fork(leafId, { position: "at" });
|
|
3577
|
-
if (result.cancelled) {
|
|
3578
|
-
this.ui.requestRender();
|
|
3579
|
-
return;
|
|
3580
|
-
}
|
|
3581
|
-
this.renderCurrentSessionState();
|
|
3582
|
-
this.editor.setText("");
|
|
3583
|
-
this.showStatus("Cloned to new session");
|
|
3584
|
-
}
|
|
3585
|
-
catch (error) {
|
|
3586
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3587
|
-
}
|
|
3588
|
-
}
|
|
3589
|
-
async handleSubagentCommand(text) {
|
|
3590
|
-
const prefix = "/subagent ";
|
|
3591
|
-
const args = text.startsWith(prefix) ? text.slice(prefix.length).trim() : "";
|
|
3592
|
-
if (!args) {
|
|
3593
|
-
this.showStatus("Usage: /subagent <mode> <task>");
|
|
3594
|
-
return;
|
|
3595
|
-
}
|
|
3596
|
-
const firstSpace = args.indexOf(" ");
|
|
3597
|
-
if (firstSpace === -1) {
|
|
3598
|
-
this.showStatus("Usage: /subagent <mode> <task>");
|
|
3599
|
-
return;
|
|
3600
|
-
}
|
|
3601
|
-
const mode = args.slice(0, firstSpace).trim();
|
|
3602
|
-
const task = args.slice(firstSpace + 1).trim();
|
|
3603
|
-
if (!task) {
|
|
3604
|
-
this.showStatus("Usage: /subagent <mode> <task>");
|
|
3605
|
-
return;
|
|
3606
|
-
}
|
|
3607
|
-
const validModes = loadAgentRegistry({ cwd: this.session.sessionManager.getCwd() })
|
|
3608
|
-
.list()
|
|
3609
|
-
.map((a) => a.name);
|
|
3610
|
-
if (!validModes.includes(mode)) {
|
|
3611
|
-
this.showStatus(`Unknown subagent_type: ${mode}. Available: ${validModes.join(", ")}`);
|
|
3612
|
-
return;
|
|
3613
|
-
}
|
|
3614
|
-
this.showStatus(`Spawning ${mode} subagent...`);
|
|
3615
|
-
try {
|
|
3616
|
-
const pool = getSubagentPool(this.session.sessionManager.getCwd());
|
|
3617
|
-
const dispatchResult = await pool.dispatch(task, {
|
|
3618
|
-
forceAgent: mode,
|
|
3619
|
-
model: this.session.model?.id,
|
|
3620
|
-
provider: this.session.model?.provider,
|
|
3621
|
-
});
|
|
3622
|
-
const result = dispatchResult.result;
|
|
3623
|
-
const resultData = result?.result_data;
|
|
3624
|
-
if (result?.ok) {
|
|
3625
|
-
this.showStatus(`${mode} subagent completed`);
|
|
3626
|
-
// Inject the subagent answer as a custom message so the user can see it in the chat
|
|
3627
|
-
this.sessionManager.appendMessage({
|
|
3628
|
-
role: "custom",
|
|
3629
|
-
customType: "subagent",
|
|
3630
|
-
content: resultData?.summary || "(no output)",
|
|
3631
|
-
display: true,
|
|
3632
|
-
timestamp: Date.now(),
|
|
3633
|
-
});
|
|
3634
|
-
}
|
|
3635
|
-
else {
|
|
3636
|
-
this.showError(`Subagent (${mode}) failed: ${result?.error ?? "unknown error"}`);
|
|
3637
|
-
}
|
|
3638
|
-
}
|
|
3639
|
-
catch (error) {
|
|
3640
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3641
|
-
}
|
|
3642
|
-
}
|
|
3643
3608
|
showTreeSelector(initialSelectedId) {
|
|
3644
3609
|
const tree = this.sessionManager.getTree();
|
|
3645
3610
|
const realLeafId = this.sessionManager.getLeafId();
|
|
@@ -4201,443 +4166,12 @@ export class InteractiveMode {
|
|
|
4201
4166
|
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4202
4167
|
}
|
|
4203
4168
|
}
|
|
4204
|
-
async handleExportCommand(text) {
|
|
4205
|
-
const outputPath = this.getPathCommandArgument(text, "/export");
|
|
4206
|
-
try {
|
|
4207
|
-
if (outputPath?.endsWith(".jsonl")) {
|
|
4208
|
-
const filePath = this.session.exportToJsonl(outputPath);
|
|
4209
|
-
this.showStatus(`Session exported to: ${filePath}`);
|
|
4210
|
-
}
|
|
4211
|
-
else {
|
|
4212
|
-
const filePath = await this.session.exportToHtml(outputPath);
|
|
4213
|
-
this.showStatus(`Session exported to: ${filePath}`);
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
catch (error) {
|
|
4217
|
-
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4218
|
-
}
|
|
4219
|
-
}
|
|
4220
|
-
getPathCommandArgument(text, command) {
|
|
4221
|
-
if (text === command) {
|
|
4222
|
-
return undefined;
|
|
4223
|
-
}
|
|
4224
|
-
if (!text.startsWith(`${command} `)) {
|
|
4225
|
-
return undefined;
|
|
4226
|
-
}
|
|
4227
|
-
const argsString = text.slice(command.length + 1).trimStart();
|
|
4228
|
-
if (!argsString) {
|
|
4229
|
-
return undefined;
|
|
4230
|
-
}
|
|
4231
|
-
const firstChar = argsString[0];
|
|
4232
|
-
if (firstChar === '"' || firstChar === "'") {
|
|
4233
|
-
const closingQuoteIndex = argsString.indexOf(firstChar, 1);
|
|
4234
|
-
if (closingQuoteIndex < 0) {
|
|
4235
|
-
return undefined;
|
|
4236
|
-
}
|
|
4237
|
-
return argsString.slice(1, closingQuoteIndex);
|
|
4238
|
-
}
|
|
4239
|
-
const firstWhitespaceIndex = argsString.search(/\s/);
|
|
4240
|
-
if (firstWhitespaceIndex < 0) {
|
|
4241
|
-
return argsString;
|
|
4242
|
-
}
|
|
4243
|
-
return argsString.slice(0, firstWhitespaceIndex);
|
|
4244
|
-
}
|
|
4245
|
-
async handleImportCommand(text) {
|
|
4246
|
-
const inputPath = this.getPathCommandArgument(text, "/import");
|
|
4247
|
-
if (!inputPath) {
|
|
4248
|
-
this.showError("Usage: /import <path.jsonl>");
|
|
4249
|
-
return;
|
|
4250
|
-
}
|
|
4251
|
-
const confirmed = await this.showExtensionConfirm("Import session", `Replace current session with ${inputPath}?`);
|
|
4252
|
-
if (!confirmed) {
|
|
4253
|
-
this.showStatus("Import cancelled");
|
|
4254
|
-
return;
|
|
4255
|
-
}
|
|
4256
|
-
try {
|
|
4257
|
-
if (this.loadingAnimation) {
|
|
4258
|
-
this.loadingAnimation.stop();
|
|
4259
|
-
this.loadingAnimation = undefined;
|
|
4260
|
-
}
|
|
4261
|
-
this.statusContainer.clear();
|
|
4262
|
-
const result = await this.runtimeHost.importFromJsonl(inputPath);
|
|
4263
|
-
if (result.cancelled) {
|
|
4264
|
-
this.showStatus("Import cancelled");
|
|
4265
|
-
return;
|
|
4266
|
-
}
|
|
4267
|
-
this.renderCurrentSessionState();
|
|
4268
|
-
this.showStatus(`Session imported from: ${inputPath}`);
|
|
4269
|
-
}
|
|
4270
|
-
catch (error) {
|
|
4271
|
-
if (error instanceof MissingSessionCwdError) {
|
|
4272
|
-
const selectedCwd = await this.promptForMissingSessionCwd(error);
|
|
4273
|
-
if (!selectedCwd) {
|
|
4274
|
-
this.showStatus("Import cancelled");
|
|
4275
|
-
return;
|
|
4276
|
-
}
|
|
4277
|
-
const result = await this.runtimeHost.importFromJsonl(inputPath, selectedCwd);
|
|
4278
|
-
if (result.cancelled) {
|
|
4279
|
-
this.showStatus("Import cancelled");
|
|
4280
|
-
return;
|
|
4281
|
-
}
|
|
4282
|
-
this.renderCurrentSessionState();
|
|
4283
|
-
this.showStatus(`Session imported from: ${inputPath}`);
|
|
4284
|
-
return;
|
|
4285
|
-
}
|
|
4286
|
-
if (error instanceof SessionImportFileNotFoundError) {
|
|
4287
|
-
this.showError(`Failed to import session: ${error.message}`);
|
|
4288
|
-
return;
|
|
4289
|
-
}
|
|
4290
|
-
await this.handleFatalRuntimeError("Failed to import session", error);
|
|
4291
|
-
}
|
|
4292
|
-
}
|
|
4293
|
-
async handleShareCommand() {
|
|
4294
|
-
// Check if gh is available and logged in
|
|
4295
|
-
try {
|
|
4296
|
-
const authResult = spawnSync("gh", ["auth", "status"], { encoding: "utf-8" });
|
|
4297
|
-
if (authResult.status !== 0) {
|
|
4298
|
-
this.showError("GitHub CLI is not logged in. Run 'gh auth login' first.");
|
|
4299
|
-
return;
|
|
4300
|
-
}
|
|
4301
|
-
}
|
|
4302
|
-
catch {
|
|
4303
|
-
this.showError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/");
|
|
4304
|
-
return;
|
|
4305
|
-
}
|
|
4306
|
-
// Export to a temp file
|
|
4307
|
-
const tmpFile = path.join(os.tmpdir(), "session.html");
|
|
4308
|
-
try {
|
|
4309
|
-
await this.session.exportToHtml(tmpFile);
|
|
4310
|
-
}
|
|
4311
|
-
catch (error) {
|
|
4312
|
-
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4313
|
-
return;
|
|
4314
|
-
}
|
|
4315
|
-
// Show cancellable loader, replacing the editor
|
|
4316
|
-
const loader = new BorderedLoader(this.ui, theme, "Creating gist...");
|
|
4317
|
-
this.editorContainer.clear();
|
|
4318
|
-
this.editorContainer.addChild(loader);
|
|
4319
|
-
this.ui.setFocus(loader);
|
|
4320
|
-
this.ui.requestRender();
|
|
4321
|
-
const restoreEditor = () => {
|
|
4322
|
-
loader.dispose();
|
|
4323
|
-
this.editorContainer.clear();
|
|
4324
|
-
this.editorContainer.addChild(this.editor);
|
|
4325
|
-
this.ui.setFocus(this.editor);
|
|
4326
|
-
try {
|
|
4327
|
-
fs.unlinkSync(tmpFile);
|
|
4328
|
-
}
|
|
4329
|
-
catch {
|
|
4330
|
-
// Ignore cleanup errors
|
|
4331
|
-
}
|
|
4332
|
-
};
|
|
4333
|
-
// Create a secret gist asynchronously
|
|
4334
|
-
let proc = null;
|
|
4335
|
-
loader.onAbort = () => {
|
|
4336
|
-
proc?.kill();
|
|
4337
|
-
restoreEditor();
|
|
4338
|
-
this.showStatus("Share cancelled");
|
|
4339
|
-
};
|
|
4340
|
-
try {
|
|
4341
|
-
const result = await new Promise((resolve) => {
|
|
4342
|
-
proc = spawn("gh", ["gist", "create", "--public=false", tmpFile]);
|
|
4343
|
-
let stdout = "";
|
|
4344
|
-
let stderr = "";
|
|
4345
|
-
proc.stdout?.on("data", (data) => {
|
|
4346
|
-
stdout += data.toString();
|
|
4347
|
-
});
|
|
4348
|
-
proc.stderr?.on("data", (data) => {
|
|
4349
|
-
stderr += data.toString();
|
|
4350
|
-
});
|
|
4351
|
-
proc.on("close", (code) => resolve({ stdout, stderr, code }));
|
|
4352
|
-
});
|
|
4353
|
-
if (loader.signal.aborted)
|
|
4354
|
-
return;
|
|
4355
|
-
restoreEditor();
|
|
4356
|
-
if (result.code !== 0) {
|
|
4357
|
-
const errorMsg = result.stderr?.trim() || "Unknown error";
|
|
4358
|
-
this.showError(`Failed to create gist: ${errorMsg}`);
|
|
4359
|
-
return;
|
|
4360
|
-
}
|
|
4361
|
-
// Extract gist ID from the URL returned by gh
|
|
4362
|
-
// gh returns something like: https://gist.github.com/username/GIST_ID
|
|
4363
|
-
const gistUrl = result.stdout?.trim();
|
|
4364
|
-
const gistId = gistUrl?.split("/").pop();
|
|
4365
|
-
if (!gistId) {
|
|
4366
|
-
this.showError("Failed to parse gist ID from gh output");
|
|
4367
|
-
return;
|
|
4368
|
-
}
|
|
4369
|
-
// Create the preview URL
|
|
4370
|
-
const previewUrl = getShareViewerUrl(gistId);
|
|
4371
|
-
this.showStatus(`Share URL: ${previewUrl}\nGist: ${gistUrl}`);
|
|
4372
|
-
}
|
|
4373
|
-
catch (error) {
|
|
4374
|
-
if (!loader.signal.aborted) {
|
|
4375
|
-
restoreEditor();
|
|
4376
|
-
this.showError(`Failed to create gist: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4377
|
-
}
|
|
4378
|
-
}
|
|
4379
|
-
}
|
|
4380
|
-
async handleCopyCommand() {
|
|
4381
|
-
const text = this.session.getLastAssistantText();
|
|
4382
|
-
if (!text) {
|
|
4383
|
-
this.showError("No agent messages to copy yet.");
|
|
4384
|
-
return;
|
|
4385
|
-
}
|
|
4386
|
-
try {
|
|
4387
|
-
await copyToClipboard(text);
|
|
4388
|
-
this.showStatus("Copied last agent message to clipboard");
|
|
4389
|
-
}
|
|
4390
|
-
catch (error) {
|
|
4391
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
4392
|
-
}
|
|
4393
|
-
}
|
|
4394
|
-
handleNameCommand(text) {
|
|
4395
|
-
const name = text.replace(/^\/name\s*/, "").trim();
|
|
4396
|
-
if (!name) {
|
|
4397
|
-
const currentName = this.sessionManager.getSessionName();
|
|
4398
|
-
if (currentName) {
|
|
4399
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4400
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name: ${currentName}`), 1, 0));
|
|
4401
|
-
}
|
|
4402
|
-
else {
|
|
4403
|
-
this.showWarning("Usage: /name <name>");
|
|
4404
|
-
}
|
|
4405
|
-
this.ui.requestRender();
|
|
4406
|
-
return;
|
|
4407
|
-
}
|
|
4408
|
-
this.session.setSessionName(name);
|
|
4409
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4410
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
|
|
4411
|
-
this.ui.requestRender();
|
|
4412
|
-
}
|
|
4413
|
-
handleSessionCommand() {
|
|
4414
|
-
const stats = this.session.getSessionStats();
|
|
4415
|
-
const sessionName = this.sessionManager.getSessionName();
|
|
4416
|
-
let info = `${theme.bold("Session Info")}\n\n`;
|
|
4417
|
-
if (sessionName) {
|
|
4418
|
-
info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
|
|
4419
|
-
}
|
|
4420
|
-
info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
|
|
4421
|
-
info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
|
|
4422
|
-
info += `${theme.bold("Messages")}\n`;
|
|
4423
|
-
info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
|
|
4424
|
-
info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
|
|
4425
|
-
info += `${theme.fg("dim", "Tool Calls:")} ${stats.toolCalls}\n`;
|
|
4426
|
-
info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
|
|
4427
|
-
info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
|
|
4428
|
-
info += `${theme.bold("Tokens")}\n`;
|
|
4429
|
-
info += `${theme.fg("dim", "Input:")} ${stats.tokens.input.toLocaleString()}\n`;
|
|
4430
|
-
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
4431
|
-
if (stats.tokens.cacheRead > 0) {
|
|
4432
|
-
info += `${theme.fg("dim", "Cache Read:")} ${stats.tokens.cacheRead.toLocaleString()}\n`;
|
|
4433
|
-
}
|
|
4434
|
-
if (stats.tokens.cacheWrite > 0) {
|
|
4435
|
-
info += `${theme.fg("dim", "Cache Write:")} ${stats.tokens.cacheWrite.toLocaleString()}\n`;
|
|
4436
|
-
}
|
|
4437
|
-
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
4438
|
-
if (stats.cost > 0) {
|
|
4439
|
-
info += `\n${theme.bold("Cost")}\n`;
|
|
4440
|
-
info += `${theme.fg("dim", "Total:")} ${stats.cost.toFixed(4)}`;
|
|
4441
|
-
}
|
|
4442
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4443
|
-
this.chatContainer.addChild(new Text(info, 1, 0));
|
|
4444
|
-
this.ui.requestRender();
|
|
4445
|
-
}
|
|
4446
|
-
handleChangelogCommand() {
|
|
4447
|
-
const changelogPath = getChangelogPath();
|
|
4448
|
-
const allEntries = parseChangelog(changelogPath);
|
|
4449
|
-
if (allEntries.length === 0) {
|
|
4450
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4451
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", "No changelog entries found."), 1, 0));
|
|
4452
|
-
this.ui.requestRender();
|
|
4453
|
-
return;
|
|
4454
|
-
}
|
|
4455
|
-
const changelogMarkdown = allEntries
|
|
4456
|
-
.slice()
|
|
4457
|
-
.reverse()
|
|
4458
|
-
.map((e) => e.content)
|
|
4459
|
-
.join("\n\n");
|
|
4460
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4461
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4462
|
-
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
|
|
4463
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4464
|
-
this.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, this.getMarkdownThemeWithSettings()));
|
|
4465
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4466
|
-
this.ui.requestRender();
|
|
4467
|
-
}
|
|
4468
4169
|
/**
|
|
4469
4170
|
* Get capitalized display string for an app keybinding action.
|
|
4470
4171
|
*/
|
|
4471
4172
|
getAppKeyDisplay(action) {
|
|
4472
4173
|
return keyDisplayText(action);
|
|
4473
4174
|
}
|
|
4474
|
-
/**
|
|
4475
|
-
* Get capitalized display string for an editor keybinding action.
|
|
4476
|
-
*/
|
|
4477
|
-
getEditorKeyDisplay(action) {
|
|
4478
|
-
return keyDisplayText(action);
|
|
4479
|
-
}
|
|
4480
|
-
handleHotkeysCommand() {
|
|
4481
|
-
// Navigation keybindings
|
|
4482
|
-
const cursorUp = this.getEditorKeyDisplay("tui.editor.cursorUp");
|
|
4483
|
-
const cursorDown = this.getEditorKeyDisplay("tui.editor.cursorDown");
|
|
4484
|
-
const cursorLeft = this.getEditorKeyDisplay("tui.editor.cursorLeft");
|
|
4485
|
-
const cursorRight = this.getEditorKeyDisplay("tui.editor.cursorRight");
|
|
4486
|
-
const cursorWordLeft = this.getEditorKeyDisplay("tui.editor.cursorWordLeft");
|
|
4487
|
-
const cursorWordRight = this.getEditorKeyDisplay("tui.editor.cursorWordRight");
|
|
4488
|
-
const cursorLineStart = this.getEditorKeyDisplay("tui.editor.cursorLineStart");
|
|
4489
|
-
const cursorLineEnd = this.getEditorKeyDisplay("tui.editor.cursorLineEnd");
|
|
4490
|
-
const jumpForward = this.getEditorKeyDisplay("tui.editor.jumpForward");
|
|
4491
|
-
const jumpBackward = this.getEditorKeyDisplay("tui.editor.jumpBackward");
|
|
4492
|
-
const pageUp = this.getEditorKeyDisplay("tui.editor.pageUp");
|
|
4493
|
-
const pageDown = this.getEditorKeyDisplay("tui.editor.pageDown");
|
|
4494
|
-
// Editing keybindings
|
|
4495
|
-
const submit = this.getEditorKeyDisplay("tui.input.submit");
|
|
4496
|
-
const newLine = this.getEditorKeyDisplay("tui.input.newLine");
|
|
4497
|
-
const deleteWordBackward = this.getEditorKeyDisplay("tui.editor.deleteWordBackward");
|
|
4498
|
-
const deleteWordForward = this.getEditorKeyDisplay("tui.editor.deleteWordForward");
|
|
4499
|
-
const deleteToLineStart = this.getEditorKeyDisplay("tui.editor.deleteToLineStart");
|
|
4500
|
-
const deleteToLineEnd = this.getEditorKeyDisplay("tui.editor.deleteToLineEnd");
|
|
4501
|
-
const yank = this.getEditorKeyDisplay("tui.editor.yank");
|
|
4502
|
-
const yankPop = this.getEditorKeyDisplay("tui.editor.yankPop");
|
|
4503
|
-
const undo = this.getEditorKeyDisplay("tui.editor.undo");
|
|
4504
|
-
const tab = this.getEditorKeyDisplay("tui.input.tab");
|
|
4505
|
-
// App keybindings
|
|
4506
|
-
const interrupt = this.getAppKeyDisplay("app.interrupt");
|
|
4507
|
-
const clear = this.getAppKeyDisplay("app.clear");
|
|
4508
|
-
const exit = this.getAppKeyDisplay("app.exit");
|
|
4509
|
-
const suspend = this.getAppKeyDisplay("app.suspend");
|
|
4510
|
-
const cycleThinkingLevel = this.getAppKeyDisplay("app.thinking.cycle");
|
|
4511
|
-
const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
|
|
4512
|
-
const selectModel = this.getAppKeyDisplay("app.model.select");
|
|
4513
|
-
const expandTools = this.getAppKeyDisplay("app.tools.expand");
|
|
4514
|
-
const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
|
|
4515
|
-
const externalEditor = this.getAppKeyDisplay("app.editor.external");
|
|
4516
|
-
const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
|
|
4517
|
-
const followUp = this.getAppKeyDisplay("app.message.followUp");
|
|
4518
|
-
const dequeue = this.getAppKeyDisplay("app.message.dequeue");
|
|
4519
|
-
const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
|
|
4520
|
-
let hotkeys = `
|
|
4521
|
-
**Navigation**
|
|
4522
|
-
| Key | Action |
|
|
4523
|
-
|-----|--------|
|
|
4524
|
-
| \`${cursorUp}\` / \`${cursorDown}\` / \`${cursorLeft}\` / \`${cursorRight}\` | Move cursor / browse history (Up when empty) |
|
|
4525
|
-
| \`${cursorWordLeft}\` / \`${cursorWordRight}\` | Move by word |
|
|
4526
|
-
| \`${cursorLineStart}\` | Start of line |
|
|
4527
|
-
| \`${cursorLineEnd}\` | End of line |
|
|
4528
|
-
| \`${jumpForward}\` | Jump forward to character |
|
|
4529
|
-
| \`${jumpBackward}\` | Jump backward to character |
|
|
4530
|
-
| \`${pageUp}\` / \`${pageDown}\` | Scroll by page |
|
|
4531
|
-
|
|
4532
|
-
**Editing**
|
|
4533
|
-
| Key | Action |
|
|
4534
|
-
|-----|--------|
|
|
4535
|
-
| \`${submit}\` | Send message |
|
|
4536
|
-
| \`${newLine}\` | New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""} |
|
|
4537
|
-
| \`${deleteWordBackward}\` | Delete word backwards |
|
|
4538
|
-
| \`${deleteWordForward}\` | Delete word forwards |
|
|
4539
|
-
| \`${deleteToLineStart}\` | Delete to start of line |
|
|
4540
|
-
| \`${deleteToLineEnd}\` | Delete to end of line |
|
|
4541
|
-
| \`${yank}\` | Paste the most-recently-deleted text |
|
|
4542
|
-
| \`${yankPop}\` | Cycle through the deleted text after pasting |
|
|
4543
|
-
| \`${undo}\` | Undo |
|
|
4544
|
-
|
|
4545
|
-
**Other**
|
|
4546
|
-
| Key | Action |
|
|
4547
|
-
|-----|--------|
|
|
4548
|
-
| \`${tab}\` | Path completion / accept autocomplete |
|
|
4549
|
-
| \`${interrupt}\` | Cancel autocomplete / abort streaming |
|
|
4550
|
-
| \`${clear}\` | Clear editor (first) / exit (second) |
|
|
4551
|
-
| \`${exit}\` | Exit (when editor is empty) |
|
|
4552
|
-
| \`${suspend}\` | Suspend to background |
|
|
4553
|
-
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
4554
|
-
| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
|
|
4555
|
-
| \`${selectModel}\` | Open model selector |
|
|
4556
|
-
| \`${expandTools}\` | Toggle tool output expansion |
|
|
4557
|
-
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
4558
|
-
| \`${externalEditor}\` | Edit message in external editor |
|
|
4559
|
-
| \`${followUp}\` | Queue follow-up message |
|
|
4560
|
-
| \`${dequeue}\` | Restore queued messages |
|
|
4561
|
-
| \`${pasteImage}\` | Paste image from clipboard |
|
|
4562
|
-
| \`/\` | Slash commands |
|
|
4563
|
-
| \`!\` | Run bash command |
|
|
4564
|
-
| \`!!\` | Run bash command (excluded from context) |
|
|
4565
|
-
`;
|
|
4566
|
-
// Add extension-registered shortcuts
|
|
4567
|
-
const extensionRunner = this.session.extensionRunner;
|
|
4568
|
-
const shortcuts = extensionRunner.getShortcuts(this.keybindings.getEffectiveConfig());
|
|
4569
|
-
if (shortcuts.size > 0) {
|
|
4570
|
-
hotkeys += `
|
|
4571
|
-
**Extensions**
|
|
4572
|
-
| Key | Action |
|
|
4573
|
-
|-----|--------|
|
|
4574
|
-
`;
|
|
4575
|
-
for (const [key, shortcut] of shortcuts) {
|
|
4576
|
-
const description = shortcut.description ?? shortcut.extensionPath;
|
|
4577
|
-
const keyDisplay = formatKeyText(key, { capitalize: true });
|
|
4578
|
-
hotkeys += `| \`${keyDisplay}\` | ${description} |\n`;
|
|
4579
|
-
}
|
|
4580
|
-
}
|
|
4581
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4582
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4583
|
-
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Keyboard Shortcuts")), 1, 0));
|
|
4584
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4585
|
-
this.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, this.getMarkdownThemeWithSettings()));
|
|
4586
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
4587
|
-
this.ui.requestRender();
|
|
4588
|
-
}
|
|
4589
|
-
async handleClearCommand() {
|
|
4590
|
-
if (this.loadingAnimation) {
|
|
4591
|
-
this.loadingAnimation.stop();
|
|
4592
|
-
this.loadingAnimation = undefined;
|
|
4593
|
-
}
|
|
4594
|
-
this.statusContainer.clear();
|
|
4595
|
-
try {
|
|
4596
|
-
const result = await this.runtimeHost.newSession();
|
|
4597
|
-
if (result.cancelled) {
|
|
4598
|
-
return;
|
|
4599
|
-
}
|
|
4600
|
-
this.renderCurrentSessionState();
|
|
4601
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4602
|
-
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
|
|
4603
|
-
this.ui.requestRender();
|
|
4604
|
-
}
|
|
4605
|
-
catch (error) {
|
|
4606
|
-
await this.handleFatalRuntimeError("Failed to create session", error);
|
|
4607
|
-
}
|
|
4608
|
-
}
|
|
4609
|
-
handleDebugCommand() {
|
|
4610
|
-
const width = this.ui.terminal.columns;
|
|
4611
|
-
const height = this.ui.terminal.rows;
|
|
4612
|
-
const allLines = this.ui.render(width);
|
|
4613
|
-
const debugLogPath = getDebugLogPath();
|
|
4614
|
-
const debugData = [
|
|
4615
|
-
`Debug output at ${new Date().toISOString()}`,
|
|
4616
|
-
`Terminal: ${width}x${height}`,
|
|
4617
|
-
`Total lines: ${allLines.length}`,
|
|
4618
|
-
"",
|
|
4619
|
-
"=== All rendered lines with visible widths ===",
|
|
4620
|
-
...allLines.map((line, idx) => {
|
|
4621
|
-
const vw = visibleWidth(line);
|
|
4622
|
-
const escaped = JSON.stringify(line);
|
|
4623
|
-
return `[${idx}] (w=${vw}) ${escaped}`;
|
|
4624
|
-
}),
|
|
4625
|
-
"",
|
|
4626
|
-
"=== Agent messages (JSONL) ===",
|
|
4627
|
-
...this.session.messages.map((msg) => JSON.stringify(msg)),
|
|
4628
|
-
"",
|
|
4629
|
-
].join("\n");
|
|
4630
|
-
fs.mkdirSync(path.dirname(debugLogPath), { recursive: true });
|
|
4631
|
-
fs.writeFileSync(debugLogPath, debugData);
|
|
4632
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4633
|
-
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ Debug log written")}\n${theme.fg("muted", debugLogPath)}`, 1, 1));
|
|
4634
|
-
this.ui.requestRender();
|
|
4635
|
-
}
|
|
4636
|
-
handleArminSaysHi() {
|
|
4637
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
4638
|
-
this.chatContainer.addChild(new ArminComponent(this.ui));
|
|
4639
|
-
this.ui.requestRender();
|
|
4640
|
-
}
|
|
4641
4175
|
handleDaxnuts() {
|
|
4642
4176
|
this.chatContainer.addChild(new Spacer(1));
|
|
4643
4177
|
this.chatContainer.addChild(new DaxnutsComponent(this.ui));
|