atom-agent 1.3.0 → 1.5.0
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 +62 -0
- package/README.md +220 -224
- package/dist/App.js +922 -341
- package/dist/adapters.js +127 -14
- package/dist/agent/goal-evaluator.js +3 -0
- package/dist/agent/loop.js +211 -430
- package/dist/agent/tool-pipeline.js +398 -0
- package/dist/agent/turn-events.js +12 -0
- package/dist/cli.js +57 -8
- package/dist/compact.js +72 -8
- package/dist/config.js +19 -0
- package/dist/context-manager.js +6 -2
- package/dist/extensions.js +6 -0
- package/dist/file-diffs.js +108 -0
- package/dist/kilo.js +1 -1
- package/dist/local-discovery.js +2 -2
- package/dist/media.js +276 -0
- package/dist/overflow.js +140 -0
- package/dist/policy.js +8 -0
- package/dist/scheduler.js +38 -9
- package/dist/session-revert.js +125 -0
- package/dist/sessions.js +101 -0
- package/dist/snapshots.js +69 -0
- package/dist/system.js +2 -89
- package/dist/telemetry.js +26 -1
- package/dist/todos.js +241 -0
- package/dist/tools/filesystem.js +102 -22
- package/dist/tools/registry.js +184 -45
- package/dist/tools/ripgrep.js +7 -6
- package/dist/tools/search.js +172 -17
- package/dist/tools/shared.js +6 -0
- package/dist/tools.js +7 -39
- package/dist/ui/diff-panel.js +5 -5
- package/dist/ui/diff-view.js +16 -7
- package/dist/ui/diff.js +73 -51
- package/dist/ui/errors.js +20 -6
- package/dist/ui/input.js +24 -20
- package/dist/ui/live-tail.js +36 -1
- package/dist/ui/markdown.js +9 -4
- package/dist/ui/modals.js +6 -4
- package/dist/ui/paint-scheduler.js +120 -0
- package/dist/ui/palette.js +4 -2
- package/dist/ui/pickers.js +4 -1
- package/dist/ui/side-by-side.js +88 -27
- package/dist/ui/status-bar.js +63 -8
- package/dist/ui/stream-store.js +7 -0
- package/dist/ui/theme.js +23 -1
- package/dist/ui/todo-panel.js +5 -2
- package/dist/ui/tool-inspector.js +33 -4
- package/dist/ui/transcript.js +9 -6
- package/dist/web/events.js +93 -0
- package/dist/web/runtime.js +790 -0
- package/dist/web/server.js +570 -0
- package/dist/web/ui/app.js +1925 -0
- package/dist/web/ui/index.html +135 -0
- package/dist/web/ui/styles.css +515 -0
- package/dist/zen.js +115 -4
- package/documentation/cli.md +5 -5
- package/documentation/configuration.md +11 -6
- package/documentation/development.md +4 -3
- package/documentation/extensions.md +1 -1
- package/documentation/goals.md +1 -1
- package/documentation/index.md +4 -4
- package/documentation/providers.md +2 -3
- package/documentation/skills.md +3 -3
- package/documentation/tools.md +8 -3
- package/documentation/troubleshooting.md +1 -1
- package/examples/extensions/01-audit-gate.js +2 -2
- package/examples/extensions/02-notes-tool.js +2 -2
- package/examples/extensions/03-custom-command.js +2 -2
- package/package.json +3 -2
package/dist/zen.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// different Zen request shapes and are out of scope.
|
|
6
6
|
import { existsSync, readFileSync } from "node:fs";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import { MAX_TOOL_STEPS, allToolDefinitions, getExtensionPromptHints, } from "./tools.js";
|
|
8
|
+
import { MAX_TOOL_STEPS, allToolDefinitions, chatToolDefinitions, getExtensionPromptHints, } from "./tools.js";
|
|
9
9
|
import { chatEndpointFor, getProvider, isLocalProviderId, modelsUrlForProvider, providerLabel, } from "./providers.js";
|
|
10
10
|
import { discoverLocalProvider } from "./local-discovery.js";
|
|
11
11
|
import { ANTHROPIC_MAX_TOKENS, ANTHROPIC_VERSION, anthropicHeaders, anthropicThinkingFor, buildAnthropicBody, buildGeminiBody, geminiChatUrl, geminiGenerateUrl, geminiHeaders, geminiThinkingLevelFor, isEffortRejection, parseAnthropicJson, parseAnthropicModelsList, parseGeminiJson, parseGeminiModelsList, parseOpenAIModelsList, readAnthropicSSEMessage, readGeminiSSEMessage, isStallError, readWithStall, sseStallTimeoutMs, } from "./adapters.js";
|
|
@@ -86,6 +86,7 @@ export function reasoningEffortParam(effort, _model) {
|
|
|
86
86
|
return undefined;
|
|
87
87
|
return normalized;
|
|
88
88
|
}
|
|
89
|
+
import { historyHasMedia, isImageRejection, lowerOpenAIContent, } from "./media.js";
|
|
89
90
|
export { CHARS_PER_TOKEN, createContextManager, estimateTokensForChars, historyChars, messageChars, } from "./context-manager.js";
|
|
90
91
|
export { openTodoNeedles } from "./agent/gates.js";
|
|
91
92
|
function finiteCount(value) {
|
|
@@ -782,6 +783,9 @@ opts, errorLabel = "Zen") {
|
|
|
782
783
|
// Server-authoritative unsupported: when a 400 names the effort knob, the
|
|
783
784
|
// flag below drops it and the loop retries without it (once per call).
|
|
784
785
|
let effortDropped = false;
|
|
786
|
+
// Same contract for vision input (see src/media.ts): a 400 naming image
|
|
787
|
+
// input retries once with descriptors stripped to prose markers.
|
|
788
|
+
let mediaStripped = false;
|
|
785
789
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
786
790
|
try {
|
|
787
791
|
throwIfCancelled(signal);
|
|
@@ -795,13 +799,27 @@ opts, errorLabel = "Zen") {
|
|
|
795
799
|
? undefined
|
|
796
800
|
: reasoningEffortParam(opts?.reasoningEffort, model);
|
|
797
801
|
const summaryOpts = opts;
|
|
802
|
+
const mediaMode = opts?.stripMedia === true || mediaStripped
|
|
803
|
+
? "strip"
|
|
804
|
+
: "send";
|
|
798
805
|
// Stable-prefix split (prompt-cache architecture): history[0]'s env
|
|
799
806
|
// tail becomes its own system message so the stable head + tools stay
|
|
800
807
|
// byte-identical across POSTs for implicit prefix caching. Consecutive
|
|
801
808
|
// system messages concatenate on every OpenAI-protocol server, so this
|
|
802
809
|
// is content-neutral. No env tail (tests, old saves) → history passes
|
|
803
810
|
// through untouched, byte-identical to before.
|
|
804
|
-
|
|
811
|
+
// Media lowering runs after the split: histories without descriptors
|
|
812
|
+
// lower byte-identically (lowerOpenAIContent returns the string as-is).
|
|
813
|
+
const messages = splitSystemHead(outgoingHistory).map((m) => {
|
|
814
|
+
const c = m.content;
|
|
815
|
+
if (typeof c !== "string")
|
|
816
|
+
return m;
|
|
817
|
+
const lowered = lowerOpenAIContent(m.role, c, mediaMode);
|
|
818
|
+
// Identity means untouched (no descriptors): keep the original ref
|
|
819
|
+
// so media-free payloads stay byte-identical. Anything else
|
|
820
|
+
// (stripped string or parts array) replaces the content.
|
|
821
|
+
return lowered === c ? m : { ...m, content: lowered };
|
|
822
|
+
});
|
|
805
823
|
const payload = {
|
|
806
824
|
model,
|
|
807
825
|
messages,
|
|
@@ -810,9 +828,15 @@ opts, errorLabel = "Zen") {
|
|
|
810
828
|
// Compaction path only: tools disabled means NO `tools` key at all
|
|
811
829
|
// (asserted in tests); the normal loop always sends the schema —
|
|
812
830
|
// builtins plus extension-registered custom tools, so the model can
|
|
813
|
-
// discover and call them exactly like builtins.
|
|
831
|
+
// discover and call them exactly like builtins. update_goal rides
|
|
832
|
+
// along only for live goal turns (includeUpdateGoal, set per POST by
|
|
833
|
+
// the runAgenticLoop* entry points) — otherwise the model cannot
|
|
834
|
+
// misuse what it cannot see.
|
|
814
835
|
if (!summaryOpts?.disableTools) {
|
|
815
|
-
payload["tools"] =
|
|
836
|
+
payload["tools"] =
|
|
837
|
+
opts?.includeUpdateGoal === false
|
|
838
|
+
? chatToolDefinitions(false)
|
|
839
|
+
: allToolDefinitions();
|
|
816
840
|
}
|
|
817
841
|
// Compaction path only: cap output (openai-chat kind uses max_tokens).
|
|
818
842
|
if (typeof summaryOpts?.maxOutputTokens === "number" &&
|
|
@@ -871,6 +895,25 @@ opts, errorLabel = "Zen") {
|
|
|
871
895
|
}
|
|
872
896
|
if (!res.ok) {
|
|
873
897
|
const errText = await safeErrorText(res);
|
|
898
|
+
// The server is the authority on vision support: a 400 naming
|
|
899
|
+
// image input means this model/deployment takes no images — warn,
|
|
900
|
+
// strip descriptors to markers, and retry without them (once per
|
|
901
|
+
// call). Checked before effort so a joint rejection still strips.
|
|
902
|
+
if (res.status === 400 &&
|
|
903
|
+
!mediaStripped &&
|
|
904
|
+
opts?.stripMedia !== true &&
|
|
905
|
+
historyHasMedia(outgoingHistory) &&
|
|
906
|
+
isImageRejection(errText)) {
|
|
907
|
+
mediaStripped = true;
|
|
908
|
+
try {
|
|
909
|
+
opts?.onWarning?.(`image input is not supported by ${model} — continuing without images`);
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
// ignore observer errors
|
|
913
|
+
}
|
|
914
|
+
throwIfCancelled(signal);
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
874
917
|
// The server is the authority on effort support: a 400 naming the
|
|
875
918
|
// knob means this model/deployment has no such control — warn,
|
|
876
919
|
// drop the knob, and retry without it (setting kept). Any other
|
|
@@ -1007,6 +1050,20 @@ opts, errorLabel = "Zen") {
|
|
|
1007
1050
|
// on display). A length-truncated response (`finish_reason: "length"`) does
|
|
1008
1051
|
// not throw: the loop fails each carried tool call inline with a repair
|
|
1009
1052
|
// error and continues to the next model round.
|
|
1053
|
+
// Per-POST goal-tool visibility: update_goal rides the schema only while a
|
|
1054
|
+
// live goal turn is engaged (guarded — a throwing accessor reads as no
|
|
1055
|
+
// goal, exactly like the loop's readLiveGoal). Evaluated per POST so a goal
|
|
1056
|
+
// set, paused, or cleared mid-turn reshapes the very next schema; callers
|
|
1057
|
+
// without a goal hook (compaction, web, tests) read as no-goal and send the
|
|
1058
|
+
// legacy full surface only when they leave includeUpdateGoal undefined.
|
|
1059
|
+
function isGoalTurnLive(opts) {
|
|
1060
|
+
try {
|
|
1061
|
+
return opts?.goal?.getGoal?.()?.active === true;
|
|
1062
|
+
}
|
|
1063
|
+
catch {
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1010
1067
|
export async function runAgenticLoop(endpoint, apiKey, model, history, opts) {
|
|
1011
1068
|
return runLoopWithChat((h, o) => chatCompletion(endpoint, apiKey, model, h, {
|
|
1012
1069
|
onToken: o?.onToken,
|
|
@@ -1017,6 +1074,7 @@ export async function runAgenticLoop(endpoint, apiKey, model, history, opts) {
|
|
|
1017
1074
|
sleep: o?.sleep,
|
|
1018
1075
|
reasoningEffort: o?.reasoningEffort,
|
|
1019
1076
|
signal: o?.signal,
|
|
1077
|
+
includeUpdateGoal: isGoalTurnLive(opts),
|
|
1020
1078
|
}), history, opts);
|
|
1021
1079
|
}
|
|
1022
1080
|
// AGENTS.md loading: <cwd>/AGENTS.md (or $OPENCODE_AGENTS_PATH when set)
|
|
@@ -1068,6 +1126,9 @@ export async function chatCompletionAnthropic(apiKey, model, history, opts) {
|
|
|
1068
1126
|
// Same server-authoritative unsupported contract as the openai-chat path:
|
|
1069
1127
|
// a 400 naming the thinking knob drops it for the rest of the call.
|
|
1070
1128
|
let anthropicEffortDropped = false;
|
|
1129
|
+
// Vision fallback (see src/media.ts): a 400 naming image input retries
|
|
1130
|
+
// once with descriptors stripped to markers.
|
|
1131
|
+
let anthropicMediaStripped = false;
|
|
1071
1132
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1072
1133
|
try {
|
|
1073
1134
|
throwIfCancelled(signal);
|
|
@@ -1080,6 +1141,8 @@ export async function chatCompletionAnthropic(apiKey, model, history, opts) {
|
|
|
1080
1141
|
const summaryOpts = opts;
|
|
1081
1142
|
const base = buildAnthropicBody(outgoingHistory, model, {
|
|
1082
1143
|
includeTools: !summaryOpts?.disableTools,
|
|
1144
|
+
includeUpdateGoal: opts?.includeUpdateGoal !== false,
|
|
1145
|
+
stripMedia: opts?.stripMedia === true || anthropicMediaStripped,
|
|
1083
1146
|
});
|
|
1084
1147
|
const body = { ...base, stream: true };
|
|
1085
1148
|
// Compaction cap (anthropic kind uses max_tokens; default is already
|
|
@@ -1131,6 +1194,23 @@ export async function chatCompletionAnthropic(apiKey, model, history, opts) {
|
|
|
1131
1194
|
}
|
|
1132
1195
|
if (!res.ok) {
|
|
1133
1196
|
const errText = await safeErrorText(res);
|
|
1197
|
+
// Vision fallback (see src/media.ts): a 400 naming image input
|
|
1198
|
+
// retries once with descriptors stripped to markers.
|
|
1199
|
+
if (res.status === 400 &&
|
|
1200
|
+
!anthropicMediaStripped &&
|
|
1201
|
+
opts?.stripMedia !== true &&
|
|
1202
|
+
historyHasMedia(outgoingHistory) &&
|
|
1203
|
+
isImageRejection(errText)) {
|
|
1204
|
+
anthropicMediaStripped = true;
|
|
1205
|
+
try {
|
|
1206
|
+
opts?.onWarning?.(`image input is not supported by ${model} — continuing without images`);
|
|
1207
|
+
}
|
|
1208
|
+
catch {
|
|
1209
|
+
// ignore observer errors
|
|
1210
|
+
}
|
|
1211
|
+
throwIfCancelled(signal);
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1134
1214
|
if (res.status === 400 &&
|
|
1135
1215
|
anthropicBudget !== undefined &&
|
|
1136
1216
|
!anthropicEffortDropped &&
|
|
@@ -1215,6 +1295,9 @@ export async function chatCompletionGemini(apiKey, model, history, opts) {
|
|
|
1215
1295
|
// Same server-authoritative unsupported contract as the other paths: a
|
|
1216
1296
|
// 400 naming the thinking knob drops it for the rest of the call.
|
|
1217
1297
|
let geminiEffortDropped = false;
|
|
1298
|
+
// Vision fallback (see src/media.ts): a 400 naming image input retries
|
|
1299
|
+
// once with descriptors stripped to markers.
|
|
1300
|
+
let geminiMediaStripped = false;
|
|
1218
1301
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1219
1302
|
try {
|
|
1220
1303
|
throwIfCancelled(signal);
|
|
@@ -1227,11 +1310,13 @@ export async function chatCompletionGemini(apiKey, model, history, opts) {
|
|
|
1227
1310
|
const summaryOpts = opts;
|
|
1228
1311
|
const body = buildGeminiBody(outgoingHistory, model, {
|
|
1229
1312
|
includeTools: !summaryOpts?.disableTools,
|
|
1313
|
+
includeUpdateGoal: opts?.includeUpdateGoal !== false,
|
|
1230
1314
|
...(typeof summaryOpts?.maxOutputTokens === "number" &&
|
|
1231
1315
|
Number.isFinite(summaryOpts.maxOutputTokens) &&
|
|
1232
1316
|
summaryOpts.maxOutputTokens > 0
|
|
1233
1317
|
? { maxOutputTokens: Math.floor(summaryOpts.maxOutputTokens) }
|
|
1234
1318
|
: {}),
|
|
1319
|
+
stripMedia: opts?.stripMedia === true || geminiMediaStripped,
|
|
1235
1320
|
});
|
|
1236
1321
|
// /effort maps to the native thinkingLevel (Auto omits it; Max rides
|
|
1237
1322
|
// high, the deepest level the API offers). Merged into
|
|
@@ -1278,6 +1363,23 @@ export async function chatCompletionGemini(apiKey, model, history, opts) {
|
|
|
1278
1363
|
}
|
|
1279
1364
|
if (!res.ok) {
|
|
1280
1365
|
const errText = await safeErrorText(res);
|
|
1366
|
+
// Vision fallback (see src/media.ts): a 400 naming image input
|
|
1367
|
+
// retries once with descriptors stripped to markers.
|
|
1368
|
+
if (res.status === 400 &&
|
|
1369
|
+
!geminiMediaStripped &&
|
|
1370
|
+
opts?.stripMedia !== true &&
|
|
1371
|
+
historyHasMedia(outgoingHistory) &&
|
|
1372
|
+
isImageRejection(errText)) {
|
|
1373
|
+
geminiMediaStripped = true;
|
|
1374
|
+
try {
|
|
1375
|
+
opts?.onWarning?.(`image input is not supported by ${model} — continuing without images`);
|
|
1376
|
+
}
|
|
1377
|
+
catch {
|
|
1378
|
+
// ignore observer errors
|
|
1379
|
+
}
|
|
1380
|
+
throwIfCancelled(signal);
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1281
1383
|
if (res.status === 400 &&
|
|
1282
1384
|
geminiLevel !== undefined &&
|
|
1283
1385
|
!geminiEffortDropped &&
|
|
@@ -1430,9 +1532,17 @@ export async function chatCompletionForProvider(provider, apiKey, model, history
|
|
|
1430
1532
|
...effortOpts,
|
|
1431
1533
|
// Compaction path only (undefined for the normal loop → tools sent).
|
|
1432
1534
|
...(opts?.disableTools !== undefined ? { disableTools: opts.disableTools } : {}),
|
|
1535
|
+
// Goal-tool visibility (undefined for compaction/summary callers →
|
|
1536
|
+
// legacy full surface; the loop entry points always set it per POST).
|
|
1537
|
+
...(opts?.includeUpdateGoal !== undefined
|
|
1538
|
+
? { includeUpdateGoal: opts.includeUpdateGoal }
|
|
1539
|
+
: {}),
|
|
1433
1540
|
...(opts?.maxOutputTokens !== undefined
|
|
1434
1541
|
? { maxOutputTokens: opts.maxOutputTokens }
|
|
1435
1542
|
: {}),
|
|
1543
|
+
// Media strip (compaction/summarization callers set it; the normal
|
|
1544
|
+
// loop leaves it undefined → images expand natively).
|
|
1545
|
+
...(opts?.stripMedia !== undefined ? { stripMedia: opts.stripMedia } : {}),
|
|
1436
1546
|
};
|
|
1437
1547
|
// Kilo rides the shared OpenAI-chat path (streaming, tool reconstruction,
|
|
1438
1548
|
// retry, effort with server-rejection fallback) with its registry
|
|
@@ -1486,6 +1596,7 @@ export async function runAgenticLoopForProvider(provider, apiKey, model, history
|
|
|
1486
1596
|
reasoningEffort: o?.reasoningEffort,
|
|
1487
1597
|
baseURL: opts?.baseURL,
|
|
1488
1598
|
endpointOverride: opts?.endpointOverride,
|
|
1599
|
+
includeUpdateGoal: isGoalTurnLive(opts),
|
|
1489
1600
|
}), history, opts);
|
|
1490
1601
|
}
|
|
1491
1602
|
// Per-provider model list: live list per kind with curated fallback on ANY
|
package/documentation/cli.md
CHANGED
|
@@ -10,9 +10,11 @@ atom # run the installed binary (runs dist/cli.js)
|
|
|
10
10
|
atom --help # usage, env vars, commands, providers (exits, no TUI)
|
|
11
11
|
atom --dashboard # write ~/.atom/telemetry/dashboard.html and exit (no TUI)
|
|
12
12
|
atom --serve [--port <n>] # serve the live observability webUI on loopback (no TUI, Ctrl+C stops)
|
|
13
|
+
atom --web [--port <n>] # serve the local agentic Web UI on loopback (no TUI, Ctrl+C stops)
|
|
14
|
+
atom --no-extensions # boot with zero third-party extensions (alias: --lockdown)
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
`--help` (or `-h`) prints usage and exits. `--dashboard` and `--serve` handle local observability without starting the TUI (see [Observability](observability.md)). Any other invocation starts the TUI, even without a key.
|
|
17
|
+
`--help` (or `-h`) prints usage and exits. `--dashboard` and `--serve` handle local observability without starting the TUI (see [Observability](observability.md)). `--web` starts the agentic Web UI over the same runtime as the TUI (loopback-only; JSON API at `/api/health`, `/api/providers`, `/api/sessions`). Extension flags (`--no-extensions` / `--lockdown`, repeatable `--enable-extension <glob>` / `--disable-extension <glob>`) control third-party extension loading and win over `atom.json` (see [Extensions](extensions.md) and [Configuration](configuration.md)). Any other invocation starts the TUI, even without a key.
|
|
16
18
|
|
|
17
19
|
## Slash commands
|
|
18
20
|
|
|
@@ -20,16 +22,14 @@ Type `/` to autocomplete as you type. Full registry (`src/App.tsx`):
|
|
|
20
22
|
|
|
21
23
|
| Command | What it does |
|
|
22
24
|
|---|---|
|
|
23
|
-
| `/model` | Unified model picker: active provider first, then other keyed providers plus the always-visible keyless Kilo list (free models badged `(free)`, `free` filters them). Cross-provider pick switches provider |
|
|
24
|
-
| `/models [refresh]` | Local discovery status; `refresh` re-probes local servers (or the Kilo gateway catalog while Kilo is active) |
|
|
25
|
+
| `/model [filter\|refresh]` | Unified model picker: active provider first, then other keyed providers plus the always-visible keyless Kilo list (free models badged `(free)`, `free` filters them). Cross-provider pick switches provider. `refresh` re-probes local servers (or the Kilo gateway catalog while Kilo is active); plain text pre-filters the picker |
|
|
25
26
|
| `/provider` | Provider plus key picker; validates and stores in `~/.atom/auth.json` (Kilo key optional — empty Enter continues anonymously) |
|
|
26
27
|
| `/new` | Start a brand-new session (conversation plus counters reset, previous kept for `/resume`) |
|
|
27
28
|
| `/rename <name>` | Rename the current session (id and history untouched; quotes optional) |
|
|
28
29
|
| `/plan`, `/yolo` | Retired as typed commands — `Tab` is the only mode switcher (normal → yolo → plan → normal); typing them explains this instead of switching |
|
|
29
30
|
| `/effort` | Reasoning-effort picker (`Auto`/`Low`/`Medium`/`High`/`Max`; sent for every model on every provider — `reasoning_effort` on OpenAI-chat, thinking budget on Anthropic, thinking level on Gemini; `Auto` omits it) |
|
|
30
31
|
| `/tools` | List tools with one-line descriptions |
|
|
31
|
-
| `/
|
|
32
|
-
| `/skill` | Invoke a skill by name (`/skill:name`; skills also complete in the `/` menu) |
|
|
32
|
+
| `/skill [name]` | Skill picker (list, filter, invoke); `/skill:name` invokes directly (skills also complete in the `/` menu) |
|
|
33
33
|
| `/context` | Show context usage by source (system, tools, history, skills, config, prefix-cache) |
|
|
34
34
|
| `/queue` | List queued follow-ups (`/queue clear` wipes; cap 10, in-memory only) |
|
|
35
35
|
| `/steer` | Steer the running turn, or send when idle (`/steer <text>`) |
|
|
@@ -24,9 +24,12 @@ Template lives in `.env.example`. Never commit a real key.
|
|
|
24
24
|
| `ATOM_TELEMETRY` | Local observability recording (`0`/`false`/`no`/`off` disables; `1`/`true`/`yes`/`on` forces on) | on (wins over `atom.json`) |
|
|
25
25
|
| `ATOM_TELEMETRY_PORT` | Pinned port for the observability webUI (`atom --serve`; `--port` wins over this) | ephemeral (OS-assigned, printed on start) |
|
|
26
26
|
| `ATOM_EXTENSIONS` | Extra extension directory for discovery (project, global, then this; see [Extensions](extensions.md)) | none |
|
|
27
|
-
| `ATOM_OLLAMA_URL` | Ollama base URL override for local discovery | `http://
|
|
28
|
-
| `ATOM_LMSTUDIO_URL` | LM Studio base URL override for local discovery | `http://
|
|
29
|
-
| `ATOM_LLAMACPP_URL` | llama.cpp base URL override for local discovery | `http://
|
|
27
|
+
| `ATOM_OLLAMA_URL` | Ollama base URL override for local discovery | `http://127.0.0.1:11434` |
|
|
28
|
+
| `ATOM_LMSTUDIO_URL` | LM Studio base URL override for local discovery | `http://127.0.0.1:1234` |
|
|
29
|
+
| `ATOM_LLAMACPP_URL` | llama.cpp base URL override for local discovery | `http://127.0.0.1:8080` |
|
|
30
|
+
| `ATOM_STALL_TIMEOUT_MS` | Silent-stream stall guard for streaming readers | `60000` (60s; fails fast instead of hanging a silent 200-OK stream) |
|
|
31
|
+
| `ATOM_FAST_LIST` | File-listing fast path (`0` forces the legacy walker) | fast path on |
|
|
32
|
+
| `ATOM_INCREMENTAL` | Incremental Ink rendering (`0` restores full-frame rendering) | incremental on |
|
|
30
33
|
|
|
31
34
|
`openai-compatible` uses stored key plus baseURL only. No env vars.
|
|
32
35
|
|
|
@@ -44,8 +47,10 @@ Precedence overall: env vars > saved session picks (`/model`, `/provider`, `/eff
|
|
|
44
47
|
| `provider` | First-run default provider (needs its key, except keyless Kilo/local) | known provider id |
|
|
45
48
|
| `model` | Default model id | non-empty string |
|
|
46
49
|
| `reasoningEffort` | Default reasoning effort | `auto`/`low`/`medium`/`high`/`max` (`default` still accepted as an alias for `auto`) |
|
|
47
|
-
| `maxToolSteps` |
|
|
50
|
+
| `maxToolSteps` | Optional cap on tool rounds per turn (`ATOM_MAX_TOOL_STEPS` wins over this) | 5-100 (default uncapped; the shipped `atom.example.json` sets `30` as a starting point) |
|
|
48
51
|
| `compactPct` | Auto-compact percent of verified window | 50-95 (default 83) |
|
|
52
|
+
| `compactAuto` | Master switch for automatic compaction (manual `/compact` always works) | boolean (default on) |
|
|
53
|
+
| `compactReserve` | Reserved output buffer in tokens for the usable-limit calculation | 4096-100000 (tokens) |
|
|
49
54
|
| `network` | Webfetch SSRF policy: which network zones the model may retrieve | object with boolean `allowPublic` (default true), `allowLocalhost` (default true), `allowPrivate` (default false), `allowLinkLocal` (default false) |
|
|
50
55
|
| `telemetry` | Local observability recording (see [Observability](observability.md)) | `{enabled?: boolean}` (default on; `ATOM_TELEMETRY=0` wins) |
|
|
51
56
|
| `extensions` | Extension enable/disable patterns by name (see [Extensions](extensions.md); CLI `--enable-extension`/`--disable-extension` win over this) | `{enabled?: string[], disabled?: string[]}` (default load all; `disabled` wins over `enabled`) |
|
|
@@ -90,7 +95,7 @@ The allowance is informational only: history is never truncated — there are no
|
|
|
90
95
|
|
|
91
96
|
## Session file
|
|
92
97
|
|
|
93
|
-
`~/.atom/session.json`, version 1, atomic temp-plus-rename saves, `0600` POSIX. See [Sessions](sessions.md).
|
|
98
|
+
Single-turn save: `~/.atom/session.json`, version 1, atomic temp-plus-rename saves, `0600` POSIX. Durable multi-session records live under `~/.atom/sessions/<id>.json` plus a plaintext `active` pointer. See [Sessions](sessions.md).
|
|
94
99
|
|
|
95
100
|
## AGENTS.md and system prompt
|
|
96
101
|
|
|
@@ -104,7 +109,7 @@ Final system prompt is two layers (`src/system.ts`, `src/zen.ts`):
|
|
|
104
109
|
- Repo overlay: `AGENTS.md` in cwd, or `OPENCODE_AGENTS_PATH` override. Capped at 12KB
|
|
105
110
|
- To change bot identity, edit the one-liner. To add project instructions, edit `AGENTS.md`
|
|
106
111
|
|
|
107
|
-
ATOM loads the project `AGENTS.md` at startup so it
|
|
112
|
+
ATOM loads the project `AGENTS.md` at startup when present, so it picks up repo tools, rules, and permission model. This checkout ships no `AGENTS.md` (per-project overlay only).
|
|
108
113
|
|
|
109
114
|
## Context windows
|
|
110
115
|
|
|
@@ -17,7 +17,8 @@ Build output goes to `dist/` (`atom` runs `dist/cli.js`). `dist/` is gitignored
|
|
|
17
17
|
npm start # tsx src/cli.tsx
|
|
18
18
|
npm test # vitest run (fully mocked, never hits live APIs)
|
|
19
19
|
npm run typecheck # tsc --noEmit
|
|
20
|
-
npm run build # tsc -p tsconfig.build.json (src -> dist)
|
|
20
|
+
npm run build # tsc -p tsconfig.build.json (src -> dist) plus copy-web-ui (web UI assets into dist/)
|
|
21
|
+
npm run bench # node scripts/bench-render.mjs (render-throughput benchmark)
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
Tests use `"test-key"` placeholders. Never paste a real key into fixtures, logs, or commits.
|
|
@@ -27,7 +28,7 @@ Tests use `"test-key"` placeholders. Never paste a real key into fixtures, logs,
|
|
|
27
28
|
```text
|
|
28
29
|
.
|
|
29
30
|
├── src/
|
|
30
|
-
│ ├── cli.tsx # entry: --help/--dashboard/--serve, always starts TUI (missing key guides to /provider)
|
|
31
|
+
│ ├── cli.tsx # entry: --help/--dashboard/--serve/--web, extension flags; always starts TUI otherwise (missing key guides to /provider)
|
|
31
32
|
│ ├── App.tsx # Ink TUI: transcript, pickers (/model /provider /effort), modes, status line
|
|
32
33
|
│ ├── context-windows.ts # curated per-model context windows + `token: (P%) NK` format
|
|
33
34
|
│ ├── compact.ts # context compaction: load/trigger math, split, summary POST (tools off, 4096 cap)
|
|
@@ -59,4 +60,4 @@ Add or update tests for behavior changes. A fix without a test that would have c
|
|
|
59
60
|
|
|
60
61
|
## Agent workflow in this repo
|
|
61
62
|
|
|
62
|
-
The repo `AGENTS.md` defines the loop the agent follows: read before edit,
|
|
63
|
+
The repo `AGENTS.md` defines the loop the agent follows: read before edit, uncapped tool rounds per turn by default (optional cap via `ATOM_MAX_TOOL_STEPS` / `maxToolSteps`, clamped 5-100), todowrite list for 3 or more steps with exactly one `in_progress`, verify every change with the suite. Issues live as local markdown under `.scratch/` (see [Issue tracker](../docs/agents/issue-tracker.md)).
|
|
@@ -146,7 +146,7 @@ Exact `ExtensionAPI` methods — nothing else exists:
|
|
|
146
146
|
| `isProjectTrusted()` | Whether this load is trusted (degrade gracefully when false) |
|
|
147
147
|
| `setStatusSegment(text)` | One status-bar slot per extension (upsert by owner) |
|
|
148
148
|
| `setWidget(def)` | Panel widget (`placement: "panel"`, keyed by owner + id) |
|
|
149
|
-
| `notify(message)` | Transient `(name) message` transcript line |
|
|
149
|
+
| `notify(message)` | Transient `(name) message` transcript line (staged queue caps at 100, drop-oldest) |
|
|
150
150
|
| `promptUser(question, options?, allowCustom?)` | Modal dialog; rejects headless, during activation, or while one is open |
|
|
151
151
|
|
|
152
152
|
Stores behind the API: `src/tools/custom.ts`, `intercept.ts`, `overrides.ts`, `provider-hooks.ts`, `compaction-hooks.ts`, `src/extension-commands.ts`, `src/extension-ui.ts`, `src/project-trust.ts`.
|
package/documentation/goals.md
CHANGED
|
@@ -30,7 +30,7 @@ One pinned session goal that keeps the agent working turn-to-turn until it is do
|
|
|
30
30
|
|
|
31
31
|
## Known limits
|
|
32
32
|
|
|
33
|
-
- The `update_goal` schema
|
|
33
|
+
- The `update_goal` schema rides the chat-payload `tools` list only while a goal turn is live (hidden on non-goal turns so it cannot be misused); the model discovers it through the continuation message prose plus the live tool definition.
|
|
34
34
|
- Multi-turn goal behavior against live models is unproven; the loop, judge, and gate paths are covered by mocked suites.
|
|
35
35
|
|
|
36
36
|
## Code
|
package/documentation/index.md
CHANGED
|
@@ -35,7 +35,7 @@ This index is the entry point. The README stays focused on evaluate, install, an
|
|
|
35
35
|
|
|
36
36
|
Project conventions the agent itself loads at runtime:
|
|
37
37
|
|
|
38
|
-
-
|
|
39
|
-
- [Issue tracker](agents/issue-tracker.md) - local markdown issues under `.scratch/`
|
|
40
|
-
- [Triage labels](agents/triage-labels.md) - canonical triage roles
|
|
41
|
-
- [Domain docs](agents/domain.md) - CONTEXT.md plus ADR conventions
|
|
38
|
+
- `AGENTS.md` (per-project, when present) - agent instructions loaded into the system prompt
|
|
39
|
+
- [Issue tracker](../docs/agents/issue-tracker.md) - local markdown issues under `.scratch/`
|
|
40
|
+
- [Triage labels](../docs/agents/triage-labels.md) - canonical triage roles
|
|
41
|
+
- [Domain docs](../docs/agents/domain.md) - CONTEXT.md plus ADR conventions
|
|
@@ -20,7 +20,7 @@ Keys are never printed full (masked as last4), never logged, never in fixtures (
|
|
|
20
20
|
## Kilo Gateway (default)
|
|
21
21
|
|
|
22
22
|
- Kilo is ATOM's default provider: fresh installs start on Kilo with no key required
|
|
23
|
-
- The model catalog is discovered live via `GET https://api.kilo.ai/api/gateway/models` (cached for 5 minutes; `/
|
|
23
|
+
- The model catalog is discovered live via `GET https://api.kilo.ai/api/gateway/models` (cached for 5 minutes; `/model refresh` re-fetches while Kilo is active). Nothing is hardcoded — the catalog is authoritative, and free-model availability can change as Kilo updates it
|
|
24
24
|
- Anonymous access covers eligible free models (ids ending in `:free`, including the `kilo-auto/free` dynamic routing model, which Kilo resolves server-side). Without a key ATOM prefers `kilo-auto/free` when exposed, else the first free model, else the first live id
|
|
25
25
|
- Configure a key with `/provider` (validated, stored in `~/.atom/auth.json`) or `KILO_API_KEY` to unlock the full catalog; authenticated requests send `Authorization: Bearer <key>`, anonymous requests send no auth header at all
|
|
26
26
|
- Free models show a `(free)` badge in `/model` and match the `free` filter
|
|
@@ -59,8 +59,7 @@ File lives at `~/.atom/auth.json` (`ATOM_HOME` overrides the home dir). `0600` o
|
|
|
59
59
|
## Switching
|
|
60
60
|
|
|
61
61
|
- `/provider`: pick provider, paste key once (validated, stored), chat. Kilo's key is optional — without one the prompt offers anonymous free-model use. Switching provider keeps session history text. System prompt stays
|
|
62
|
-
- `/model`: unified picker — active provider's live models first (fallback on any failure), then every other keyed provider's models plus the always-visible keyless Kilo and local lists (cached live list when warm, else fallback). `openai-compatible` joins only with both a key and a stored baseURL. Type to filter (`free` matches free Kilo models), list windows to 10 rows, picking another provider's model switches provider too
|
|
63
|
-
- `/models refresh`: re-probes local servers; while Kilo is active it refreshes the Kilo gateway catalog instead
|
|
62
|
+
- `/model`: unified picker — active provider's live models first (fallback on any failure), then every other keyed provider's models plus the always-visible keyless Kilo and local lists (cached live list when warm, else fallback). `openai-compatible` joins only with both a key and a stored baseURL. Type to filter (`free` matches free Kilo models), list windows to 10 rows, picking another provider's model switches provider too. `/model <text>` opens pre-filtered; `/model refresh` re-probes local servers (Kilo gateway catalog while Kilo is active)
|
|
64
63
|
- `/effort`: reasoning-effort picker (`Auto`/`Low`/`Medium`/`High`/`Max`). Sent for every model on every provider: `reasoning_effort` on OpenAI-chat kinds (zen, OpenAI, DeepSeek, Mistral, Kilo, openai-compatible, locals), a `thinking` budget on Anthropic, a `thinkingConfig.thinkingLevel` on Gemini. `Auto` omits the knob. A model that truly lacks the knob fails the POST with a 400 naming it — the turn warns and retries once without it, so `(unsupported)` only ever reflects an actual server rejection
|
|
65
64
|
|
|
66
65
|
Custom server: pick `openai-compatible`, paste the baseURL (validated as http/https, trailing slashes trimmed) and key. Endpoint helper appends `/chat/completions` when missing.
|
package/documentation/skills.md
CHANGED
|
@@ -34,7 +34,7 @@ Support files: `references/<...>` and `scripts/<...>` mentions inside the body a
|
|
|
34
34
|
|
|
35
35
|
## Listing and precedence
|
|
36
36
|
|
|
37
|
-
`/
|
|
37
|
+
`/skill` opens the searchable picker (names only, type to filter, arrows to browse, `Enter` stages for confirm). `skillsListText` (headless use) prints `Skills (N):` with one runnable `/skill:name` plus source per line — no descriptions in either surface. Model-only skills show `[auto-only]` instead of hiding. Notes and warnings ride along visibly. Empty with no warnings prints the install hint (`add SKILL.md skills under .claude/skills/, .agents/skills/, or the ~/. counterparts`).
|
|
38
38
|
|
|
39
39
|
Name clashes: global (personal) wins over project on exact-name matches, with a visible note. Same-level duplicates keep the first with a note. Pure function `resolveSkills`, covered by `tests/skills.test.ts`.
|
|
40
40
|
|
|
@@ -42,7 +42,7 @@ Name clashes: global (personal) wins over project on exact-name matches, with a
|
|
|
42
42
|
|
|
43
43
|
Three tiers: (1) name plus description of every skill is known to the matcher at all times; (2) the `SKILL.md` body loads only on activation; (3) `references/` and `scripts/` files load on demand (inlined for explicit manual loads; the model reads them via `read` for auto loads). The transcript always shows one plain line per load (`deploy loaded`), never the body.
|
|
44
44
|
|
|
45
|
-
- Manual: `/skill:name` (canonical; legacy `/skill-name` still works) loads the full body plus inlined references into context for that turn. Discovery without dispatch: the `/
|
|
45
|
+
- Manual: `/skill:name` or `/skill <name>` (canonical; legacy `/skill-name` still works) loads the full body plus inlined references into context for that turn. Discovery without dispatch: the `/skill` picker (type to filter, arrows to browse, `Enter` stages `/skill:name` into the input — nothing is sent) and the `/` slash menu (skill rows complete on first `Enter`, run on the second) both confirm before loading; a fully typed `/skill:name`, `/skill <name>`, or `/skill-name` runs on first `Enter`. `allowed-tools` in frontmatter become turn-scoped auto-approvals. User-invocable `false` entries reject manual invocation
|
|
46
46
|
- Auto: deterministic whole-word description match with a high bar — distinct `name` plus `description` words (length 3 or more, stopwords dropped) appearing as whole message words, at least 3 hits, best score first, at most 1 skill per turn. Auto loads Tier 2 only (body without inlined references, truncated at 12KB with a read pointer). `disable-model-invocation` skills never match. See `matchSkills` in `src/skills.ts`
|
|
47
47
|
- Deny rules still win over skill grants. See [Permissions](permissions.md)
|
|
48
48
|
|
|
@@ -54,4 +54,4 @@ Grant trust (`skillGrantsFor` in `src/policy.ts`, covered by `tests/policy.test.
|
|
|
54
54
|
2. Keep the body self-contained; reference large helpers via `references/...` so they inline only when needed
|
|
55
55
|
3. Declare least-privilege `allowed-tools`
|
|
56
56
|
4. Set `user-invocable: false` for auto-only helpers, `disable-model-invocation: true` for manual-only helpers
|
|
57
|
-
5. Verify with `/
|
|
57
|
+
5. Verify with `/skill` listing plus `tests/skills*.test.ts` patterns
|
package/documentation/tools.md
CHANGED
|
@@ -12,11 +12,11 @@ Extensions can register brand-new model-callable tools via `api.registerTool({ n
|
|
|
12
12
|
|
|
13
13
|
| Tool | What it does | Permission in normal mode |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| `read` | Read files, list directories. Args: `path`, optional 1-based `offset`/`limit` | auto |
|
|
15
|
+
| `read` | Read files (UTF-8 text, or PNG/JPEG/GIF/WebP as vision input), list directories. Args: `path`, optional 1-based `offset`/`limit` (text only) | auto |
|
|
16
16
|
| `write` | Create or overwrite files (creates parent dirs). Silent pre-mutation snapshot for rewind | asks |
|
|
17
17
|
| `edit` | Exact-match patch. Fails on no match, on multiple matches without `replaceAll`, on stale read | asks |
|
|
18
|
-
| `grep` | Line-regex search under `dir
|
|
19
|
-
| `glob` | List paths matching pattern under `dir
|
|
18
|
+
| `grep` | Line-regex search under `dir` (a directory, or a single file to search just it). Case-sensitive; `(?i)` prefix = case-insensitive. `include` glob with `{a,b}` (e.g. `*.{ts,tsx}`), `outputMode`: `content`, `files_with_matches`, `count` | auto |
|
|
19
|
+
| `glob` | List paths matching pattern (`*`, `?`, `**`, `{a,b}`) under `dir` (a directory, or a single file to test just it), newest-first | auto |
|
|
20
20
|
| `bash` | Shell command. JSON result with `exitCode`, `stdout`, `stderr`. Optional `runInBackground` | asks |
|
|
21
21
|
| `bash_output` | Poll a background shell task by `taskId` | auto |
|
|
22
22
|
| `webfetch` | Fetch a page as `markdown`, `text`, or `html`. http upgrades to https. Gated by the network SSRF policy (see below) | auto |
|
|
@@ -33,6 +33,7 @@ Read-only set: `read`, `grep`, `glob`, `webfetch`, `websearch`, `bash_output`, `
|
|
|
33
33
|
| Path | Cap | Behavior |
|
|
34
34
|
|---|---|---|
|
|
35
35
|
| `read` output | ~64KB | Head plus truncation note. Full text spills to `<tmpdir>/atom-overflow/` with a `read` pointer |
|
|
36
|
+
| `read` image | 8 MiB per image | PNG/JPEG/GIF/WebP attach as vision input (see below); larger images refused with downscale guidance |
|
|
36
37
|
| `bash` stdout/stderr | ~8KB each | Each stream capped independently, JSON flags `stdoutTruncated`/`stderrTruncated`, overflow pointer on spill |
|
|
37
38
|
| `bash_output` streams | ~8KB each | Same spill behavior for background stdout/stderr |
|
|
38
39
|
| `webfetch` download | ~1MB | Noted as `[truncated: download exceeded ~1MB]` |
|
|
@@ -53,6 +54,10 @@ Count-cap notes (`grep`/`glob` over-cap) and prompt-assembly caps (skills, compa
|
|
|
53
54
|
|
|
54
55
|
No path sandbox. Relative paths resolve against cwd. Absolute paths and `..` escapes are allowed anywhere on the machine, including sensitive locations like `~/.ssh/`. Treat those contents as untrusted. Never exfiltrate or commit secrets. The permission mode is the control plane. See [Permissions](permissions.md).
|
|
55
56
|
|
|
57
|
+
## Image input (vision)
|
|
58
|
+
|
|
59
|
+
`read` on a PNG, JPEG, GIF, or WebP file (detected by magic bytes, up to 8 MiB) attaches it as vision input: the result reads `Image read successfully: <path> (<mime>, <bytes> bytes, attached as vision input)` plus a `[media:<id> ...]` token. History, transcript, and `session.json` carry only that short token (images live under `~/.atom/media/`, pruned after 7 days); at POST time the token expands to provider-native image blocks (OpenAI `image_url`, Anthropic base64 `image` blocks, Gemini `inline_data`). Context accounting charges the deterministic base64 wire cost per token, so the load stays honest. Compaction summaries and the goal judge always strip images to `[image omitted: ...]` markers (text-only, cheap). A model that rejects image input (400 naming images) retries once automatically with images stripped. Anything else binary — PDF, AVIF, BMP, audio, video — is refused with convert-first guidance (e.g. `pdftoppm`/`pdftotext`); oversize images are refused with downscale guidance. Covered by `tests/media.test.ts`.
|
|
60
|
+
|
|
56
61
|
## Network policy (`webfetch` SSRF gate)
|
|
57
62
|
|
|
58
63
|
Every URL — the initial one and every redirect hop — classifies into a zone (`public`, `localhost`, `private` RFC1918/CGNAT/TEST-NET, `link-local` incl. cloud metadata `169.254.169.254`, `blocked` for unparseable/unresolvable) and is checked against the `network` policy from `atom.json` (defaults: public + localhost allowed). Redirects are followed manually (cap 5, loop-detected, credentialed/scheme-changing targets refused) so a public URL can never bounce to metadata or the LAN unseen; each hop re-resolves DNS and the worst zone wins for multi-address hosts. IP-literal tricks (octal/hex forms, IPv4-mapped IPv6) classify by their real address. Known limitation: DNS rebind between check and fetch (TOCTOU) would need connection-level IP pinning, which global fetch does not offer. See [Configuration](configuration.md). Covered by `tests/policy.test.ts`.
|
|
@@ -15,7 +15,7 @@ Never print full keys, never commit them, never put them in fixtures.
|
|
|
15
15
|
|
|
16
16
|
## Model list fails
|
|
17
17
|
|
|
18
|
-
`/model` falls back to the offline list when the live `/models` call fails (Kilo falls back to the `kilo-auto/free` routing placeholder). That is expected offline. Check endpoint override (`OPENCODE_ZEN_ENDPOINT`), network, and key validity before assuming a bug. While Kilo is active, `/
|
|
18
|
+
`/model` falls back to the offline list when the live `/models` call fails (Kilo falls back to the `kilo-auto/free` routing placeholder). That is expected offline. Check endpoint override (`OPENCODE_ZEN_ENDPOINT`), network, and key validity before assuming a bug. While Kilo is active, `/model refresh` re-fetches the gateway catalog.
|
|
19
19
|
|
|
20
20
|
Effort (`/effort`: `Auto`/`Low`/`Medium`/`High`/`Max`) is sent for every model on every provider. If a turn warns that an effort level "is not supported by" a model, the server rejected the knob with a 400 and the turn continued without it — the setting is kept, so switching back to a supporting model re-applies it.
|
|
21
21
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// as the normal model-visible result (approval is skipped, the turn
|
|
5
5
|
// continues); anything this hook does not block runs untouched.
|
|
6
6
|
//
|
|
7
|
-
// Gallery sample for documentation/extensions.md — that guide
|
|
8
|
-
// this file
|
|
7
|
+
// Gallery sample for documentation/extensions.md — that guide excerpts
|
|
8
|
+
// this file and references it by name. Covered by
|
|
9
9
|
// tests/extension-gallery.test.ts, which loads this exact file through the
|
|
10
10
|
// real loadExtensions path.
|
|
11
11
|
module.exports = function auditGate(api) {
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// `Error: invalid call: ...` result and the implementation never runs), and
|
|
6
6
|
// dispatches through the shared loop.
|
|
7
7
|
//
|
|
8
|
-
// Gallery sample for documentation/extensions.md — that guide
|
|
9
|
-
// this file
|
|
8
|
+
// Gallery sample for documentation/extensions.md — that guide excerpts
|
|
9
|
+
// this file and references it by name. Covered by
|
|
10
10
|
// tests/extension-gallery.test.ts, which loads this exact file through the
|
|
11
11
|
// real loadExtensions path.
|
|
12
12
|
const notes = [];
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
// transcript) so the dialog flow is testable headless — tests drive the
|
|
7
7
|
// command with a stub askUser and unit-test the helper directly.
|
|
8
8
|
//
|
|
9
|
-
// Gallery sample for documentation/extensions.md — that guide
|
|
10
|
-
// this file
|
|
9
|
+
// Gallery sample for documentation/extensions.md — that guide excerpts
|
|
10
|
+
// this file and references it by name. Covered by
|
|
11
11
|
// tests/extension-gallery.test.ts, which loads this exact file through the
|
|
12
12
|
// real loadExtensions path.
|
|
13
13
|
function summarizeChoice(choice, extra) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atom-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Agentic terminal coding assistant: multi-provider LLM loop with local file/shell/web tools in an Ink (React) TUI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,9 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"start": "tsx src/cli.tsx",
|
|
40
40
|
"test": "vitest run",
|
|
41
|
+
"bench": "node scripts/bench-render.mjs",
|
|
41
42
|
"typecheck": "tsc --noEmit",
|
|
42
|
-
"build": "tsc -p tsconfig.build.json",
|
|
43
|
+
"build": "tsc -p tsconfig.build.json && node scripts/copy-web-ui.mjs",
|
|
43
44
|
"prepublishOnly": "npm run build"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|