atom-lite 0.6.2 → 0.6.3

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 (71) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +25 -14
  3. package/dist/agent/loop.d.ts +10 -0
  4. package/dist/agent/loop.js +114 -1
  5. package/dist/agent/loop.js.map +1 -1
  6. package/dist/agent/turn-recorder.d.ts +4 -0
  7. package/dist/agent/turn-recorder.js +1 -0
  8. package/dist/agent/turn-recorder.js.map +1 -1
  9. package/dist/agent/types.d.ts +22 -0
  10. package/dist/cli/app.js +160 -40
  11. package/dist/cli/app.js.map +1 -1
  12. package/dist/cli/commands.js +180 -0
  13. package/dist/cli/commands.js.map +1 -1
  14. package/dist/cli/main.js +2 -1
  15. package/dist/cli/main.js.map +1 -1
  16. package/dist/cli/tool-box.js +3 -2
  17. package/dist/cli/tool-box.js.map +1 -1
  18. package/dist/config/defaults.js +1 -1
  19. package/dist/config/load.js +2 -2
  20. package/dist/config/load.js.map +1 -1
  21. package/dist/config/schema.d.ts +4 -0
  22. package/dist/config/schema.js +5 -1
  23. package/dist/config/schema.js.map +1 -1
  24. package/dist/context/budget.js +19 -1
  25. package/dist/context/budget.js.map +1 -1
  26. package/dist/eval/runner.d.ts +51 -0
  27. package/dist/eval/runner.js +230 -0
  28. package/dist/eval/runner.js.map +1 -0
  29. package/dist/mcp/client.js +2 -1
  30. package/dist/mcp/client.js.map +1 -1
  31. package/dist/providers/context-window.d.ts +4 -0
  32. package/dist/providers/context-window.js +54 -0
  33. package/dist/providers/context-window.js.map +1 -1
  34. package/dist/providers/factory.js +3 -0
  35. package/dist/providers/factory.js.map +1 -1
  36. package/dist/providers/gemini.d.ts +2 -0
  37. package/dist/providers/gemini.js +18 -11
  38. package/dist/providers/gemini.js.map +1 -1
  39. package/dist/providers/ollama.d.ts +3 -0
  40. package/dist/providers/ollama.js +9 -0
  41. package/dist/providers/ollama.js.map +1 -1
  42. package/dist/providers/openai-compatible.d.ts +2 -0
  43. package/dist/providers/openai-compatible.js +24 -3
  44. package/dist/providers/openai-compatible.js.map +1 -1
  45. package/dist/runtime/background-tasks.d.ts +23 -0
  46. package/dist/runtime/background-tasks.js +141 -0
  47. package/dist/runtime/background-tasks.js.map +1 -0
  48. package/dist/runtime/checkpoint-store.d.ts +64 -0
  49. package/dist/runtime/checkpoint-store.js +419 -0
  50. package/dist/runtime/checkpoint-store.js.map +1 -0
  51. package/dist/tools/bash.js +45 -9
  52. package/dist/tools/bash.js.map +1 -1
  53. package/dist/tools/edit.js +28 -8
  54. package/dist/tools/edit.js.map +1 -1
  55. package/dist/tools/index.d.ts +1 -0
  56. package/dist/tools/index.js +1 -0
  57. package/dist/tools/index.js.map +1 -1
  58. package/dist/tools/read.js +70 -9
  59. package/dist/tools/read.js.map +1 -1
  60. package/dist/tools/task.d.ts +2 -0
  61. package/dist/tools/task.js +55 -0
  62. package/dist/tools/task.js.map +1 -0
  63. package/dist/tools/types.d.ts +8 -1
  64. package/dist/tools/write.js +29 -8
  65. package/dist/tools/write.js.map +1 -1
  66. package/dist/types.d.ts +9 -0
  67. package/dist/types.js.map +1 -1
  68. package/dist/version.d.ts +1 -0
  69. package/dist/version.js +14 -0
  70. package/dist/version.js.map +1 -0
  71. package/package.json +3 -2
package/dist/cli/app.js CHANGED
@@ -9,14 +9,15 @@ import { execSync, spawn } from "node:child_process";
9
9
  import { defaultShell, terminateProcessTree } from "../security/process.js";
10
10
  import { openSearchablePicker } from "./picker.js";
11
11
  import { openCommandPalette } from "./palette.js";
12
- import { bareCommandName, createCommandRegistry } from "./commands.js";
12
+ import { bareCommandName, createCommandRegistry, } from "./commands.js";
13
13
  import { resolveTheme, themeLabel, themeNames, markdownThemeFor, } from "./theme.js";
14
14
  import { SecretInput } from "./secret-input.js";
15
15
  import { StatusLine } from "./status-line.js";
16
16
  import { Activity } from "./activity.js";
17
17
  import { ToolBox } from "./tool-box.js";
18
+ import { ApprovalPrompt } from "./approval-prompt.js";
18
19
  import { Banner } from "./banner.js";
19
- import { formatContextUsage, knownContextWindow, } from "../providers/context-window.js";
20
+ import { formatContextUsage, knownContextWindow, knownVisionModel, } from "../providers/context-window.js";
20
21
  import { StreamText } from "./stream-text.js";
21
22
  import { Transcript } from "./transcript.js";
22
23
  import { AgentRuntime } from "../agent/loop.js";
@@ -28,8 +29,8 @@ import { ProjectTrustPrompt } from "./trust-prompt.js";
28
29
  import { formatSessionList, formatSessionSearch, searchSessions, sessionTitleFromEvents, } from "../sessions/search.js";
29
30
  import { providerSwitchNotice, sessionHistoryLines, sessionSelectionFromEvents, } from "../sessions/history.js";
30
31
  import { blockAtLine, createMouseCapture, handleMouseEvent, parseSgrMouse, shouldEnableMouse, } from "./mouse.js";
31
- import { readTool, writeTool, editTool, bashTool, webfetchTool, websearchTool, grepTool, globTool } from "../tools/index.js";
32
- import { McpManager, formatMcpStatus, formatMcpDoctor, createMcpTools } from "../mcp/index.js";
32
+ import { readTool, writeTool, editTool, bashTool, webfetchTool, websearchTool, grepTool, globTool, taskTool, } from "../tools/index.js";
33
+ import { McpManager, formatMcpStatus, formatMcpDoctor, createMcpTools, } from "../mcp/index.js";
33
34
  import { createProvider } from "../providers/factory.js";
34
35
  import { availableProviderNames, getPreset, listModelsForProvider, materializeProviderConfig, probeApiKey, providerLabel, PROVIDER_PRESETS, } from "../providers/catalog.js";
35
36
  import { ContextManager } from "../context/manager.js";
@@ -90,7 +91,8 @@ export async function run(args) {
90
91
  }
91
92
  }
92
93
  if (showVersion) {
93
- process.stdout.write("0.3.1\n");
94
+ const { VERSION } = await import("../version.js");
95
+ process.stdout.write(VERSION + "\n");
94
96
  return;
95
97
  }
96
98
  if (listSessions || searchSessionsQuery !== undefined) {
@@ -125,12 +127,23 @@ export async function run(args) {
125
127
  modelByProvider.set(provider.id, modelOverride);
126
128
  const useTools = !positional.includes("__notools__");
127
129
  const builtInTools = useTools
128
- ? [readTool, writeTool, editTool, bashTool, webfetchTool, websearchTool, grepTool, globTool]
130
+ ? [
131
+ readTool,
132
+ writeTool,
133
+ editTool,
134
+ bashTool,
135
+ webfetchTool,
136
+ websearchTool,
137
+ grepTool,
138
+ globTool,
139
+ taskTool,
140
+ ]
129
141
  : [];
130
142
  const interactive = process.stdin.isTTY === true && !jsonMode;
131
143
  // one-shot task from positional or stdin
132
144
  let stdinTask = "";
133
- if (!process.stdin.isTTY && positional.filter((p) => !p.startsWith("__")).length === 0) {
145
+ if (!process.stdin.isTTY &&
146
+ positional.filter((p) => !p.startsWith("__")).length === 0) {
134
147
  const chunks = [];
135
148
  for await (const chunk of process.stdin)
136
149
  chunks.push(chunk);
@@ -140,9 +153,11 @@ export async function run(args) {
140
153
  const oneShot = (taskArgs.join(" ").trim() || stdinTask).trim();
141
154
  const store = new JsonlSessionStore();
142
155
  let session = sessionOverride
143
- ? store.resume(sessionOverride) ?? store.create(process.cwd(), provider.id)
156
+ ? (store.resume(sessionOverride) ??
157
+ store.create(process.cwd(), provider.id))
144
158
  : positional.includes("__continue__")
145
- ? store.resumeLatest(process.cwd()) ?? store.create(process.cwd(), provider.id)
159
+ ? (store.resumeLatest(process.cwd()) ??
160
+ store.create(process.cwd(), provider.id))
146
161
  : store.create(process.cwd(), provider.id);
147
162
  const mcpServers = (config.mcpServers ?? {});
148
163
  const mcpManager = new McpManager({
@@ -165,7 +180,9 @@ export async function run(args) {
165
180
  try {
166
181
  const { execFileSync } = require("node:child_process");
167
182
  const parts = value.slice(1).trim().split(/\s+/);
168
- return execFileSync(parts[0], parts.slice(1), { encoding: "utf8" }).trim();
183
+ return execFileSync(parts[0], parts.slice(1), {
184
+ encoding: "utf8",
185
+ }).trim();
169
186
  }
170
187
  catch {
171
188
  return undefined;
@@ -203,7 +220,7 @@ export async function run(args) {
203
220
  // Bridge Pi-style extension providers into Atom-lite's config so they
204
221
  // appear in /provider, /model, and the command palette.
205
222
  for (const loaded of extensionLoader.list()) {
206
- for (const [name, provider] of (loaded.registries?.providers ?? new Map())) {
223
+ for (const [name, provider] of loaded.registries?.providers ?? new Map()) {
207
224
  const cfg = provider?.config ?? {};
208
225
  const firstModel = Array.isArray(cfg.models) && cfg.models.length > 0
209
226
  ? (cfg.models[0].id ?? cfg.models[0].name ?? "default")
@@ -278,9 +295,14 @@ export async function run(args) {
278
295
  contextManager,
279
296
  extensionDispatch,
280
297
  bashConcurrency: config.runtime.bashConcurrency,
298
+ checkpointingEnabled: interactive || Boolean(config.runtime.checkpointing),
281
299
  });
282
300
  mcpManager.onToolsChanged = () => {
283
- runtime.setTools([...builtInTools, ...extensionTools, ...createMcpTools(mcpManager)]);
301
+ runtime.setTools([
302
+ ...builtInTools,
303
+ ...extensionTools,
304
+ ...createMcpTools(mcpManager),
305
+ ]);
284
306
  };
285
307
  if (oneShot || !interactive) {
286
308
  // non-interactive / one-shot path
@@ -290,7 +312,11 @@ export async function run(args) {
290
312
  try {
291
313
  if (useTools) {
292
314
  await mcpManager.syncTools();
293
- runtime.setTools([...builtInTools, ...extensionTools, ...createMcpTools(mcpManager)]);
315
+ runtime.setTools([
316
+ ...builtInTools,
317
+ ...extensionTools,
318
+ ...createMcpTools(mcpManager),
319
+ ]);
294
320
  }
295
321
  const result = await runtime.runAgentTurn(oneShot || " ", session, controller.signal);
296
322
  if (jsonMode) {
@@ -302,7 +328,9 @@ export async function run(args) {
302
328
  }
303
329
  catch (err) {
304
330
  if (jsonMode)
305
- process.stderr.write(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }) + "\n");
331
+ process.stderr.write(JSON.stringify({
332
+ error: err instanceof Error ? err.message : String(err),
333
+ }) + "\n");
306
334
  else
307
335
  process.stderr.write(`atom-lite: ${err instanceof Error ? err.message : String(err)}\n`);
308
336
  process.exit(1);
@@ -314,14 +342,16 @@ export async function run(args) {
314
342
  return;
315
343
  }
316
344
  // ---- Interactive TUI (first-class) ----
317
- const { TuiAltScreen, Text, Input, Editor, Container, Spacer, ScrollView, VStack, Loader, Markdown, Box } = await import("@earendil-works/pi-tui");
345
+ const { TuiAltScreen, Text, Input, Editor, Container, Spacer, ScrollView, VStack, Loader, Markdown, Box, } = await import("@earendil-works/pi-tui");
318
346
  const mouseEnabled = shouldEnableMouse({
319
347
  interactive,
320
348
  configured: config.ui.mouse,
321
349
  stdoutIsTTY: process.stdout.isTTY === true,
322
350
  });
323
351
  const terminal = new ProcessTerminal();
324
- const tui = new TuiAltScreen(terminal, false, undefined, { mouse: mouseEnabled });
352
+ const tui = new TuiAltScreen(terminal, false, undefined, {
353
+ mouse: mouseEnabled,
354
+ });
325
355
  const mouseCapture = createMouseCapture((data) => terminal.write(data));
326
356
  let effortLevel = "medium";
327
357
  runtime.setEffort(effortLevel);
@@ -349,9 +379,17 @@ export async function run(args) {
349
379
  displayPath = "~" + cwd.slice(home.length);
350
380
  let branch = "";
351
381
  try {
352
- branch = execSync("git rev-parse --abbrev-ref HEAD", { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
382
+ branch = execSync("git rev-parse --abbrev-ref HEAD", {
383
+ cwd,
384
+ encoding: "utf8",
385
+ stdio: ["ignore", "pipe", "ignore"],
386
+ }).trim();
353
387
  if (branch === "HEAD") {
354
- branch = execSync("git rev-parse --short HEAD", { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
388
+ branch = execSync("git rev-parse --short HEAD", {
389
+ cwd,
390
+ encoding: "utf8",
391
+ stdio: ["ignore", "pipe", "ignore"],
392
+ }).trim();
355
393
  }
356
394
  }
357
395
  catch {
@@ -401,7 +439,9 @@ export async function run(args) {
401
439
  const sig = skillCacheSignature(process.cwd());
402
440
  if (sig === registrySkillSignature)
403
441
  return;
404
- commandRegistry = createCommandRegistry({ skills: cachedSkillRegistry().list() });
442
+ commandRegistry = createCommandRegistry({
443
+ skills: cachedSkillRegistry().list(),
444
+ });
405
445
  registrySkillSignature = sig;
406
446
  input.setAutocompleteProvider(new CombinedAutocompleteProvider(commandRegistry.autocomplete(), process.cwd(), fdPath));
407
447
  }
@@ -433,7 +473,9 @@ export async function run(args) {
433
473
  input.autocompleteList) {
434
474
  const selected = input.autocompleteList.getSelectedItem?.();
435
475
  const name = selected?.value ?? selected?.name ?? selected?.label;
436
- const cmd = name ? commandRegistry.get(String(name).replace(/^\//, "")) : undefined;
476
+ const cmd = name
477
+ ? commandRegistry.get(String(name).replace(/^\//, ""))
478
+ : undefined;
437
479
  if (cmd?.bareInvokable) {
438
480
  originalHandleInput("\t");
439
481
  return;
@@ -595,7 +637,11 @@ export async function run(args) {
595
637
  try {
596
638
  if (useTools) {
597
639
  await mcpManager.syncTools();
598
- runtime.setTools([...builtInTools, ...extensionTools, ...createMcpTools(mcpManager)]);
640
+ runtime.setTools([
641
+ ...builtInTools,
642
+ ...extensionTools,
643
+ ...createMcpTools(mcpManager),
644
+ ]);
599
645
  }
600
646
  const turnResult = await runtime.runAgentTurn(prompt, session, controller.signal);
601
647
  if (turnResult === "TOKEN_BUDGET_REACHED") {
@@ -746,11 +792,21 @@ export async function run(args) {
746
792
  function refreshContextWindow() {
747
793
  const profile = provider.id;
748
794
  const model = currentModelLabel();
749
- const configured = profileConfig(profile)?.contextWindow;
750
- const window = typeof configured === "number" && configured > 0
751
- ? configured
795
+ const pcfg = profileConfig(profile);
796
+ const configuredWindow = pcfg?.contextWindow;
797
+ const window = typeof configuredWindow === "number" && configuredWindow > 0
798
+ ? configuredWindow
752
799
  : knownContextWindow(model);
753
800
  runtime.setContextWindow(window);
801
+ // Config override for vision takes precedence over provider metadata.
802
+ if (typeof pcfg?.supportsVision === "boolean") {
803
+ runtime.setSupportsVision(pcfg.supportsVision);
804
+ }
805
+ else {
806
+ const guessed = knownVisionModel(model);
807
+ if (typeof guessed === "boolean")
808
+ runtime.setSupportsVision(guessed);
809
+ }
754
810
  updateStatus();
755
811
  const getModelInfo = provider.getModelInfo;
756
812
  if (typeof getModelInfo !== "function")
@@ -761,10 +817,16 @@ export async function run(args) {
761
817
  if (provider.id !== profile || currentModelLabel() !== model)
762
818
  return;
763
819
  const resolved = info?.contextWindow;
764
- if (typeof resolved === "number" && resolved > 0) {
820
+ if (typeof resolved === "number" &&
821
+ resolved > 0 &&
822
+ typeof configuredWindow !== "number") {
765
823
  runtime.setContextWindow(resolved);
766
824
  updateStatus();
767
825
  }
826
+ if (typeof pcfg?.supportsVision !== "boolean" &&
827
+ typeof info?.supportsVision === "boolean") {
828
+ runtime.setSupportsVision(info.supportsVision);
829
+ }
768
830
  })
769
831
  .catch(() => {
770
832
  // Keep the static/config fallback.
@@ -775,7 +837,7 @@ export async function run(args) {
775
837
  const MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
776
838
  const modelCache = new Map();
777
839
  for (const loaded of extensionLoader.list()) {
778
- for (const [name, provider] of (loaded.registries?.providers ?? new Map())) {
840
+ for (const [name, provider] of loaded.registries?.providers ?? new Map()) {
779
841
  const cfg = provider?.config ?? {};
780
842
  const models = Array.isArray(cfg.models)
781
843
  ? cfg.models.map((m) => m.id ?? m.name).filter(Boolean)
@@ -1011,7 +1073,11 @@ export async function run(args) {
1011
1073
  provider = createProvider(config, preset.id, chosen);
1012
1074
  runtime.switchModel(provider, chosen);
1013
1075
  if (typeof session.appendEvent === "function") {
1014
- session.appendEvent({ type: "provider_selected", provider: preset.id, model: chosen });
1076
+ session.appendEvent({
1077
+ type: "provider_selected",
1078
+ provider: preset.id,
1079
+ model: chosen,
1080
+ });
1015
1081
  }
1016
1082
  refreshContextWindow();
1017
1083
  appendLine(`provider: ${preset.label} (${preset.id}) · model: ${chosen}`);
@@ -1262,7 +1328,9 @@ export async function run(args) {
1262
1328
  return "No extensions discovered.";
1263
1329
  return report
1264
1330
  .map((ext) => {
1265
- const origin = ext.origin ? `${ext.source}:${ext.origin}` : ext.source;
1331
+ const origin = ext.origin
1332
+ ? `${ext.source}:${ext.origin}`
1333
+ : ext.source;
1266
1334
  const version = ext.version ? `v${ext.version}` : "-";
1267
1335
  const counts = `${ext.toolCount} tools / ${ext.commandCount} commands`;
1268
1336
  const error = ext.error ? `\t${ext.error}` : "";
@@ -1280,7 +1348,9 @@ export async function run(args) {
1280
1348
  return showThinking;
1281
1349
  },
1282
1350
  setTheme: setThemeTarget,
1283
- listSkills: () => cachedSkillRegistry().list().map((skill) => ({
1351
+ listSkills: () => cachedSkillRegistry()
1352
+ .list()
1353
+ .map((skill) => ({
1284
1354
  name: skill.name,
1285
1355
  description: skill.description,
1286
1356
  })),
@@ -1347,9 +1417,7 @@ export async function run(args) {
1347
1417
  const summaries = scope === "all" ? store.list() : store.list(process.cwd());
1348
1418
  const toggle = {
1349
1419
  value: "__scope__",
1350
- label: scope === "all"
1351
- ? "Show this workspace only"
1352
- : "Show all workspaces",
1420
+ label: scope === "all" ? "Show this workspace only" : "Show all workspaces",
1353
1421
  description: `scope: ${scope === "all" ? "all workspaces" : process.cwd()}`,
1354
1422
  };
1355
1423
  return [
@@ -1373,7 +1441,9 @@ export async function run(args) {
1373
1441
  if (item.value === "__scope__") {
1374
1442
  scope = scope === "all" ? "workspace" : "all";
1375
1443
  picker.updateItems(buildItems());
1376
- picker.setStatus(scope === "all" ? "showing all workspaces" : "showing this workspace");
1444
+ picker.setStatus(scope === "all"
1445
+ ? "showing all workspaces"
1446
+ : "showing this workspace");
1377
1447
  return;
1378
1448
  }
1379
1449
  picker.close();
@@ -1530,7 +1600,8 @@ export async function run(args) {
1530
1600
  }
1531
1601
  extensionProviderNames.clear();
1532
1602
  for (const loaded of extensionLoader.list()) {
1533
- for (const [name, provider] of (loaded.registries?.providers ?? new Map())) {
1603
+ for (const [name, provider] of loaded.registries?.providers ??
1604
+ new Map()) {
1534
1605
  const cfg = provider?.config ?? {};
1535
1606
  const firstModel = Array.isArray(cfg.models) && cfg.models.length > 0
1536
1607
  ? (cfg.models[0].id ?? cfg.models[0].name ?? "default")
@@ -1715,6 +1786,22 @@ export async function run(args) {
1715
1786
  else if (e.type === "context_usage") {
1716
1787
  updateStatus();
1717
1788
  }
1789
+ else if (e.type === "background_task_complete") {
1790
+ appendLine(`background task ${e.id}: ${e.status} (exit ${e.exitCode ?? "null"})`, theme.tokens.dim);
1791
+ tui.requestRender();
1792
+ }
1793
+ else if (e.type === "checkpoint_created") {
1794
+ appendLine(`checkpoint ${e.id} created`, theme.tokens.dim);
1795
+ tui.requestRender();
1796
+ }
1797
+ else if (e.type === "checkpoint_failed") {
1798
+ appendLine(`checkpoint failed: ${e.reason}`, theme.tokens.warning);
1799
+ tui.requestRender();
1800
+ }
1801
+ else if (e.type === "checkpoint_unavailable") {
1802
+ appendLine(`checkpoint unavailable: ${e.reason ?? "not a git repository"}`, theme.tokens.warning);
1803
+ tui.requestRender();
1804
+ }
1718
1805
  else if (e.type === "budget_warning") {
1719
1806
  appendLine(`budget warning (${e.scope}): ${e.used}/${e.limit} tokens`, theme.tokens.warning);
1720
1807
  tui.requestRender();
@@ -1722,7 +1809,11 @@ export async function run(args) {
1722
1809
  else if (e.type === "model_switched") {
1723
1810
  appendLine(`switched model to ${e.provider}/${e.model}`);
1724
1811
  if (typeof session.appendEvent === "function") {
1725
- session.appendEvent({ type: "model_switched", provider: e.provider, model: e.model });
1812
+ session.appendEvent({
1813
+ type: "model_switched",
1814
+ provider: e.provider,
1815
+ model: e.model,
1816
+ });
1726
1817
  }
1727
1818
  updateStatus();
1728
1819
  }
@@ -1768,7 +1859,11 @@ export async function run(args) {
1768
1859
  lines.push(` config: FAIL ${e instanceof Error ? e.message : String(e)}`);
1769
1860
  }
1770
1861
  try {
1771
- const r = await provider.generate({ model: modelOverride ?? "default", messages: [{ role: "user", content: [{ type: "text", text: "ping" }] }], tools: [] });
1862
+ const r = await provider.generate({
1863
+ model: modelOverride ?? "default",
1864
+ messages: [{ role: "user", content: [{ type: "text", text: "ping" }] }],
1865
+ tools: [],
1866
+ });
1772
1867
  lines.push(` provider generate: ok (${r.finishReason})`);
1773
1868
  }
1774
1869
  catch (e) {
@@ -1793,7 +1888,11 @@ export async function run(args) {
1793
1888
  }
1794
1889
  if (useTools) {
1795
1890
  await mcpManager.syncTools();
1796
- runtime.setTools([...builtInTools, ...extensionTools, ...createMcpTools(mcpManager)]);
1891
+ runtime.setTools([
1892
+ ...builtInTools,
1893
+ ...extensionTools,
1894
+ ...createMcpTools(mcpManager),
1895
+ ]);
1797
1896
  }
1798
1897
  appendLine(formatMcpStatus(mcpManager.status()));
1799
1898
  return;
@@ -1901,7 +2000,9 @@ export async function run(args) {
1901
2000
  for (const message of session.messages) {
1902
2001
  if (message.role === "user") {
1903
2002
  turn++;
1904
- const snippet = redactText(messageText(message)).split("\\n")[0].slice(0, 80);
2003
+ const snippet = redactText(messageText(message))
2004
+ .split("\\n")[0]
2005
+ .slice(0, 80);
1905
2006
  lines.push(`${turn}. ${snippet || "(empty)"}`);
1906
2007
  }
1907
2008
  }
@@ -2030,8 +2131,27 @@ export async function run(args) {
2030
2131
  appendLine(`> ${trimmed}`, theme.tokens.user);
2031
2132
  await runTurn(trimmed);
2032
2133
  };
2033
- // Approval prompts are superseded by plan/yolo modes.
2034
- approvals.handler = undefined;
2134
+ approvals.handler = interactive
2135
+ ? (req) => new Promise((resolve) => {
2136
+ uiModalOpen = true;
2137
+ closeOverlay();
2138
+ dropdownContainer.clear();
2139
+ activity.setPhase("waiting");
2140
+ const prompt = new ApprovalPrompt(req, (decision) => {
2141
+ uiModalOpen = false;
2142
+ dropdownContainer.clear();
2143
+ tui.setFocus(input);
2144
+ activity.setPhase("working");
2145
+ if (decision === "trust-session") {
2146
+ appendLine("trusted this session: outside-workspace operations will run without approval", theme.tokens.dim);
2147
+ }
2148
+ resolve(decision);
2149
+ });
2150
+ dropdownContainer.addChild(prompt);
2151
+ tui.setFocus(prompt);
2152
+ tui.requestRender();
2153
+ })
2154
+ : undefined;
2035
2155
  process.on("SIGTERM", () => {
2036
2156
  void mcpManager.close().finally(() => {
2037
2157
  tui.stop();