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/dist/snapshots.js CHANGED
@@ -8,11 +8,15 @@
8
8
  //
9
9
  // Session-scoped and in-memory (small files stay as Buffers; files over
10
10
  // SNAPSHOT_OVERFLOW_BYTES spill a copy under the OS temp dir — never the
11
- // repo itself). Restores are byte-exact and hash-verified (sha256 of the
11
+ // repo itself). Checkpoints are bound to the history lineage they were
12
+ // captured in: any history replacement (/clear, /new, /resume, compaction)
13
+ // drops them via clearSnapshots (see src/rollback.ts) — disk files are
14
+ // unaffected, only the undo evidence goes. Restores are byte-exact and hash-verified (sha256 of the
12
15
  // bytes on disk must equal the pre-mutation hash, not a model rewrite).
13
16
  // Shell side effects (bash) are explicitly out of scope: commands are never
14
17
  // snapshotted and cannot be undone — the /rewind UI says so outright.
15
18
  import { createHash, randomBytes } from "node:crypto";
19
+ import * as fs from "node:fs";
16
20
  import { promises as fsp } from "node:fs";
17
21
  import * as os from "node:os";
18
22
  import * as path from "node:path";
@@ -47,8 +51,12 @@ function currentMarks() {
47
51
  }
48
52
  return { history: 0, turns: 0 };
49
53
  }
50
- /** Test isolation (plus any future session reset): drops every checkpoint. */
54
+ /** Test isolation (plus history-lineage resets): drops every checkpoint.
55
+ * Returns the number dropped so callers can report discarded undo evidence.
56
+ * Disk files are untouched — only the in-memory evidence (and its temp
57
+ * overflow copies) goes. */
51
58
  export function clearSnapshots() {
59
+ const dropped = checkpoints.length;
52
60
  for (const cp of checkpoints) {
53
61
  for (const f of cp.files) {
54
62
  if (f.overflowPath) {
@@ -58,6 +66,7 @@ export function clearSnapshots() {
58
66
  }
59
67
  checkpoints = [];
60
68
  seq = 0;
69
+ return dropped;
61
70
  }
62
71
  /** Newest-last copy for the picker and tests (the stored entries stay private). */
63
72
  export function listCheckpoints() {
@@ -69,6 +78,43 @@ export function getCheckpoint(id) {
69
78
  function snapshotDir() {
70
79
  return path.join(os.tmpdir(), SNAPSHOT_DIR);
71
80
  }
81
+ // Crash-leftover overflow copies (a killed process never runs its eviction)
82
+ // would leak in the temp dir forever: prune files older than maxAgeMs on
83
+ // every new spill, mirroring the tool overflow-file precedent. Synchronous
84
+ // and best-effort, never throws. Returns the number removed (for tests).
85
+ export const SNAPSHOT_OVERFLOW_MAX_AGE_MS = 24 * 60 * 60 * 1000;
86
+ export function pruneStaleSnapshotOverflow(maxAgeMs = SNAPSHOT_OVERFLOW_MAX_AGE_MS) {
87
+ try {
88
+ const dir = snapshotDir();
89
+ let entries;
90
+ try {
91
+ entries = fs.readdirSync(dir);
92
+ }
93
+ catch {
94
+ return 0; // nothing spilled yet — nothing to prune
95
+ }
96
+ const now = Date.now();
97
+ let removed = 0;
98
+ for (const name of entries) {
99
+ if (!name.startsWith("snapshot-"))
100
+ continue;
101
+ try {
102
+ const p = path.join(dir, name);
103
+ if (now - fs.statSync(p).mtimeMs > maxAgeMs) {
104
+ fs.rmSync(p, { force: true });
105
+ removed += 1;
106
+ }
107
+ }
108
+ catch {
109
+ // ignore per-file failures (a stale spill is harmless)
110
+ }
111
+ }
112
+ return removed;
113
+ }
114
+ catch {
115
+ return 0;
116
+ }
117
+ }
72
118
  function newCheckpointId(nextSeq) {
73
119
  return `${Date.now().toString(36)}-${nextSeq.toString(36)}${randomBytes(3).toString("hex")}`;
74
120
  }
@@ -83,6 +129,7 @@ async function readPrior(abs) {
83
129
  const hash = createHash("sha256").update(bytes).digest("hex");
84
130
  if (bytes.byteLength > SNAPSHOT_OVERFLOW_BYTES) {
85
131
  try {
132
+ pruneStaleSnapshotOverflow();
86
133
  const dir = snapshotDir();
87
134
  await fsp.mkdir(dir, { recursive: true });
88
135
  const name = `snapshot-${process.pid}-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}.bin`;
@@ -179,6 +226,12 @@ export async function restoreCheckpointFiles(id, onRestored) {
179
226
  const prior = await priorBytesOf(f);
180
227
  if (prior === null)
181
228
  return `Error: rewind failed: snapshot for ${f.abs} is unreadable`;
229
+ // Pre-write verification: the snapshot bytes in hand must hash to the
230
+ // pre-mutation hash BEFORE anything touches disk, so a corrupt snapshot
231
+ // fails loudly while leaving the live file exactly as it was.
232
+ if (createHash("sha256").update(prior).digest("hex") !== f.hash) {
233
+ return `Error: rewind failed: hash mismatch restoring ${f.abs}`;
234
+ }
182
235
  try {
183
236
  await fsp.mkdir(path.dirname(f.abs), { recursive: true });
184
237
  await fsp.writeFile(f.abs, prior);
@@ -186,6 +239,8 @@ export async function restoreCheckpointFiles(id, onRestored) {
186
239
  catch {
187
240
  return `Error: rewind failed: cannot restore ${f.abs}`;
188
241
  }
242
+ // Post-write re-read: guards a concurrent modification racing the
243
+ // restore itself (the bytes just written must still hash correctly).
189
244
  let check;
190
245
  try {
191
246
  check = await fsp.readFile(f.abs);
package/dist/system.js CHANGED
@@ -7,7 +7,11 @@
7
7
  // add project/repo instructions, edit AGENTS.md.
8
8
  //
9
9
  // NOTE: the first line is pinned — tests/app.test.tsx asserts the prompt
10
- // starts with it. Keep it stable.
10
+ // starts with it. Keep it stable. The last line orients the model to the
11
+ // harness contract (single copy — tool descriptions must NOT repeat it):
12
+ // every executor returns a string and never throws, so failures always
13
+ // arrive as `Error: ...` text inside the result (invalid args say how to
14
+ // fix; a denial means replan, never retry).
11
15
  export const SYSTEM_PROMPT = [
12
16
  "You are ATOM, a long-horizon coding agent that works through tools.",
13
17
  "",
@@ -17,6 +21,9 @@ export const SYSTEM_PROMPT = [
17
21
  "Prefer the smallest correct change. Fix root causes. Handle errors and edge cases. Remove dead code.",
18
22
  "Ground every claim in tool output, never in memory. Run commands to check facts.",
19
23
  "After each tool result, reflect briefly, then take the best next action toward the goal.",
24
+ "Batch independent work in one block: parallel-safe reads and searches in the same response run concurrently and finish in a single round-trip — one call per response is the slow path.",
20
25
  "Keep calling tools until verified done. Never end on an unverified summary or a guess.",
21
26
  "Done means tests and typecheck pass, or the blocker is named with its evidence.",
27
+ "",
28
+ "Harness contract: tools never throw — results are strings, failures arrive as `Error: ...` text. Read the error and adapt: invalid args say how to fix, a denial means replan around it, never retry it.",
22
29
  ].join("\n");