atom-agent 1.2.0 → 1.3.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 (54) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +13 -4
  3. package/atom.example.json +11 -0
  4. package/dist/App.js +923 -200
  5. package/dist/adapters.js +82 -13
  6. package/dist/agent/goal-evaluator.js +69 -0
  7. package/dist/agent/loop.js +517 -76
  8. package/dist/cli.js +11 -3
  9. package/dist/compact.js +41 -15
  10. package/dist/config.js +43 -7
  11. package/dist/context-manager.js +16 -198
  12. package/dist/context-windows.js +4 -2
  13. package/dist/env-block.js +5 -5
  14. package/dist/extension-commands.js +196 -0
  15. package/dist/extension-ui.js +153 -0
  16. package/dist/extensions.js +1571 -0
  17. package/dist/goal.js +583 -0
  18. package/dist/project-trust.js +96 -0
  19. package/dist/providers.js +6 -6
  20. package/dist/scheduler.js +74 -36
  21. package/dist/session.js +23 -5
  22. package/dist/sessions.js +25 -6
  23. package/dist/telemetry-dashboard.js +28 -0
  24. package/dist/telemetry.js +39 -0
  25. package/dist/tools/compaction-hooks.js +165 -0
  26. package/dist/tools/custom.js +189 -0
  27. package/dist/tools/intercept.js +145 -0
  28. package/dist/tools/overrides.js +105 -0
  29. package/dist/tools/provider-hooks.js +224 -0
  30. package/dist/tools/registry.js +246 -17
  31. package/dist/tools.js +44 -0
  32. package/dist/ui/palette.js +1 -1
  33. package/dist/ui/status-bar.js +80 -5
  34. package/dist/zen.js +305 -75
  35. package/documentation/architecture.md +114 -0
  36. package/documentation/cli.md +82 -0
  37. package/documentation/compaction.md +50 -0
  38. package/documentation/configuration.md +111 -0
  39. package/documentation/development.md +62 -0
  40. package/documentation/extensions.md +160 -0
  41. package/documentation/getting-started.md +63 -0
  42. package/documentation/goals.md +41 -0
  43. package/documentation/index.md +41 -0
  44. package/documentation/observability.md +70 -0
  45. package/documentation/permissions.md +66 -0
  46. package/documentation/providers.md +78 -0
  47. package/documentation/sessions.md +92 -0
  48. package/documentation/skills.md +57 -0
  49. package/documentation/tools.md +94 -0
  50. package/documentation/troubleshooting.md +54 -0
  51. package/examples/extensions/01-audit-gate.js +24 -0
  52. package/examples/extensions/02-notes-tool.js +32 -0
  53. package/examples/extensions/03-custom-command.js +32 -0
  54. package/package.json +6 -2
@@ -0,0 +1,114 @@
1
+ # Architecture
2
+
3
+ ATOM is a flat-modules codebase with a few directories where a real seam
4
+ exists. The rule is responsibility + dependency direction, never line counts:
5
+ coherent single-file modules stay single files.
6
+
7
+ ## Module map
8
+
9
+ ```text
10
+ cli.tsx -> App.tsx (App) -> everything (UI root, the only React owner)
11
+ |
12
+ ui/{transcript,input,todo-panel} (prop-driven memo leaves, no App import)
13
+ ui/{pickers,modals,status-bar,live-tail,palette} (presentational shells; status-bar
14
+ formats via context-windows, approval text arrives pre-formatted)
15
+ ui/theme (design tokens: every color/glyph/separator/border/spacing value;
16
+ components reference tokens, never literals)
17
+ ui/markdown (zero-dep markdown for assistant turns: headings/lists/code/
18
+ links/quotes; bounded parse cache; MarkdownStream auto-closes transient
19
+ markers mid-stream and converges to the committed shape; tool lines stay
20
+ full-fidelity — ToolLine renders call/warning/denied/retry/cancel states
21
+ from shape, suffixing `· Ns` on slow calls from display-only Turn.ms)
22
+ ui/errors (typed error cards for tool turns: tool/denial/network/model/
23
+ cancelled/config/internal; adjacent [audit label, error detail] pairs
24
+ merge in TranscriptView; full diagnostics stay in the inspector store)
25
+ ui/transcript (static scrollback: committed turns print once via <Static>
26
+ and are never rewritten — a full-page transcript no longer flashes on
27
+ every keystroke (Ink clearTerminal path); commit frontier follows by
28
+ default, PgUp//autoscroll-off freezes new commits with a `↓ N new`
29
+ indicator, End resumes the backlog; banner once per Static identity;
30
+ /thinking toggle is forward-only for committed blocks; global turn keys
31
+ keep rows stable)
32
+ ui/tool-inspector (Ctrl+O browse + expand panel for retained tool results:
33
+ capped store, windowed list, viewport-scrolled output with explicit
34
+ truncation; transcript untouched — expansion lives in the
35
+ dynamic zone)
36
+ ui/activity (working-state model: thinking-gap + verb-mapped tool lines;
37
+ liveness from ticking elapsed seconds, never animated spinners)
38
+ ui/modals (approval/question dialogs: arrows+Enter select, y/a/t/n pinned;
39
+ command preview split from the audit prefix; policy untouched)
40
+ ui/diff, ui/highlight, ui/diff-view, ui/side-by-side (zero-dep diff stack:
41
+ unified engine + line-scoped syntax tokenizer + unified view +
42
+ side-by-side BEFORE/AFTER view with narrow-terminal fallback; approved
43
+ write/edit results commit their diff on the audit turn, stripped on save)
44
+ ui/diff-panel (session-changes review: file list grouped by path with
45
+ per-file side-by-side detail; same renderer as the transcript)
46
+ ui/input + input-model (multiline box with line/col cursor, Ctrl+J newline,
47
+ bracketed paste via usePaste, readline kills, in-memory prompt history;
48
+ Enter always sends; slash menu stays single-line)
49
+ slash matching (App-owned: exact input collapses to one command;
50
+ otherwise prefix tier stable + fuzzy tier scored, one
51
+ matcher for commands and skills; skill rows carry truncated
52
+ descriptions; usage footer reuses the commands' own usage strings)
53
+ |
54
+ agent/loop -> agent/gates -> tools/* (getTodos)
55
+ | \---> agent/types (types only)
56
+ | \---> scheduler -> tools (validators)
57
+ | \---> config, context-manager, tools (executors)
58
+ v
59
+ zen.ts (transports, dispatch, prefs, prompt assembly)
60
+ ├── re-exports agent/* + context-manager surface (compat)
61
+ └── imports agent/loop (one direction only)
62
+ |
63
+ tools.ts (pure barrel)
64
+ ├── tools/registry (names, validation, dispatch, schemas)
65
+ ├── tools/filesystem (read/write/edit + snapshots + fingerprints)
66
+ ├── tools/search (grep/glob) tools/shell (bash/tasks)
67
+ ├── tools/web (fetch/search) tools/todo (checklist state)
68
+ └── tools/shared, tools/overflow, tools/fingerprints (kernel)
69
+ |
70
+ context-manager.ts, compact.ts, prompt-cache.ts, env-block.ts, context-windows.ts (context systems: flat files, one seam each)
71
+ session.ts + snapshots.ts (persistence + pre-mutation snapshots)
72
+ policy.ts + permissions.ts + rollback.ts (policy decisions, rule matching, turn rollback)
73
+ skills.ts — registry, loader, matcher (intentionally one file:
74
+ discovery and parsing must stay byte-identical)
75
+ providers.ts, adapters.ts, kilo.ts (provider types, wire adapters, Kilo gateway)
76
+ ```
77
+
78
+ ## Dependency rules (enforced by tests/architecture.test.ts)
79
+
80
+ - The runtime import graph is acyclic (type-only imports don't count —
81
+ they erase at compile).
82
+ - `react`/`ink` live only in `cli.tsx`, `App.tsx`, `ui/*`. No runtime
83
+ module imports the UI; tools, providers, context-manager, policy,
84
+ scheduler, and everything under `agent/` are UI-free.
85
+ - `policy.ts` depends only on `permissions.ts` at runtime (plus types):
86
+ it stays usable by future subagents with no fs/net/UI baggage.
87
+ - Inside `tools/`, executors never import the registry — the registry
88
+ owns names/validation/dispatch and sits above them.
89
+ - Inside `agent/`, loop/gates/types never import `zen` at runtime —
90
+ `zen.ts` re-exports them for compatibility, never the reverse.
91
+ - The scheduler reasons from `TOOL_EFFECTS` metadata, not per-tool
92
+ branches; missing metadata fails safe to serial.
93
+
94
+ ## What was deliberately NOT split
95
+
96
+ - `skills.ts`: registry + loader + matcher share one parser that must
97
+ stay byte-identical across paths (says so in its header).
98
+ - `App.tsx` beyond the `ui/` leaves: the rest is hooks-entangled
99
+ session state — splitting it further would be prop-drilling, not
100
+ boundaries. Pure helpers already live at module scope and are
101
+ directly unit-tested (slash menu, pickers, filters).
102
+ - `zen.ts` transports: one dispatch + per-kind wire functions belong
103
+ together; the loop, gates, and types now live in `agent/`.
104
+ - Flat coherent modules (`config`, `session`, `snapshots`, `compact`,
105
+ `prompt-cache`, `env-block`, `auth`, `system`): renaming them into
106
+ directories would be motion without meaning.
107
+
108
+ ## Verification
109
+
110
+ `tests/architecture.test.ts` scans the real source on every run, so
111
+ drift fails loudly with the exact file + edge. Behavior is pinned by
112
+ the full suite: every extraction above kept all
113
+ importer paths working (barrels + re-exports), and the suite is the
114
+ proof — no behavior change was made or needed.
@@ -0,0 +1,82 @@
1
+ # CLI and TUI
2
+
3
+ ATOM is an Ink (React) TUI. Entry is `src/cli.tsx`, rendered by `src/App.tsx`. There is no persistent header, only the launch-time banner. The status line is the sole info bar.
4
+
5
+ ## Launch
6
+
7
+ ```bash
8
+ npm start # run the TUI from source (needs a TTY)
9
+ atom # run the installed binary (runs dist/cli.js)
10
+ atom --help # usage, env vars, commands, providers (exits, no TUI)
11
+ atom --dashboard # write ~/.atom/telemetry/dashboard.html and exit (no TUI)
12
+ atom --serve [--port <n>] # serve the live observability webUI on loopback (no TUI, Ctrl+C stops)
13
+ ```
14
+
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.
16
+
17
+ ## Slash commands
18
+
19
+ Type `/` to autocomplete as you type. Full registry (`src/App.tsx`):
20
+
21
+ | Command | What it does |
22
+ |---|---|
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
+ | `/provider` | Provider plus key picker; validates and stores in `~/.atom/auth.json` (Kilo key optional — empty Enter continues anonymously) |
26
+ | `/new` | Start a brand-new session (conversation plus counters reset, previous kept for `/resume`) |
27
+ | `/rename <name>` | Rename the current session (id and history untouched; quotes optional) |
28
+ | `/plan`, `/yolo` | Retired as typed commands — `Tab` is the only mode switcher (normal → yolo → plan → normal); typing them explains this instead of switching |
29
+ | `/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
+ | `/tools` | List tools with one-line descriptions |
31
+ | `/skills` | List installed skills (project plus global) |
32
+ | `/skill` | Invoke a skill by name (`/skill:name`; skills also complete in the `/` menu) |
33
+ | `/context` | Show context usage by source (system, tools, history, skills, config, prefix-cache) |
34
+ | `/queue` | List queued follow-ups (`/queue clear` wipes; cap 10, in-memory only) |
35
+ | `/steer` | Steer the running turn, or send when idle (`/steer <text>`) |
36
+ | `/autoscroll` | Toggle following new output (on by default; bare toggles, `on|off` sets it; off freezes the view mid-turn) |
37
+ | `/mode` | Print the current permission mode |
38
+ | `/trust` | Toggle session trust: auto-approve write/edit/bash without full yolo. Again revokes |
39
+ | `/allow <tool[:glob]>` | Pre-approve a tool pattern this session |
40
+ | `/deny <tool[:glob]>` | Forbid a tool pattern this session. Deny wins over trust/yolo |
41
+ | `/rules` | List session allow/deny rules. `/rules clear` wipes them |
42
+ | `/clear` | Clear conversation history (keeps session token totals) |
43
+ | `/compact [focus]` | Summarize older turns into one summary. Optional focus text |
44
+ | `/goal <objective>` | Pin one session goal (bare shows it; `pause` / `resume` / `clear` manage it; see [Goals](goals.md)) |
45
+ | `/resume` | Restore the last saved session (turns, history, settings, usage) |
46
+ | `/session [filter]` | Switch the active session (interactive most-recent-first picker with fuzzy filter; `Enter` switches, `Esc` cancels) |
47
+ | `/telemetry` | Show the local observability summary (sessions, tokens, tools) |
48
+ | `/dashboard` | Write the local observability dashboard page and show its path |
49
+ | `/rewind` | Restore files to a session checkpoint. Files only, never shell side effects |
50
+ | `/help` | List commands with one-liners |
51
+ | `/exit`, `/quit` | Exit ATOM |
52
+
53
+ `/compact`, `/allow`, `/deny`, `/rules` accept prefix forms (`/compact focus...`, `/allow bash:npm test*`). `/rename` takes the rest of the line as the name; `/session` takes an optional initial filter.
54
+
55
+ ## Keyboard
56
+
57
+ - `Tab`: cycle permission mode normal → yolo → plan → normal (in the `/` menu, Tab runs the highlighted command instead)
58
+ - `Esc`: stop a running response (footer shows `esc stops` while busy); deny a pending approval/question
59
+ - `/`: open command autocomplete (typing a full command name collapses the menu to it)
60
+ - `Ctrl+O`: open the tool-output inspector (browse past tool calls; `↑`/`↓` select, `Enter` expands, `PgUp`/`PgDn` scroll, `Esc` closes)
61
+ - `Ctrl+P`: command palette (searchable, same registry)
62
+ - `Ctrl+C`: cancel the running turn; exit when idle
63
+ - `PgUp`/`PgDn`: scroll the transcript (`End` follows latest)
64
+ - `↑`/`↓`: recall past prompts; `Ctrl+J` inserts a newline (`Enter` always sends)
65
+ - `y` once, `a` always, `t` trust all, `n` deny: answer write/shell approval prompts in normal mode
66
+
67
+ ## Status line
68
+
69
+ Format when idle: provider/model │ token │ cwd[` : `branch] │ reasoning │ mode (`+trust` when session trust is on, hidden in plan mode) │ goal (only while a goal is live: `goal: <objective> [active|paused]`, truncated to fit — it yields first under width pressure and never displaces other segments). While busy: live activity │ elapsed │ token │ reasoning │ mode │ goal (same goal segment when live) │ `esc stops` (+`waiting…` / `waiting approval` flags).
70
+
71
+ Token segment (`src/context-windows.ts`):
72
+
73
+ - `token: n/a`: no usage reported yet. Never estimated
74
+ - `token: (P%) NK`: known context window. NK is cumulative session spend in K (`round(total/1024)`). P% is current context load over the verified window (last POST input tokens including prefix-cache reads, else the 4 chars/token estimate)
75
+ - `token: NK`: model has no verified window. Bare total only
76
+ - `token: 0K` / `token: (0%) 0K`: zero usage, with/without a known window
77
+
78
+ ## Reasoning and streaming
79
+
80
+ Tokens, tool activity, and phase status render live. Reasoning streams in its own dim block above the answer draft (transient). Tool calls execute locally and results feed back into the loop with no step cap by default (optional cap via `ATOM_MAX_TOOL_STEPS`, clamped 5-100).
81
+
82
+ See [Sessions](sessions.md), [Compaction](compaction.md), and [Permissions](permissions.md) for the systems behind these commands.
@@ -0,0 +1,50 @@
1
+ # Compaction and Token Display
2
+
3
+ Claude-Code and opencode-style context management (`src/compact.ts`, `src/context-windows.ts`, `src/context-manager.ts`). History budgets derive from the model window (see [Configuration](configuration.md)); compaction mechanics below are unchanged.
4
+
5
+ ## Auto-compact
6
+
7
+ Triggers at about 83% of the model verified context window:
8
+
9
+ - Threshold fraction default `0.83`
10
+ - Env `ATOM_COMPACT_PCT` is a percent (example `"83"`), clamped 50-95. Invalid or unset falls back to default
11
+ - Load metric: last POST reported input-side tokens (prompt counts normalized to include exclusive prefix-cache counters like Anthropic's `cache_read`/`cache_creation`) when available, else the 4 chars/token estimate of sent history chars
12
+ - No verified window for the model: never auto-compacts, never invents a window
13
+
14
+ ## Manual compact
15
+
16
+ ```text
17
+ /compact [focus text]
18
+ ```
19
+
20
+ Summarizes older turns into one summary with tools disabled and a 4096 output cap. Optional focus text narrows the summary (example `/compact focus auth flow`).
21
+
22
+ Mechanics:
23
+
24
+ - Split history (after system) into head plus retained newest tail of whole user-turns up to about 20000 estimated tokens (chars/4)
25
+ - Tool outputs in the tail capped at 2000 chars each
26
+ - Always keeps at least the newest turn. When everything fits but there is more than one turn, keeps only the newest turn in the tail so manual compact still has an older turn to summarize
27
+ - Summary instruction uses fixed headings (omit a section only when empty): Objective, Important Details, Work State (Completed, Active, Blocked), Next Move, Relevant Files
28
+ - Rules line: no tools available for the request, answer with summary text only
29
+ - Swap is atomic plus saved. Size-overflow truncates head to budget once (drops oldest half of user-turns, preserves pairing) and retries once, then suggests `/clear`. Other failures throw with history untouched
30
+ - Touched files: the compacted summary records the head's read/modified paths (collected from the committed tool calls the loop already recorded — no new tracking), appended as a `Touched files:` block (`Read:` / `Modified:` lines). Over-budget lists shrink oldest-first to the summary budget instead of failing compaction; the model text is never cut. `/resume` surfaces the stored block verbatim
31
+ - Goal block: when a goal is live, compaction appends a `Goal:` line (objective, state, cumulative stats, open todos) to the summary as context for the continued run. Restore still rides the persisted session record (`goal` field, see [Sessions](sessions.md)) — the `Goal:` text keeps the objective visible in history without re-exploring the tree
32
+
33
+ Thrash guard: 3 auto-compactions without the load dropping below threshold disables auto for the session (manual `/compact` still works and resets the counter on success).
34
+
35
+ ## Token display
36
+
37
+ Exact footer format (`formatTokenSegment`):
38
+
39
+ - `token: n/a`: no usage reported yet. Never estimated
40
+ - `token: (P%) NK`: known window. NK is `round(total/1024)` plus `K` from cumulative session spend (`total_tokens`, else `prompt_tokens` plus `completion_tokens`). P is `round(100*load/window)` from current context load, not cumulative spend
41
+ - `token: NK`: unknown window. Bare total only
42
+ - Zero usage with known window: `token: (0%) 0K`. Without one: `token: 0K`
43
+
44
+ Cumulative spend keeps growing after compaction, so it must not drive P. Load does.
45
+
46
+ ## Verified windows
47
+
48
+ Curated per-model map in `src/context-windows.ts` (build-time vendor docs, comments cite sources). Missing models render without a percent. Examples from the map: DeepSeek V4 family 1M, Kimi K2.6 256K, GLM-5.2 1M, GPT-6/GPT-5.6 1.05M, Gemini 3 family about 1M. Read the file for the full table; do not assume a window for unlisted models.
49
+
50
+ Related: [Sessions](sessions.md) for persistence, [CLI](cli.md) for the footer, [Configuration](configuration.md) for `ATOM_COMPACT_PCT`.
@@ -0,0 +1,111 @@
1
+ # Configuration
2
+
3
+ All knobs, files, and prompt layering in one place. Env vars win over stored keys. Nothing here requires code changes.
4
+
5
+ ## Environment variables
6
+
7
+ Template lives in `.env.example`. Never commit a real key.
8
+
9
+ | Variable | Purpose | Default |
10
+ |---|---|---|
11
+ | `KILO_API_KEY` | Kilo key (optional — free models work anonymously). Wins over stored Kilo key | none (Kilo free models still chat; `/provider` shows the key as optional) |
12
+ | `OPENCODE_ZEN_API_KEY` | Zen key. Wins over stored zen key | none (TUI still starts, chat errors inline with a `/provider` pointer) |
13
+ | `OPENCODE_ZEN_MODEL` | Zen model id | `deepseek-v4-pro` |
14
+ | `OPENCODE_ZEN_ENDPOINT` | Zen endpoint override | `https://opencode.ai/zen/v1/chat/completions` |
15
+ | `OPENCODE_AGENTS_PATH` | Override for the AGENTS.md appended to the system prompt | `<cwd>/AGENTS.md` |
16
+ | `OPENAI_API_KEY` | OpenAI key (env wins over stored) | none |
17
+ | `ANTHROPIC_API_KEY` | Anthropic key | none |
18
+ | `DEEPSEEK_API_KEY` | DeepSeek key | none |
19
+ | `MISTRAL_API_KEY` | Mistral key | none |
20
+ | `GEMINI_API_KEY` / `GOOGLE_API_KEY` | Gemini key (either accepted, first non-empty wins) | none |
21
+ | `ATOM_COMPACT_PCT` | Auto-compact percent, clamped 50-95 | `83` (about 83% of verified window) |
22
+ | `ATOM_MAX_TOOL_STEPS` | Optional cap on tool rounds per turn, clamped 5-100 | uncapped |
23
+ | `ATOM_HOME` | Override home for `~/.atom/` files (auth, session) | OS homedir |
24
+ | `ATOM_TELEMETRY` | Local observability recording (`0`/`false`/`no`/`off` disables; `1`/`true`/`yes`/`on` forces on) | on (wins over `atom.json`) |
25
+ | `ATOM_TELEMETRY_PORT` | Pinned port for the observability webUI (`atom --serve`; `--port` wins over this) | ephemeral (OS-assigned, printed on start) |
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://localhost:11434` |
28
+ | `ATOM_LMSTUDIO_URL` | LM Studio base URL override for local discovery | `http://localhost:1234` |
29
+ | `ATOM_LLAMACPP_URL` | llama.cpp base URL override for local discovery | `http://localhost:8080` |
30
+
31
+ `openai-compatible` uses stored key plus baseURL only. No env vars.
32
+
33
+ ## atom.json config file
34
+
35
+ Template lives in `atom.example.json`. Two levels, merged per key (project wins over global):
36
+
37
+ - Project: `<cwd>/atom.json`
38
+ - Global: `~/.atom/atom.json` (`ATOM_HOME` overrides home)
39
+
40
+ Precedence overall: env vars > saved session picks (`/model`, `/provider`, `/effort`) > project `atom.json` > global `atom.json` > compiled defaults. So `atom.json` sets first-run and project defaults; a later explicit pick (saved each turn and on exit) still wins across restarts; env always wins.
41
+
42
+ | Key | Purpose | Range / values |
43
+ |---|---|---|
44
+ | `provider` | First-run default provider (needs its key, except keyless Kilo/local) | known provider id |
45
+ | `model` | Default model id | non-empty string |
46
+ | `reasoningEffort` | Default reasoning effort | `auto`/`low`/`medium`/`high`/`max` (`default` still accepted as an alias for `auto`) |
47
+ | `maxToolSteps` | Tool rounds per turn | 5-100 (default 30) |
48
+ | `compactPct` | Auto-compact percent of verified window | 50-95 (default 83) |
49
+ | `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
+ | `telemetry` | Local observability recording (see [Observability](observability.md)) | `{enabled?: boolean}` (default on; `ATOM_TELEMETRY=0` wins) |
51
+ | `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`) |
52
+
53
+ Missing files are normal and silent. Unknown keys are ignored; invalid values fall back per key with warnings surfaced in `/context`. Reads are fresh per call, so edits apply without restart. Never commit keys here (there are no key fields — keys stay in env/`auth.json`).
54
+
55
+ Example: open the LAN but keep cloud metadata closed:
56
+
57
+ ```json
58
+ { "network": { "allowPrivate": true } }
59
+ ```
60
+
61
+ ## Context budget (`src/context-manager.ts`)
62
+
63
+ The `ContextManager` is the single place answering: how much context is available, how much is used, should we compact. History allowance derives from the model's verified window:
64
+
65
+ ```text
66
+ available history = window − system prompt − tool definitions
67
+ − output reserve (4096 tok) − safety margin (5%)
68
+ ```
69
+
70
+ The allowance is informational only: history is never truncated — there are no message/char caps (`ATOM_MAX_HISTORY_*` env vars and `maxHistory*` config keys no longer exist; if present in `atom.json` they are ignored as unknown keys). Compaction is the only pressure valve.
71
+
72
+ - Known-window models (256K, 1M, …) use their real windows — no fixed 200K-char assumption.
73
+ - Accounting is incremental: the `ContextLedger` (`trackHistory` in `src/context-manager.ts`) keeps exact running counters (messages, chars, est. tokens, system/tool chars, per-role counts) across pushes, splices, and replacements — per-step reads are O(1) instead of rescanning history. `verifyLedger` diffs counters against an independent scan (tests enforce it; exact provider-reported usage stays separate in the token totals).
74
+ - See `/context` for the live per-source breakdown and [Compaction](compaction.md) for the trigger mechanics.
75
+
76
+ ## Auth file
77
+
78
+ `~/.atom/auth.json` (`ATOM_HOME` overrides home):
79
+
80
+ ```json
81
+ {
82
+ "version": 1,
83
+ "providers": {
84
+ "<id>": { "apiKey": "...", "baseURL?": "..." }
85
+ }
86
+ }
87
+ ```
88
+
89
+ `0600` on POSIX, best-effort on Windows. Missing or corrupt loads as empty auth. Save via `/provider` paste flow; resolution is env-first per provider. See [Providers](providers.md).
90
+
91
+ ## Session file
92
+
93
+ `~/.atom/session.json`, version 1, atomic temp-plus-rename saves, `0600` POSIX. See [Sessions](sessions.md).
94
+
95
+ ## AGENTS.md and system prompt
96
+
97
+ Final system prompt is two layers (`src/system.ts`, `src/zen.ts`):
98
+
99
+ ```text
100
+ <base one-liner from src/system.ts> + "\n\n" + <repo AGENTS.md>
101
+ ```
102
+
103
+ - Base identity: long-horizon coding agent loop (explore, plan with todowrite for 3 or more steps, implement, verify with tests and typecheck, report with evidence)
104
+ - Repo overlay: `AGENTS.md` in cwd, or `OPENCODE_AGENTS_PATH` override. Capped at 12KB
105
+ - To change bot identity, edit the one-liner. To add project instructions, edit `AGENTS.md`
106
+
107
+ ATOM loads the project `AGENTS.md` at startup so it knows tools, rules, and permission model. This repo own instructions live in `AGENTS.md` at the root.
108
+
109
+ ## Context windows
110
+
111
+ Curated map in `src/context-windows.ts`. Drives the footer percent and auto-compact threshold. Never invented for unlisted models. See [Compaction](compaction.md).
@@ -0,0 +1,62 @@
1
+ # Development
2
+
3
+ Setup, scripts, structure, and verification for contributors. Commands below come from `package.json` scripts.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ npm install
9
+ npm start # run the TUI from source (needs a TTY)
10
+ ```
11
+
12
+ Build output goes to `dist/` (`atom` runs `dist/cli.js`). `dist/` is gitignored and shipped in the tarball.
13
+
14
+ ## Scripts
15
+
16
+ ```bash
17
+ npm start # tsx src/cli.tsx
18
+ npm test # vitest run (fully mocked, never hits live APIs)
19
+ npm run typecheck # tsc --noEmit
20
+ npm run build # tsc -p tsconfig.build.json (src -> dist)
21
+ ```
22
+
23
+ Tests use `"test-key"` placeholders. Never paste a real key into fixtures, logs, or commits.
24
+
25
+ ## Project structure
26
+
27
+ ```text
28
+ .
29
+ ├── src/
30
+ │ ├── cli.tsx # entry: --help/--dashboard/--serve, always starts TUI (missing key guides to /provider)
31
+ │ ├── App.tsx # Ink TUI: transcript, pickers (/model /provider /effort), modes, status line
32
+ │ ├── context-windows.ts # curated per-model context windows + `token: (P%) NK` format
33
+ │ ├── compact.ts # context compaction: load/trigger math, split, summary POST (tools off, 4096 cap)
34
+ │ ├── zen.ts # provider dispatch: streaming SSE, retries, agentic-loop wrappers (shared core in src/agent/loop.ts)
35
+ │ ├── providers.ts # 8 remote providers + 3 local runtimes (kind/endpoint/env/default + fallback models; kilo default)
36
+ │ ├── kilo.ts # Kilo Gateway: live catalog parsing, :free detection, TTL cache, error normalization
37
+ │ ├── auth.ts # ~/.atom/auth.json store (env wins, 0600 POSIX)
38
+ │ ├── adapters.ts # anthropic/gemini translation + SSE + models-list parsing + key validation
39
+ │ └── tools.ts # 13 local tool executors + function schemas
40
+ ├── dist/ # `npm run build` output (`atom` runs dist/cli.js; gitignored, shipped in the tarball)
41
+ ├── tests/ # fully mocked (never live APIs; keys use "test-key")
42
+ ├── AGENTS.md # the agent's own instructions (loaded at startup)
43
+ ├── tsconfig.build.json # build-only config (src -> dist)
44
+ └── .env.example # env template (never commit a real key)
45
+ ```
46
+
47
+ Full source adds: `env-block.ts`, `permissions.ts`, `policy.ts`, `rollback.ts`, `session.ts`, `sessions.ts`, `goal.ts`, `agent/goal-evaluator.ts`, `extensions.ts`, `extension-commands.ts`, `extension-ui.ts`, `project-trust.ts`, `skills.ts`, `snapshots.ts`, `system.ts`, `config.ts`, `scheduler.ts`, `context-manager.ts`, `prompt-cache.ts`, `local-discovery.ts`, `telemetry.ts` (local observability recorder + store), `telemetry-dashboard.ts` (self-contained HTML drill-down), `telemetry-server.ts` (loopback-only live webUI + read-only JSON API), `agent/` (shared loop core, gates, types), `tools/` (per-tool executors + registry, including `custom.ts`, `intercept.ts`, `overrides.ts`, `provider-hooks.ts`, `compaction-hooks.ts`), `ui/` (transcript, diff stack, panels, pickers, status line). Tests live in `tests/` (including `app`, `agent`, `loop-core`, `permissions`, `skills`, `compact`, `session`, `adapters`, `providers`, `kilo`, `tools`, `telemetry`, `diff`, `status-bar`, `slash` suites).
48
+
49
+ ## Verification standard
50
+
51
+ Done means tests and typecheck pass, or the blocker is named with evidence. Minimum for a change:
52
+
53
+ ```bash
54
+ npm test
55
+ npm run typecheck
56
+ ```
57
+
58
+ Add or update tests for behavior changes. A fix without a test that would have caught it is incomplete. Keep blast radius small: match existing patterns, remove dead code and debug leftovers, handle errors and edge cases explicitly.
59
+
60
+ ## Agent workflow in this repo
61
+
62
+ The repo `AGENTS.md` defines the loop the agent follows: read before edit, 30 tool rounds per turn by default, 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](agents/issue-tracker.md)).
@@ -0,0 +1,160 @@
1
+ # Extensions
2
+
3
+ Write a local file, trust the project, restart — your code runs inside ATOM with the full ExtensionAPI. Three copy-paste samples under `examples/extensions/` prove each surface; the guide names them, the tests load them.
4
+
5
+ ## Install location (global vs project)
6
+
7
+ | Scope | Directory | Trust |
8
+ |---|---|---|
9
+ | Global | `~/.atom/extensions/` (`ATOM_HOME` overrides home) | Implicitly trusted — user-owned, like your own config |
10
+ | Project | `<cwd>/.atom/extensions/` | Gated — never executes until the project is trusted |
11
+ | Explicit | `ATOM_EXTENSIONS` env (delimiter-separated paths) | Gated, like project scope |
12
+
13
+ Each scope entry is a `.ts`, `.js`, `.mjs`, or `.cjs` file, or a subdirectory holding an `index.ts`/`index.js`/`index.mjs`/`index.cjs` (or a `package.json` with an `atom.extensions` manifest listing entry files). Dotfiles are skipped. Order is project, global, explicit.
14
+
15
+ ## Minimal file shape (factory export)
16
+
17
+ One file, one factory — CJS or TS, both load same-process via jiti:
18
+
19
+ ```js
20
+ // hello.js — put in ~/.atom/extensions/ (trusted) or <cwd>/.atom/extensions/ (gated)
21
+ module.exports = function (api) {
22
+ api.notify("hello extension loaded");
23
+ };
24
+ ```
25
+
26
+ ```ts
27
+ // hello.ts — same contract, typed
28
+ import type { ExtensionAPI } from "../../src/extensions.js";
29
+ export default function (api: ExtensionAPI) {
30
+ api.notify("hello extension loaded");
31
+ }
32
+ ```
33
+
34
+ The export must be a function (or a default-exported function). Anything else is a recorded load error, not a crash — `loadExtensions` never throws; per-extension failures land on `runtime.errors` and the rest still load.
35
+
36
+ ## Trust prompt expectation
37
+
38
+ With a project-scope extension present and the project not yet trusted, startup asks once via the question modal:
39
+
40
+ ```text
41
+ This project contains 1 extension(s) (hello) that run unsandboxed with your full user privileges — they can read/write your files and run commands as you. Load them?
42
+ ```
43
+
44
+ Answers are `Trust and load` (persists a per-project grant in `~/.atom/trusted-projects.json`) and `Keep disabled` (nothing executes, a visible skipped notice posts, asks again next boot). Esc declines. Extension code runs with your full privileges — no sandbox — so read anything you install first.
45
+
46
+ ## Enable, disable, lockdown
47
+
48
+ - `--enable-extension <glob>` (repeatable): allowlist — only matching names load, the rest skip as `not-enabled`.
49
+ - `--disable-extension <glob>` (repeatable): wins over enable; matches skip as `disabled`.
50
+ - `atom.json` `extensions: { enabled: [...], disabled: [...] }`: same patterns in config; CLI wins over config when set; project config wins over global. See [Configuration](configuration.md).
51
+ - `--no-extensions` (`--lockdown` alias): boots with zero third-party extensions — project, global, and explicit paths alike skip as `lockdown`. Builtins are untouched.
52
+
53
+ Precedence, highest first: lockdown > untrusted-project > disabled > enabled > load.
54
+
55
+ ## How to see it loaded
56
+
57
+ Startup posts one info line when anything loaded or failed:
58
+
59
+ ```text
60
+ (extensions: 1 loaded (hello))
61
+ ```
62
+
63
+ Skipped extensions stay visible with the fix:
64
+
65
+ ```text
66
+ (extensions: 1 skipped (hello) — the project is not trusted; trust the project when asked on next startup to load them)
67
+ ```
68
+
69
+ Then confirm the surface: `/tools` lists registered custom tools, the command palette (`Ctrl+P`) and the `/` menu list registered slash commands, the status line shows contributed segments.
70
+
71
+ ## Gallery (copy-paste samples)
72
+
73
+ Single source of truth: the files under `examples/extensions/`. The guide excerpts them; the tests (`tests/extension-gallery.test.ts`) load the checked-in files through the real `loadExtensions` path — never a mock API, never a copy.
74
+
75
+ **`01-audit-gate.js`** — destructive-command audit gate via `onBeforeToolCall`. Returning `{ block: reason }` vetoes the call: the reason commits as the model-visible result, approval is skipped, the tool never runs.
76
+
77
+ ```js
78
+ api.onBeforeToolCall(({ name, args }) => {
79
+ if (name !== "bash") return;
80
+ const command = String(args?.command ?? "");
81
+ const DESTRUCTIVE = ["rm -rf /", "rm -rf ~", "rm -rf .", "mkfs", ":(){:|:&};:"];
82
+ if (DESTRUCTIVE.some((sig) => command.includes(sig))) {
83
+ return {
84
+ block:
85
+ "destructive shell commands need explicit confirmation — " +
86
+ "narrow the command or confirm it with the user first",
87
+ };
88
+ }
89
+ });
90
+ ```
91
+
92
+ **`02-notes-tool.js`** — model-callable tool via `registerTool` with a parameters schema and `execute`. Bad args are an inline `Error: invalid call: ...` and the implementation never runs; the tool dispatches through the shared loop like a builtin.
93
+
94
+ ```js
95
+ api.registerTool({
96
+ name: "gallery_notes",
97
+ description: "Save a short note and read it back. Use it to remember user preferences across the turn.",
98
+ parameters: {
99
+ type: "object",
100
+ properties: { text: { type: "string" } },
101
+ required: ["text"],
102
+ additionalProperties: false,
103
+ },
104
+ execute: async (args) => {
105
+ notes.push(String(args.text));
106
+ return `saved note #${notes.length}: ${notes[notes.length - 1]}`;
107
+ },
108
+ requireApproval: false,
109
+ });
110
+ ```
111
+
112
+ **`03-custom-command.js`** — real slash command via `registerCommand` with a modal dialog plus transcript posts. The workflow logic is a pure helper (`summarizeChoice`) so the dialog flow is testable headless — tests drive the command with a stub `askUser` and unit-test the helper directly.
113
+
114
+ ```js
115
+ api.registerCommand({
116
+ name: "gallery-plan",
117
+ description: "Pick a deploy target and post the plan.",
118
+ handler: async (ctx) => {
119
+ const choice = await ctx.askUser("Which environment?", ["staging", "prod"]);
120
+ const plan = summarizeChoice(choice, ctx.args);
121
+ ctx.say(plan);
122
+ return `plan posted for ${choice}`;
123
+ },
124
+ });
125
+ ```
126
+
127
+ ## API surface (`src/extensions.ts`)
128
+
129
+ Exact `ExtensionAPI` methods — nothing else exists:
130
+
131
+ | Method | What it does |
132
+ |---|---|
133
+ | `on(event, handler)` | `session_start` / `session_shutdown` lifecycle |
134
+ | `registerTool(def)` | New model-callable tool (`name`, `description`, `parameters`, `execute`, `requireApproval?`) |
135
+ | `overrideTool(def)` | Audited, reversible builtin shadow (deny a subset or `ctx.passthrough`) |
136
+ | `addPromptHint(hint)` | Model-facing guidance appended under "Extension hints" |
137
+ | `registerCommand(def)` | Real `/name args` slash command (builtins always win name collisions) |
138
+ | `onBeforeToolCall(handler)` | Rewrite `{ args }` or veto `{ block: reason }` pre-validation/pre-approval |
139
+ | `onAfterToolCall(handler)` | Patch committed results (`string` or `{ content }`) |
140
+ | `onBeforeSwitch(handler)` | Veto a pending session switch (throws fail open) |
141
+ | `onTransformContext(handler)` | Replace the per-POST message array |
142
+ | `onBeforeRequest(handler)` | Replace payload / mutate headers per POST |
143
+ | `onAfterResponse(handler)` | Observe-only provider response hook |
144
+ | `onBeforeCompact(handler)` | Veto compaction or replace the summary |
145
+ | `getSessionState()` / `setSessionState(value)` | Per-session namespaced state (durable, JSON-serializable) |
146
+ | `isProjectTrusted()` | Whether this load is trusted (degrade gracefully when false) |
147
+ | `setStatusSegment(text)` | One status-bar slot per extension (upsert by owner) |
148
+ | `setWidget(def)` | Panel widget (`placement: "panel"`, keyed by owner + id) |
149
+ | `notify(message)` | Transient `(name) message` transcript line |
150
+ | `promptUser(question, options?, allowCustom?)` | Modal dialog; rejects headless, during activation, or while one is open |
151
+
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`.
153
+
154
+ ## Rules that bite
155
+
156
+ - **Staged activation is atomic.** Validation runs eagerly; a factory that throws (or a duplicate/colliding name at commit) leaves zero registrations behind — nothing half-loads.
157
+ - **Stale generation.** Every session replacement invalidates handed-out APIs; captured handles throw loudly, event handlers always receive a fresh API.
158
+ - **Approval fail-closed.** Custom tools require approval by default; opt out only with `requireApproval: false` for pure side-effect-free helpers.
159
+ - **Serial by default.** Custom tools carry no scheduler metadata and run as serial singletons; `executionMode: "sequential"` forces the whole sibling batch one-at-a-time.
160
+ - **Dialogs are single-flight and interactive-only.** A second request rejects, headless rejects, activation-time prompts reject — never a hang. Command `askUser` works because commands run outside activation with the TUI fulfilling the modal.
@@ -0,0 +1,63 @@
1
+ # Getting Started
2
+
3
+ Fastest path from zero to chatting with an agent that can read, edit, and run your code.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js `>=18` (see `engines` in `package.json`)
8
+ - No API key required to start: the default provider is Kilo Gateway, whose free models work anonymously
9
+ - A TTY for `npm start` (the TUI needs an interactive terminal)
10
+
11
+ ## Install
12
+
13
+ Global install gives you the `atom` binary:
14
+
15
+ ```bash
16
+ npm i -g atom-agent
17
+ atom
18
+ ```
19
+
20
+ Or run from source:
21
+
22
+ ```bash
23
+ npm install
24
+ ```
25
+
26
+ ## First run
27
+
28
+ 1. Just start it — no key needed:
29
+
30
+ ```powershell
31
+ npm start
32
+ ```
33
+
34
+ 2. ATOM selects Kilo automatically, discovers its live model catalog, and starts on the free routing model (`kilo-auto/free`) when no Kilo key is configured. Open `/model` to see the discovered models (free ones carry a `(free)` badge) and pick one.
35
+ 3. Type `/` to see every command. Type `/provider` to paste a Kilo key once (optional — unlocks the full catalog) or to switch to another provider.
36
+
37
+ No key at all: the TUI still starts, and Kilo's free models chat immediately. Providers that need a key error inline and point at `/provider` instead of posting. Nothing is posted without a usable route.
38
+
39
+ To use a keyed provider instead (e.g. OpenCode Zen), get a key at `https://opencode.ai/auth` and set it for the session (PowerShell shown; use `export` on POSIX):
40
+
41
+ ```powershell
42
+ $env:OPENCODE_ZEN_API_KEY="sk-your-key"
43
+ npm start
44
+ ```
45
+
46
+ ## Quickstart path
47
+
48
+ ```text
49
+ prerequisites
50
+ -> npm install
51
+ -> npm start (Kilo free model, no key)
52
+ -> /model to pick a discovered model, ask something about your repo
53
+ -> optional: KILO_API_KEY (or paste via /provider) for the full Kilo catalog
54
+ ```
55
+
56
+ Expected result: streaming answer with live tool activity and a status line showing provider, model, token usage, reasoning effort, and mode.
57
+
58
+ ## Next steps
59
+
60
+ - [CLI and TUI](cli.md) for slash commands and keyboard control
61
+ - [Providers and Models](providers.md) to switch off Kilo, add a Kilo key, or use a local OpenAI-compatible server
62
+ - [Configuration](configuration.md) for all env knobs and the auth file
63
+ - [Troubleshooting](troubleshooting.md) if the first run fails