@di-code/coding-agent 0.1.4 → 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.
Files changed (111) hide show
  1. package/README.md +74 -23
  2. package/dist/cli.d.ts +15 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +96 -17
  5. package/dist/cli.js.map +1 -1
  6. package/dist/core/clipboard-image.d.ts +3 -3
  7. package/dist/core/clipboard-image.d.ts.map +1 -1
  8. package/dist/core/clipboard-image.js +26 -13
  9. package/dist/core/clipboard-image.js.map +1 -1
  10. package/dist/core/resources/loader.d.ts.map +1 -1
  11. package/dist/core/resources/loader.js +15 -42
  12. package/dist/core/resources/loader.js.map +1 -1
  13. package/dist/core/resources/types.d.ts +6 -9
  14. package/dist/core/resources/types.d.ts.map +1 -1
  15. package/dist/core/resources/types.js.map +1 -1
  16. package/dist/core/session/session-manager.d.ts +12 -1
  17. package/dist/core/session/session-manager.d.ts.map +1 -1
  18. package/dist/core/session/session-manager.js +66 -3
  19. package/dist/core/session/session-manager.js.map +1 -1
  20. package/dist/core/session/session-storage.d.ts.map +1 -1
  21. package/dist/core/session/session-storage.js +29 -15
  22. package/dist/core/session/session-storage.js.map +1 -1
  23. package/dist/core/session/types.d.ts +6 -1
  24. package/dist/core/session/types.d.ts.map +1 -1
  25. package/dist/core/session/types.js +1 -1
  26. package/dist/core/session/types.js.map +1 -1
  27. package/dist/core/session.d.ts +21 -1
  28. package/dist/core/session.d.ts.map +1 -1
  29. package/dist/core/session.js +81 -12
  30. package/dist/core/session.js.map +1 -1
  31. package/dist/core/system-prompt.js +2 -2
  32. package/dist/core/system-prompt.js.map +1 -1
  33. package/dist/core/tools/file-search.d.ts +14 -0
  34. package/dist/core/tools/file-search.d.ts.map +1 -0
  35. package/dist/core/tools/file-search.js +99 -0
  36. package/dist/core/tools/file-search.js.map +1 -0
  37. package/dist/core/tools/glob.d.ts +11 -0
  38. package/dist/core/tools/glob.d.ts.map +1 -0
  39. package/dist/core/tools/glob.js +44 -0
  40. package/dist/core/tools/glob.js.map +1 -0
  41. package/dist/core/tools/grep.d.ts +13 -0
  42. package/dist/core/tools/grep.d.ts.map +1 -0
  43. package/dist/core/tools/grep.js +67 -0
  44. package/dist/core/tools/grep.js.map +1 -0
  45. package/dist/core/tools/load-skill.d.ts +10 -0
  46. package/dist/core/tools/load-skill.d.ts.map +1 -0
  47. package/dist/core/tools/load-skill.js +22 -0
  48. package/dist/core/tools/load-skill.js.map +1 -0
  49. package/dist/core/tools/path-boundary.d.ts +2 -0
  50. package/dist/core/tools/path-boundary.d.ts.map +1 -1
  51. package/dist/core/tools/path-boundary.js +4 -0
  52. package/dist/core/tools/path-boundary.js.map +1 -1
  53. package/dist/core/user-data.d.ts +3 -0
  54. package/dist/core/user-data.d.ts.map +1 -0
  55. package/dist/core/user-data.js +8 -0
  56. package/dist/core/user-data.js.map +1 -0
  57. package/dist/entry.js +13 -0
  58. package/dist/entry.js.map +1 -1
  59. package/dist/extensions/runtime.js +2 -2
  60. package/dist/extensions/runtime.js.map +1 -1
  61. package/dist/i18n.d.ts +5 -0
  62. package/dist/i18n.d.ts.map +1 -0
  63. package/dist/i18n.js +223 -0
  64. package/dist/i18n.js.map +1 -0
  65. package/dist/index.d.ts +3 -0
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +3 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/main.d.ts +17 -0
  70. package/dist/main.d.ts.map +1 -1
  71. package/dist/main.js +238 -11
  72. package/dist/main.js.map +1 -1
  73. package/dist/mcp/config.d.ts +33 -0
  74. package/dist/mcp/config.d.ts.map +1 -0
  75. package/dist/mcp/config.js +211 -0
  76. package/dist/mcp/config.js.map +1 -0
  77. package/dist/mcp/loader.d.ts +21 -0
  78. package/dist/mcp/loader.d.ts.map +1 -0
  79. package/dist/mcp/loader.js +60 -0
  80. package/dist/mcp/loader.js.map +1 -0
  81. package/dist/mcp/tool-adapter.d.ts +5 -0
  82. package/dist/mcp/tool-adapter.d.ts.map +1 -0
  83. package/dist/mcp/tool-adapter.js +149 -0
  84. package/dist/mcp/tool-adapter.js.map +1 -0
  85. package/dist/modes/interactive-components.d.ts +2 -2
  86. package/dist/modes/interactive-components.d.ts.map +1 -1
  87. package/dist/modes/interactive-components.js +30 -33
  88. package/dist/modes/interactive-components.js.map +1 -1
  89. package/dist/modes/interactive-state.d.ts.map +1 -1
  90. package/dist/modes/interactive-state.js +1 -0
  91. package/dist/modes/interactive-state.js.map +1 -1
  92. package/dist/modes/interactive.d.ts +9 -0
  93. package/dist/modes/interactive.d.ts.map +1 -1
  94. package/dist/modes/interactive.js +191 -62
  95. package/dist/modes/interactive.js.map +1 -1
  96. package/dist/modes/tree-selector.d.ts +30 -0
  97. package/dist/modes/tree-selector.d.ts.map +1 -0
  98. package/dist/modes/tree-selector.js +205 -0
  99. package/dist/modes/tree-selector.js.map +1 -0
  100. package/dist/plugins/loader.d.ts +19 -1
  101. package/dist/plugins/loader.d.ts.map +1 -1
  102. package/dist/plugins/loader.js +21 -2
  103. package/dist/plugins/loader.js.map +1 -1
  104. package/dist/provider-onboarding.d.ts.map +1 -1
  105. package/dist/provider-onboarding.js +119 -22
  106. package/dist/provider-onboarding.js.map +1 -1
  107. package/dist/startup.d.ts +24 -1
  108. package/dist/startup.d.ts.map +1 -1
  109. package/dist/startup.js +200 -9
  110. package/dist/startup.js.map +1 -1
  111. package/package.json +6 -4
@@ -1,31 +1,38 @@
1
1
  import { statSync } from "node:fs";
2
- import { extname, resolve } from "node:path";
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 { loadStartupConfiguration, removeGlobalProviderApiKey, resolveStartupRuntime } from "../startup.js";
9
+ import { removeGlobalProviderApiKey, saveGlobalLocale } 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
- const BUILTIN_SLASH_COMMANDS = [
16
- { name: "help", description: "Show interactive commands" },
17
- { name: "clear", description: "Clear visible messages" },
18
- { name: "model", description: "Open the model selector" },
19
- { name: "session", description: "Open the session selector" },
20
- { name: "theme", description: "Open the theme selector" },
21
- { name: "settings", description: "Open the settings selector" },
22
- { name: "login", description: "Choose a Provider and save its global API key" },
23
- { name: "logout", description: "Remove the current Provider global API key" },
24
- { name: "compact", description: "Compact the persisted context now" },
25
- { name: "usage", description: "Show token and cost usage" },
26
- { name: "retry", description: "Retry the last failed prompt" },
27
- { name: "steer", description: "Steer the running agent" },
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),
@@ -120,6 +131,7 @@ export class InteractiveMode {
120
131
  ...this.projection.state,
121
132
  model: `${this.session.modelId}${this.session.thinkingLevel ? `(${this.session.thinkingLevel})` : ""}`,
122
133
  theme: this.theme,
134
+ locale: this.locale,
123
135
  pasteImageShortcut: PASTE_IMAGE_SHORTCUT,
124
136
  });
125
137
  this.root = new InteractiveLayout({
@@ -141,7 +153,7 @@ export class InteractiveMode {
141
153
  this.projection.replaceTranscript(this.session.transcript);
142
154
  this.projection.setUsage(this.session.usage);
143
155
  this.subscribeToSession();
144
- void cleanupStaleClipboardImages(this.session.allowedRoot).catch((cause) => {
156
+ void cleanupStaleClipboardImages(this.agentDir, this.session.allowedRoot).catch((cause) => {
145
157
  this.projection.setError(cause instanceof Error ? cause.message : String(cause));
146
158
  this.refresh();
147
159
  });
@@ -227,7 +239,7 @@ export class InteractiveMode {
227
239
  try {
228
240
  const path = await this.readClipboardImagePath(this.clipboardDirectory);
229
241
  if (!path) {
230
- this.projection.setStatus("Clipboard has no supported image.");
242
+ this.projection.setStatus(this.locale === "zh-CN" ? "剪贴板中没有受支持的图片。" : "Clipboard has no supported image.");
231
243
  }
232
244
  else {
233
245
  this.editor.insertTextAtCursor(path);
@@ -241,9 +253,10 @@ export class InteractiveMode {
241
253
  this.refresh();
242
254
  }
243
255
  openThemeSelector() {
256
+ const t = (key) => translate(this.locale, key);
244
257
  const list = new SelectList([
245
- { value: "dark", label: "Dark", description: "Dark terminal theme" },
246
- { value: "light", label: "Light", description: "Light terminal theme" },
258
+ { value: "dark", label: t("dark"), description: t("darkTheme") },
259
+ { value: "light", label: t("light"), description: t("lightTheme") },
247
260
  ]);
248
261
  list.onSelect = (item) => {
249
262
  this.theme = item.value === "light" ? "light" : "dark";
@@ -275,11 +288,12 @@ export class InteractiveMode {
275
288
  this.showOverlay(list);
276
289
  }
277
290
  openSessionSelector() {
291
+ const t = (key) => translate(this.locale, key);
278
292
  const list = new SelectList([
279
293
  {
280
294
  value: "__current__",
281
- label: "Current session",
282
- description: this.session.sessionFile ?? "In-memory session",
295
+ label: t("currentSession"),
296
+ description: this.session.sessionFile ?? t("inMemorySession"),
283
297
  },
284
298
  ...this.sessionChoices.map((choice) => ({
285
299
  value: choice.id,
@@ -303,28 +317,124 @@ export class InteractiveMode {
303
317
  list.onCancel = () => this.closeOverlay();
304
318
  this.showOverlay(list);
305
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
+ }
306
402
  openSettingsSelector() {
403
+ const t = (key) => translate(this.locale, key);
307
404
  const list = new SettingsList([
308
405
  {
309
406
  id: "compaction",
310
- label: "Context compaction",
311
- currentValue: this.session.compactionEnabled ? "on" : "off",
312
- values: ["on", "off"],
313
- 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")],
314
410
  },
315
- ]);
411
+ {
412
+ id: "locale",
413
+ label: t("language"),
414
+ currentValue: this.locale,
415
+ values: ["en", "zh-CN"],
416
+ },
417
+ ], { title: t("settings") });
316
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
+ }
317
429
  if (id !== "compaction")
318
430
  return;
319
431
  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");
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"));
325
435
  }
326
436
  catch (cause) {
327
- list.updateValue(id, this.session.compactionEnabled ? "on" : "off");
437
+ list.updateValue(id, this.session.compactionEnabled ? t("on") : t("off"));
328
438
  this.projection.setError(cause instanceof Error ? cause.message : String(cause));
329
439
  this.closeOverlay();
330
440
  }
@@ -333,15 +443,16 @@ export class InteractiveMode {
333
443
  list.onCancel = () => this.closeOverlay();
334
444
  this.showOverlay(list);
335
445
  }
336
- showOverlay(component) {
446
+ showOverlay(component, preserveLastLine = false, maxHeight = "60%") {
337
447
  this.editor.cancelAutocomplete();
338
448
  this.closeAutocompleteOverlay();
339
449
  this.closeOverlay();
340
450
  this.overlay = this.tui.showOverlay(component, {
341
451
  width: "70%",
342
- maxHeight: "60%",
452
+ maxHeight,
343
453
  anchor: "center",
344
454
  margin: 1,
455
+ preserveLastLine,
345
456
  });
346
457
  }
347
458
  closeOverlay() {
@@ -361,7 +472,7 @@ export class InteractiveMode {
361
472
  const editorBounds = this.root.getEditorBounds(this.tui.columns);
362
473
  this.autocompleteOverlay = this.tui.showOverlay(menu, {
363
474
  width: "55%",
364
- maxHeight: 8,
475
+ maxHeight: 9,
365
476
  anchor: "bottom-left",
366
477
  placement: {
367
478
  anchorRow: editorBounds.end - 1,
@@ -369,6 +480,7 @@ export class InteractiveMode {
369
480
  preferred: "below",
370
481
  },
371
482
  margin: 1,
483
+ preserveLastLine: true,
372
484
  nonCapturing: true,
373
485
  });
374
486
  this.refresh();
@@ -399,6 +511,11 @@ export class InteractiveMode {
399
511
  this.unsubscribeSession = this.session.subscribeSession((event) => {
400
512
  if (!this.started)
401
513
  return;
514
+ if (event.type === "tree_navigated") {
515
+ this.projection.replaceTranscript(this.session.transcript);
516
+ this.refresh();
517
+ return;
518
+ }
402
519
  if (event.type === "queue_update") {
403
520
  this.steeringPrompts = [...event.steering];
404
521
  this.refreshQueue();
@@ -411,7 +528,7 @@ export class InteractiveMode {
411
528
  }
412
529
  async switchSession(choice) {
413
530
  if (this.promptInFlight || this.sessionSwitching) {
414
- 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.");
415
532
  this.refresh();
416
533
  return;
417
534
  }
@@ -425,8 +542,8 @@ export class InteractiveMode {
425
542
  this.unsubscribeSession?.();
426
543
  this.session = next;
427
544
  this.projection.configureFilePreview(next.allowedRoot, () => this.refresh());
428
- this.clipboardDirectory = clipboardImageDirectory(next.allowedRoot);
429
- void cleanupStaleClipboardImages(next.allowedRoot);
545
+ this.clipboardDirectory = clipboardImageDirectory(this.agentDir, next.allowedRoot);
546
+ void cleanupStaleClipboardImages(this.agentDir, next.allowedRoot);
430
547
  this.queuedPrompts = [];
431
548
  this.steeringPrompts = [];
432
549
  this.refreshQueue();
@@ -458,12 +575,16 @@ export class InteractiveMode {
458
575
  return;
459
576
  }
460
577
  if (this.sessionSwitching) {
461
- this.projection.setError("A session is opening; wait before submitting a prompt.");
578
+ this.projection.setError(this.locale === "zh-CN"
579
+ ? "会话正在打开,请等待后再提交提示词。"
580
+ : "A session is opening; wait before submitting a prompt.");
462
581
  this.refresh();
463
582
  return;
464
583
  }
465
584
  if (this.compactionInFlight) {
466
- this.projection.setError("A compaction is running; wait before submitting a prompt.");
585
+ this.projection.setError(this.locale === "zh-CN"
586
+ ? "正在压缩上下文,请等待后再提交提示词。"
587
+ : "A compaction is running; wait before submitting a prompt.");
467
588
  this.refresh();
468
589
  return;
469
590
  }
@@ -509,22 +630,28 @@ export class InteractiveMode {
509
630
  async submitSteering(text) {
510
631
  const prompt = text.trim();
511
632
  if (prompt.length === 0) {
512
- this.projection.setError("Steering content must not be empty.");
633
+ this.projection.setError(this.locale === "zh-CN" ? "引导内容不能为空。" : "Steering content must not be empty.");
513
634
  this.refresh();
514
635
  return;
515
636
  }
516
637
  if (this.sessionSwitching) {
517
- this.projection.setError("A session is opening; wait before steering the agent.");
638
+ this.projection.setError(this.locale === "zh-CN"
639
+ ? "会话正在打开,请等待后再引导 Agent。"
640
+ : "A session is opening; wait before steering the agent.");
518
641
  this.refresh();
519
642
  return;
520
643
  }
521
644
  if (this.compactionInFlight) {
522
- this.projection.setError("A compaction is running; wait before steering the agent.");
645
+ this.projection.setError(this.locale === "zh-CN"
646
+ ? "正在压缩上下文,请等待后再引导 Agent。"
647
+ : "A compaction is running; wait before steering the agent.");
523
648
  this.refresh();
524
649
  return;
525
650
  }
526
651
  if (!this.promptInFlight) {
527
- this.projection.setError("Steering is only available while a prompt is running.");
652
+ this.projection.setError(this.locale === "zh-CN"
653
+ ? "只能在提示词运行时引导 Agent。"
654
+ : "Steering is only available while a prompt is running.");
528
655
  this.refresh();
529
656
  return;
530
657
  }
@@ -570,7 +697,7 @@ export class InteractiveMode {
570
697
  break;
571
698
  case "clear":
572
699
  this.projection.clearVisibleMessages();
573
- this.projection.setStatus("visible messages cleared");
700
+ this.projection.setStatus(translate(this.locale, "visibleMessagesCleared"));
574
701
  break;
575
702
  case "model":
576
703
  this.openModelSelector();
@@ -578,6 +705,9 @@ export class InteractiveMode {
578
705
  case "session":
579
706
  this.openSessionSelector();
580
707
  return;
708
+ case "tree":
709
+ this.openTreeSelector();
710
+ return;
581
711
  case "theme":
582
712
  this.openThemeSelector();
583
713
  return;
@@ -600,7 +730,7 @@ export class InteractiveMode {
600
730
  if (this.lastFailedPrompt && !this.promptInFlight)
601
731
  void this.submit(this.lastFailedPrompt, true);
602
732
  else
603
- this.projection.setStatus("nothing to retry");
733
+ this.projection.setStatus(translate(this.locale, "nothingToRetry"));
604
734
  break;
605
735
  default: {
606
736
  if (this.extensionHost?.listCommands().some((entry) => entry.name === command)) {
@@ -628,7 +758,11 @@ export class InteractiveMode {
628
758
  }
629
759
  this.editor.cancelAutocomplete();
630
760
  this.closeAutocompleteOverlay();
631
- void showInteractiveProviderOnboarding({ ...this.providerOnboarding, tui: this.tui })
761
+ void showInteractiveProviderOnboarding({
762
+ ...this.providerOnboarding,
763
+ configuration: { ...this.providerOnboarding.configuration, locale: this.locale },
764
+ tui: this.tui,
765
+ })
632
766
  .then((runtime) => {
633
767
  if (!this.started)
634
768
  return;
@@ -660,7 +794,6 @@ export class InteractiveMode {
660
794
  return;
661
795
  }
662
796
  const providerId = this.session.providerId;
663
- const modelId = this.session.modelId;
664
797
  try {
665
798
  const removed = await removeGlobalProviderApiKey(this.providerOnboarding.agentDir, providerId);
666
799
  if (!removed) {
@@ -668,14 +801,7 @@ export class InteractiveMode {
668
801
  this.refresh();
669
802
  return;
670
803
  }
671
- const configuration = await loadStartupConfiguration(this.session.allowedRoot, this.providerOnboarding.configuration.environment, this.providerOnboarding.agentDir);
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`);
804
+ this.projection.setStatus(`global API key removed for provider=${providerId}; it remains active until this session ends`);
679
805
  }
680
806
  catch (cause) {
681
807
  this.projection.setError(cause instanceof Error ? cause.message : String(cause));
@@ -687,7 +813,7 @@ export class InteractiveMode {
687
813
  name: `skill:${skill.name}`,
688
814
  description: skill.description,
689
815
  }));
690
- return [...BUILTIN_SLASH_COMMANDS, ...(this.extensionHost?.listCommands() ?? []), ...skills];
816
+ return [...builtinSlashCommands(this.locale), ...(this.extensionHost?.listCommands() ?? []), ...skills];
691
817
  }
692
818
  isLoadedSkillCommand(input) {
693
819
  const name = /^\/skill:([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s|$)/.exec(input)?.[1];
@@ -695,20 +821,23 @@ export class InteractiveMode {
695
821
  }
696
822
  formatUsage() {
697
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
+ }
698
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}`;
699
828
  }
700
829
  async runManualCompaction() {
701
830
  if (this.promptInFlight || this.compactionInFlight) {
702
- 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.");
703
832
  this.refresh();
704
833
  return;
705
834
  }
706
835
  this.compactionInFlight = true;
707
- this.projection.setStatus("starting manual compaction");
836
+ this.projection.setStatus(translate(this.locale, "manualCompactionStarting"));
708
837
  this.refresh();
709
838
  try {
710
839
  await this.session.compact();
711
- this.projection.setStatus("manual compaction complete");
840
+ this.projection.setStatus(translate(this.locale, "manualCompactionComplete"));
712
841
  }
713
842
  catch (cause) {
714
843
  this.projection.setError(cause instanceof Error ? cause.message : String(cause));