@di-code/coding-agent 0.1.3 → 0.1.6
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/README.md +91 -30
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +96 -17
- package/dist/cli.js.map +1 -1
- package/dist/core/clipboard-image.d.ts +3 -3
- package/dist/core/clipboard-image.d.ts.map +1 -1
- package/dist/core/clipboard-image.js +26 -13
- package/dist/core/clipboard-image.js.map +1 -1
- package/dist/core/context-builder.d.ts.map +1 -1
- package/dist/core/context-builder.js +6 -2
- package/dist/core/context-builder.js.map +1 -1
- package/dist/core/image-input.d.ts +1 -1
- package/dist/core/image-input.d.ts.map +1 -1
- package/dist/core/image-input.js +55 -10
- package/dist/core/image-input.js.map +1 -1
- package/dist/core/resources/loader.d.ts.map +1 -1
- package/dist/core/resources/loader.js +15 -42
- package/dist/core/resources/loader.js.map +1 -1
- package/dist/core/resources/types.d.ts +6 -9
- package/dist/core/resources/types.d.ts.map +1 -1
- package/dist/core/resources/types.js.map +1 -1
- package/dist/core/session/session-manager.d.ts +12 -1
- package/dist/core/session/session-manager.d.ts.map +1 -1
- package/dist/core/session/session-manager.js +66 -3
- package/dist/core/session/session-manager.js.map +1 -1
- package/dist/core/session/session-storage.d.ts.map +1 -1
- package/dist/core/session/session-storage.js +30 -15
- package/dist/core/session/session-storage.js.map +1 -1
- package/dist/core/session/types.d.ts +6 -1
- package/dist/core/session/types.d.ts.map +1 -1
- package/dist/core/session/types.js +1 -1
- package/dist/core/session/types.js.map +1 -1
- package/dist/core/session.d.ts +38 -3
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +167 -9
- package/dist/core/session.js.map +1 -1
- package/dist/core/system-prompt.js +2 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -2
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +25 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -0
- package/dist/core/tools/edit-diff.js +136 -0
- package/dist/core/tools/edit-diff.js.map +1 -0
- package/dist/core/tools/edit.d.ts +13 -4
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +64 -30
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-search.d.ts +14 -0
- package/dist/core/tools/file-search.d.ts.map +1 -0
- package/dist/core/tools/file-search.js +99 -0
- package/dist/core/tools/file-search.js.map +1 -0
- package/dist/core/tools/glob.d.ts +11 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +44 -0
- package/dist/core/tools/glob.js.map +1 -0
- package/dist/core/tools/grep.d.ts +13 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +67 -0
- package/dist/core/tools/grep.js.map +1 -0
- package/dist/core/tools/load-skill.d.ts +10 -0
- package/dist/core/tools/load-skill.d.ts.map +1 -0
- package/dist/core/tools/load-skill.js +22 -0
- package/dist/core/tools/load-skill.js.map +1 -0
- package/dist/core/tools/path-boundary.d.ts +2 -0
- package/dist/core/tools/path-boundary.d.ts.map +1 -1
- package/dist/core/tools/path-boundary.js +4 -0
- package/dist/core/tools/path-boundary.js.map +1 -1
- package/dist/core/tools/read.d.ts +2 -2
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/write.d.ts +2 -2
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/user-data.d.ts +3 -0
- package/dist/core/user-data.d.ts.map +1 -0
- package/dist/core/user-data.js +8 -0
- package/dist/core/user-data.js.map +1 -0
- package/dist/entry.js +22 -2
- package/dist/entry.js.map +1 -1
- package/dist/extensions/runtime.js +2 -2
- package/dist/extensions/runtime.js.map +1 -1
- package/dist/i18n.d.ts +5 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +223 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +20 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +272 -17
- package/dist/main.js.map +1 -1
- package/dist/mcp/config.d.ts +33 -0
- package/dist/mcp/config.d.ts.map +1 -0
- package/dist/mcp/config.js +211 -0
- package/dist/mcp/config.js.map +1 -0
- package/dist/mcp/loader.d.ts +21 -0
- package/dist/mcp/loader.d.ts.map +1 -0
- package/dist/mcp/loader.js +60 -0
- package/dist/mcp/loader.js.map +1 -0
- package/dist/mcp/tool-adapter.d.ts +5 -0
- package/dist/mcp/tool-adapter.d.ts.map +1 -0
- package/dist/mcp/tool-adapter.js +149 -0
- package/dist/mcp/tool-adapter.js.map +1 -0
- package/dist/modes/interactive-components.d.ts +2 -0
- package/dist/modes/interactive-components.d.ts.map +1 -1
- package/dist/modes/interactive-components.js +55 -31
- package/dist/modes/interactive-components.js.map +1 -1
- package/dist/modes/interactive-diff.d.ts +8 -0
- package/dist/modes/interactive-diff.d.ts.map +1 -0
- package/dist/modes/interactive-diff.js +78 -0
- package/dist/modes/interactive-diff.js.map +1 -0
- package/dist/modes/interactive-layout.d.ts +4 -0
- package/dist/modes/interactive-layout.d.ts.map +1 -1
- package/dist/modes/interactive-layout.js +4 -0
- package/dist/modes/interactive-layout.js.map +1 -1
- package/dist/modes/interactive-state.d.ts +5 -0
- package/dist/modes/interactive-state.d.ts.map +1 -1
- package/dist/modes/interactive-state.js +65 -11
- package/dist/modes/interactive-state.js.map +1 -1
- package/dist/modes/interactive.d.ts +19 -0
- package/dist/modes/interactive.d.ts.map +1 -1
- package/dist/modes/interactive.js +351 -52
- package/dist/modes/interactive.js.map +1 -1
- package/dist/modes/tree-selector.d.ts +30 -0
- package/dist/modes/tree-selector.d.ts.map +1 -0
- package/dist/modes/tree-selector.js +205 -0
- package/dist/modes/tree-selector.js.map +1 -0
- package/dist/plugins/loader.d.ts +19 -1
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +21 -2
- package/dist/plugins/loader.js.map +1 -1
- package/dist/provider-onboarding.d.ts +8 -1
- package/dist/provider-onboarding.d.ts.map +1 -1
- package/dist/provider-onboarding.js +160 -40
- package/dist/provider-onboarding.js.map +1 -1
- package/dist/rpc-entry.js +1 -1
- package/dist/rpc-entry.js.map +1 -1
- package/dist/startup.d.ts +34 -3
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +407 -87
- package/dist/startup.js.map +1 -1
- package/package.json +7 -4
|
@@ -1,15 +1,38 @@
|
|
|
1
1
|
import { statSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { extname, join, resolve } from "node:path";
|
|
3
4
|
import { CombinedAutocompleteProvider, Editor, Key, matchesKey, SelectList, SettingsList, } from "@di-code/tui";
|
|
4
5
|
import { cleanupStaleClipboardImages, clipboardImageDirectory, isClipboardImagePath, readClipboardImagePath, removeClipboardImage, } from "../core/clipboard-image.js";
|
|
5
6
|
import { extractImageAttachments } from "../core/image-input.js";
|
|
7
|
+
import { DEFAULT_LOCALE, translate } from "../i18n.js";
|
|
8
|
+
import { showInteractiveProviderOnboarding, } from "../provider-onboarding.js";
|
|
9
|
+
import { removeGlobalProviderApiKey, saveGlobalLocale } from "../startup.js";
|
|
6
10
|
import { AutocompleteMenu, InteractiveChat, InteractiveComposer, InteractiveFooter, InteractiveHeader, } from "./interactive-components.js";
|
|
7
11
|
import { InteractiveLayout } from "./interactive-layout.js";
|
|
8
12
|
import { InteractiveProjection } from "./interactive-state.js";
|
|
13
|
+
import { TreeSelector } from "./tree-selector.js";
|
|
9
14
|
export { InteractiveProjection } from "./interactive-state.js";
|
|
10
15
|
const IMAGE_EXTENSIONS = new Set([".gif", ".jpeg", ".jpg", ".png", ".webp"]);
|
|
11
16
|
const PASTE_IMAGE_KEY = process.platform === "win32" ? Key.alt("v") : Key.ctrl("v");
|
|
12
17
|
const PASTE_IMAGE_SHORTCUT = process.platform === "win32" ? "Alt+V" : "Ctrl+V";
|
|
18
|
+
function builtinSlashCommands(locale) {
|
|
19
|
+
const t = (key) => translate(locale, key);
|
|
20
|
+
return [
|
|
21
|
+
{ name: "help", description: t("showInteractiveCommands") },
|
|
22
|
+
{ name: "clear", description: t("clearVisibleMessages") },
|
|
23
|
+
{ name: "model", description: t("openModelSelector") },
|
|
24
|
+
{ name: "session", description: t("openSessionSelector") },
|
|
25
|
+
{ name: "tree", description: t("openTreeSelector") },
|
|
26
|
+
{ name: "theme", description: t("openThemeSelector") },
|
|
27
|
+
{ name: "settings", description: t("openSettingsSelector") },
|
|
28
|
+
{ name: "login", description: t("chooseProvider") },
|
|
29
|
+
{ name: "logout", description: t("removeProviderKey") },
|
|
30
|
+
{ name: "compact", description: t("compactContext") },
|
|
31
|
+
{ name: "usage", description: t("showUsage") },
|
|
32
|
+
{ name: "retry", description: t("retryPrompt") },
|
|
33
|
+
{ name: "steer", description: t("steerAgent") },
|
|
34
|
+
];
|
|
35
|
+
}
|
|
13
36
|
function asImageAttachmentReference(pasted, cwd) {
|
|
14
37
|
if (pasted.includes("\n"))
|
|
15
38
|
return pasted;
|
|
@@ -37,6 +60,10 @@ function normalizeDroppedImagePrompt(prompt, cwd) {
|
|
|
37
60
|
? prompt
|
|
38
61
|
: `${imageReference}${normalizeDroppedImagePrompt(prefix[2] ?? "", cwd)}`;
|
|
39
62
|
}
|
|
63
|
+
function steeringCommandArgument(prompt) {
|
|
64
|
+
const match = /^\/steer(?:\s+([\s\S]*))?$/i.exec(prompt);
|
|
65
|
+
return match ? (match[1] ?? "").trim() : undefined;
|
|
66
|
+
}
|
|
40
67
|
export class InteractiveMode {
|
|
41
68
|
projection = new InteractiveProjection();
|
|
42
69
|
root;
|
|
@@ -45,6 +72,8 @@ export class InteractiveMode {
|
|
|
45
72
|
tui;
|
|
46
73
|
sessionChoices;
|
|
47
74
|
extensionHost;
|
|
75
|
+
providerOnboarding;
|
|
76
|
+
agentDir;
|
|
48
77
|
readClipboardImagePath;
|
|
49
78
|
clipboardDirectory;
|
|
50
79
|
clipboardFiles = new Set();
|
|
@@ -56,6 +85,7 @@ export class InteractiveMode {
|
|
|
56
85
|
started = false;
|
|
57
86
|
lastFailedPrompt;
|
|
58
87
|
queuedPrompts = [];
|
|
88
|
+
steeringPrompts = [];
|
|
59
89
|
onExit;
|
|
60
90
|
theme = "dark";
|
|
61
91
|
overlay;
|
|
@@ -63,29 +93,22 @@ export class InteractiveMode {
|
|
|
63
93
|
spinnerTimer;
|
|
64
94
|
shutdownEmitted = false;
|
|
65
95
|
preserveClipboardFiles = false;
|
|
96
|
+
locale;
|
|
66
97
|
constructor(options) {
|
|
67
98
|
this.session = options.session;
|
|
99
|
+
this.projection.configureFilePreview(this.session.allowedRoot, () => this.refresh());
|
|
68
100
|
this.tui = options.tui;
|
|
69
101
|
this.onExit = options.onExit;
|
|
70
102
|
this.sessionChoices = [...(options.sessions ?? [])];
|
|
71
103
|
this.extensionHost = options.extensionHost;
|
|
104
|
+
this.providerOnboarding = options.providerOnboarding;
|
|
105
|
+
this.agentDir = resolve(options.agentDir ?? join(homedir(), ".di-code"));
|
|
106
|
+
this.locale = options.locale ?? DEFAULT_LOCALE;
|
|
72
107
|
this.readClipboardImagePath = options.readClipboardImagePath ?? readClipboardImagePath;
|
|
73
|
-
this.clipboardDirectory = clipboardImageDirectory(this.session.allowedRoot);
|
|
74
|
-
const commands = [
|
|
75
|
-
{ name: "help", description: "Show interactive commands" },
|
|
76
|
-
{ name: "clear", description: "Clear visible messages" },
|
|
77
|
-
{ name: "model", description: "Open the model selector" },
|
|
78
|
-
{ name: "session", description: "Open the session selector" },
|
|
79
|
-
{ name: "theme", description: "Open the theme selector" },
|
|
80
|
-
{ name: "settings", description: "Open the settings selector" },
|
|
81
|
-
{ name: "compact", description: "Compact the persisted context now" },
|
|
82
|
-
{ name: "usage", description: "Show token and cost usage" },
|
|
83
|
-
{ name: "retry", description: "Retry the last failed prompt" },
|
|
84
|
-
];
|
|
85
|
-
const allCommands = [...commands, ...(this.extensionHost?.listCommands() ?? [])];
|
|
108
|
+
this.clipboardDirectory = clipboardImageDirectory(this.agentDir, this.session.allowedRoot);
|
|
86
109
|
const autocomplete = {
|
|
87
|
-
getSuggestions: (context, autocompleteOptions) => new CombinedAutocompleteProvider(
|
|
88
|
-
applyCompletion: (context, item, prefix) => new CombinedAutocompleteProvider(
|
|
110
|
+
getSuggestions: (context, autocompleteOptions) => new CombinedAutocompleteProvider(this.listSlashCommands(), this.session.allowedRoot).getSuggestions(context, autocompleteOptions),
|
|
111
|
+
applyCompletion: (context, item, prefix) => new CombinedAutocompleteProvider(this.listSlashCommands(), this.session.allowedRoot).applyCompletion(context, item, prefix),
|
|
89
112
|
};
|
|
90
113
|
this.editor = new Editor({
|
|
91
114
|
maxHeight: 3,
|
|
@@ -106,8 +129,9 @@ export class InteractiveMode {
|
|
|
106
129
|
this.editor.onAutocompleteChange = () => this.updateAutocompleteOverlay();
|
|
107
130
|
const readViewState = () => ({
|
|
108
131
|
...this.projection.state,
|
|
109
|
-
model: this.session.modelId
|
|
132
|
+
model: `${this.session.modelId}${this.session.thinkingLevel ? `(${this.session.thinkingLevel})` : ""}`,
|
|
110
133
|
theme: this.theme,
|
|
134
|
+
locale: this.locale,
|
|
111
135
|
pasteImageShortcut: PASTE_IMAGE_SHORTCUT,
|
|
112
136
|
});
|
|
113
137
|
this.root = new InteractiveLayout({
|
|
@@ -129,7 +153,7 @@ export class InteractiveMode {
|
|
|
129
153
|
this.projection.replaceTranscript(this.session.transcript);
|
|
130
154
|
this.projection.setUsage(this.session.usage);
|
|
131
155
|
this.subscribeToSession();
|
|
132
|
-
void cleanupStaleClipboardImages(this.session.allowedRoot).catch((cause) => {
|
|
156
|
+
void cleanupStaleClipboardImages(this.agentDir, this.session.allowedRoot).catch((cause) => {
|
|
133
157
|
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
134
158
|
this.refresh();
|
|
135
159
|
});
|
|
@@ -170,6 +194,21 @@ export class InteractiveMode {
|
|
|
170
194
|
this.onExit?.();
|
|
171
195
|
}
|
|
172
196
|
handleCommand(data) {
|
|
197
|
+
if (matchesKey(data, Key.alt("s"))) {
|
|
198
|
+
void this.submitSteering(this.editor.getValue());
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
if (data === "\x1b[Z") {
|
|
202
|
+
try {
|
|
203
|
+
const level = this.session.cycleThinkingLevel();
|
|
204
|
+
this.projection.setStatus(level ? `thinking=${level}` : "Current model does not support thinking levels.");
|
|
205
|
+
}
|
|
206
|
+
catch (cause) {
|
|
207
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
208
|
+
}
|
|
209
|
+
this.refresh();
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
173
212
|
if (data === "\x14") {
|
|
174
213
|
this.openThemeSelector();
|
|
175
214
|
return true;
|
|
@@ -200,7 +239,7 @@ export class InteractiveMode {
|
|
|
200
239
|
try {
|
|
201
240
|
const path = await this.readClipboardImagePath(this.clipboardDirectory);
|
|
202
241
|
if (!path) {
|
|
203
|
-
this.projection.setStatus("Clipboard has no supported image.");
|
|
242
|
+
this.projection.setStatus(this.locale === "zh-CN" ? "剪贴板中没有受支持的图片。" : "Clipboard has no supported image.");
|
|
204
243
|
}
|
|
205
244
|
else {
|
|
206
245
|
this.editor.insertTextAtCursor(path);
|
|
@@ -214,9 +253,10 @@ export class InteractiveMode {
|
|
|
214
253
|
this.refresh();
|
|
215
254
|
}
|
|
216
255
|
openThemeSelector() {
|
|
256
|
+
const t = (key) => translate(this.locale, key);
|
|
217
257
|
const list = new SelectList([
|
|
218
|
-
{ value: "dark", label: "
|
|
219
|
-
{ value: "light", label: "
|
|
258
|
+
{ value: "dark", label: t("dark"), description: t("darkTheme") },
|
|
259
|
+
{ value: "light", label: t("light"), description: t("lightTheme") },
|
|
220
260
|
]);
|
|
221
261
|
list.onSelect = (item) => {
|
|
222
262
|
this.theme = item.value === "light" ? "light" : "dark";
|
|
@@ -248,11 +288,12 @@ export class InteractiveMode {
|
|
|
248
288
|
this.showOverlay(list);
|
|
249
289
|
}
|
|
250
290
|
openSessionSelector() {
|
|
291
|
+
const t = (key) => translate(this.locale, key);
|
|
251
292
|
const list = new SelectList([
|
|
252
293
|
{
|
|
253
294
|
value: "__current__",
|
|
254
|
-
label: "
|
|
255
|
-
description: this.session.sessionFile ?? "
|
|
295
|
+
label: t("currentSession"),
|
|
296
|
+
description: this.session.sessionFile ?? t("inMemorySession"),
|
|
256
297
|
},
|
|
257
298
|
...this.sessionChoices.map((choice) => ({
|
|
258
299
|
value: choice.id,
|
|
@@ -276,28 +317,124 @@ export class InteractiveMode {
|
|
|
276
317
|
list.onCancel = () => this.closeOverlay();
|
|
277
318
|
this.showOverlay(list);
|
|
278
319
|
}
|
|
320
|
+
openTreeSelector() {
|
|
321
|
+
if (this.promptInFlight || this.compactionInFlight || this.sessionSwitching) {
|
|
322
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
323
|
+
? "提示词运行时不能浏览会话树。"
|
|
324
|
+
: "Cannot browse the session tree while a prompt is running.");
|
|
325
|
+
this.refresh();
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (this.session.sessionTree.length === 0) {
|
|
329
|
+
this.projection.setStatus(this.locale === "zh-CN" ? "当前会话还没有历史节点。" : "The current session has no history nodes.");
|
|
330
|
+
this.refresh();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const selector = new TreeSelector({
|
|
334
|
+
nodes: this.session.sessionTree,
|
|
335
|
+
leafId: this.session.sessionLeafId,
|
|
336
|
+
locale: this.locale,
|
|
337
|
+
onContinue: (entry) => {
|
|
338
|
+
this.closeOverlay();
|
|
339
|
+
void this.navigateTreeEntry(entry.id);
|
|
340
|
+
},
|
|
341
|
+
onEdit: (entry) => {
|
|
342
|
+
if (entry.type !== "message" || entry.message.role !== "user") {
|
|
343
|
+
this.projection.setError(this.locale === "zh-CN" ? "只能编辑历史用户消息。" : "Only historical user messages can be edited.");
|
|
344
|
+
this.refresh();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
this.closeOverlay();
|
|
348
|
+
void this.navigateTreeEntry(entry.id);
|
|
349
|
+
},
|
|
350
|
+
onSummarize: (entry) => {
|
|
351
|
+
this.closeOverlay();
|
|
352
|
+
void this.summarizeTreeBranch(entry.id);
|
|
353
|
+
},
|
|
354
|
+
onCancel: () => this.closeOverlay(),
|
|
355
|
+
});
|
|
356
|
+
this.showOverlay(selector, true, "80%");
|
|
357
|
+
}
|
|
358
|
+
async navigateTreeEntry(entryId) {
|
|
359
|
+
try {
|
|
360
|
+
const result = await this.session.navigateTree(entryId);
|
|
361
|
+
if (!this.started)
|
|
362
|
+
return false;
|
|
363
|
+
this.projection.replaceTranscript(this.session.transcript);
|
|
364
|
+
this.editor.setValue(result.editorText ?? "");
|
|
365
|
+
this.projection.setStatus(result.imagesOmitted
|
|
366
|
+
? this.locale === "zh-CN"
|
|
367
|
+
? "已恢复文本;请重新附加图片。"
|
|
368
|
+
: "Text restored; reattach images before sending."
|
|
369
|
+
: `tree=${result.selectedEntryId}`);
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
catch (cause) {
|
|
373
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
this.refresh();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async summarizeTreeBranch(entryId) {
|
|
381
|
+
if (this.compactionInFlight || this.promptInFlight)
|
|
382
|
+
return;
|
|
383
|
+
this.compactionInFlight = true;
|
|
384
|
+
this.projection.setStatus(this.locale === "zh-CN" ? "正在为分支生成摘要。" : "Summarizing selected branch.");
|
|
385
|
+
this.refresh();
|
|
386
|
+
try {
|
|
387
|
+
if (!(await this.navigateTreeEntry(entryId)))
|
|
388
|
+
return;
|
|
389
|
+
await this.session.compact();
|
|
390
|
+
this.editor.setValue("");
|
|
391
|
+
this.projection.replaceTranscript(this.session.transcript);
|
|
392
|
+
this.projection.setStatus(this.locale === "zh-CN" ? "摘要分支已创建。" : "Summary branch created.");
|
|
393
|
+
}
|
|
394
|
+
catch (cause) {
|
|
395
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
396
|
+
}
|
|
397
|
+
finally {
|
|
398
|
+
this.compactionInFlight = false;
|
|
399
|
+
this.refresh();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
279
402
|
openSettingsSelector() {
|
|
403
|
+
const t = (key) => translate(this.locale, key);
|
|
280
404
|
const list = new SettingsList([
|
|
281
405
|
{
|
|
282
406
|
id: "compaction",
|
|
283
|
-
label: "
|
|
284
|
-
currentValue: this.session.compactionEnabled ? "on" : "off",
|
|
285
|
-
values: ["on", "off"],
|
|
286
|
-
description: "Summarize older persisted context before the model limit is reached.",
|
|
407
|
+
label: t("contextCompaction"),
|
|
408
|
+
currentValue: this.session.compactionEnabled ? t("on") : t("off"),
|
|
409
|
+
values: [t("on"), t("off")],
|
|
287
410
|
},
|
|
288
|
-
|
|
411
|
+
{
|
|
412
|
+
id: "locale",
|
|
413
|
+
label: t("language"),
|
|
414
|
+
currentValue: this.locale,
|
|
415
|
+
values: ["en", "zh-CN"],
|
|
416
|
+
},
|
|
417
|
+
], { title: t("settings") });
|
|
289
418
|
list.onChange = (id, value) => {
|
|
419
|
+
if (id === "locale") {
|
|
420
|
+
this.locale = value === "zh-CN" ? "zh-CN" : "en";
|
|
421
|
+
void saveGlobalLocale(this.agentDir, this.locale).catch((cause) => {
|
|
422
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
423
|
+
this.refresh();
|
|
424
|
+
});
|
|
425
|
+
this.closeOverlay();
|
|
426
|
+
this.openSettingsSelector();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
290
429
|
if (id !== "compaction")
|
|
291
430
|
return;
|
|
292
431
|
try {
|
|
293
|
-
const enabled = this.session.setCompactionEnabled(value === "on");
|
|
294
|
-
list.updateValue(id, enabled ? "on" : "off");
|
|
295
|
-
this.projection.setStatus(enabled === (value === "on")
|
|
296
|
-
? `compaction=${enabled ? "on" : "off"}`
|
|
297
|
-
: "compaction unavailable without a persisted session");
|
|
432
|
+
const enabled = this.session.setCompactionEnabled(value === t("on"));
|
|
433
|
+
list.updateValue(id, enabled ? t("on") : t("off"));
|
|
434
|
+
this.projection.setStatus(enabled === (value === t("on")) ? `compaction=${enabled ? t("on") : t("off")}` : t("compactionUnavailable"));
|
|
298
435
|
}
|
|
299
436
|
catch (cause) {
|
|
300
|
-
list.updateValue(id, this.session.compactionEnabled ? "on" : "off");
|
|
437
|
+
list.updateValue(id, this.session.compactionEnabled ? t("on") : t("off"));
|
|
301
438
|
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
302
439
|
this.closeOverlay();
|
|
303
440
|
}
|
|
@@ -306,15 +443,16 @@ export class InteractiveMode {
|
|
|
306
443
|
list.onCancel = () => this.closeOverlay();
|
|
307
444
|
this.showOverlay(list);
|
|
308
445
|
}
|
|
309
|
-
showOverlay(component) {
|
|
446
|
+
showOverlay(component, preserveLastLine = false, maxHeight = "60%") {
|
|
310
447
|
this.editor.cancelAutocomplete();
|
|
311
448
|
this.closeAutocompleteOverlay();
|
|
312
449
|
this.closeOverlay();
|
|
313
450
|
this.overlay = this.tui.showOverlay(component, {
|
|
314
451
|
width: "70%",
|
|
315
|
-
maxHeight
|
|
452
|
+
maxHeight,
|
|
316
453
|
anchor: "center",
|
|
317
454
|
margin: 1,
|
|
455
|
+
preserveLastLine,
|
|
318
456
|
});
|
|
319
457
|
}
|
|
320
458
|
closeOverlay() {
|
|
@@ -331,11 +469,18 @@ export class InteractiveMode {
|
|
|
331
469
|
items: this.editor.getAutocompleteItems(),
|
|
332
470
|
index: this.editor.getAutocompleteIndex(),
|
|
333
471
|
}));
|
|
472
|
+
const editorBounds = this.root.getEditorBounds(this.tui.columns);
|
|
334
473
|
this.autocompleteOverlay = this.tui.showOverlay(menu, {
|
|
335
474
|
width: "55%",
|
|
336
|
-
maxHeight:
|
|
475
|
+
maxHeight: 9,
|
|
337
476
|
anchor: "bottom-left",
|
|
477
|
+
placement: {
|
|
478
|
+
anchorRow: editorBounds.end - 1,
|
|
479
|
+
avoidStartRow: editorBounds.start,
|
|
480
|
+
preferred: "below",
|
|
481
|
+
},
|
|
338
482
|
margin: 1,
|
|
483
|
+
preserveLastLine: true,
|
|
339
484
|
nonCapturing: true,
|
|
340
485
|
});
|
|
341
486
|
this.refresh();
|
|
@@ -366,13 +511,24 @@ export class InteractiveMode {
|
|
|
366
511
|
this.unsubscribeSession = this.session.subscribeSession((event) => {
|
|
367
512
|
if (!this.started)
|
|
368
513
|
return;
|
|
514
|
+
if (event.type === "tree_navigated") {
|
|
515
|
+
this.projection.replaceTranscript(this.session.transcript);
|
|
516
|
+
this.refresh();
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (event.type === "queue_update") {
|
|
520
|
+
this.steeringPrompts = [...event.steering];
|
|
521
|
+
this.refreshQueue();
|
|
522
|
+
this.refresh();
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
369
525
|
this.projection.apply(event);
|
|
370
526
|
this.refresh();
|
|
371
527
|
});
|
|
372
528
|
}
|
|
373
529
|
async switchSession(choice) {
|
|
374
530
|
if (this.promptInFlight || this.sessionSwitching) {
|
|
375
|
-
this.projection.setError("Cannot switch sessions while a prompt is running.");
|
|
531
|
+
this.projection.setError(this.locale === "zh-CN" ? "提示词运行时不能切换会话。" : "Cannot switch sessions while a prompt is running.");
|
|
376
532
|
this.refresh();
|
|
377
533
|
return;
|
|
378
534
|
}
|
|
@@ -385,10 +541,12 @@ export class InteractiveMode {
|
|
|
385
541
|
return;
|
|
386
542
|
this.unsubscribeSession?.();
|
|
387
543
|
this.session = next;
|
|
388
|
-
this.
|
|
389
|
-
|
|
544
|
+
this.projection.configureFilePreview(next.allowedRoot, () => this.refresh());
|
|
545
|
+
this.clipboardDirectory = clipboardImageDirectory(this.agentDir, next.allowedRoot);
|
|
546
|
+
void cleanupStaleClipboardImages(this.agentDir, next.allowedRoot);
|
|
390
547
|
this.queuedPrompts = [];
|
|
391
|
-
this.
|
|
548
|
+
this.steeringPrompts = [];
|
|
549
|
+
this.refreshQueue();
|
|
392
550
|
this.projection.replaceTranscript(next.transcript);
|
|
393
551
|
this.projection.setUsage(next.usage);
|
|
394
552
|
this.projection.setStatus(`session=${choice.id}`);
|
|
@@ -406,24 +564,33 @@ export class InteractiveMode {
|
|
|
406
564
|
const prompt = text.trim();
|
|
407
565
|
if (prompt.length === 0 || !this.started)
|
|
408
566
|
return;
|
|
409
|
-
|
|
567
|
+
const steeringArgument = steeringCommandArgument(prompt);
|
|
568
|
+
if (steeringArgument !== undefined) {
|
|
569
|
+
await this.submitSteering(steeringArgument);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (prompt.startsWith("/") && !this.isLoadedSkillCommand(prompt)) {
|
|
410
573
|
this.editor.setValue("");
|
|
411
574
|
this.handleSlashCommand(prompt);
|
|
412
575
|
return;
|
|
413
576
|
}
|
|
414
577
|
if (this.sessionSwitching) {
|
|
415
|
-
this.projection.setError(
|
|
578
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
579
|
+
? "会话正在打开,请等待后再提交提示词。"
|
|
580
|
+
: "A session is opening; wait before submitting a prompt.");
|
|
416
581
|
this.refresh();
|
|
417
582
|
return;
|
|
418
583
|
}
|
|
419
584
|
if (this.compactionInFlight) {
|
|
420
|
-
this.projection.setError(
|
|
585
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
586
|
+
? "正在压缩上下文,请等待后再提交提示词。"
|
|
587
|
+
: "A compaction is running; wait before submitting a prompt.");
|
|
421
588
|
this.refresh();
|
|
422
589
|
return;
|
|
423
590
|
}
|
|
424
591
|
if (this.promptInFlight) {
|
|
425
592
|
this.queuedPrompts.push(prompt);
|
|
426
|
-
this.
|
|
593
|
+
this.refreshQueue();
|
|
427
594
|
this.root.invalidate();
|
|
428
595
|
this.tui.requestRender();
|
|
429
596
|
return;
|
|
@@ -453,13 +620,57 @@ export class InteractiveMode {
|
|
|
453
620
|
this.activeAbort = undefined;
|
|
454
621
|
this.projection.setRetrying(false);
|
|
455
622
|
const next = this.queuedPrompts.shift();
|
|
456
|
-
this.
|
|
623
|
+
this.refreshQueue();
|
|
457
624
|
this.root.invalidate();
|
|
458
625
|
this.tui.requestRender();
|
|
459
626
|
if (next && this.started)
|
|
460
627
|
void this.submit(next);
|
|
461
628
|
}
|
|
462
629
|
}
|
|
630
|
+
async submitSteering(text) {
|
|
631
|
+
const prompt = text.trim();
|
|
632
|
+
if (prompt.length === 0) {
|
|
633
|
+
this.projection.setError(this.locale === "zh-CN" ? "引导内容不能为空。" : "Steering content must not be empty.");
|
|
634
|
+
this.refresh();
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (this.sessionSwitching) {
|
|
638
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
639
|
+
? "会话正在打开,请等待后再引导 Agent。"
|
|
640
|
+
: "A session is opening; wait before steering the agent.");
|
|
641
|
+
this.refresh();
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (this.compactionInFlight) {
|
|
645
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
646
|
+
? "正在压缩上下文,请等待后再引导 Agent。"
|
|
647
|
+
: "A compaction is running; wait before steering the agent.");
|
|
648
|
+
this.refresh();
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (!this.promptInFlight) {
|
|
652
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
653
|
+
? "只能在提示词运行时引导 Agent。"
|
|
654
|
+
: "Steering is only available while a prompt is running.");
|
|
655
|
+
this.refresh();
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
const input = await extractImageAttachments(normalizeDroppedImagePrompt(prompt, this.session.allowedRoot), this.session.allowedRoot);
|
|
660
|
+
await this.session.steerWithImages(input.text, input.images);
|
|
661
|
+
this.editor.setValue("");
|
|
662
|
+
}
|
|
663
|
+
catch (cause) {
|
|
664
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
665
|
+
}
|
|
666
|
+
this.refresh();
|
|
667
|
+
}
|
|
668
|
+
refreshQueue() {
|
|
669
|
+
this.projection.setQueue([
|
|
670
|
+
...this.steeringPrompts.map((prompt) => `steer: ${prompt}`),
|
|
671
|
+
...this.queuedPrompts.map((prompt) => `queued: ${prompt}`),
|
|
672
|
+
]);
|
|
673
|
+
}
|
|
463
674
|
async cleanupUnreferencedClipboardFiles() {
|
|
464
675
|
const text = this.editor.getValue();
|
|
465
676
|
const files = [...this.clipboardFiles].filter((path) => !text.includes(path));
|
|
@@ -480,11 +691,13 @@ export class InteractiveMode {
|
|
|
480
691
|
const args = argParts.join(" ");
|
|
481
692
|
switch (command) {
|
|
482
693
|
case "help":
|
|
483
|
-
this.projection.setStatus(
|
|
694
|
+
this.projection.setStatus(`commands: ${this.listSlashCommands()
|
|
695
|
+
.map((item) => `/${item.name}`)
|
|
696
|
+
.join(" ")}`);
|
|
484
697
|
break;
|
|
485
698
|
case "clear":
|
|
486
699
|
this.projection.clearVisibleMessages();
|
|
487
|
-
this.projection.setStatus(
|
|
700
|
+
this.projection.setStatus(translate(this.locale, "visibleMessagesCleared"));
|
|
488
701
|
break;
|
|
489
702
|
case "model":
|
|
490
703
|
this.openModelSelector();
|
|
@@ -492,12 +705,21 @@ export class InteractiveMode {
|
|
|
492
705
|
case "session":
|
|
493
706
|
this.openSessionSelector();
|
|
494
707
|
return;
|
|
708
|
+
case "tree":
|
|
709
|
+
this.openTreeSelector();
|
|
710
|
+
return;
|
|
495
711
|
case "theme":
|
|
496
712
|
this.openThemeSelector();
|
|
497
713
|
return;
|
|
498
714
|
case "settings":
|
|
499
715
|
this.openSettingsSelector();
|
|
500
716
|
return;
|
|
717
|
+
case "login":
|
|
718
|
+
this.openProviderLogin();
|
|
719
|
+
return;
|
|
720
|
+
case "logout":
|
|
721
|
+
void this.logoutProvider();
|
|
722
|
+
return;
|
|
501
723
|
case "compact":
|
|
502
724
|
void this.runManualCompaction();
|
|
503
725
|
return;
|
|
@@ -508,7 +730,7 @@ export class InteractiveMode {
|
|
|
508
730
|
if (this.lastFailedPrompt && !this.promptInFlight)
|
|
509
731
|
void this.submit(this.lastFailedPrompt, true);
|
|
510
732
|
else
|
|
511
|
-
this.projection.setStatus(
|
|
733
|
+
this.projection.setStatus(translate(this.locale, "nothingToRetry"));
|
|
512
734
|
break;
|
|
513
735
|
default: {
|
|
514
736
|
if (this.extensionHost?.listCommands().some((entry) => entry.name === command)) {
|
|
@@ -523,22 +745,99 @@ export class InteractiveMode {
|
|
|
523
745
|
}
|
|
524
746
|
this.refresh();
|
|
525
747
|
}
|
|
748
|
+
openProviderLogin() {
|
|
749
|
+
if (!this.providerOnboarding) {
|
|
750
|
+
this.projection.setError("Provider login is unavailable in this session.");
|
|
751
|
+
this.refresh();
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (this.promptInFlight || this.sessionSwitching) {
|
|
755
|
+
this.projection.setError("Cannot change Provider while a prompt is running.");
|
|
756
|
+
this.refresh();
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
this.editor.cancelAutocomplete();
|
|
760
|
+
this.closeAutocompleteOverlay();
|
|
761
|
+
void showInteractiveProviderOnboarding({
|
|
762
|
+
...this.providerOnboarding,
|
|
763
|
+
configuration: { ...this.providerOnboarding.configuration, locale: this.locale },
|
|
764
|
+
tui: this.tui,
|
|
765
|
+
})
|
|
766
|
+
.then((runtime) => {
|
|
767
|
+
if (!this.started)
|
|
768
|
+
return;
|
|
769
|
+
this.tui.setFocus(this.editor);
|
|
770
|
+
if (!runtime) {
|
|
771
|
+
this.refresh();
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
this.session.setRuntime(runtime.provider, runtime.model);
|
|
775
|
+
this.projection.setStatus(`provider=${runtime.provider.id} model=${runtime.model.id}`);
|
|
776
|
+
this.refresh();
|
|
777
|
+
})
|
|
778
|
+
.catch((cause) => {
|
|
779
|
+
if (this.started)
|
|
780
|
+
this.tui.setFocus(this.editor);
|
|
781
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
782
|
+
this.refresh();
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
async logoutProvider() {
|
|
786
|
+
if (!this.providerOnboarding) {
|
|
787
|
+
this.projection.setError("Provider logout is unavailable in this session.");
|
|
788
|
+
this.refresh();
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (this.promptInFlight || this.sessionSwitching) {
|
|
792
|
+
this.projection.setError("Cannot change Provider while a prompt is running.");
|
|
793
|
+
this.refresh();
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
const providerId = this.session.providerId;
|
|
797
|
+
try {
|
|
798
|
+
const removed = await removeGlobalProviderApiKey(this.providerOnboarding.agentDir, providerId);
|
|
799
|
+
if (!removed) {
|
|
800
|
+
this.projection.setStatus(`no global API key stored for provider=${providerId}`);
|
|
801
|
+
this.refresh();
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
this.projection.setStatus(`global API key removed for provider=${providerId}; it remains active until this session ends`);
|
|
805
|
+
}
|
|
806
|
+
catch (cause) {
|
|
807
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
808
|
+
}
|
|
809
|
+
this.refresh();
|
|
810
|
+
}
|
|
811
|
+
listSlashCommands() {
|
|
812
|
+
const skills = this.session.availableSkills.map((skill) => ({
|
|
813
|
+
name: `skill:${skill.name}`,
|
|
814
|
+
description: skill.description,
|
|
815
|
+
}));
|
|
816
|
+
return [...builtinSlashCommands(this.locale), ...(this.extensionHost?.listCommands() ?? []), ...skills];
|
|
817
|
+
}
|
|
818
|
+
isLoadedSkillCommand(input) {
|
|
819
|
+
const name = /^\/skill:([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s|$)/.exec(input)?.[1];
|
|
820
|
+
return name !== undefined && this.session.availableSkills.some((skill) => skill.name === name);
|
|
821
|
+
}
|
|
526
822
|
formatUsage() {
|
|
527
823
|
const usage = this.session.usage;
|
|
824
|
+
if (this.locale === "zh-CN") {
|
|
825
|
+
return `用量:请求=${usage.requestCount} 输入=${usage.inputTokens} 输出=${usage.outputTokens} 总计=${usage.totalTokens} 费用=$${usage.cost.total.toFixed(4)} 上下文=${usage.estimatedContextTokens}/${usage.contextWindow}`;
|
|
826
|
+
}
|
|
528
827
|
return `usage: requests=${usage.requestCount} input=${usage.inputTokens} output=${usage.outputTokens} total=${usage.totalTokens} cost=$${usage.cost.total.toFixed(4)} context=${usage.estimatedContextTokens}/${usage.contextWindow}`;
|
|
529
828
|
}
|
|
530
829
|
async runManualCompaction() {
|
|
531
830
|
if (this.promptInFlight || this.compactionInFlight) {
|
|
532
|
-
this.projection.setError("Cannot compact while a prompt is running.");
|
|
831
|
+
this.projection.setError(this.locale === "zh-CN" ? "提示词运行时不能压缩上下文。" : "Cannot compact while a prompt is running.");
|
|
533
832
|
this.refresh();
|
|
534
833
|
return;
|
|
535
834
|
}
|
|
536
835
|
this.compactionInFlight = true;
|
|
537
|
-
this.projection.setStatus(
|
|
836
|
+
this.projection.setStatus(translate(this.locale, "manualCompactionStarting"));
|
|
538
837
|
this.refresh();
|
|
539
838
|
try {
|
|
540
839
|
await this.session.compact();
|
|
541
|
-
this.projection.setStatus(
|
|
840
|
+
this.projection.setStatus(translate(this.locale, "manualCompactionComplete"));
|
|
542
841
|
}
|
|
543
842
|
catch (cause) {
|
|
544
843
|
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|