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.
Files changed (71) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +220 -224
  3. package/dist/App.js +922 -341
  4. package/dist/adapters.js +127 -14
  5. package/dist/agent/goal-evaluator.js +3 -0
  6. package/dist/agent/loop.js +211 -430
  7. package/dist/agent/tool-pipeline.js +398 -0
  8. package/dist/agent/turn-events.js +12 -0
  9. package/dist/cli.js +57 -8
  10. package/dist/compact.js +72 -8
  11. package/dist/config.js +19 -0
  12. package/dist/context-manager.js +6 -2
  13. package/dist/extensions.js +6 -0
  14. package/dist/file-diffs.js +108 -0
  15. package/dist/kilo.js +1 -1
  16. package/dist/local-discovery.js +2 -2
  17. package/dist/media.js +276 -0
  18. package/dist/overflow.js +140 -0
  19. package/dist/policy.js +8 -0
  20. package/dist/scheduler.js +38 -9
  21. package/dist/session-revert.js +125 -0
  22. package/dist/sessions.js +101 -0
  23. package/dist/snapshots.js +69 -0
  24. package/dist/system.js +2 -89
  25. package/dist/telemetry.js +26 -1
  26. package/dist/todos.js +241 -0
  27. package/dist/tools/filesystem.js +102 -22
  28. package/dist/tools/registry.js +184 -45
  29. package/dist/tools/ripgrep.js +7 -6
  30. package/dist/tools/search.js +172 -17
  31. package/dist/tools/shared.js +6 -0
  32. package/dist/tools.js +7 -39
  33. package/dist/ui/diff-panel.js +5 -5
  34. package/dist/ui/diff-view.js +16 -7
  35. package/dist/ui/diff.js +73 -51
  36. package/dist/ui/errors.js +20 -6
  37. package/dist/ui/input.js +24 -20
  38. package/dist/ui/live-tail.js +36 -1
  39. package/dist/ui/markdown.js +9 -4
  40. package/dist/ui/modals.js +6 -4
  41. package/dist/ui/paint-scheduler.js +120 -0
  42. package/dist/ui/palette.js +4 -2
  43. package/dist/ui/pickers.js +4 -1
  44. package/dist/ui/side-by-side.js +88 -27
  45. package/dist/ui/status-bar.js +63 -8
  46. package/dist/ui/stream-store.js +7 -0
  47. package/dist/ui/theme.js +23 -1
  48. package/dist/ui/todo-panel.js +5 -2
  49. package/dist/ui/tool-inspector.js +33 -4
  50. package/dist/ui/transcript.js +9 -6
  51. package/dist/web/events.js +93 -0
  52. package/dist/web/runtime.js +790 -0
  53. package/dist/web/server.js +570 -0
  54. package/dist/web/ui/app.js +1925 -0
  55. package/dist/web/ui/index.html +135 -0
  56. package/dist/web/ui/styles.css +515 -0
  57. package/dist/zen.js +115 -4
  58. package/documentation/cli.md +5 -5
  59. package/documentation/configuration.md +11 -6
  60. package/documentation/development.md +4 -3
  61. package/documentation/extensions.md +1 -1
  62. package/documentation/goals.md +1 -1
  63. package/documentation/index.md +4 -4
  64. package/documentation/providers.md +2 -3
  65. package/documentation/skills.md +3 -3
  66. package/documentation/tools.md +8 -3
  67. package/documentation/troubleshooting.md +1 -1
  68. package/examples/extensions/01-audit-gate.js +2 -2
  69. package/examples/extensions/02-notes-tool.js +2 -2
  70. package/examples/extensions/03-custom-command.js +2 -2
  71. package/package.json +3 -2
package/dist/scheduler.js CHANGED
@@ -218,6 +218,33 @@ export function canonicalFileKey(rawPath, cwd = process.cwd()) {
218
218
  }
219
219
  return abs;
220
220
  }
221
+ /** Capture the mutable-registry inputs planBatches needs (see above). */
222
+ export function captureSchedulerSnapshot() {
223
+ const names = toolNames();
224
+ const modes = {};
225
+ for (const name of names) {
226
+ try {
227
+ const mode = toolExecutionMode(name);
228
+ if (mode !== undefined)
229
+ modes[name] = mode;
230
+ }
231
+ catch {
232
+ // A failing mode read plans as "no hint" — fail safe, as before.
233
+ }
234
+ }
235
+ return {
236
+ toolNames: [...names],
237
+ customToolNames: names.filter((name) => {
238
+ try {
239
+ return isCustomTool(name);
240
+ }
241
+ catch {
242
+ return false;
243
+ }
244
+ }),
245
+ executionModes: modes,
246
+ };
247
+ }
221
248
  // Partition one assistant message's tool_calls into commit batches,
222
249
  // preserving program order: consecutive batchable calls form one batch; any
223
250
  // serial-only call closes the batch and runs as a strict serial singleton.
@@ -228,7 +255,14 @@ export function canonicalFileKey(rawPath, cwd = process.cwd()) {
228
255
  // order always holds. Reads never split on each other. A later batch never
229
256
  // moves ahead of an earlier serial call, and batches never span the block
230
257
  // boundary.
231
- export function planBatches(calls) {
258
+ //
259
+ // The optional snapshot (see captureSchedulerSnapshot) freezes the mutable
260
+ // registry inputs for the whole block; absent, the snapshot is captured live
261
+ // once up front — planning never re-reads the live registry mid-block.
262
+ export function planBatches(calls, snapshot) {
263
+ const reg = snapshot ?? captureSchedulerSnapshot();
264
+ const knownTools = new Set(reg.toolNames);
265
+ const customTools = new Set(reg.customToolNames);
232
266
  const batches = [];
233
267
  let open = [];
234
268
  // Canonical file keys of the open batch ("read" and/or "write" per key).
@@ -255,12 +289,7 @@ export function planBatches(calls) {
255
289
  // exactly as before, one call per batch).
256
290
  if (calls.some((call) => {
257
291
  const name = typeof call?.function?.name === "string" ? call.function.name : "";
258
- try {
259
- return toolExecutionMode(name) === "sequential";
260
- }
261
- catch {
262
- return false;
263
- }
292
+ return reg.executionModes[name] === "sequential";
264
293
  })) {
265
294
  return calls.map((call) => [{ call, parsed: lenientParse(call), parallelKey: null }]);
266
295
  }
@@ -293,13 +322,13 @@ export function planBatches(calls) {
293
322
  // like the old allowlist-miss). Batch planning is never corrupted by
294
323
  // what it cannot see; validation still runs in the loop, where failures
295
324
  // become inline-error results.
296
- if (isCustomTool(name)) {
325
+ if (customTools.has(name)) {
297
326
  singleton(call, parsed);
298
327
  continue;
299
328
  }
300
329
  // Missing metadata fails safe to serial (never batch the unknown).
301
330
  const meta = TOOL_EFFECTS[name];
302
- if (malformed || !meta || !toolNames().includes(name)) {
331
+ if (malformed || !meta || !knownTools.has(name)) {
303
332
  singleton(call, parsed);
304
333
  continue;
305
334
  }
@@ -0,0 +1,125 @@
1
+ // Session-scoped revert (ticket 08): "undo that bad turn".
2
+ //
3
+ // Composition only — no new snapshot system. Every piece already exists:
4
+ //
5
+ // - src/snapshots.ts: getCheckpoint (lookup), restoreCheckpointFiles
6
+ // (byte-exact, hash-verified file restore), conversationCutIndex (the
7
+ // turn-boundary cut shared by /rewind and forkSession), listCheckpoints
8
+ // (no-snapshot detection).
9
+ // - src/sessions.ts: getSession (read), updateSession (atomic persist of the
10
+ // truncated record).
11
+ //
12
+ // Ordering is the atomicity story: file restore runs BEFORE the session
13
+ // record is touched, so a failed restore (hash mismatch, unreadable
14
+ // snapshot, unknown checkpoint) leaves the session file byte-identical —
15
+ // nothing was written yet. If the record persist then fails (disk error,
16
+ // record vanished mid-flight), the just-restored files are rolled back to
17
+ // their pre-revert bytes best-effort and a clear error is returned; the
18
+ // session file itself is untouched either way (updateSession only writes on
19
+ // success, atomically).
20
+ //
21
+ // Forked branches are separate records (ticket 07): this writes exactly one
22
+ // session file and never touches any other session or fork.
23
+ //
24
+ // Import budget: node:fs (promises only) + ./sessions.js + ./snapshots.js.
25
+ // Never touches compact/overflow/config/context-manager, loop, todos,
26
+ // file-diffs, legacy save, tool execution/registry, permissions/policy,
27
+ // skills, or provider adapters.
28
+ import { promises as fsp } from "node:fs";
29
+ import { getSession, updateSession } from "./sessions.js";
30
+ import { conversationCutIndex, getCheckpoint, listCheckpoints, restoreCheckpointFiles, } from "./snapshots.js";
31
+ async function backupDisk(files) {
32
+ const out = [];
33
+ for (const f of files) {
34
+ try {
35
+ out.push({ abs: f.abs, existed: true, bytes: await fsp.readFile(f.abs) });
36
+ }
37
+ catch {
38
+ out.push({ abs: f.abs, existed: false, bytes: null });
39
+ }
40
+ }
41
+ return out;
42
+ }
43
+ async function rollbackDisk(backups) {
44
+ for (const b of backups) {
45
+ try {
46
+ if (!b.existed || b.bytes === null) {
47
+ await fsp.rm(b.abs, { force: true });
48
+ }
49
+ else {
50
+ await fsp.writeFile(b.abs, b.bytes);
51
+ }
52
+ }
53
+ catch {
54
+ // Best-effort: the session record is already byte-identical; a
55
+ // failed file rollback is reported, never thrown.
56
+ }
57
+ }
58
+ }
59
+ function assistantHasToolCalls(m) {
60
+ return m.role === "assistant" && m.tool_calls !== undefined;
61
+ }
62
+ // Revert one session's conversation AND files to a checkpoint captured
63
+ // earlier in its lineage. Returns a success with the persisted session and
64
+ // a confirmation line, or a failure with a clear error — failures never
65
+ // write the session file.
66
+ export async function revertSessionToCheckpoint(sessionId, checkpointId, home) {
67
+ if (typeof sessionId !== "string" || sessionId.length === 0) {
68
+ return { ok: false, error: "revert failed: missing session id — nothing was changed" };
69
+ }
70
+ if (typeof checkpointId !== "string" || checkpointId.length === 0) {
71
+ return {
72
+ ok: false,
73
+ error: "revert failed: missing checkpoint — nothing was changed. " +
74
+ "Pick a checkpoint from /rewind (every write/edit auto-snapshots).",
75
+ };
76
+ }
77
+ const session = getSession(sessionId, home);
78
+ if (!session) {
79
+ return { ok: false, error: "revert failed: unknown session — nothing was changed" };
80
+ }
81
+ const cp = getCheckpoint(checkpointId);
82
+ if (!cp) {
83
+ if (listCheckpoints().length === 0) {
84
+ return {
85
+ ok: false,
86
+ error: "no snapshots recorded — nothing to revert and nothing was changed. " +
87
+ "Every write/edit auto-snapshots; make a file edit, then pick a checkpoint via /rewind.",
88
+ };
89
+ }
90
+ return { ok: false, error: "revert failed: checkpoint no longer available — nothing was changed" };
91
+ }
92
+ const backups = await backupDisk(cp.files);
93
+ // Files first: a failed restore returns before the record is touched,
94
+ // so the session file stays byte-identical.
95
+ const filesMsg = await restoreCheckpointFiles(cp.id);
96
+ if (filesMsg.startsWith("Error:")) {
97
+ return { ok: false, error: `${filesMsg} — session left exactly as it was` };
98
+ }
99
+ // Turn-boundary cut (the /rewind + forkSession rule, reused — never
100
+ // reimplemented): drops the whole turn containing the mark so
101
+ // assistant/tool_call pairing can never split. Marks clamp to the live
102
+ // lengths, so a checkpoint from a longer lineage can only shrink.
103
+ const historyCut = conversationCutIndex(session.history.map((m) => ({ role: m.role, hasToolCalls: assistantHasToolCalls(m) })), cp.historyLength, 1);
104
+ const turnsCut = conversationCutIndex(session.turns.map((t) => ({ role: t.role })), cp.turnsLength, 0);
105
+ const droppedMessages = session.history.length - historyCut;
106
+ const next = updateSession(sessionId, {
107
+ history: session.history.slice(0, historyCut),
108
+ turns: session.turns.slice(0, turnsCut),
109
+ }, home);
110
+ if (!next) {
111
+ await rollbackDisk(backups);
112
+ return {
113
+ ok: false,
114
+ error: "revert failed: could not save the rewound session (record rejected or vanished) — " +
115
+ "files were restored to their pre-revert state and the session is unchanged",
116
+ };
117
+ }
118
+ return {
119
+ ok: true,
120
+ session: next,
121
+ message: `(reverted "${session.title}" to checkpoint #${cp.seq} "${cp.label}" — ` +
122
+ `dropped ${droppedMessages} message(s), restored ${cp.files.length} file(s); ` +
123
+ `shell side effects were never snapshotted and are unchanged)`,
124
+ };
125
+ }
package/dist/sessions.js CHANGED
@@ -355,6 +355,107 @@ export function getSession(id, home) {
355
355
  export function loadSession(id, home) {
356
356
  return getSession(id, home);
357
357
  }
358
+ // Turn-boundary cut for forks (ticket 07): the /rewind precedent's rule
359
+ // (conversationCutIndex in snapshots.ts) — drop the whole turn containing
360
+ // the mark so assistant/tool_call pairing can never split. Duplicated
361
+ // locally (not imported) to honor this module's import budget (node:fs,
362
+ // node:path, node:crypto, ./auth.js, ./goal.js only).
363
+ function forkCutIndex(messages, mark, keepFirst) {
364
+ const len = messages.length;
365
+ const floor = Math.max(0, Math.floor(keepFirst));
366
+ if (len <= floor)
367
+ return len;
368
+ const m = Math.max(floor, Math.min(Number.isFinite(mark) ? Math.floor(mark) : len, len));
369
+ let turnStart = -1;
370
+ for (let i = m - 1; i >= floor; i--) {
371
+ if (messages[i]?.role === "user") {
372
+ turnStart = i;
373
+ break;
374
+ }
375
+ }
376
+ if (turnStart === -1)
377
+ return floor;
378
+ for (let i = turnStart; i < m; i++) {
379
+ const msg = messages[i];
380
+ if (msg !== undefined &&
381
+ msg.role === "assistant" &&
382
+ msg.hasToolCalls !== true) {
383
+ return m;
384
+ }
385
+ }
386
+ return turnStart;
387
+ }
388
+ // JSON deep copy for forked payloads (history/turns/metadata): the fork's
389
+ // file is independent on disk either way, but a deep copy also keeps the
390
+ // two in-memory records from aliasing nested objects. Falls back to the
391
+ // original reference when the value is not JSON-serializable (records are
392
+ // JSON-file shaped, so this path is defensive only).
393
+ function deepCopyJson(value) {
394
+ try {
395
+ const text = JSON.stringify(value);
396
+ if (text === undefined)
397
+ return value;
398
+ return JSON.parse(text);
399
+ }
400
+ catch {
401
+ return value;
402
+ }
403
+ }
404
+ // Fork a session at a message (ticket 07): "try another approach from here".
405
+ // Reads the source record and persists a brand-new session (fresh stable id
406
+ // from the same scheme as createSession, createdAt/updatedAt = now) holding
407
+ // the source's history up to atMessageIndex and nothing after, cut at a turn
408
+ // boundary via forkCutIndex. atMessageIndex counts history messages to keep
409
+ // (checkpoint-length semantics, like cp.historyLength in the rewind path);
410
+ // omitted/NaN/Infinity forks at the tip, out-of-range values clamp.
411
+ // The display transcript (turns) is sliced at the analogous boundary (the
412
+ // system prompt at history[0] has no turns counterpart, hence the offset).
413
+ // goal + metadata (todos/filediffs/extension keys) ride over opaquely so the
414
+ // branch continues with full context; usageTotals resets to null so the new
415
+ // branch accrues its own spend. provider/model/effort/mode/cwd are carried.
416
+ // The source file is only read, never written; the active pointer is never
417
+ // touched. Returns null when the source id is missing/unknown. Disk errors
418
+ // from the fork write propagate to the caller.
419
+ export function forkSession(sourceId, atMessageIndex, home) {
420
+ if (typeof sourceId !== "string" || sourceId.length === 0)
421
+ return null;
422
+ const source = getSession(sourceId, home);
423
+ if (!source)
424
+ return null;
425
+ const mark = atMessageIndex === undefined ? source.history.length : atMessageIndex;
426
+ const historyCut = forkCutIndex(source.history.map((m) => ({
427
+ role: m.role,
428
+ hasToolCalls: m.role === "assistant" &&
429
+ m.tool_calls !== undefined,
430
+ })), mark, 1);
431
+ const systemOffset = source.history[0]?.role === "system" ? 1 : 0;
432
+ const turnsMark = Math.max(0, Math.min(historyCut - systemOffset, source.turns.length));
433
+ const turnsCut = forkCutIndex(source.turns.map((t) => ({
434
+ role: t.role,
435
+ hasToolCalls: t.tool_calls !== undefined,
436
+ })), turnsMark, 0);
437
+ const at = new Date().toISOString();
438
+ const forked = {
439
+ id: newSessionId(),
440
+ title: `${source.title} (fork)`,
441
+ createdAt: at,
442
+ updatedAt: at,
443
+ cwd: source.cwd,
444
+ provider: source.provider,
445
+ model: source.model,
446
+ effort: source.effort,
447
+ mode: source.mode,
448
+ usageTotals: null,
449
+ // Opaque carry-over (tolerantly validated — a trashed goal reads as
450
+ // no-goal, never throws), same posture as createSession.
451
+ goal: serializeGoalForPersist(restoreGoalFromPersist(source.goal)),
452
+ history: deepCopyJson(source.history.slice(0, historyCut)),
453
+ turns: deepCopyJson(source.turns.slice(0, turnsCut)),
454
+ metadata: deepCopyJson({ ...source.metadata }),
455
+ };
456
+ persistSession(forked, home);
457
+ return forked;
458
+ }
358
459
  export function listSessions(home) {
359
460
  let entries;
360
461
  try {
package/dist/snapshots.js CHANGED
@@ -119,6 +119,34 @@ function newCheckpointId(nextSeq) {
119
119
  return `${Date.now().toString(36)}-${nextSeq.toString(36)}${randomBytes(3).toString("hex")}`;
120
120
  }
121
121
  async function readPrior(abs) {
122
+ let st;
123
+ try {
124
+ const s = await fsp.stat(abs);
125
+ st = s;
126
+ }
127
+ catch {
128
+ return { abs, existed: false, hash: null, bytes: null, overflowPath: null };
129
+ }
130
+ if (!st.isFile())
131
+ return { abs, existed: false, hash: null, bytes: null, overflowPath: null };
132
+ // Large files spill via STREAMING copy (constant memory): a full readFile
133
+ // just to decide the file is too big would itself OOM on GB inputs.
134
+ if (st.size > SNAPSHOT_OVERFLOW_BYTES) {
135
+ try {
136
+ pruneStaleSnapshotOverflow();
137
+ const dir = snapshotDir();
138
+ await fsp.mkdir(dir, { recursive: true });
139
+ const name = `snapshot-${process.pid}-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}.bin`;
140
+ const file = path.join(dir, name);
141
+ const hash = await streamCopyWithHash(abs, file);
142
+ return { abs, existed: true, hash, bytes: null, overflowPath: file };
143
+ }
144
+ catch {
145
+ // Streaming failed (disk/perm) — fall through to the bounded read
146
+ // below, which keeps small files restorable; a huge file here can
147
+ // still press memory, but only when the disk path already failed.
148
+ }
149
+ }
122
150
  let bytes = null;
123
151
  try {
124
152
  bytes = await fsp.readFile(abs);
@@ -143,6 +171,47 @@ async function readPrior(abs) {
143
171
  }
144
172
  return { abs, existed: true, hash, bytes, overflowPath: null };
145
173
  }
174
+ // Constant-memory file copy that hashes while streaming: the hash covers
175
+ // exactly the bytes landed on disk (restore re-verifies against it).
176
+ function streamCopyWithHash(src, dest) {
177
+ return new Promise((resolve, reject) => {
178
+ const hash = createHash("sha256");
179
+ let settled = false;
180
+ const fail = (e) => {
181
+ if (settled)
182
+ return;
183
+ settled = true;
184
+ reject(e instanceof Error ? e : new Error(String(e)));
185
+ };
186
+ let rs;
187
+ let ws;
188
+ try {
189
+ rs = fs.createReadStream(src);
190
+ ws = fs.createWriteStream(dest);
191
+ }
192
+ catch (e) {
193
+ fail(e);
194
+ return;
195
+ }
196
+ rs.on("error", fail);
197
+ ws.on("error", fail);
198
+ rs.on("data", (chunk) => {
199
+ hash.update(chunk);
200
+ });
201
+ ws.on("finish", () => {
202
+ if (settled)
203
+ return;
204
+ settled = true;
205
+ try {
206
+ resolve(hash.digest("hex"));
207
+ }
208
+ catch (e) {
209
+ reject(e instanceof Error ? e : new Error(String(e)));
210
+ }
211
+ });
212
+ rs.pipe(ws);
213
+ });
214
+ }
146
215
  function pushCheckpoint(label, files, marks) {
147
216
  seq += 1;
148
217
  const cp = {
package/dist/system.js CHANGED
@@ -13,93 +13,6 @@
13
13
  // arrive as `Error: ...` text inside the result (invalid args say how to
14
14
  // fix; a denial means replan, never retry).
15
15
  export const SYSTEM_PROMPT = [
16
- "You are ATOM, an autonomous AI coding agent created by beast-ofcourse (Bhavin).",
17
- "Your job is to solve software-engineering tasks accurately, efficiently, and with minimal unnecessary changes.",
18
- "",
19
- "## Core Loop",
20
- "For every task, continuously follow:",
21
- "UNDERSTAND → EXPLORE → PLAN → EXECUTE → VERIFY → COMPLETE",
22
- "",
23
- "Do not stop merely because the code was changed. A task is complete only when the result has been verified or a concrete blocker has been established with evidence.",
24
- "",
25
- "## Understand",
26
- "- Identify the user's actual goal, constraints, and acceptance criteria.",
27
- "- Resolve ambiguity from the repository before asking questions when the answer can be discovered with tools.",
28
- "- Do not assume repository structure, APIs, behavior, or configuration. Inspect them.",
29
- "",
30
- "## Explore",
31
- "- Read relevant files before modifying them.",
32
- "- Search the repository before creating new code.",
33
- "- Trace existing implementations, call sites, types, configuration, and tests.",
34
- "- Prefer understanding existing architecture over introducing parallel implementations.",
35
- "- For unfamiliar code, inspect enough surrounding context to understand how it actually works.",
36
- "",
37
- "## Plan",
38
- "- For non-trivial tasks, create a concise ordered todo list before implementation.",
39
- "- Keep exactly one todo in progress at a time.",
40
- "- Mark todos complete immediately after their work is actually finished.",
41
- "- Adapt the plan when exploration or verification reveals new information.",
42
- "- Do not create unnecessary work just to satisfy the plan.",
43
- "",
44
- "## Execute",
45
- "- Make the smallest correct change that solves the underlying problem.",
46
- "- Preserve existing architecture, conventions, APIs, and behavior unless the task requires changing them.",
47
- "- Reuse existing utilities, abstractions, and patterns before introducing new ones.",
48
- "- Fix root causes rather than symptoms.",
49
- "- Handle relevant errors, edge cases, race conditions, and failure paths.",
50
- "- Avoid speculative features, unnecessary refactors, and unrelated formatting changes.",
51
- "- Remove dead code or obsolete logic when your change makes it unnecessary.",
52
- "",
53
- "## Tool Strategy",
54
- "- Tools are your source of truth for the repository and environment.",
55
- "- Never claim something is true when it has not been established by tool output.",
56
- "- Batch independent reads, searches, inspections, and other safe operations whenever possible.",
57
- "- Prefer parallel tool execution over sequential calls when operations have no dependencies.",
58
- "- Do not parallelize operations that depend on each other's results or could conflict.",
59
- "- After each tool result, determine what information it provides, what remains unknown, and what action has the highest value next.",
60
- "- Avoid repeatedly reading the same information unless the repository changed or verification requires it.",
61
- "",
62
- "## Verification",
63
- "- Verify behavior after implementation.",
64
- "- Run the most relevant tests, typechecks, linters, builds, or targeted checks available.",
65
- "- Prefer targeted verification first, then broader verification when appropriate.",
66
- "- Inspect failures instead of blindly retrying.",
67
- "- If a test, command, or check fails because of your change, fix it before declaring completion.",
68
- "- Do not declare success based solely on compilation if runtime behavior remains unverified.",
69
- "- Do not end with an unverified summary.",
70
- "",
71
- "## Failure Recovery",
72
- "- Tool failures are information, not reasons to stop.",
73
- "- Tools return failures as text such as `Error: ...`; read the error carefully and adapt.",
74
- "- Invalid arguments: correct the arguments using the tool's feedback.",
75
- "- Permission or capability denial: replan using an available approach.",
76
- "- Environment failure: determine whether the failure is caused by ATOM, the repository, or the environment.",
77
- "- Never repeat the same failed action without changing the underlying cause.",
78
- "- If progress is impossible, report the exact blocker and the evidence that proves it.",
79
- "",
80
- "## Efficiency",
81
- "- Minimize unnecessary tool calls, context usage, latency, and duplicated work.",
82
- "- Prefer high-information actions that answer multiple questions at once.",
83
- "- Use repository search and targeted inspection instead of reading large unrelated files.",
84
- "- Keep tool chains moving: when one result enables several independent next actions, perform them together.",
85
- "- Do not waste time narrating internal reasoning to the user.",
86
- "",
87
- "## Code Quality",
88
- "- Favor simple, readable, maintainable code.",
89
- "- Follow the repository's existing style rather than imposing a personal style.",
90
- "- Keep abstractions proportional to the problem.",
91
- "- Avoid duplicated logic.",
92
- "- Keep types accurate and explicit where they improve correctness.",
93
- "- Preserve backwards compatibility unless breaking behavior is explicitly required.",
94
- "- Consider security, performance, concurrency, resource cleanup, and error handling when relevant.",
95
- "",
96
- "## Completion Contract",
97
- "Before finishing, confirm:",
98
- "1. The requested behavior was implemented.",
99
- "2. Relevant existing behavior was preserved.",
100
- "3. The implementation is internally consistent with the repository.",
101
- "4. Appropriate verification was performed.",
102
- "5. Remaining failures or limitations are explicitly identified.",
103
- "",
104
- "Your final response should be concise and factual: summarize what changed, what was verified, and any remaining blocker."
16
+ "You are ATOM, An AI coding agent created by beast-ofcourse (Bhavin bogam).",
17
+ "Tools: read, write, edit, grep, glob, bash, bash_output, webfetch, websearch, ask_question, todowrite, todo_get, todo_update, update_goal (goal turns only).",
105
18
  ].join("\n");
package/dist/telemetry.js CHANGED
@@ -52,6 +52,7 @@
52
52
  import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
53
53
  import * as path from "node:path";
54
54
  import { randomUUID } from "node:crypto";
55
+ import { fileURLToPath } from "node:url";
55
56
  import { scrubSecrets } from "./policy.js";
56
57
  import { atomDir } from "./auth.js";
57
58
  export const TELEMETRY_VERSION = 1;
@@ -578,6 +579,30 @@ export function summarizeTelemetry(sessions) {
578
579
  .sort((a, b) => b.calls - a.calls || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
579
580
  return agg;
580
581
  }
582
+ // App version stamped onto session records (Temp-session gap: every record
583
+ // carried atomVersion:null, so traces from different builds were
584
+ // indistinguishable). Resolved once from the package manifest beside the
585
+ // source tree — dist/ mirrors src/, so ../package.json holds in both
586
+ // layouts. Cached, never throws: null when unreadable keeps today's shape.
587
+ let cachedAtomVersion;
588
+ export function resolveAtomVersion() {
589
+ if (cachedAtomVersion !== undefined)
590
+ return cachedAtomVersion;
591
+ try {
592
+ const here = fileURLToPath(import.meta.url);
593
+ const raw = readFileSync(path.join(path.dirname(here), "..", "package.json"), "utf8");
594
+ const v = JSON.parse(raw).version;
595
+ cachedAtomVersion = typeof v === "string" && v.length > 0 ? v : null;
596
+ }
597
+ catch {
598
+ cachedAtomVersion = null;
599
+ }
600
+ return cachedAtomVersion;
601
+ }
602
+ // Test seam: drop the cached manifest read (production never calls this).
603
+ export function resetAtomVersion() {
604
+ cachedAtomVersion = undefined;
605
+ }
581
606
  // In-memory trace for one session plus atomic turn-boundary persistence.
582
607
  // Every public method is safe to call with null/undefined turn ids and never
583
608
  // throws; when disabled, all record methods are no-ops.
@@ -607,7 +632,7 @@ export class TelemetryRecorder {
607
632
  sessionId: this.sessionId,
608
633
  startedAt: toIso(startedMs),
609
634
  endedAt: null,
610
- atomVersion: opts.atomVersion ?? null,
635
+ atomVersion: opts.atomVersion !== undefined ? opts.atomVersion : resolveAtomVersion(),
611
636
  project: opts.project !== undefined ? opts.project : projectBasename(),
612
637
  provider: opts.provider ?? "unknown",
613
638
  model: opts.model ?? "unknown",