atom-lite 0.5.1 → 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.
- package/CHANGELOG.md +33 -1
- package/README.md +60 -116
- package/dist/agent/loop.d.ts +16 -0
- package/dist/agent/loop.js +158 -11
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/system-prompt.d.ts +1 -0
- package/dist/agent/system-prompt.js +23 -0
- package/dist/agent/system-prompt.js.map +1 -0
- package/dist/agent/turn-recorder.d.ts +4 -0
- package/dist/agent/turn-recorder.js +1 -0
- package/dist/agent/turn-recorder.js.map +1 -1
- package/dist/agent/types.d.ts +33 -0
- package/dist/cli/app.js +337 -55
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/commands.js +180 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/diff-view.d.ts +18 -0
- package/dist/cli/diff-view.js +66 -0
- package/dist/cli/diff-view.js.map +1 -0
- package/dist/cli/main.js +2 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/panel.js +8 -8
- package/dist/cli/panel.js.map +1 -1
- package/dist/cli/status-line.d.ts +2 -0
- package/dist/cli/status-line.js +9 -1
- package/dist/cli/status-line.js.map +1 -1
- package/dist/cli/tool-box.d.ts +1 -1
- package/dist/cli/tool-box.js +47 -41
- package/dist/cli/tool-box.js.map +1 -1
- package/dist/config/defaults.d.ts +4 -5
- package/dist/config/defaults.js +6 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/load.js +29 -3
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +14 -14
- package/dist/config/schema.js +8 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/context/budget.js +19 -1
- package/dist/context/budget.js.map +1 -1
- package/dist/context/manager.js +1 -1
- package/dist/context/manager.js.map +1 -1
- package/dist/eval/runner.d.ts +51 -0
- package/dist/eval/runner.js +230 -0
- package/dist/eval/runner.js.map +1 -0
- package/dist/mcp/client.js +2 -1
- package/dist/mcp/client.js.map +1 -1
- package/dist/providers/context-window.d.ts +4 -0
- package/dist/providers/context-window.js +54 -0
- package/dist/providers/context-window.js.map +1 -1
- package/dist/providers/factory.js +3 -0
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/gemini.d.ts +2 -0
- package/dist/providers/gemini.js +18 -11
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/ollama.d.ts +3 -0
- package/dist/providers/ollama.js +9 -0
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts +2 -0
- package/dist/providers/openai-compatible.js +24 -3
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/runtime/background-tasks.d.ts +23 -0
- package/dist/runtime/background-tasks.js +141 -0
- package/dist/runtime/background-tasks.js.map +1 -0
- package/dist/runtime/checkpoint-store.d.ts +64 -0
- package/dist/runtime/checkpoint-store.js +419 -0
- package/dist/runtime/checkpoint-store.js.map +1 -0
- package/dist/runtime/modes.d.ts +3 -0
- package/dist/runtime/modes.js +8 -0
- package/dist/runtime/modes.js.map +1 -0
- package/dist/runtime/tool-executor.js +10 -0
- package/dist/runtime/tool-executor.js.map +1 -1
- package/dist/skills/registry.js +8 -1
- package/dist/skills/registry.js.map +1 -1
- package/dist/tools/bash.js +46 -10
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.js +28 -8
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/glob.d.ts +2 -0
- package/dist/tools/glob.js +97 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +2 -0
- package/dist/tools/grep.js +290 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.js +70 -9
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/task.d.ts +2 -0
- package/dist/tools/task.js +55 -0
- package/dist/tools/task.js.map +1 -0
- package/dist/tools/types.d.ts +10 -1
- package/dist/tools/write.js +29 -8
- package/dist/tools/write.js.map +1 -1
- package/dist/types.d.ts +9 -3
- package/dist/types.js +1 -4
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +14 -0
- package/dist/version.js.map +1 -0
- package/package.json +5 -3
- package/skills/atom-lite-extensions/SKILL.md +240 -0
- package/skills/caveman/SKILL.md +89 -0
- package/skills/code-review/SKILL.md +46 -0
- package/skills/optimization/SKILL.md +51 -0
package/dist/cli/app.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { loadConfig } from "../config/load.js";
|
|
2
|
+
import { DEFAULT_SYSTEM_PROMPT } from "../agent/system-prompt.js";
|
|
2
3
|
import { defaultAgentDir, userConfigDir } from "../config/paths.js";
|
|
3
4
|
import { saveProviderSelection, saveTheme, } from "../config/write.js";
|
|
4
5
|
import { resolveApiKey, saveApiKey } from "../config/credentials.js";
|
|
5
6
|
import { join } from "node:path";
|
|
6
7
|
import { writeFileSync, existsSync } from "node:fs";
|
|
7
|
-
import { execSync } from "node:child_process";
|
|
8
|
+
import { execSync, spawn } from "node:child_process";
|
|
9
|
+
import { defaultShell, terminateProcessTree } from "../security/process.js";
|
|
8
10
|
import { openSearchablePicker } from "./picker.js";
|
|
9
11
|
import { openCommandPalette } from "./palette.js";
|
|
10
|
-
import { bareCommandName, createCommandRegistry } from "./commands.js";
|
|
12
|
+
import { bareCommandName, createCommandRegistry, } from "./commands.js";
|
|
11
13
|
import { resolveTheme, themeLabel, themeNames, markdownThemeFor, } from "./theme.js";
|
|
12
14
|
import { SecretInput } from "./secret-input.js";
|
|
13
15
|
import { StatusLine } from "./status-line.js";
|
|
@@ -15,7 +17,7 @@ import { Activity } from "./activity.js";
|
|
|
15
17
|
import { ToolBox } from "./tool-box.js";
|
|
16
18
|
import { ApprovalPrompt } from "./approval-prompt.js";
|
|
17
19
|
import { Banner } from "./banner.js";
|
|
18
|
-
import { formatContextUsage, knownContextWindow, } from "../providers/context-window.js";
|
|
20
|
+
import { formatContextUsage, knownContextWindow, knownVisionModel, } from "../providers/context-window.js";
|
|
19
21
|
import { StreamText } from "./stream-text.js";
|
|
20
22
|
import { Transcript } from "./transcript.js";
|
|
21
23
|
import { AgentRuntime } from "../agent/loop.js";
|
|
@@ -27,8 +29,8 @@ import { ProjectTrustPrompt } from "./trust-prompt.js";
|
|
|
27
29
|
import { formatSessionList, formatSessionSearch, searchSessions, sessionTitleFromEvents, } from "../sessions/search.js";
|
|
28
30
|
import { providerSwitchNotice, sessionHistoryLines, sessionSelectionFromEvents, } from "../sessions/history.js";
|
|
29
31
|
import { blockAtLine, createMouseCapture, handleMouseEvent, parseSgrMouse, shouldEnableMouse, } from "./mouse.js";
|
|
30
|
-
import { readTool, writeTool, editTool, bashTool, webfetchTool, websearchTool } from "../tools/index.js";
|
|
31
|
-
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";
|
|
32
34
|
import { createProvider } from "../providers/factory.js";
|
|
33
35
|
import { availableProviderNames, getPreset, listModelsForProvider, materializeProviderConfig, probeApiKey, providerLabel, PROVIDER_PRESETS, } from "../providers/catalog.js";
|
|
34
36
|
import { ContextManager } from "../context/manager.js";
|
|
@@ -89,7 +91,8 @@ export async function run(args) {
|
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
if (showVersion) {
|
|
92
|
-
|
|
94
|
+
const { VERSION } = await import("../version.js");
|
|
95
|
+
process.stdout.write(VERSION + "\n");
|
|
93
96
|
return;
|
|
94
97
|
}
|
|
95
98
|
if (listSessions || searchSessionsQuery !== undefined) {
|
|
@@ -124,12 +127,23 @@ export async function run(args) {
|
|
|
124
127
|
modelByProvider.set(provider.id, modelOverride);
|
|
125
128
|
const useTools = !positional.includes("__notools__");
|
|
126
129
|
const builtInTools = useTools
|
|
127
|
-
? [
|
|
130
|
+
? [
|
|
131
|
+
readTool,
|
|
132
|
+
writeTool,
|
|
133
|
+
editTool,
|
|
134
|
+
bashTool,
|
|
135
|
+
webfetchTool,
|
|
136
|
+
websearchTool,
|
|
137
|
+
grepTool,
|
|
138
|
+
globTool,
|
|
139
|
+
taskTool,
|
|
140
|
+
]
|
|
128
141
|
: [];
|
|
129
142
|
const interactive = process.stdin.isTTY === true && !jsonMode;
|
|
130
143
|
// one-shot task from positional or stdin
|
|
131
144
|
let stdinTask = "";
|
|
132
|
-
if (!process.stdin.isTTY &&
|
|
145
|
+
if (!process.stdin.isTTY &&
|
|
146
|
+
positional.filter((p) => !p.startsWith("__")).length === 0) {
|
|
133
147
|
const chunks = [];
|
|
134
148
|
for await (const chunk of process.stdin)
|
|
135
149
|
chunks.push(chunk);
|
|
@@ -139,9 +153,11 @@ export async function run(args) {
|
|
|
139
153
|
const oneShot = (taskArgs.join(" ").trim() || stdinTask).trim();
|
|
140
154
|
const store = new JsonlSessionStore();
|
|
141
155
|
let session = sessionOverride
|
|
142
|
-
? store.resume(sessionOverride) ??
|
|
156
|
+
? (store.resume(sessionOverride) ??
|
|
157
|
+
store.create(process.cwd(), provider.id))
|
|
143
158
|
: positional.includes("__continue__")
|
|
144
|
-
? store.resumeLatest(process.cwd()) ??
|
|
159
|
+
? (store.resumeLatest(process.cwd()) ??
|
|
160
|
+
store.create(process.cwd(), provider.id))
|
|
145
161
|
: store.create(process.cwd(), provider.id);
|
|
146
162
|
const mcpServers = (config.mcpServers ?? {});
|
|
147
163
|
const mcpManager = new McpManager({
|
|
@@ -164,7 +180,9 @@ export async function run(args) {
|
|
|
164
180
|
try {
|
|
165
181
|
const { execFileSync } = require("node:child_process");
|
|
166
182
|
const parts = value.slice(1).trim().split(/\s+/);
|
|
167
|
-
return execFileSync(parts[0], parts.slice(1), {
|
|
183
|
+
return execFileSync(parts[0], parts.slice(1), {
|
|
184
|
+
encoding: "utf8",
|
|
185
|
+
}).trim();
|
|
168
186
|
}
|
|
169
187
|
catch {
|
|
170
188
|
return undefined;
|
|
@@ -202,7 +220,7 @@ export async function run(args) {
|
|
|
202
220
|
// Bridge Pi-style extension providers into Atom-lite's config so they
|
|
203
221
|
// appear in /provider, /model, and the command palette.
|
|
204
222
|
for (const loaded of extensionLoader.list()) {
|
|
205
|
-
for (const [name, provider] of
|
|
223
|
+
for (const [name, provider] of loaded.registries?.providers ?? new Map()) {
|
|
206
224
|
const cfg = provider?.config ?? {};
|
|
207
225
|
const firstModel = Array.isArray(cfg.models) && cfg.models.length > 0
|
|
208
226
|
? (cfg.models[0].id ?? cfg.models[0].name ?? "default")
|
|
@@ -244,7 +262,7 @@ export async function run(args) {
|
|
|
244
262
|
return results;
|
|
245
263
|
};
|
|
246
264
|
const contextManager = new ContextManager({
|
|
247
|
-
systemPrompt:
|
|
265
|
+
systemPrompt: DEFAULT_SYSTEM_PROMPT,
|
|
248
266
|
maxInputTokens: config.context.maxInputTokens ?? null,
|
|
249
267
|
reserveOutputTokens: config.context.reserveOutputTokens,
|
|
250
268
|
compactAtPercent: config.context.compactAtPercent,
|
|
@@ -256,7 +274,7 @@ export async function run(args) {
|
|
|
256
274
|
});
|
|
257
275
|
let sessionTrusted = false;
|
|
258
276
|
const approvals = {
|
|
259
|
-
mode:
|
|
277
|
+
mode: "ask",
|
|
260
278
|
// Installed below once the interactive UI helpers exist.
|
|
261
279
|
handler: undefined,
|
|
262
280
|
isTrusted: () => sessionTrusted,
|
|
@@ -271,12 +289,20 @@ export async function run(args) {
|
|
|
271
289
|
workspace: process.cwd(),
|
|
272
290
|
tools: allTools,
|
|
273
291
|
approvals,
|
|
292
|
+
mode: config.runtime.mode,
|
|
293
|
+
maxTokensPerSession: config.runtime.maxTokensPerSession,
|
|
294
|
+
maxTokensPerTurn: config.runtime.maxTokensPerTurn,
|
|
274
295
|
contextManager,
|
|
275
296
|
extensionDispatch,
|
|
276
297
|
bashConcurrency: config.runtime.bashConcurrency,
|
|
298
|
+
checkpointingEnabled: interactive || Boolean(config.runtime.checkpointing),
|
|
277
299
|
});
|
|
278
300
|
mcpManager.onToolsChanged = () => {
|
|
279
|
-
runtime.setTools([
|
|
301
|
+
runtime.setTools([
|
|
302
|
+
...builtInTools,
|
|
303
|
+
...extensionTools,
|
|
304
|
+
...createMcpTools(mcpManager),
|
|
305
|
+
]);
|
|
280
306
|
};
|
|
281
307
|
if (oneShot || !interactive) {
|
|
282
308
|
// non-interactive / one-shot path
|
|
@@ -286,7 +312,11 @@ export async function run(args) {
|
|
|
286
312
|
try {
|
|
287
313
|
if (useTools) {
|
|
288
314
|
await mcpManager.syncTools();
|
|
289
|
-
runtime.setTools([
|
|
315
|
+
runtime.setTools([
|
|
316
|
+
...builtInTools,
|
|
317
|
+
...extensionTools,
|
|
318
|
+
...createMcpTools(mcpManager),
|
|
319
|
+
]);
|
|
290
320
|
}
|
|
291
321
|
const result = await runtime.runAgentTurn(oneShot || " ", session, controller.signal);
|
|
292
322
|
if (jsonMode) {
|
|
@@ -298,7 +328,9 @@ export async function run(args) {
|
|
|
298
328
|
}
|
|
299
329
|
catch (err) {
|
|
300
330
|
if (jsonMode)
|
|
301
|
-
process.stderr.write(JSON.stringify({
|
|
331
|
+
process.stderr.write(JSON.stringify({
|
|
332
|
+
error: err instanceof Error ? err.message : String(err),
|
|
333
|
+
}) + "\n");
|
|
302
334
|
else
|
|
303
335
|
process.stderr.write(`atom-lite: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
304
336
|
process.exit(1);
|
|
@@ -310,14 +342,16 @@ export async function run(args) {
|
|
|
310
342
|
return;
|
|
311
343
|
}
|
|
312
344
|
// ---- Interactive TUI (first-class) ----
|
|
313
|
-
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");
|
|
314
346
|
const mouseEnabled = shouldEnableMouse({
|
|
315
347
|
interactive,
|
|
316
348
|
configured: config.ui.mouse,
|
|
317
349
|
stdoutIsTTY: process.stdout.isTTY === true,
|
|
318
350
|
});
|
|
319
351
|
const terminal = new ProcessTerminal();
|
|
320
|
-
const tui = new TuiAltScreen(terminal, false, undefined, {
|
|
352
|
+
const tui = new TuiAltScreen(terminal, false, undefined, {
|
|
353
|
+
mouse: mouseEnabled,
|
|
354
|
+
});
|
|
321
355
|
const mouseCapture = createMouseCapture((data) => terminal.write(data));
|
|
322
356
|
let effortLevel = "medium";
|
|
323
357
|
runtime.setEffort(effortLevel);
|
|
@@ -345,9 +379,17 @@ export async function run(args) {
|
|
|
345
379
|
displayPath = "~" + cwd.slice(home.length);
|
|
346
380
|
let branch = "";
|
|
347
381
|
try {
|
|
348
|
-
branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
382
|
+
branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
383
|
+
cwd,
|
|
384
|
+
encoding: "utf8",
|
|
385
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
386
|
+
}).trim();
|
|
349
387
|
if (branch === "HEAD") {
|
|
350
|
-
branch = execSync("git rev-parse --short HEAD", {
|
|
388
|
+
branch = execSync("git rev-parse --short HEAD", {
|
|
389
|
+
cwd,
|
|
390
|
+
encoding: "utf8",
|
|
391
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
392
|
+
}).trim();
|
|
351
393
|
}
|
|
352
394
|
}
|
|
353
395
|
catch {
|
|
@@ -359,10 +401,12 @@ export async function run(args) {
|
|
|
359
401
|
const header = new Text(theme.tokens.dim(headerLabel()), 1, 0);
|
|
360
402
|
const statusBar = new StatusLine(theme);
|
|
361
403
|
const activity = new Activity({ theme, onChange: () => updateStatus() });
|
|
404
|
+
let currentMode = config.runtime.mode;
|
|
362
405
|
statusBar.setParts({
|
|
363
406
|
left: `${provider.id}/${provider.model ?? "default"}`,
|
|
364
407
|
tokens: "tokens: ?",
|
|
365
408
|
effort: "effort: medium",
|
|
409
|
+
mode: currentMode,
|
|
366
410
|
activity: activity.text(),
|
|
367
411
|
});
|
|
368
412
|
const selectListTheme = {
|
|
@@ -395,20 +439,65 @@ export async function run(args) {
|
|
|
395
439
|
const sig = skillCacheSignature(process.cwd());
|
|
396
440
|
if (sig === registrySkillSignature)
|
|
397
441
|
return;
|
|
398
|
-
commandRegistry = createCommandRegistry({
|
|
442
|
+
commandRegistry = createCommandRegistry({
|
|
443
|
+
skills: cachedSkillRegistry().list(),
|
|
444
|
+
});
|
|
399
445
|
registrySkillSignature = sig;
|
|
400
|
-
input.setAutocompleteProvider(new CombinedAutocompleteProvider(commandRegistry.autocomplete(), process.cwd()));
|
|
446
|
+
input.setAutocompleteProvider(new CombinedAutocompleteProvider(commandRegistry.autocomplete(), process.cwd(), fdPath));
|
|
401
447
|
}
|
|
402
|
-
|
|
448
|
+
const fdPath = (() => {
|
|
449
|
+
for (const bin of ["fd", "fdfind"]) {
|
|
450
|
+
try {
|
|
451
|
+
const found = execSync(`command -v ${bin}`, {
|
|
452
|
+
encoding: "utf8",
|
|
453
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
454
|
+
}).trim();
|
|
455
|
+
if (found)
|
|
456
|
+
return found;
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
// ignore
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return null;
|
|
463
|
+
})();
|
|
464
|
+
input.setAutocompleteProvider(new CombinedAutocompleteProvider(commandRegistry.autocomplete(), process.cwd(), fdPath));
|
|
465
|
+
// Skills should autocomplete (fill the input) rather than execute immediately
|
|
466
|
+
// when selected from the autocomplete list; Enter on a typed command still
|
|
467
|
+
// runs them. Convert a confirming Enter into a Tab completion for skills.
|
|
468
|
+
const originalHandleInput = input.handleInput.bind(input);
|
|
469
|
+
input.handleInput = (data) => {
|
|
470
|
+
try {
|
|
471
|
+
if ((data === "\r" || data === "\n") &&
|
|
472
|
+
input.autocompleteState &&
|
|
473
|
+
input.autocompleteList) {
|
|
474
|
+
const selected = input.autocompleteList.getSelectedItem?.();
|
|
475
|
+
const name = selected?.value ?? selected?.name ?? selected?.label;
|
|
476
|
+
const cmd = name
|
|
477
|
+
? commandRegistry.get(String(name).replace(/^\//, ""))
|
|
478
|
+
: undefined;
|
|
479
|
+
if (cmd?.bareInvokable) {
|
|
480
|
+
originalHandleInput("\t");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
// Fall through to default handling on any introspection failure.
|
|
487
|
+
}
|
|
488
|
+
originalHandleInput(data);
|
|
489
|
+
};
|
|
403
490
|
const root = new VStack([]);
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
root.addChild(
|
|
407
|
-
root.addChild(
|
|
491
|
+
// Keep fixed chrome (spacer, input, status, header, dropdown) from being
|
|
492
|
+
// collapsed when the transcript grows; only the output region may shrink.
|
|
493
|
+
root.addChild(output, { grow: 1, shrink: 1, minSize: 0 });
|
|
494
|
+
root.addChild(new Spacer(1), { grow: 0, shrink: 0, minSize: 1 });
|
|
495
|
+
root.addChild(input, { grow: 0, shrink: 0 });
|
|
496
|
+
root.addChild(statusBar, { grow: 0, shrink: 0 });
|
|
408
497
|
// folder:branch info below the status line, dimmed
|
|
409
|
-
root.addChild(header);
|
|
498
|
+
root.addChild(header, { grow: 0, shrink: 0 });
|
|
410
499
|
const dropdownContainer = new Container();
|
|
411
|
-
root.addChild(dropdownContainer);
|
|
500
|
+
root.addChild(dropdownContainer, { grow: 0, shrink: 0 });
|
|
412
501
|
tui.setLayoutRoot(root);
|
|
413
502
|
tui.setFocus(input);
|
|
414
503
|
if (!noColor) {
|
|
@@ -536,6 +625,7 @@ export async function run(args) {
|
|
|
536
625
|
streamText.finalize();
|
|
537
626
|
}
|
|
538
627
|
let activeTurnController = null;
|
|
628
|
+
let shellMode = false;
|
|
539
629
|
const messageQueue = [];
|
|
540
630
|
const steeringQueue = [];
|
|
541
631
|
runtime.setSteeringQueue(steeringQueue);
|
|
@@ -547,9 +637,17 @@ export async function run(args) {
|
|
|
547
637
|
try {
|
|
548
638
|
if (useTools) {
|
|
549
639
|
await mcpManager.syncTools();
|
|
550
|
-
runtime.setTools([
|
|
640
|
+
runtime.setTools([
|
|
641
|
+
...builtInTools,
|
|
642
|
+
...extensionTools,
|
|
643
|
+
...createMcpTools(mcpManager),
|
|
644
|
+
]);
|
|
645
|
+
}
|
|
646
|
+
const turnResult = await runtime.runAgentTurn(prompt, session, controller.signal);
|
|
647
|
+
if (turnResult === "TOKEN_BUDGET_REACHED") {
|
|
648
|
+
appendLine("token budget reached; stopping", theme.tokens.warning);
|
|
649
|
+
tui.requestRender();
|
|
551
650
|
}
|
|
552
|
-
await runtime.runAgentTurn(prompt, session, controller.signal);
|
|
553
651
|
}
|
|
554
652
|
catch (err) {
|
|
555
653
|
if (err instanceof Error && err.message === "USER_CANCELLED") {
|
|
@@ -569,12 +667,94 @@ export async function run(args) {
|
|
|
569
667
|
}
|
|
570
668
|
}
|
|
571
669
|
}
|
|
670
|
+
/** Run a shell command directly (shell mode / `!` prefix). */
|
|
671
|
+
async function runShellCommand(command) {
|
|
672
|
+
const shell = defaultShell();
|
|
673
|
+
const flag = process.platform === "win32" ? "/c" : "-c";
|
|
674
|
+
const start = Date.now();
|
|
675
|
+
const cap = 30000;
|
|
676
|
+
let stdout = "";
|
|
677
|
+
let stderr = "";
|
|
678
|
+
let truncated = false;
|
|
679
|
+
const append = (target, chunk) => {
|
|
680
|
+
const s = chunk.toString("utf8");
|
|
681
|
+
let accepted = s;
|
|
682
|
+
if (target === "out") {
|
|
683
|
+
if (stdout.length + s.length > cap) {
|
|
684
|
+
truncated = true;
|
|
685
|
+
accepted = s.slice(0, Math.max(0, cap - stdout.length));
|
|
686
|
+
stdout += accepted;
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
stdout += s;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
if (stderr.length + s.length > cap) {
|
|
694
|
+
truncated = true;
|
|
695
|
+
accepted = s.slice(0, Math.max(0, cap - stderr.length));
|
|
696
|
+
stderr += accepted;
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
stderr += s;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (accepted) {
|
|
703
|
+
tui.requestRender();
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
try {
|
|
707
|
+
const child = spawn(shell, [flag, command], {
|
|
708
|
+
cwd: process.cwd(),
|
|
709
|
+
env: process.env,
|
|
710
|
+
detached: process.platform !== "win32",
|
|
711
|
+
});
|
|
712
|
+
child.stdout?.on("data", (c) => append("out", c));
|
|
713
|
+
child.stderr?.on("data", (c) => append("err", c));
|
|
714
|
+
const exit = await new Promise((resolve) => {
|
|
715
|
+
const timer = setTimeout(() => {
|
|
716
|
+
terminateProcessTree(child, "SIGTERM");
|
|
717
|
+
setTimeout(() => terminateProcessTree(child, "SIGKILL"), 1000).unref();
|
|
718
|
+
resolve({ code: null, signal: "SIGTERM" });
|
|
719
|
+
}, 120000);
|
|
720
|
+
timer.unref();
|
|
721
|
+
child.on("exit", (code, signal) => {
|
|
722
|
+
clearTimeout(timer);
|
|
723
|
+
resolve({ code, signal });
|
|
724
|
+
});
|
|
725
|
+
child.on("error", (err) => {
|
|
726
|
+
clearTimeout(timer);
|
|
727
|
+
resolve({ code: null, signal: null });
|
|
728
|
+
stderr = `COMMAND_FAILED: ${err instanceof Error ? err.message : String(err)}`;
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
const parts = [];
|
|
732
|
+
parts.push(`exit_code: ${exit.code ?? "null"}`);
|
|
733
|
+
if (exit.signal)
|
|
734
|
+
parts.push(`signal: ${exit.signal}`);
|
|
735
|
+
if (truncated)
|
|
736
|
+
parts.push("OUTPUT_TRUNCATED");
|
|
737
|
+
parts.push(`duration_ms: ${Date.now() - start}`);
|
|
738
|
+
if (stdout)
|
|
739
|
+
parts.push(`stdout:\n${stdout}`);
|
|
740
|
+
if (stderr)
|
|
741
|
+
parts.push(`stderr:\n${stderr}`);
|
|
742
|
+
const content = parts.join("\n");
|
|
743
|
+
appendLine(content, exit.code === 0 && !exit.signal && !stderr
|
|
744
|
+
? theme.tokens.success
|
|
745
|
+
: theme.tokens.error);
|
|
746
|
+
}
|
|
747
|
+
catch (err) {
|
|
748
|
+
appendLine(`shell error: ${err instanceof Error ? err.message : String(err)}`, theme.tokens.error);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
572
751
|
function updateStatus() {
|
|
573
752
|
const usage = runtime.getContextUsage();
|
|
574
753
|
statusBar.setParts({
|
|
575
754
|
left: `${provider.id}/${currentModelLabel()}`,
|
|
576
755
|
tokens: `tokens: ${formatContextUsage(usage.used, usage.window)}`,
|
|
577
756
|
effort: `effort: ${effortLevel}`,
|
|
757
|
+
mode: currentMode,
|
|
578
758
|
activity: activity.text(),
|
|
579
759
|
});
|
|
580
760
|
tui.requestRender();
|
|
@@ -612,11 +792,21 @@ export async function run(args) {
|
|
|
612
792
|
function refreshContextWindow() {
|
|
613
793
|
const profile = provider.id;
|
|
614
794
|
const model = currentModelLabel();
|
|
615
|
-
const
|
|
616
|
-
const
|
|
617
|
-
|
|
795
|
+
const pcfg = profileConfig(profile);
|
|
796
|
+
const configuredWindow = pcfg?.contextWindow;
|
|
797
|
+
const window = typeof configuredWindow === "number" && configuredWindow > 0
|
|
798
|
+
? configuredWindow
|
|
618
799
|
: knownContextWindow(model);
|
|
619
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
|
+
}
|
|
620
810
|
updateStatus();
|
|
621
811
|
const getModelInfo = provider.getModelInfo;
|
|
622
812
|
if (typeof getModelInfo !== "function")
|
|
@@ -627,10 +817,16 @@ export async function run(args) {
|
|
|
627
817
|
if (provider.id !== profile || currentModelLabel() !== model)
|
|
628
818
|
return;
|
|
629
819
|
const resolved = info?.contextWindow;
|
|
630
|
-
if (typeof resolved === "number" &&
|
|
820
|
+
if (typeof resolved === "number" &&
|
|
821
|
+
resolved > 0 &&
|
|
822
|
+
typeof configuredWindow !== "number") {
|
|
631
823
|
runtime.setContextWindow(resolved);
|
|
632
824
|
updateStatus();
|
|
633
825
|
}
|
|
826
|
+
if (typeof pcfg?.supportsVision !== "boolean" &&
|
|
827
|
+
typeof info?.supportsVision === "boolean") {
|
|
828
|
+
runtime.setSupportsVision(info.supportsVision);
|
|
829
|
+
}
|
|
634
830
|
})
|
|
635
831
|
.catch(() => {
|
|
636
832
|
// Keep the static/config fallback.
|
|
@@ -641,7 +837,7 @@ export async function run(args) {
|
|
|
641
837
|
const MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
642
838
|
const modelCache = new Map();
|
|
643
839
|
for (const loaded of extensionLoader.list()) {
|
|
644
|
-
for (const [name, provider] of
|
|
840
|
+
for (const [name, provider] of loaded.registries?.providers ?? new Map()) {
|
|
645
841
|
const cfg = provider?.config ?? {};
|
|
646
842
|
const models = Array.isArray(cfg.models)
|
|
647
843
|
? cfg.models.map((m) => m.id ?? m.name).filter(Boolean)
|
|
@@ -877,7 +1073,11 @@ export async function run(args) {
|
|
|
877
1073
|
provider = createProvider(config, preset.id, chosen);
|
|
878
1074
|
runtime.switchModel(provider, chosen);
|
|
879
1075
|
if (typeof session.appendEvent === "function") {
|
|
880
|
-
session.appendEvent({
|
|
1076
|
+
session.appendEvent({
|
|
1077
|
+
type: "provider_selected",
|
|
1078
|
+
provider: preset.id,
|
|
1079
|
+
model: chosen,
|
|
1080
|
+
});
|
|
881
1081
|
}
|
|
882
1082
|
refreshContextWindow();
|
|
883
1083
|
appendLine(`provider: ${preset.label} (${preset.id}) · model: ${chosen}`);
|
|
@@ -1085,13 +1285,15 @@ export async function run(args) {
|
|
|
1085
1285
|
appendLine(`skill not found: ${name}`);
|
|
1086
1286
|
return false;
|
|
1087
1287
|
}
|
|
1288
|
+
if (extra) {
|
|
1289
|
+
appendLine(`> ${extra}`, theme.tokens.user);
|
|
1290
|
+
}
|
|
1088
1291
|
session.appendMessage({ role: "system", content });
|
|
1089
1292
|
if (typeof session.appendEvent === "function") {
|
|
1090
1293
|
session.appendEvent({ type: "skill_loaded", name });
|
|
1091
1294
|
}
|
|
1092
|
-
appendLine(`Loaded skill: ${name}
|
|
1295
|
+
appendLine(`Loaded skill: ${name}`, theme.tokens.dim);
|
|
1093
1296
|
if (extra) {
|
|
1094
|
-
appendLine(`> ${extra}`);
|
|
1095
1297
|
await runTurn(extra);
|
|
1096
1298
|
}
|
|
1097
1299
|
return true;
|
|
@@ -1126,7 +1328,9 @@ export async function run(args) {
|
|
|
1126
1328
|
return "No extensions discovered.";
|
|
1127
1329
|
return report
|
|
1128
1330
|
.map((ext) => {
|
|
1129
|
-
const origin = ext.origin
|
|
1331
|
+
const origin = ext.origin
|
|
1332
|
+
? `${ext.source}:${ext.origin}`
|
|
1333
|
+
: ext.source;
|
|
1130
1334
|
const version = ext.version ? `v${ext.version}` : "-";
|
|
1131
1335
|
const counts = `${ext.toolCount} tools / ${ext.commandCount} commands`;
|
|
1132
1336
|
const error = ext.error ? `\t${ext.error}` : "";
|
|
@@ -1144,7 +1348,9 @@ export async function run(args) {
|
|
|
1144
1348
|
return showThinking;
|
|
1145
1349
|
},
|
|
1146
1350
|
setTheme: setThemeTarget,
|
|
1147
|
-
listSkills: () => cachedSkillRegistry()
|
|
1351
|
+
listSkills: () => cachedSkillRegistry()
|
|
1352
|
+
.list()
|
|
1353
|
+
.map((skill) => ({
|
|
1148
1354
|
name: skill.name,
|
|
1149
1355
|
description: skill.description,
|
|
1150
1356
|
})),
|
|
@@ -1211,9 +1417,7 @@ export async function run(args) {
|
|
|
1211
1417
|
const summaries = scope === "all" ? store.list() : store.list(process.cwd());
|
|
1212
1418
|
const toggle = {
|
|
1213
1419
|
value: "__scope__",
|
|
1214
|
-
label: scope === "all"
|
|
1215
|
-
? "Show this workspace only"
|
|
1216
|
-
: "Show all workspaces",
|
|
1420
|
+
label: scope === "all" ? "Show this workspace only" : "Show all workspaces",
|
|
1217
1421
|
description: `scope: ${scope === "all" ? "all workspaces" : process.cwd()}`,
|
|
1218
1422
|
};
|
|
1219
1423
|
return [
|
|
@@ -1237,7 +1441,9 @@ export async function run(args) {
|
|
|
1237
1441
|
if (item.value === "__scope__") {
|
|
1238
1442
|
scope = scope === "all" ? "workspace" : "all";
|
|
1239
1443
|
picker.updateItems(buildItems());
|
|
1240
|
-
picker.setStatus(scope === "all"
|
|
1444
|
+
picker.setStatus(scope === "all"
|
|
1445
|
+
? "showing all workspaces"
|
|
1446
|
+
: "showing this workspace");
|
|
1241
1447
|
return;
|
|
1242
1448
|
}
|
|
1243
1449
|
picker.close();
|
|
@@ -1359,6 +1565,7 @@ export async function run(args) {
|
|
|
1359
1565
|
for (const box of toolBoxes) {
|
|
1360
1566
|
box.setExpanded(expandTools);
|
|
1361
1567
|
}
|
|
1568
|
+
transcript.invalidate();
|
|
1362
1569
|
tui.requestRender();
|
|
1363
1570
|
}
|
|
1364
1571
|
async function reloadExtensions() {
|
|
@@ -1393,7 +1600,8 @@ export async function run(args) {
|
|
|
1393
1600
|
}
|
|
1394
1601
|
extensionProviderNames.clear();
|
|
1395
1602
|
for (const loaded of extensionLoader.list()) {
|
|
1396
|
-
for (const [name, provider] of
|
|
1603
|
+
for (const [name, provider] of loaded.registries?.providers ??
|
|
1604
|
+
new Map()) {
|
|
1397
1605
|
const cfg = provider?.config ?? {};
|
|
1398
1606
|
const firstModel = Array.isArray(cfg.models) && cfg.models.length > 0
|
|
1399
1607
|
? (cfg.models[0].id ?? cfg.models[0].name ?? "default")
|
|
@@ -1482,6 +1690,7 @@ export async function run(args) {
|
|
|
1482
1690
|
if (!(child instanceof ToolBox))
|
|
1483
1691
|
return false;
|
|
1484
1692
|
child.toggleExpanded();
|
|
1693
|
+
transcript.invalidate();
|
|
1485
1694
|
return true;
|
|
1486
1695
|
}
|
|
1487
1696
|
tui.addInputListener((data) => {
|
|
@@ -1516,11 +1725,28 @@ export async function run(args) {
|
|
|
1516
1725
|
toggleTools();
|
|
1517
1726
|
return { consume: true };
|
|
1518
1727
|
}
|
|
1519
|
-
//
|
|
1520
|
-
if (matchesKey(data, Key.
|
|
1521
|
-
|
|
1728
|
+
// Tab toggles plan/yolo mode.
|
|
1729
|
+
if (matchesKey(data, Key.tab) && !uiModalOpen) {
|
|
1730
|
+
currentMode = currentMode === "plan" ? "yolo" : "plan";
|
|
1731
|
+
runtime.setMode(currentMode);
|
|
1732
|
+
updateStatus();
|
|
1733
|
+
appendLine(`mode: ${currentMode}`, theme.tokens.dim);
|
|
1734
|
+
tui.requestRender();
|
|
1522
1735
|
return { consume: true };
|
|
1523
1736
|
}
|
|
1737
|
+
// Escape cancels the active turn, or exits shell mode.
|
|
1738
|
+
if (matchesKey(data, Key.escape) && !uiModalOpen) {
|
|
1739
|
+
if (activeTurnController) {
|
|
1740
|
+
activeTurnController.abort();
|
|
1741
|
+
return { consume: true };
|
|
1742
|
+
}
|
|
1743
|
+
if (shellMode) {
|
|
1744
|
+
shellMode = false;
|
|
1745
|
+
appendLine("shell mode: off", theme.tokens.dim);
|
|
1746
|
+
tui.requestRender();
|
|
1747
|
+
return { consume: true };
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1524
1750
|
return undefined;
|
|
1525
1751
|
});
|
|
1526
1752
|
runtime["onEvent"] = (e) => {
|
|
@@ -1560,10 +1786,34 @@ export async function run(args) {
|
|
|
1560
1786
|
else if (e.type === "context_usage") {
|
|
1561
1787
|
updateStatus();
|
|
1562
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
|
+
}
|
|
1805
|
+
else if (e.type === "budget_warning") {
|
|
1806
|
+
appendLine(`budget warning (${e.scope}): ${e.used}/${e.limit} tokens`, theme.tokens.warning);
|
|
1807
|
+
tui.requestRender();
|
|
1808
|
+
}
|
|
1563
1809
|
else if (e.type === "model_switched") {
|
|
1564
1810
|
appendLine(`switched model to ${e.provider}/${e.model}`);
|
|
1565
1811
|
if (typeof session.appendEvent === "function") {
|
|
1566
|
-
session.appendEvent({
|
|
1812
|
+
session.appendEvent({
|
|
1813
|
+
type: "model_switched",
|
|
1814
|
+
provider: e.provider,
|
|
1815
|
+
model: e.model,
|
|
1816
|
+
});
|
|
1567
1817
|
}
|
|
1568
1818
|
updateStatus();
|
|
1569
1819
|
}
|
|
@@ -1581,6 +1831,7 @@ export async function run(args) {
|
|
|
1581
1831
|
if (box) {
|
|
1582
1832
|
activity.setPhase("working", e.name);
|
|
1583
1833
|
box.appendLive(e.text);
|
|
1834
|
+
transcript.invalidate();
|
|
1584
1835
|
tui.requestRender();
|
|
1585
1836
|
}
|
|
1586
1837
|
}
|
|
@@ -1608,7 +1859,11 @@ export async function run(args) {
|
|
|
1608
1859
|
lines.push(` config: FAIL ${e instanceof Error ? e.message : String(e)}`);
|
|
1609
1860
|
}
|
|
1610
1861
|
try {
|
|
1611
|
-
const r = await provider.generate({
|
|
1862
|
+
const r = await provider.generate({
|
|
1863
|
+
model: modelOverride ?? "default",
|
|
1864
|
+
messages: [{ role: "user", content: [{ type: "text", text: "ping" }] }],
|
|
1865
|
+
tools: [],
|
|
1866
|
+
});
|
|
1612
1867
|
lines.push(` provider generate: ok (${r.finishReason})`);
|
|
1613
1868
|
}
|
|
1614
1869
|
catch (e) {
|
|
@@ -1633,7 +1888,11 @@ export async function run(args) {
|
|
|
1633
1888
|
}
|
|
1634
1889
|
if (useTools) {
|
|
1635
1890
|
await mcpManager.syncTools();
|
|
1636
|
-
runtime.setTools([
|
|
1891
|
+
runtime.setTools([
|
|
1892
|
+
...builtInTools,
|
|
1893
|
+
...extensionTools,
|
|
1894
|
+
...createMcpTools(mcpManager),
|
|
1895
|
+
]);
|
|
1637
1896
|
}
|
|
1638
1897
|
appendLine(formatMcpStatus(mcpManager.status()));
|
|
1639
1898
|
return;
|
|
@@ -1741,7 +2000,9 @@ export async function run(args) {
|
|
|
1741
2000
|
for (const message of session.messages) {
|
|
1742
2001
|
if (message.role === "user") {
|
|
1743
2002
|
turn++;
|
|
1744
|
-
const snippet = redactText(messageText(message))
|
|
2003
|
+
const snippet = redactText(messageText(message))
|
|
2004
|
+
.split("\\n")[0]
|
|
2005
|
+
.slice(0, 80);
|
|
1745
2006
|
lines.push(`${turn}. ${snippet || "(empty)"}`);
|
|
1746
2007
|
}
|
|
1747
2008
|
}
|
|
@@ -1806,6 +2067,27 @@ export async function run(args) {
|
|
|
1806
2067
|
tui.requestRender();
|
|
1807
2068
|
return;
|
|
1808
2069
|
}
|
|
2070
|
+
// Shell mode: every line is a shell command. `!` alone (or as first char)
|
|
2071
|
+
// enters shell mode; Escape exits it.
|
|
2072
|
+
if (shellMode) {
|
|
2073
|
+
const cmd = trimmed.startsWith("!") ? trimmed.slice(1).trim() : trimmed;
|
|
2074
|
+
appendLine(`$ ${cmd}`, theme.tokens.user);
|
|
2075
|
+
await runShellCommand(cmd);
|
|
2076
|
+
tui.requestRender();
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
// `!` as first character enters shell mode.
|
|
2080
|
+
if (trimmed === "!" || trimmed.startsWith("! ")) {
|
|
2081
|
+
shellMode = true;
|
|
2082
|
+
const rest = trimmed.slice(1).trim();
|
|
2083
|
+
appendLine("shell mode: on (Escape to exit)", theme.tokens.dim);
|
|
2084
|
+
if (rest) {
|
|
2085
|
+
appendLine(`$ ${rest}`, theme.tokens.user);
|
|
2086
|
+
await runShellCommand(rest);
|
|
2087
|
+
}
|
|
2088
|
+
tui.requestRender();
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
1809
2091
|
refreshCommandRegistry();
|
|
1810
2092
|
if (trimmed === "/steer" || trimmed.startsWith("/steer ")) {
|
|
1811
2093
|
const text = trimmed.slice("/steer".length).trim();
|
|
@@ -1861,7 +2143,7 @@ export async function run(args) {
|
|
|
1861
2143
|
tui.setFocus(input);
|
|
1862
2144
|
activity.setPhase("working");
|
|
1863
2145
|
if (decision === "trust-session") {
|
|
1864
|
-
appendLine("trusted this session:
|
|
2146
|
+
appendLine("trusted this session: outside-workspace operations will run without approval", theme.tokens.dim);
|
|
1865
2147
|
}
|
|
1866
2148
|
resolve(decision);
|
|
1867
2149
|
});
|