atom-agent 0.3.0 → 1.1.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +83 -32
  3. package/dist/App.js +2178 -318
  4. package/dist/adapters.js +146 -15
  5. package/dist/agent/gates.js +153 -0
  6. package/dist/agent/loop-guard.js +184 -0
  7. package/dist/agent/loop.js +908 -0
  8. package/dist/agent/normalize.js +144 -0
  9. package/dist/agent/types.js +1 -0
  10. package/dist/auth.js +2 -1
  11. package/dist/cli.js +68 -6
  12. package/dist/compact.js +6 -48
  13. package/dist/config.js +171 -0
  14. package/dist/context-manager.js +564 -0
  15. package/dist/kilo.js +343 -0
  16. package/dist/local-discovery.js +308 -0
  17. package/dist/policy.js +286 -0
  18. package/dist/prompt-cache.js +99 -0
  19. package/dist/providers.js +183 -2
  20. package/dist/rollback.js +21 -0
  21. package/dist/scheduler.js +247 -0
  22. package/dist/session.js +35 -3
  23. package/dist/skills.js +214 -43
  24. package/dist/snapshots.js +57 -2
  25. package/dist/system.js +8 -1
  26. package/dist/telemetry-dashboard.js +589 -0
  27. package/dist/telemetry-server.js +301 -0
  28. package/dist/telemetry.js +1056 -0
  29. package/dist/tools/dir-cache.js +207 -0
  30. package/dist/tools/filesystem.js +149 -0
  31. package/dist/tools/fingerprints.js +33 -0
  32. package/dist/tools/overflow.js +76 -0
  33. package/dist/tools/read-cache.js +160 -0
  34. package/dist/tools/registry.js +802 -0
  35. package/dist/tools/search.js +242 -0
  36. package/dist/tools/shared.js +31 -0
  37. package/dist/tools/shell.js +273 -0
  38. package/dist/tools/todo.js +191 -0
  39. package/dist/tools/web.js +454 -0
  40. package/dist/tools.js +17 -1863
  41. package/dist/ui/activity.js +51 -0
  42. package/dist/ui/diff-panel.js +55 -0
  43. package/dist/ui/diff-view.js +112 -0
  44. package/dist/ui/diff.js +422 -0
  45. package/dist/ui/errors.js +129 -0
  46. package/dist/ui/highlight.js +120 -0
  47. package/dist/ui/input-model.js +115 -0
  48. package/dist/ui/input.js +40 -0
  49. package/dist/ui/live-tail.js +15 -0
  50. package/dist/ui/markdown.js +525 -0
  51. package/dist/ui/modals.js +47 -0
  52. package/dist/ui/palette.js +70 -0
  53. package/dist/ui/pickers.js +32 -0
  54. package/dist/ui/side-by-side.js +144 -0
  55. package/dist/ui/status-bar.js +75 -0
  56. package/dist/ui/theme.js +128 -0
  57. package/dist/ui/todo-panel.js +30 -0
  58. package/dist/ui/tool-inspector.js +59 -0
  59. package/dist/ui/transcript.js +128 -0
  60. package/dist/zen.js +145 -666
  61. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,87 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0 — 2026-09-10
4
+
5
+ - Agentic loop hardening (`src/agent/loop.ts`, `types.ts`, `loop-guard.ts`,
6
+ `normalize.ts`; `src/zen.ts`, `adapters.ts`): tool-result and
7
+ model-response normalization, per-tool execution timeouts, total tool-call
8
+ budget per turn, error-streak recovery (holds final text for a fix-forward
9
+ attempt instead of ending on unaddressed failures), opt-in repetition guard
10
+ with background-poll exclusions, and a per-turn `LoopStats` rollup
11
+ (iterations, calls, failures, cache hits, guard hits, bottleneck, context
12
+ growth) reported via `AgenticOpts.onLoopStats`, even on failed turns.
13
+ SSE stall guard (`ATOM_STALL_TIMEOUT_MS`, default 60s) in every streaming
14
+ reader: a silent 200-OK stream fails fast on the permanent Truncated
15
+ contract instead of hanging the turn
16
+ - LoopStats into observability (`src/telemetry.ts`,
17
+ `telemetry-dashboard.ts`, `src/App.tsx`): `recordLoopStats` attaches the
18
+ harness rollup to the open turn trace, aggregates total cache/guard hits,
19
+ dashboard renders per-turn loop fragments plus overview cards
20
+ - Search speed (`src/tools/search.ts`, `dir-cache.ts`, `read-cache.ts`,
21
+ `filesystem.ts`, `shell.ts`): `git ls-files` enumeration (tracked plus
22
+ untracked-non-ignored, `node_modules`/`.git` still excluded), single-pass
23
+ grep (half the file reads), 32-wide bounded-parallel scan with identical
24
+ output order, mtime-checked listing cache with exact invalidation on
25
+ write/edit/bash (`ATOM_FAST_LIST=0` forces the legacy walker), and a
26
+ stat-validated read cache. Measured 3–7x on content search; batching nudge
27
+ added to the system prompt
28
+ - TUI flow (`src/App.tsx`, `ui/transcript.tsx`, `live-tail.tsx`,
29
+ `status-bar.tsx`, `todo-panel.tsx`, `modals.tsx`, `palette.tsx`):
30
+ `/autoscroll on|off` (freezes a following view mid-turn instead of yanking
31
+ it), `/thinking` toggle with per-round model reasoning persisted to the
32
+ transcript (rendering-only; never model history), memoized status bar,
33
+ todo panel, approval/question modals, and palette plus memoized
34
+ palette/checkpoint derivations (timer-tick and picker-nav flicker fix)
35
+
36
+ ## 1.0.0 — 2026-09-09
37
+
38
+ - Kilo Gateway is the default provider (`src/kilo.ts` + registry entry in
39
+ `src/providers.ts`): OpenAI-compatible `POST /chat/completions` on the
40
+ shared streaming/tool-call path, live `GET /models` catalog (5-minute
41
+ TTL, `/models refresh` while Kilo is active), `:free` detection with
42
+ `(free)` picker badges, anonymous free-model use (no auth header sent),
43
+ optional `KILO_API_KEY` via `/provider`, short actionable errors
44
+ (`Kilo: anonymous free-model rate limit reached.`, `Kilo: API key is
45
+ invalid.`, `Kilo: model is unavailable.`, `Kilo: gateway temporarily
46
+ unavailable.`). Fresh installs start on `kilo-auto/free` with no key;
47
+ saved Kilo sessions restore keyless. All other providers unchanged
48
+ (existing zen-path suites pinned via `initialProvider="opencode-zen"`)
49
+ - Observability: local per-turn telemetry (iterations, model calls with
50
+ API-reported tokens only, per-tool durations and ok/fail, retries,
51
+ outcomes) under `~/.atom/telemetry/` plus a self-contained drill-down
52
+ dashboard (`/dashboard`, `atom --dashboard`) and `/telemetry` summary.
53
+ Local-only, truncated + secret-scrubbed, off via `ATOM_TELEMETRY=0` or
54
+ `telemetry.enabled=false`. Unavailable values render as n/a (never
55
+ estimated); cost stays n/a until a provider reports it
56
+ - Observability webUI: `atom --serve [--port <n>]` serves the live dashboard
57
+ (auto-refreshing) plus a read-only JSON API (`/api/health`,
58
+ `/api/aggregates`, `/api/sessions`) on loopback only. No new dependencies;
59
+ the static `--dashboard` file output is unchanged
60
+ - Security hardening: explicit Policy layer (`src/policy.ts` — approval
61
+ order, skill-grant trust, network zones, secret scrubbing); project-local
62
+ skills never silently arm shell/filesystem grants; webfetch SSRF gate
63
+ (configurable `atom.json` network zones, per-hop redirect checks);
64
+ shell-output secret redaction; symlink targets in activity lines
65
+ - Rollback semantics: explicit conversation (automatic) vs filesystem
66
+ (explicit `/rewind` only) vs process (never) contract (`src/rollback.ts`);
67
+ cancel line states no-revert truth; checkpoints drop on history lineage
68
+ resets; hash pre-verified restores; stale snapshot-temp pruning
69
+ - Scheduler: effect metadata per tool (`src/scheduler.ts`) driving parallel
70
+ read batches; writes/spawns stay serial, order/cancel/approval preserved
71
+ - Prompt cleanup: tool descriptions trimmed of runtime-guaranteed prose
72
+ (~17% smaller system prompt); harness contract in one system line
73
+ - Code organization: `agent/` (loop, gates, types), `tools/` (9 modules),
74
+ `ui/` (transcript, input, todo-panel) extracted with compat re-exports;
75
+ boundary rules enforced by `tests/architecture.test.ts`
76
+ - TUI scrolling: PgUp/Home hold the view mid-turn (frozen window plus a
77
+ static live-tail line, so streaming stops yanking the terminal);
78
+ End/PgDn re-follows; `/clear`, `/resume`, `/new`, rewind-truncate reset
79
+ - Modes: `/plan` and `/yolo` retired — Tab is the only switcher
80
+ (normal → yolo → plan → normal); busy status line keeps the mode segment
81
+ - Input cursor renders in inverse video (no letter shifting); pending
82
+ todos use ○ (never ❌); observability dashboard redesigned (hero, sticky
83
+ section nav, refined dark system, responsive)
84
+
3
85
  ## 0.3.0 — 2026-09-08
4
86
 
5
87
  - Agentic loop: 30-step budget (`ATOM_MAX_TOOL_STEPS`), todo-completion
package/README.md CHANGED
@@ -15,8 +15,8 @@
15
15
  Atom is a small, fast, **agentic** terminal chatbot: it doesn't just answer —
16
16
  it runs an **observe → act → inspect → adjust** loop with **13 real,
17
17
  locally-executed tools** (files, shell, web), streaming output, and an
18
- interactive Ink TUI. Powered by [OpenCode Zen](https://opencode.ai/docs/zen)
19
- as the model provider. Zero ceremony: one key, one command, you're chatting
18
+ interactive Ink TUI. Powered by [Kilo Gateway](https://kilo.ai)
19
+ as the default model provider. Zero ceremony: no key, one command, you're chatting
20
20
  with an agent that can read your code, edit it, run it, and search the web.
21
21
 
22
22
  ## Documentation
@@ -26,10 +26,11 @@ Full docs live in [`documentation/`](documentation/index.md), same layout as ope
26
26
  - [Getting Started](documentation/getting-started.md) — install, key setup, first run
27
27
  - [CLI and TUI](documentation/cli.md) — slash commands, keyboard, status line
28
28
  - [Tools](documentation/tools.md) — the 13 local executors, caps, background tasks
29
- - [Providers and Models](documentation/providers.md) — 7 providers, endpoints, key resolution
30
- - [Permissions and Modes](documentation/permissions.md) — normal/yolo, trust, allow/deny rules
29
+ - [Providers and Models](documentation/providers.md) — 8 remote providers + 3 local runtimes, endpoints, key resolution (Kilo default, key-optional)
30
+ - [Permissions and Modes](documentation/permissions.md) — normal/yolo/plan, trust, allow/deny rules
31
31
  - [Skills](documentation/skills.md) — discovery, frontmatter contract, auto-invoke
32
32
  - [Sessions](documentation/sessions.md) — persistence, resume, clear, rewind
33
+ - [Observability](documentation/observability.md) — local telemetry, `/telemetry`, dashboard drill-down
33
34
  - [Compaction and Token Display](documentation/compaction.md) — auto-compact, manual compact, footer format
34
35
  - [Configuration](documentation/configuration.md) — env vars, auth file, AGENTS.md layering
35
36
  - [Development](documentation/development.md) — scripts, structure, tests, build
@@ -51,14 +52,39 @@ Or run from source:
51
52
  npm install
52
53
  ```
53
54
 
54
- Get a key at [opencode.ai/auth](https://opencode.ai/auth), then:
55
+ No key needed Kilo Gateway is the default provider and its free models
56
+ work anonymously:
55
57
 
56
58
  ```powershell
57
- $env:OPENCODE_ZEN_API_KEY="sk-your-key"
58
59
  npm start
59
60
  ```
60
61
 
61
- That's it. Type `/` to see every command. Full command reference: [CLI and TUI](documentation/cli.md).
62
+ That's it. ATOM discovers Kilo's live model catalog and starts on the free
63
+ routing model (`kilo-auto/free`); open `/model` to pick another. A
64
+ `KILO_API_KEY` (or any other provider key at
65
+ [opencode.ai/auth](https://opencode.ai/auth) for Zen, etc.) is optional —
66
+ paste one via `/provider` to unlock more. Type `/` to see every command.
67
+ Full command reference: [CLI and TUI](documentation/cli.md).
68
+
69
+ ## Updating
70
+
71
+ Check your installed version, then update to the latest release:
72
+
73
+ ```bash
74
+ npm ls -g atom-agent # installed version
75
+ npm i -g atom-agent@latest
76
+ ```
77
+
78
+ If the old version sticks around, clear the cache and reinstall:
79
+
80
+ ```bash
81
+ npm cache clean --force
82
+ npm i -g atom-agent@latest
83
+ ```
84
+
85
+ Run-from-source users just `git pull` instead. Maintainers: bump `version`
86
+ in `package.json`, add a `CHANGELOG.md` entry, commit, tag `vX.Y.Z`, push —
87
+ `prepublishOnly` rebuilds `dist/` at `npm publish` time, so never commit it.
62
88
 
63
89
  ## What Atom can do
64
90
 
@@ -72,20 +98,23 @@ That's it. Type `/` to see every command. Full command reference: [CLI and TUI](
72
98
  `bash_output`, `websearch`, `webfetch`, `ask_question` (asks *you* things
73
99
  interactively), `todowrite` / `todo_get` / `todo_update` (session task
74
100
  checklist with a live TUI panel)
75
- - 🛡️ **Normal / YOLO modes** — `Tab` toggles. Normal auto-runs reads but
101
+ - 🛡️ **Normal / YOLO modes** — `Tab` cycles normal → yolo → plan → normal. Normal auto-runs reads but
76
102
  asks before writes/shell (`y` once · `a` always · `t` trust all · `n` deny);
77
103
  `/trust` toggles a session trust tier (one approval covers the whole task,
78
104
  status shows `+trust`, never saved). YOLO never asks
79
- - 🗺️ **Plan mode** — `/plan` enters a read-only mode for risky work:
105
+ - 🗺️ **Plan mode** — `Tab` from yolo enters a read-only mode for risky work:
80
106
  exploration (`read`/`grep`/`glob`/web/todos/`ask_question`) runs free while
81
107
  `write`/`edit`/`bash` are blocked pre-execution with a replan note (never a
82
- prompt). `Tab` never enters/exits plan, `/yolo`·`/trust` can't punch through
83
- it, `/deny` still wins. Exiting `/plan` approves the recorded todo checklist
108
+ prompt). `/yolo`·`/trust` can't punch through
109
+ it, `/deny` still wins. `Tab` out of plan approves the recorded todo checklist
84
110
  into implementation (lands in normal, never yolo)
85
- - ⌨️ **Slash commands** — `/model` (interactive model picker), `/provider`
86
- (provider + key picker, keys in `~/.atom/auth.json`), `/effort`
87
- (reasoning-effort picker), `/tools`, `/help`, `/mode`, `/yolo`, `/trust`, `/plan`, `/clear`,
88
- `/exit` plus `/`-autocomplete as you type
111
+ - ⌨️ **Slash commands** — `/model` (unified picker across keyed providers,
112
+ type to filter), `/provider` (provider + key picker, keys in
113
+ `~/.atom/auth.json`), `/effort` (reasoning-effort picker), `/tools`,
114
+ `/skills`, `/skill:name` (invoke a skill; skills complete in `/`), `/context`
115
+ (context usage by source), `/queue` + `/steer <text>` (follow-ups while
116
+ busy: queue until the turn ends, or inject into the running turn), `/help`, `/mode`, `/trust`,
117
+ `/clear`, `/exit` — plus `/`-autocomplete as you type (`/yolo` and `/plan` are retired as typed commands — `Tab` switches modes)
89
118
  - 📊 **Status line** — provider · model · session token usage (`token:
90
119
  (P%) NK`: NK is the cumulative spend in K, P% is the current context load
91
120
  over the model's verified window — last `prompt_tokens`, else the
@@ -98,6 +127,13 @@ That's it. Type `/` to see every command. Full command reference: [CLI and TUI](
98
127
  (structured summary, tools disabled, newest tail kept, thrash guard).
99
128
  - 📖 **AGENTS.md-aware** — Atom loads your project's `AGENTS.md` into its
100
129
  system prompt, so it knows your tools, rules, and permission model
130
+ - 📊 **Local observability** — every turn is traced (iterations, model calls
131
+ with reported-only tokens, per-tool durations and ok/fail, retries,
132
+ outcomes) into `~/.atom/telemetry/`; `/telemetry` summarizes, `/dashboard`
133
+ (or `atom --dashboard`) writes a self-contained drill-down page, and
134
+ `atom --serve` offers the same view live in the browser plus a read-only
135
+ JSON API. Local-only, secret-scrubbed, off via `ATOM_TELEMETRY=0`. See
136
+ [Observability](documentation/observability.md)
101
137
  - 🔓 **No path sandbox** — file tools read/write anywhere on the computer
102
138
  (absolute paths and `..` escapes allowed, including sensitive locations
103
139
  like `~/.ssh/` — treat contents as untrusted, never exfiltrate or commit
@@ -138,25 +174,37 @@ auto-approved call).
138
174
 
139
175
  Full reference: [Providers and Models](documentation/providers.md) plus [Configuration](documentation/configuration.md).
140
176
 
141
- Atom talks to 7 providers behind one UI (opencode `/connect` mirror,
142
- manual-key only — no OAuth). Pick with `/provider`, paste a key once
143
- (validated, stored in `~/.atom/auth.json`, `0600` on POSIX), chat.
144
- Switching provider keeps session history text; system prompt stays.
145
- `/model` lists the active provider's live models (curated fallback on any
146
- failure). `/effort` sends `reasoning_effort` only for opencode-zen
147
- supported models; elsewhere kept but never sent.
177
+ Atom talks to 8 remote providers plus 3 local runtimes (Ollama, LM Studio, llama.cpp) behind one UI (opencode `/connect` mirror,
178
+ manual-key only — no OAuth). Kilo Gateway is the default: its free models
179
+ (`:free` ids, incl. the `kilo-auto/free` routing model) chat with no key;
180
+ paste a key once with `/provider` (validated, stored in
181
+ `~/.atom/auth.json`, `0600` on POSIX) to unlock the full catalog or another
182
+ provider, chat. Switching provider keeps session history text; system prompt
183
+ stays. `/model` is a unified picker: the active provider's live models first
184
+ (fallback on any failure), then every other keyed provider's models plus the
185
+ always-visible keyless Kilo list (free models carry a `(free)` badge) —
186
+ picking one switches provider too. `/effort` sends `reasoning_effort` only
187
+ for opencode-zen supported models; elsewhere kept but never sent. Your
188
+ `/model` + `/provider` + `/effort` picks persist across restarts (fresh
189
+ conversation each launch; `/resume` restores it). Project defaults live in
190
+ `atom.json` — see [Configuration](documentation/configuration.md).
148
191
 
149
192
  ### Model-choice policy
150
193
 
151
- The zen default is `deepseek-v4-pro` — picked from the live `/models` list
152
- for reliable multi-step tool use (tool calls + reasoning effort supported).
153
- Free models (`big-pickle`, `mimo-v2.5-free`, …) stay selectable via `/model`
154
- for quick single-turn questions. Override any time with `/model` or
194
+ The Kilo default is `kilo-auto/free` — the gateway's dynamic free routing
195
+ model, preferred while no Kilo key is configured (no paid credentials for
196
+ first run). The catalog is discovered live, so free-model availability can
197
+ change; override any time with `/model`. The zen default is
198
+ `deepseek-v4-pro` — picked from the live `/models` list for reliable
199
+ multi-step tool use (tool calls + reasoning effort supported). Free models
200
+ (`big-pickle`, `mimo-v2.5-free`, …) stay selectable via `/model` for quick
201
+ single-turn questions. Override any time with `/model` or
155
202
  `OPENCODE_ZEN_MODEL`.
156
203
 
157
204
  | Provider | Key env (wins over stored) | Endpoint | Notes |
158
205
  |---|---|---|---|
159
- | opencode-zen | `OPENCODE_ZEN_API_KEY` | `https://opencode.ai/zen/v1/chat/completions` | OpenAI-compatible chat/completions default; key at https://opencode.ai/auth |
206
+ | kilo | `KILO_API_KEY` (optional — free models work anonymously) | `https://api.kilo.ai/api/gateway/chat/completions` | Kilo Gateway default, OpenAI-compatible; live `/models` catalog is authoritative |
207
+ | opencode-zen | `OPENCODE_ZEN_API_KEY` | `https://opencode.ai/zen/v1/chat/completions` | OpenAI-compatible chat/completions; key at https://opencode.ai/auth |
160
208
  | openai | `OPENAI_API_KEY` | `https://api.openai.com/v1/chat/completions` | OpenAI-compatible; key at https://platform.openai.com/api-keys |
161
209
  | anthropic | `ANTHROPIC_API_KEY` | `https://api.anthropic.com/v1/messages` | Messages API (`x-api-key` + `anthropic-version: 2023-06-01`, `max_tokens` 4096); key at https://console.anthropic.com/settings/keys |
162
210
  | deepseek | `DEEPSEEK_API_KEY` | `https://api.deepseek.com/chat/completions` | OpenAI-compatible (no `/v1` prefix); key at https://platform.deepseek.com/api_keys |
@@ -177,7 +225,7 @@ npm run typecheck
177
225
  npm run build # emit dist/ (the `atom` binary entry is dist/cli.js)
178
226
  ```
179
227
 
180
- Env knobs: `OPENCODE_ZEN_API_KEY` (or stored zen key via `/provider`), `OPENCODE_ZEN_MODEL`,
228
+ Env knobs: `KILO_API_KEY` (optional; or stored Kilo key via `/provider`), `OPENCODE_ZEN_API_KEY` (or stored zen key via `/provider`), `OPENCODE_ZEN_MODEL`,
181
229
  `OPENCODE_ZEN_ENDPOINT`, `OPENCODE_AGENTS_PATH`, `ATOM_COMPACT_PCT` (auto-compact percent, 50–95),
182
230
  `ATOM_MAX_TOOL_STEPS` (tool rounds per turn, default 30, clamped 5–100),
183
231
  plus per-provider key env vars above.
@@ -186,15 +234,18 @@ plus per-provider key env vars above.
186
234
  ```
187
235
  .
188
236
  ├── src/
189
- │ ├── cli.tsx # entry: --help, always starts TUI (missing key guides to /provider)
190
- │ ├── App.tsx # Ink TUI: transcript, pickers, modes (/plan /trust), approvals, status line
191
- │ ├── zen.ts # agentic loop (budgets, todo/verification guards) + provider dispatch + SSE
237
+ │ ├── cli.tsx # entry: --help/--dashboard/--serve, always starts TUI (missing key guides to /provider)
238
+ │ ├── App.tsx # Ink TUI: transcript, pickers, Tab modes, /trust, approvals, status line
239
+ │ ├── telemetry.ts # local observability recorder + store (never throws, off via ATOM_TELEMETRY=0)
240
+ │ ├── telemetry-dashboard.ts # self-contained local dashboard page (session → turn → iteration → call)
241
+ │ ├── zen.ts # provider dispatch + agentic-loop wrappers (shared core in src/agent/loop.ts) + SSE
192
242
  │ ├── tools.ts # 13 local tool executors + function schemas (read/write/edit/grep/glob/bash/…)
193
243
  │ ├── permissions.ts # allow/deny rule matcher backing /allow /deny /rules
194
244
  │ ├── snapshots.ts # pre-mutation file snapshots backing /rewind
195
245
  │ ├── skills.ts # skill discovery backing /skills
196
246
  │ ├── env-block.ts # per-turn cwd/git/node environment block
197
- │ ├── providers.ts # 7-provider registry (kind/endpoint/env/default + fallback models)
247
+ │ ├── providers.ts # 8 remote providers + 3 local runtimes (kind/endpoint/env/default + fallback models; Kilo default)
248
+ │ ├── kilo.ts # Kilo Gateway: catalog parsing, free detection, TTL cache, error normalization
198
249
  │ ├── auth.ts # ~/.atom/auth.json store (env wins, 0600 POSIX)
199
250
  │ ├── adapters.ts # anthropic/gemini translation + SSE + models-list parsing + key validation
200
251
  │ ├── compact.ts # context compaction: load/trigger math, split, summary POST (tools off)