@di-code/coding-agent 0.1.4 → 0.1.7
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 +79 -42
- 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/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 +29 -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 +23 -1
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +86 -13
- 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/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/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 +13 -0
- 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 +225 -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 +17 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +243 -11
- 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 -2
- package/dist/modes/interactive-components.d.ts.map +1 -1
- package/dist/modes/interactive-components.js +30 -33
- package/dist/modes/interactive-components.js.map +1 -1
- package/dist/modes/interactive-state.d.ts.map +1 -1
- package/dist/modes/interactive-state.js +2 -1
- package/dist/modes/interactive-state.js.map +1 -1
- package/dist/modes/interactive.d.ts +9 -0
- package/dist/modes/interactive.d.ts.map +1 -1
- package/dist/modes/interactive.js +209 -63
- 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.map +1 -1
- package/dist/provider-onboarding.js +119 -22
- package/dist/provider-onboarding.js.map +1 -1
- package/dist/startup.d.ts +34 -2
- package/dist/startup.d.ts.map +1 -1
- package/dist/startup.js +278 -9
- package/dist/startup.js.map +1 -1
- package/package.json +6 -4
|
@@ -1,31 +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";
|
|
6
8
|
import { showInteractiveProviderOnboarding, } from "../provider-onboarding.js";
|
|
7
|
-
import {
|
|
9
|
+
import { removeGlobalProviderApiKey, saveGlobalLocale, saveGlobalModelSelection, saveGlobalThinkingLevel, } from "../startup.js";
|
|
8
10
|
import { AutocompleteMenu, InteractiveChat, InteractiveComposer, InteractiveFooter, InteractiveHeader, } from "./interactive-components.js";
|
|
9
11
|
import { InteractiveLayout } from "./interactive-layout.js";
|
|
10
12
|
import { InteractiveProjection } from "./interactive-state.js";
|
|
13
|
+
import { TreeSelector } from "./tree-selector.js";
|
|
11
14
|
export { InteractiveProjection } from "./interactive-state.js";
|
|
12
15
|
const IMAGE_EXTENSIONS = new Set([".gif", ".jpeg", ".jpg", ".png", ".webp"]);
|
|
13
16
|
const PASTE_IMAGE_KEY = process.platform === "win32" ? Key.alt("v") : Key.ctrl("v");
|
|
14
17
|
const PASTE_IMAGE_SHORTCUT = process.platform === "win32" ? "Alt+V" : "Ctrl+V";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
}
|
|
29
36
|
function asImageAttachmentReference(pasted, cwd) {
|
|
30
37
|
if (pasted.includes("\n"))
|
|
31
38
|
return pasted;
|
|
@@ -66,6 +73,7 @@ export class InteractiveMode {
|
|
|
66
73
|
sessionChoices;
|
|
67
74
|
extensionHost;
|
|
68
75
|
providerOnboarding;
|
|
76
|
+
agentDir;
|
|
69
77
|
readClipboardImagePath;
|
|
70
78
|
clipboardDirectory;
|
|
71
79
|
clipboardFiles = new Set();
|
|
@@ -85,6 +93,7 @@ export class InteractiveMode {
|
|
|
85
93
|
spinnerTimer;
|
|
86
94
|
shutdownEmitted = false;
|
|
87
95
|
preserveClipboardFiles = false;
|
|
96
|
+
locale;
|
|
88
97
|
constructor(options) {
|
|
89
98
|
this.session = options.session;
|
|
90
99
|
this.projection.configureFilePreview(this.session.allowedRoot, () => this.refresh());
|
|
@@ -93,8 +102,10 @@ export class InteractiveMode {
|
|
|
93
102
|
this.sessionChoices = [...(options.sessions ?? [])];
|
|
94
103
|
this.extensionHost = options.extensionHost;
|
|
95
104
|
this.providerOnboarding = options.providerOnboarding;
|
|
105
|
+
this.agentDir = resolve(options.agentDir ?? join(homedir(), ".di-code"));
|
|
106
|
+
this.locale = options.locale ?? DEFAULT_LOCALE;
|
|
96
107
|
this.readClipboardImagePath = options.readClipboardImagePath ?? readClipboardImagePath;
|
|
97
|
-
this.clipboardDirectory = clipboardImageDirectory(this.session.allowedRoot);
|
|
108
|
+
this.clipboardDirectory = clipboardImageDirectory(this.agentDir, this.session.allowedRoot);
|
|
98
109
|
const autocomplete = {
|
|
99
110
|
getSuggestions: (context, autocompleteOptions) => new CombinedAutocompleteProvider(this.listSlashCommands(), this.session.allowedRoot).getSuggestions(context, autocompleteOptions),
|
|
100
111
|
applyCompletion: (context, item, prefix) => new CombinedAutocompleteProvider(this.listSlashCommands(), this.session.allowedRoot).applyCompletion(context, item, prefix),
|
|
@@ -102,9 +113,16 @@ export class InteractiveMode {
|
|
|
102
113
|
this.editor = new Editor({
|
|
103
114
|
maxHeight: 3,
|
|
104
115
|
autocomplete,
|
|
116
|
+
submitAutocomplete: (context) => /^\/[^\s/]*$/.test(context.text.slice(0, context.cursor)),
|
|
105
117
|
});
|
|
106
118
|
this.editor.onSubmit = (text) => void this.submit(text);
|
|
107
|
-
this.editor.onEscape = () =>
|
|
119
|
+
this.editor.onEscape = () => {
|
|
120
|
+
if (!this.activeAbort)
|
|
121
|
+
return;
|
|
122
|
+
this.projection.setStatus(translate(this.locale, "cancelled"));
|
|
123
|
+
this.activeAbort.abort();
|
|
124
|
+
this.refresh();
|
|
125
|
+
};
|
|
108
126
|
this.editor.onCommand = (data) => this.handleCommand(data);
|
|
109
127
|
this.editor.onInterrupt = () => this.exit();
|
|
110
128
|
this.editor.onChange = () => {
|
|
@@ -120,6 +138,7 @@ export class InteractiveMode {
|
|
|
120
138
|
...this.projection.state,
|
|
121
139
|
model: `${this.session.modelId}${this.session.thinkingLevel ? `(${this.session.thinkingLevel})` : ""}`,
|
|
122
140
|
theme: this.theme,
|
|
141
|
+
locale: this.locale,
|
|
123
142
|
pasteImageShortcut: PASTE_IMAGE_SHORTCUT,
|
|
124
143
|
});
|
|
125
144
|
this.root = new InteractiveLayout({
|
|
@@ -141,7 +160,7 @@ export class InteractiveMode {
|
|
|
141
160
|
this.projection.replaceTranscript(this.session.transcript);
|
|
142
161
|
this.projection.setUsage(this.session.usage);
|
|
143
162
|
this.subscribeToSession();
|
|
144
|
-
void cleanupStaleClipboardImages(this.session.allowedRoot).catch((cause) => {
|
|
163
|
+
void cleanupStaleClipboardImages(this.agentDir, this.session.allowedRoot).catch((cause) => {
|
|
145
164
|
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
146
165
|
this.refresh();
|
|
147
166
|
});
|
|
@@ -190,6 +209,12 @@ export class InteractiveMode {
|
|
|
190
209
|
try {
|
|
191
210
|
const level = this.session.cycleThinkingLevel();
|
|
192
211
|
this.projection.setStatus(level ? `thinking=${level}` : "Current model does not support thinking levels.");
|
|
212
|
+
if (level) {
|
|
213
|
+
void saveGlobalThinkingLevel(this.agentDir, this.session.providerId, this.session.modelId, level).catch((cause) => {
|
|
214
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
215
|
+
this.refresh();
|
|
216
|
+
});
|
|
217
|
+
}
|
|
193
218
|
}
|
|
194
219
|
catch (cause) {
|
|
195
220
|
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -227,7 +252,7 @@ export class InteractiveMode {
|
|
|
227
252
|
try {
|
|
228
253
|
const path = await this.readClipboardImagePath(this.clipboardDirectory);
|
|
229
254
|
if (!path) {
|
|
230
|
-
this.projection.setStatus("Clipboard has no supported image.");
|
|
255
|
+
this.projection.setStatus(this.locale === "zh-CN" ? "剪贴板中没有受支持的图片。" : "Clipboard has no supported image.");
|
|
231
256
|
}
|
|
232
257
|
else {
|
|
233
258
|
this.editor.insertTextAtCursor(path);
|
|
@@ -241,9 +266,10 @@ export class InteractiveMode {
|
|
|
241
266
|
this.refresh();
|
|
242
267
|
}
|
|
243
268
|
openThemeSelector() {
|
|
269
|
+
const t = (key) => translate(this.locale, key);
|
|
244
270
|
const list = new SelectList([
|
|
245
|
-
{ value: "dark", label: "
|
|
246
|
-
{ value: "light", label: "
|
|
271
|
+
{ value: "dark", label: t("dark"), description: t("darkTheme") },
|
|
272
|
+
{ value: "light", label: t("light"), description: t("lightTheme") },
|
|
247
273
|
]);
|
|
248
274
|
list.onSelect = (item) => {
|
|
249
275
|
this.theme = item.value === "light" ? "light" : "dark";
|
|
@@ -264,6 +290,10 @@ export class InteractiveMode {
|
|
|
264
290
|
try {
|
|
265
291
|
this.session.setModel(item.value);
|
|
266
292
|
this.projection.setStatus(`model=${item.value}`);
|
|
293
|
+
void saveGlobalModelSelection(this.agentDir, this.session.providerId, this.session.modelId).catch((cause) => {
|
|
294
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
295
|
+
this.refresh();
|
|
296
|
+
});
|
|
267
297
|
this.closeOverlay();
|
|
268
298
|
this.refresh();
|
|
269
299
|
}
|
|
@@ -275,11 +305,12 @@ export class InteractiveMode {
|
|
|
275
305
|
this.showOverlay(list);
|
|
276
306
|
}
|
|
277
307
|
openSessionSelector() {
|
|
308
|
+
const t = (key) => translate(this.locale, key);
|
|
278
309
|
const list = new SelectList([
|
|
279
310
|
{
|
|
280
311
|
value: "__current__",
|
|
281
|
-
label: "
|
|
282
|
-
description: this.session.sessionFile ?? "
|
|
312
|
+
label: t("currentSession"),
|
|
313
|
+
description: this.session.sessionFile ?? t("inMemorySession"),
|
|
283
314
|
},
|
|
284
315
|
...this.sessionChoices.map((choice) => ({
|
|
285
316
|
value: choice.id,
|
|
@@ -303,28 +334,124 @@ export class InteractiveMode {
|
|
|
303
334
|
list.onCancel = () => this.closeOverlay();
|
|
304
335
|
this.showOverlay(list);
|
|
305
336
|
}
|
|
337
|
+
openTreeSelector() {
|
|
338
|
+
if (this.promptInFlight || this.compactionInFlight || this.sessionSwitching) {
|
|
339
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
340
|
+
? "提示词运行时不能浏览会话树。"
|
|
341
|
+
: "Cannot browse the session tree while a prompt is running.");
|
|
342
|
+
this.refresh();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (this.session.sessionTree.length === 0) {
|
|
346
|
+
this.projection.setStatus(this.locale === "zh-CN" ? "当前会话还没有历史节点。" : "The current session has no history nodes.");
|
|
347
|
+
this.refresh();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const selector = new TreeSelector({
|
|
351
|
+
nodes: this.session.sessionTree,
|
|
352
|
+
leafId: this.session.sessionLeafId,
|
|
353
|
+
locale: this.locale,
|
|
354
|
+
onContinue: (entry) => {
|
|
355
|
+
this.closeOverlay();
|
|
356
|
+
void this.navigateTreeEntry(entry.id);
|
|
357
|
+
},
|
|
358
|
+
onEdit: (entry) => {
|
|
359
|
+
if (entry.type !== "message" || entry.message.role !== "user") {
|
|
360
|
+
this.projection.setError(this.locale === "zh-CN" ? "只能编辑历史用户消息。" : "Only historical user messages can be edited.");
|
|
361
|
+
this.refresh();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
this.closeOverlay();
|
|
365
|
+
void this.navigateTreeEntry(entry.id);
|
|
366
|
+
},
|
|
367
|
+
onSummarize: (entry) => {
|
|
368
|
+
this.closeOverlay();
|
|
369
|
+
void this.summarizeTreeBranch(entry.id);
|
|
370
|
+
},
|
|
371
|
+
onCancel: () => this.closeOverlay(),
|
|
372
|
+
});
|
|
373
|
+
this.showOverlay(selector, true, "80%");
|
|
374
|
+
}
|
|
375
|
+
async navigateTreeEntry(entryId) {
|
|
376
|
+
try {
|
|
377
|
+
const result = await this.session.navigateTree(entryId);
|
|
378
|
+
if (!this.started)
|
|
379
|
+
return false;
|
|
380
|
+
this.projection.replaceTranscript(this.session.transcript);
|
|
381
|
+
this.editor.setValue(result.editorText ?? "");
|
|
382
|
+
this.projection.setStatus(result.imagesOmitted
|
|
383
|
+
? this.locale === "zh-CN"
|
|
384
|
+
? "已恢复文本;请重新附加图片。"
|
|
385
|
+
: "Text restored; reattach images before sending."
|
|
386
|
+
: `tree=${result.selectedEntryId}`);
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
catch (cause) {
|
|
390
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
this.refresh();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async summarizeTreeBranch(entryId) {
|
|
398
|
+
if (this.compactionInFlight || this.promptInFlight)
|
|
399
|
+
return;
|
|
400
|
+
this.compactionInFlight = true;
|
|
401
|
+
this.projection.setStatus(this.locale === "zh-CN" ? "正在为分支生成摘要。" : "Summarizing selected branch.");
|
|
402
|
+
this.refresh();
|
|
403
|
+
try {
|
|
404
|
+
if (!(await this.navigateTreeEntry(entryId)))
|
|
405
|
+
return;
|
|
406
|
+
await this.session.compact();
|
|
407
|
+
this.editor.setValue("");
|
|
408
|
+
this.projection.replaceTranscript(this.session.transcript);
|
|
409
|
+
this.projection.setStatus(this.locale === "zh-CN" ? "摘要分支已创建。" : "Summary branch created.");
|
|
410
|
+
}
|
|
411
|
+
catch (cause) {
|
|
412
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
this.compactionInFlight = false;
|
|
416
|
+
this.refresh();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
306
419
|
openSettingsSelector() {
|
|
420
|
+
const t = (key) => translate(this.locale, key);
|
|
307
421
|
const list = new SettingsList([
|
|
308
422
|
{
|
|
309
423
|
id: "compaction",
|
|
310
|
-
label: "
|
|
311
|
-
currentValue: this.session.compactionEnabled ? "on" : "off",
|
|
312
|
-
values: ["on", "off"],
|
|
313
|
-
description: "Summarize older persisted context before the model limit is reached.",
|
|
424
|
+
label: t("contextCompaction"),
|
|
425
|
+
currentValue: this.session.compactionEnabled ? t("on") : t("off"),
|
|
426
|
+
values: [t("on"), t("off")],
|
|
314
427
|
},
|
|
315
|
-
|
|
428
|
+
{
|
|
429
|
+
id: "locale",
|
|
430
|
+
label: t("language"),
|
|
431
|
+
currentValue: this.locale,
|
|
432
|
+
values: ["en", "zh-CN"],
|
|
433
|
+
},
|
|
434
|
+
], { title: t("settings") });
|
|
316
435
|
list.onChange = (id, value) => {
|
|
436
|
+
if (id === "locale") {
|
|
437
|
+
this.locale = value === "zh-CN" ? "zh-CN" : "en";
|
|
438
|
+
void saveGlobalLocale(this.agentDir, this.locale).catch((cause) => {
|
|
439
|
+
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
440
|
+
this.refresh();
|
|
441
|
+
});
|
|
442
|
+
this.closeOverlay();
|
|
443
|
+
this.openSettingsSelector();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
317
446
|
if (id !== "compaction")
|
|
318
447
|
return;
|
|
319
448
|
try {
|
|
320
|
-
const enabled = this.session.setCompactionEnabled(value === "on");
|
|
321
|
-
list.updateValue(id, enabled ? "on" : "off");
|
|
322
|
-
this.projection.setStatus(enabled === (value === "on")
|
|
323
|
-
? `compaction=${enabled ? "on" : "off"}`
|
|
324
|
-
: "compaction unavailable without a persisted session");
|
|
449
|
+
const enabled = this.session.setCompactionEnabled(value === t("on"));
|
|
450
|
+
list.updateValue(id, enabled ? t("on") : t("off"));
|
|
451
|
+
this.projection.setStatus(enabled === (value === t("on")) ? `compaction=${enabled ? t("on") : t("off")}` : t("compactionUnavailable"));
|
|
325
452
|
}
|
|
326
453
|
catch (cause) {
|
|
327
|
-
list.updateValue(id, this.session.compactionEnabled ? "on" : "off");
|
|
454
|
+
list.updateValue(id, this.session.compactionEnabled ? t("on") : t("off"));
|
|
328
455
|
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
329
456
|
this.closeOverlay();
|
|
330
457
|
}
|
|
@@ -333,15 +460,16 @@ export class InteractiveMode {
|
|
|
333
460
|
list.onCancel = () => this.closeOverlay();
|
|
334
461
|
this.showOverlay(list);
|
|
335
462
|
}
|
|
336
|
-
showOverlay(component) {
|
|
463
|
+
showOverlay(component, preserveLastLine = false, maxHeight = "60%") {
|
|
337
464
|
this.editor.cancelAutocomplete();
|
|
338
465
|
this.closeAutocompleteOverlay();
|
|
339
466
|
this.closeOverlay();
|
|
340
467
|
this.overlay = this.tui.showOverlay(component, {
|
|
341
468
|
width: "70%",
|
|
342
|
-
maxHeight
|
|
469
|
+
maxHeight,
|
|
343
470
|
anchor: "center",
|
|
344
471
|
margin: 1,
|
|
472
|
+
preserveLastLine,
|
|
345
473
|
});
|
|
346
474
|
}
|
|
347
475
|
closeOverlay() {
|
|
@@ -361,7 +489,7 @@ export class InteractiveMode {
|
|
|
361
489
|
const editorBounds = this.root.getEditorBounds(this.tui.columns);
|
|
362
490
|
this.autocompleteOverlay = this.tui.showOverlay(menu, {
|
|
363
491
|
width: "55%",
|
|
364
|
-
maxHeight:
|
|
492
|
+
maxHeight: 9,
|
|
365
493
|
anchor: "bottom-left",
|
|
366
494
|
placement: {
|
|
367
495
|
anchorRow: editorBounds.end - 1,
|
|
@@ -369,6 +497,7 @@ export class InteractiveMode {
|
|
|
369
497
|
preferred: "below",
|
|
370
498
|
},
|
|
371
499
|
margin: 1,
|
|
500
|
+
preserveLastLine: true,
|
|
372
501
|
nonCapturing: true,
|
|
373
502
|
});
|
|
374
503
|
this.refresh();
|
|
@@ -399,6 +528,11 @@ export class InteractiveMode {
|
|
|
399
528
|
this.unsubscribeSession = this.session.subscribeSession((event) => {
|
|
400
529
|
if (!this.started)
|
|
401
530
|
return;
|
|
531
|
+
if (event.type === "tree_navigated") {
|
|
532
|
+
this.projection.replaceTranscript(this.session.transcript);
|
|
533
|
+
this.refresh();
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
402
536
|
if (event.type === "queue_update") {
|
|
403
537
|
this.steeringPrompts = [...event.steering];
|
|
404
538
|
this.refreshQueue();
|
|
@@ -411,7 +545,7 @@ export class InteractiveMode {
|
|
|
411
545
|
}
|
|
412
546
|
async switchSession(choice) {
|
|
413
547
|
if (this.promptInFlight || this.sessionSwitching) {
|
|
414
|
-
this.projection.setError("Cannot switch sessions while a prompt is running.");
|
|
548
|
+
this.projection.setError(this.locale === "zh-CN" ? "提示词运行时不能切换会话。" : "Cannot switch sessions while a prompt is running.");
|
|
415
549
|
this.refresh();
|
|
416
550
|
return;
|
|
417
551
|
}
|
|
@@ -425,8 +559,8 @@ export class InteractiveMode {
|
|
|
425
559
|
this.unsubscribeSession?.();
|
|
426
560
|
this.session = next;
|
|
427
561
|
this.projection.configureFilePreview(next.allowedRoot, () => this.refresh());
|
|
428
|
-
this.clipboardDirectory = clipboardImageDirectory(next.allowedRoot);
|
|
429
|
-
void cleanupStaleClipboardImages(next.allowedRoot);
|
|
562
|
+
this.clipboardDirectory = clipboardImageDirectory(this.agentDir, next.allowedRoot);
|
|
563
|
+
void cleanupStaleClipboardImages(this.agentDir, next.allowedRoot);
|
|
430
564
|
this.queuedPrompts = [];
|
|
431
565
|
this.steeringPrompts = [];
|
|
432
566
|
this.refreshQueue();
|
|
@@ -458,12 +592,16 @@ export class InteractiveMode {
|
|
|
458
592
|
return;
|
|
459
593
|
}
|
|
460
594
|
if (this.sessionSwitching) {
|
|
461
|
-
this.projection.setError(
|
|
595
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
596
|
+
? "会话正在打开,请等待后再提交提示词。"
|
|
597
|
+
: "A session is opening; wait before submitting a prompt.");
|
|
462
598
|
this.refresh();
|
|
463
599
|
return;
|
|
464
600
|
}
|
|
465
601
|
if (this.compactionInFlight) {
|
|
466
|
-
this.projection.setError(
|
|
602
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
603
|
+
? "正在压缩上下文,请等待后再提交提示词。"
|
|
604
|
+
: "A compaction is running; wait before submitting a prompt.");
|
|
467
605
|
this.refresh();
|
|
468
606
|
return;
|
|
469
607
|
}
|
|
@@ -509,22 +647,28 @@ export class InteractiveMode {
|
|
|
509
647
|
async submitSteering(text) {
|
|
510
648
|
const prompt = text.trim();
|
|
511
649
|
if (prompt.length === 0) {
|
|
512
|
-
this.projection.setError("Steering content must not be empty.");
|
|
650
|
+
this.projection.setError(this.locale === "zh-CN" ? "引导内容不能为空。" : "Steering content must not be empty.");
|
|
513
651
|
this.refresh();
|
|
514
652
|
return;
|
|
515
653
|
}
|
|
516
654
|
if (this.sessionSwitching) {
|
|
517
|
-
this.projection.setError(
|
|
655
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
656
|
+
? "会话正在打开,请等待后再引导 Agent。"
|
|
657
|
+
: "A session is opening; wait before steering the agent.");
|
|
518
658
|
this.refresh();
|
|
519
659
|
return;
|
|
520
660
|
}
|
|
521
661
|
if (this.compactionInFlight) {
|
|
522
|
-
this.projection.setError(
|
|
662
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
663
|
+
? "正在压缩上下文,请等待后再引导 Agent。"
|
|
664
|
+
: "A compaction is running; wait before steering the agent.");
|
|
523
665
|
this.refresh();
|
|
524
666
|
return;
|
|
525
667
|
}
|
|
526
668
|
if (!this.promptInFlight) {
|
|
527
|
-
this.projection.setError(
|
|
669
|
+
this.projection.setError(this.locale === "zh-CN"
|
|
670
|
+
? "只能在提示词运行时引导 Agent。"
|
|
671
|
+
: "Steering is only available while a prompt is running.");
|
|
528
672
|
this.refresh();
|
|
529
673
|
return;
|
|
530
674
|
}
|
|
@@ -570,7 +714,7 @@ export class InteractiveMode {
|
|
|
570
714
|
break;
|
|
571
715
|
case "clear":
|
|
572
716
|
this.projection.clearVisibleMessages();
|
|
573
|
-
this.projection.setStatus(
|
|
717
|
+
this.projection.setStatus(translate(this.locale, "visibleMessagesCleared"));
|
|
574
718
|
break;
|
|
575
719
|
case "model":
|
|
576
720
|
this.openModelSelector();
|
|
@@ -578,6 +722,9 @@ export class InteractiveMode {
|
|
|
578
722
|
case "session":
|
|
579
723
|
this.openSessionSelector();
|
|
580
724
|
return;
|
|
725
|
+
case "tree":
|
|
726
|
+
this.openTreeSelector();
|
|
727
|
+
return;
|
|
581
728
|
case "theme":
|
|
582
729
|
this.openThemeSelector();
|
|
583
730
|
return;
|
|
@@ -600,7 +747,7 @@ export class InteractiveMode {
|
|
|
600
747
|
if (this.lastFailedPrompt && !this.promptInFlight)
|
|
601
748
|
void this.submit(this.lastFailedPrompt, true);
|
|
602
749
|
else
|
|
603
|
-
this.projection.setStatus(
|
|
750
|
+
this.projection.setStatus(translate(this.locale, "nothingToRetry"));
|
|
604
751
|
break;
|
|
605
752
|
default: {
|
|
606
753
|
if (this.extensionHost?.listCommands().some((entry) => entry.name === command)) {
|
|
@@ -628,7 +775,11 @@ export class InteractiveMode {
|
|
|
628
775
|
}
|
|
629
776
|
this.editor.cancelAutocomplete();
|
|
630
777
|
this.closeAutocompleteOverlay();
|
|
631
|
-
void showInteractiveProviderOnboarding({
|
|
778
|
+
void showInteractiveProviderOnboarding({
|
|
779
|
+
...this.providerOnboarding,
|
|
780
|
+
configuration: { ...this.providerOnboarding.configuration, locale: this.locale },
|
|
781
|
+
tui: this.tui,
|
|
782
|
+
})
|
|
632
783
|
.then((runtime) => {
|
|
633
784
|
if (!this.started)
|
|
634
785
|
return;
|
|
@@ -660,7 +811,6 @@ export class InteractiveMode {
|
|
|
660
811
|
return;
|
|
661
812
|
}
|
|
662
813
|
const providerId = this.session.providerId;
|
|
663
|
-
const modelId = this.session.modelId;
|
|
664
814
|
try {
|
|
665
815
|
const removed = await removeGlobalProviderApiKey(this.providerOnboarding.agentDir, providerId);
|
|
666
816
|
if (!removed) {
|
|
@@ -668,14 +818,7 @@ export class InteractiveMode {
|
|
|
668
818
|
this.refresh();
|
|
669
819
|
return;
|
|
670
820
|
}
|
|
671
|
-
|
|
672
|
-
const runtime = resolveStartupRuntime({
|
|
673
|
-
...configuration.environment,
|
|
674
|
-
DI_CODE_PROVIDER: providerId,
|
|
675
|
-
DI_CODE_MODEL: modelId,
|
|
676
|
-
}, configuration.providers, configuration.defaults);
|
|
677
|
-
this.session.setRuntime(runtime.provider, runtime.model);
|
|
678
|
-
this.projection.setStatus(`global API key removed for provider=${providerId}; environment variables may still apply`);
|
|
821
|
+
this.projection.setStatus(`global API key removed for provider=${providerId}; it remains active until this session ends`);
|
|
679
822
|
}
|
|
680
823
|
catch (cause) {
|
|
681
824
|
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -687,7 +830,7 @@ export class InteractiveMode {
|
|
|
687
830
|
name: `skill:${skill.name}`,
|
|
688
831
|
description: skill.description,
|
|
689
832
|
}));
|
|
690
|
-
return [...
|
|
833
|
+
return [...builtinSlashCommands(this.locale), ...(this.extensionHost?.listCommands() ?? []), ...skills];
|
|
691
834
|
}
|
|
692
835
|
isLoadedSkillCommand(input) {
|
|
693
836
|
const name = /^\/skill:([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s|$)/.exec(input)?.[1];
|
|
@@ -695,20 +838,23 @@ export class InteractiveMode {
|
|
|
695
838
|
}
|
|
696
839
|
formatUsage() {
|
|
697
840
|
const usage = this.session.usage;
|
|
841
|
+
if (this.locale === "zh-CN") {
|
|
842
|
+
return `用量:请求=${usage.requestCount} 输入=${usage.inputTokens} 输出=${usage.outputTokens} 总计=${usage.totalTokens} 费用=$${usage.cost.total.toFixed(4)} 上下文=${usage.estimatedContextTokens}/${usage.contextWindow}`;
|
|
843
|
+
}
|
|
698
844
|
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}`;
|
|
699
845
|
}
|
|
700
846
|
async runManualCompaction() {
|
|
701
847
|
if (this.promptInFlight || this.compactionInFlight) {
|
|
702
|
-
this.projection.setError("Cannot compact while a prompt is running.");
|
|
848
|
+
this.projection.setError(this.locale === "zh-CN" ? "提示词运行时不能压缩上下文。" : "Cannot compact while a prompt is running.");
|
|
703
849
|
this.refresh();
|
|
704
850
|
return;
|
|
705
851
|
}
|
|
706
852
|
this.compactionInFlight = true;
|
|
707
|
-
this.projection.setStatus(
|
|
853
|
+
this.projection.setStatus(translate(this.locale, "manualCompactionStarting"));
|
|
708
854
|
this.refresh();
|
|
709
855
|
try {
|
|
710
856
|
await this.session.compact();
|
|
711
|
-
this.projection.setStatus(
|
|
857
|
+
this.projection.setStatus(translate(this.locale, "manualCompactionComplete"));
|
|
712
858
|
}
|
|
713
859
|
catch (cause) {
|
|
714
860
|
this.projection.setError(cause instanceof Error ? cause.message : String(cause));
|