atom-agent 1.1.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 (74) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +18 -8
  3. package/atom.example.json +11 -0
  4. package/dist/App.js +1637 -255
  5. package/dist/adapters.js +112 -21
  6. package/dist/agent/gates.js +14 -1
  7. package/dist/agent/goal-evaluator.js +69 -0
  8. package/dist/agent/loop-guard.js +11 -13
  9. package/dist/agent/loop.js +716 -132
  10. package/dist/agent/normalize.js +9 -2
  11. package/dist/cli.js +25 -3
  12. package/dist/compact.js +169 -17
  13. package/dist/config.js +43 -7
  14. package/dist/context-manager.js +16 -198
  15. package/dist/context-windows.js +4 -2
  16. package/dist/env-block.js +46 -8
  17. package/dist/extension-commands.js +196 -0
  18. package/dist/extension-ui.js +153 -0
  19. package/dist/extensions.js +1571 -0
  20. package/dist/goal.js +583 -0
  21. package/dist/project-trust.js +96 -0
  22. package/dist/providers.js +6 -6
  23. package/dist/scheduler.js +159 -41
  24. package/dist/session.js +23 -5
  25. package/dist/sessions.js +543 -0
  26. package/dist/system.js +89 -13
  27. package/dist/telemetry-dashboard.js +28 -0
  28. package/dist/telemetry.js +39 -0
  29. package/dist/tools/compaction-hooks.js +165 -0
  30. package/dist/tools/custom.js +189 -0
  31. package/dist/tools/dir-cache.js +7 -0
  32. package/dist/tools/filesystem.js +3 -2
  33. package/dist/tools/intercept.js +145 -0
  34. package/dist/tools/overrides.js +105 -0
  35. package/dist/tools/provider-hooks.js +224 -0
  36. package/dist/tools/registry.js +247 -17
  37. package/dist/tools/ripgrep.js +256 -0
  38. package/dist/tools/search.js +119 -58
  39. package/dist/tools/shared.js +39 -0
  40. package/dist/tools/shell.js +7 -5
  41. package/dist/tools/web.js +6 -6
  42. package/dist/tools.js +45 -0
  43. package/dist/ui/diff-view.js +7 -2
  44. package/dist/ui/live-host.js +18 -0
  45. package/dist/ui/live-tail.js +9 -3
  46. package/dist/ui/markdown.js +26 -2
  47. package/dist/ui/palette.js +3 -1
  48. package/dist/ui/side-by-side.js +2 -2
  49. package/dist/ui/status-bar.js +80 -5
  50. package/dist/ui/status-host.js +22 -0
  51. package/dist/ui/stream-store.js +48 -0
  52. package/dist/ui/tool-inspector.js +7 -1
  53. package/dist/ui/transcript.js +92 -38
  54. package/dist/zen.js +370 -87
  55. package/documentation/architecture.md +114 -0
  56. package/documentation/cli.md +82 -0
  57. package/documentation/compaction.md +50 -0
  58. package/documentation/configuration.md +111 -0
  59. package/documentation/development.md +62 -0
  60. package/documentation/extensions.md +160 -0
  61. package/documentation/getting-started.md +63 -0
  62. package/documentation/goals.md +41 -0
  63. package/documentation/index.md +41 -0
  64. package/documentation/observability.md +70 -0
  65. package/documentation/permissions.md +66 -0
  66. package/documentation/providers.md +78 -0
  67. package/documentation/sessions.md +92 -0
  68. package/documentation/skills.md +57 -0
  69. package/documentation/tools.md +94 -0
  70. package/documentation/troubleshooting.md +54 -0
  71. package/examples/extensions/01-audit-gate.js +24 -0
  72. package/examples/extensions/02-notes-tool.js +32 -0
  73. package/examples/extensions/03-custom-command.js +32 -0
  74. package/package.json +6 -2
@@ -0,0 +1,543 @@
1
+ // Durable multi-session store for ATOM.
2
+ //
3
+ // One JSON file per session under ~/.atom/sessions/<id>.json (ATOM_HOME
4
+ // override honored via auth.ts's atomDir) plus a plaintext pointer file
5
+ // ~/.atom/sessions/active holding the active session id.
6
+ //
7
+ // This module never touches the legacy single-file save owned by
8
+ // src/session.ts (~/.atom/session.json) — that file stays exactly as-is.
9
+ //
10
+ // Conventions (mirroring session.ts / auth.ts):
11
+ // - Writes are atomic (temp file + rename, mkdir -p) so a kill mid-write
12
+ // can never leave a half-written record; no .tmp leftovers on failure.
13
+ // - 0600 POSIX perms, best-effort on Windows (never throws for chmod).
14
+ // - Loads never throw: missing -> null, malformed -> null, and listings
15
+ // silently skip corrupt files.
16
+ // - provider/model/effort/mode are opaque carried fields. The literal
17
+ // defaults below are documented here on purpose — this module must NOT
18
+ // depend on DEFAULT_PROVIDER from zen.js (avoid coupling) and must NEVER
19
+ // import LLM clients.
20
+ // - No transient UI state (scroll, cursor, picker, queue) is stored.
21
+ //
22
+ // Import budget: value imports are node:fs, node:path, node:crypto,
23
+ // ./auth.js, and ./goal.js (goal persistence-shape helpers only —
24
+ // serialize/restore/validate; goal.js itself is React-free and imports no
25
+ // session module, so the runtime DAG stays acyclic) plus type-only imports
26
+ // from ./zen.js / ./providers.js / ./goal.js (erased at compile).
27
+ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
28
+ import { randomUUID } from "node:crypto";
29
+ import * as path from "node:path";
30
+ import { atomDir } from "./auth.js";
31
+ import { restoreGoalFromPersist, serializeGoalForPersist, } from "./goal.js";
32
+ // Literal defaults for new sessions (opaque carried fields — see header).
33
+ export const SESSION_DEFAULT_PROVIDER = "opencode-zen";
34
+ export const SESSION_DEFAULT_MODEL = "";
35
+ export const SESSION_DEFAULT_EFFORT = "auto";
36
+ export const SESSION_DEFAULT_MODE = "normal";
37
+ // "default" is the pre-auto name for the same level: old records map to
38
+ // "auto" on load/create instead of carrying a dead value. Local one-liner
39
+ // (not imported from zen.js) to honor this module's import budget above.
40
+ function canonicalSessionEffort(value) {
41
+ if (value === "low" || value === "medium" || value === "high" || value === "max") {
42
+ return value;
43
+ }
44
+ return "auto";
45
+ }
46
+ export const SESSIONS_DIRNAME = "sessions";
47
+ export const ACTIVE_FILENAME = "active";
48
+ export function sessionsDir(home) {
49
+ return path.join(atomDir(home), SESSIONS_DIRNAME);
50
+ }
51
+ export function sessionFilePath(id, home) {
52
+ return path.join(sessionsDir(home), `${id}.json`);
53
+ }
54
+ export function activeFilePath(home) {
55
+ return path.join(sessionsDir(home), ACTIVE_FILENAME);
56
+ }
57
+ const MONTHS = [
58
+ "January",
59
+ "February",
60
+ "March",
61
+ "April",
62
+ "May",
63
+ "June",
64
+ "July",
65
+ "August",
66
+ "September",
67
+ "October",
68
+ "November",
69
+ "December",
70
+ ];
71
+ // Default display title: local date+time like "September 9, 2026 20:41:32"
72
+ // (long English month, unpadded day, 24h zero-padded HH:MM:SS local time).
73
+ // createdAt stays a separate ISO string on the record.
74
+ export function formatSessionTitle(date = new Date()) {
75
+ const d = date instanceof Date && !Number.isNaN(date.getTime()) ? date : new Date();
76
+ const pad = (n) => String(n).padStart(2, "0");
77
+ return (`${MONTHS[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()} ` +
78
+ `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`);
79
+ }
80
+ function newSessionId() {
81
+ return `ses_${randomUUID().replace(/-/g, "")}`;
82
+ }
83
+ function toISODate(now) {
84
+ if (now === undefined)
85
+ return new Date().toISOString();
86
+ const d = now instanceof Date ? now : new Date(now);
87
+ return Number.isNaN(d.getTime()) ? new Date().toISOString() : d.toISOString();
88
+ }
89
+ function safeCwd() {
90
+ try {
91
+ return process.cwd();
92
+ }
93
+ catch {
94
+ return "";
95
+ }
96
+ }
97
+ function isRecord(value) {
98
+ return typeof value === "object" && value !== null && !Array.isArray(value);
99
+ }
100
+ function isNonEmptyString(value) {
101
+ return typeof value === "string" && value.length > 0;
102
+ }
103
+ function isValidDateString(value) {
104
+ return (typeof value === "string" &&
105
+ value.length > 0 &&
106
+ !Number.isNaN(Date.parse(value)));
107
+ }
108
+ // Atomic write: temp file + rename, 0600 POSIX best-effort. Cleans up the
109
+ // temp file when the write/rename fails so no .tmp leftovers remain. Disk
110
+ // errors propagate to the caller.
111
+ function writeFileAtomic(finalPath, content) {
112
+ mkdirSync(path.dirname(finalPath), { recursive: true });
113
+ const tmpPath = `${finalPath}.tmp.${process.pid}`;
114
+ try {
115
+ writeFileSync(tmpPath, content, "utf8");
116
+ try {
117
+ chmodSync(tmpPath, 0o600);
118
+ }
119
+ catch {
120
+ // best-effort on Windows; ignore
121
+ }
122
+ renameSync(tmpPath, finalPath);
123
+ }
124
+ catch (err) {
125
+ try {
126
+ if (existsSync(tmpPath))
127
+ unlinkSync(tmpPath);
128
+ }
129
+ catch {
130
+ // cleanup best-effort; report the original failure
131
+ }
132
+ throw err;
133
+ }
134
+ }
135
+ function validateUsageTotals(value) {
136
+ if (value === null || value === undefined)
137
+ return null;
138
+ if (!isRecord(value))
139
+ return null;
140
+ const out = {};
141
+ for (const key of [
142
+ "prompt_tokens",
143
+ "completion_tokens",
144
+ "total_tokens",
145
+ "cacheReadTokens",
146
+ "cacheWriteTokens",
147
+ ]) {
148
+ const v = value[key];
149
+ if (typeof v === "number" && Number.isFinite(v) && v >= 0) {
150
+ out[key] = Math.floor(v);
151
+ }
152
+ }
153
+ return out;
154
+ }
155
+ function validateToolCall(value) {
156
+ if (!isRecord(value))
157
+ return false;
158
+ if (!isNonEmptyString(value["id"]))
159
+ return false;
160
+ const fn = value["function"];
161
+ if (!isRecord(fn))
162
+ return false;
163
+ if (typeof fn["name"] !== "string" || fn["name"].length === 0)
164
+ return false;
165
+ if (typeof fn["arguments"] !== "string")
166
+ return false;
167
+ if (value["type"] !== undefined && typeof value["type"] !== "string") {
168
+ return false;
169
+ }
170
+ return true;
171
+ }
172
+ function validateChatMessage(value) {
173
+ if (!isRecord(value))
174
+ return false;
175
+ const role = value["role"];
176
+ if (role === "system" || role === "user") {
177
+ return typeof value["content"] === "string";
178
+ }
179
+ if (role === "assistant") {
180
+ const content = value["content"];
181
+ if (content !== undefined &&
182
+ content !== null &&
183
+ typeof content !== "string") {
184
+ return false;
185
+ }
186
+ const calls = value["tool_calls"];
187
+ if (calls !== undefined) {
188
+ if (!Array.isArray(calls) || calls.length === 0)
189
+ return false;
190
+ for (const c of calls) {
191
+ if (!validateToolCall(c))
192
+ return false;
193
+ }
194
+ }
195
+ return true;
196
+ }
197
+ if (role === "tool") {
198
+ return (isNonEmptyString(value["tool_call_id"]) &&
199
+ typeof value["content"] === "string");
200
+ }
201
+ return false;
202
+ }
203
+ function validateTurn(value) {
204
+ if (!isRecord(value))
205
+ return false;
206
+ const role = value["role"];
207
+ if (role !== "user" && role !== "assistant" && role !== "tool")
208
+ return false;
209
+ if (typeof value["content"] !== "string")
210
+ return false;
211
+ if (value["error"] !== undefined && typeof value["error"] !== "boolean") {
212
+ return false;
213
+ }
214
+ if (value["thinking"] !== undefined &&
215
+ typeof value["thinking"] !== "boolean") {
216
+ return false;
217
+ }
218
+ return true;
219
+ }
220
+ // Strict-enough read validation: bad shape -> null (caller treats the file
221
+ // as missing/corrupt, never throws). Unknown extra keys are ignored.
222
+ // provider/model/effort/mode stay opaque (typeof string only — any id,
223
+ // including "", round-trips) so this store never couples to LLM clients.
224
+ function validateSessionRecord(data) {
225
+ if (!isRecord(data))
226
+ return null;
227
+ if (!isNonEmptyString(data["id"]))
228
+ return null;
229
+ if (typeof data["title"] !== "string" || data["title"].trim().length === 0) {
230
+ return null;
231
+ }
232
+ if (!isValidDateString(data["createdAt"]))
233
+ return null;
234
+ if (!isValidDateString(data["updatedAt"]))
235
+ return null;
236
+ if (typeof data["cwd"] !== "string")
237
+ return null;
238
+ if (typeof data["provider"] !== "string")
239
+ return null;
240
+ if (typeof data["model"] !== "string")
241
+ return null;
242
+ if (typeof data["effort"] !== "string")
243
+ return null;
244
+ if (typeof data["mode"] !== "string")
245
+ return null;
246
+ const history = data["history"];
247
+ if (!Array.isArray(history))
248
+ return null;
249
+ for (const m of history) {
250
+ if (!validateChatMessage(m))
251
+ return null;
252
+ }
253
+ const turns = data["turns"];
254
+ if (!Array.isArray(turns))
255
+ return null;
256
+ for (const t of turns) {
257
+ if (!validateTurn(t))
258
+ return null;
259
+ }
260
+ const metadata = data["metadata"];
261
+ return {
262
+ id: data["id"],
263
+ title: data["title"],
264
+ createdAt: data["createdAt"],
265
+ updatedAt: data["updatedAt"],
266
+ cwd: data["cwd"],
267
+ provider: data["provider"],
268
+ model: data["model"],
269
+ effort: canonicalSessionEffort(data["effort"]),
270
+ mode: data["mode"],
271
+ usageTotals: validateUsageTotals(data["usageTotals"]),
272
+ // Tolerant: a missing or trashed goal reads as no-goal (null) — the
273
+ // record still loads, so one corrupt field can never strand a session.
274
+ // Re-serialized so the loaded record always carries concrete stats.
275
+ goal: serializeGoalForPersist(restoreGoalFromPersist(data["goal"])),
276
+ history: history,
277
+ turns: turns,
278
+ metadata: isRecord(metadata) ? { ...metadata } : {},
279
+ };
280
+ }
281
+ function readSessionFile(filePath) {
282
+ let raw;
283
+ try {
284
+ if (!existsSync(filePath))
285
+ return null;
286
+ raw = readFileSync(filePath, "utf8");
287
+ }
288
+ catch {
289
+ return null;
290
+ }
291
+ let data;
292
+ try {
293
+ data = JSON.parse(raw);
294
+ }
295
+ catch {
296
+ return null;
297
+ }
298
+ return validateSessionRecord(data);
299
+ }
300
+ function persistSession(session, home) {
301
+ writeFileAtomic(sessionFilePath(session.id, home), JSON.stringify(session, null, 2) + "\n");
302
+ }
303
+ export function createSession(opts = {}, home) {
304
+ const at = toISODate(opts.now);
305
+ let id = typeof opts.id === "string" && opts.id.length > 0 ? opts.id : newSessionId();
306
+ // Never silently overwrite: an explicit id that already exists (caller
307
+ // retry / collision) falls back to a fresh id, so ids stay unique. The
308
+ // check-then-write races only across processes (last-writer-wins, the
309
+ // documented store posture); each write itself stays atomic.
310
+ if (id === opts.id && opts.id) {
311
+ let exists = false;
312
+ try {
313
+ exists = existsSync(sessionFilePath(id, home));
314
+ }
315
+ catch {
316
+ exists = false;
317
+ }
318
+ if (exists)
319
+ id = newSessionId();
320
+ }
321
+ const rawTitle = typeof opts.title === "string" ? opts.title.trim() : "";
322
+ const session = {
323
+ id,
324
+ title: rawTitle.length > 0 ? rawTitle : formatSessionTitle(new Date(at)),
325
+ createdAt: at,
326
+ updatedAt: at,
327
+ cwd: typeof opts.cwd === "string" ? opts.cwd : safeCwd(),
328
+ provider: opts.provider ?? SESSION_DEFAULT_PROVIDER,
329
+ model: opts.model ?? SESSION_DEFAULT_MODEL,
330
+ effort: canonicalSessionEffort(opts.effort ?? SESSION_DEFAULT_EFFORT),
331
+ mode: opts.mode ?? SESSION_DEFAULT_MODE,
332
+ usageTotals: opts.usageTotals === undefined || opts.usageTotals === null
333
+ ? null
334
+ : validateUsageTotals(opts.usageTotals),
335
+ // Fresh sessions start with no goal unless the caller restores one
336
+ // (tolerantly validated — corrupt input reads as no-goal, never throws).
337
+ goal: serializeGoalForPersist(restoreGoalFromPersist(opts.goal ?? null)),
338
+ history: (opts.history ?? []).map((m) => ({ ...m })),
339
+ turns: (opts.turns ?? []).map((t) => ({ ...t })),
340
+ metadata: isRecord(opts.metadata) ? { ...opts.metadata } : {},
341
+ };
342
+ persistSession(session, home);
343
+ // First session wins the active pointer; later creates leave it alone.
344
+ if (getActiveSessionId(home) === null) {
345
+ setActiveSession(session.id, home);
346
+ }
347
+ return session;
348
+ }
349
+ export function getSession(id, home) {
350
+ if (typeof id !== "string" || id.length === 0)
351
+ return null;
352
+ return readSessionFile(sessionFilePath(id, home));
353
+ }
354
+ // Alias-safe full-record read.
355
+ export function loadSession(id, home) {
356
+ return getSession(id, home);
357
+ }
358
+ export function listSessions(home) {
359
+ let entries;
360
+ try {
361
+ entries = readdirSync(sessionsDir(home));
362
+ }
363
+ catch {
364
+ return [];
365
+ }
366
+ const out = [];
367
+ for (const entry of entries) {
368
+ if (!entry.endsWith(".json"))
369
+ continue;
370
+ const session = readSessionFile(path.join(sessionsDir(home), entry));
371
+ if (session)
372
+ out.push(session);
373
+ }
374
+ out.sort((a, b) => {
375
+ const updated = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
376
+ if (updated !== 0)
377
+ return updated;
378
+ const created = Date.parse(b.createdAt) - Date.parse(a.createdAt);
379
+ if (created !== 0)
380
+ return created;
381
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
382
+ });
383
+ return out;
384
+ }
385
+ export function updateSession(id, patch, home) {
386
+ const current = getSession(id, home);
387
+ if (!current || !isRecord(patch))
388
+ return null;
389
+ const { id: _droppedId, createdAt: _droppedCreatedAt, ...rest } = patch;
390
+ void _droppedId;
391
+ void _droppedCreatedAt;
392
+ const candidate = {
393
+ ...current,
394
+ ...rest,
395
+ id: current.id,
396
+ createdAt: current.createdAt,
397
+ updatedAt: typeof rest["updatedAt"] === "string" &&
398
+ isValidDateString(rest["updatedAt"])
399
+ ? rest["updatedAt"]
400
+ : new Date().toISOString(),
401
+ };
402
+ if (typeof candidate["title"] === "string") {
403
+ candidate["title"] = candidate["title"].trim();
404
+ }
405
+ if (Array.isArray(candidate["history"])) {
406
+ candidate["history"] = candidate["history"].map((m) => isRecord(m) ? { ...m } : m);
407
+ }
408
+ if (Array.isArray(candidate["turns"])) {
409
+ candidate["turns"] = candidate["turns"].map((t) => isRecord(t) ? { ...t } : t);
410
+ }
411
+ if (candidate["metadata"] !== undefined &&
412
+ !isRecord(candidate["metadata"])) {
413
+ return null;
414
+ }
415
+ const valid = validateSessionRecord(candidate);
416
+ if (!valid)
417
+ return null;
418
+ persistSession(valid, home);
419
+ return valid;
420
+ }
421
+ export function renameSession(id, title, home) {
422
+ if (typeof title !== "string" || title.trim().length === 0)
423
+ return null;
424
+ const current = getSession(id, home);
425
+ if (!current)
426
+ return null;
427
+ const next = {
428
+ ...current,
429
+ title: title.trim(),
430
+ updatedAt: new Date().toISOString(),
431
+ };
432
+ persistSession(next, home);
433
+ return next;
434
+ }
435
+ export function deleteSession(id, home) {
436
+ if (typeof id !== "string" || id.length === 0)
437
+ return false;
438
+ const filePath = sessionFilePath(id, home);
439
+ try {
440
+ if (!existsSync(filePath))
441
+ return false;
442
+ unlinkSync(filePath);
443
+ }
444
+ catch {
445
+ return false;
446
+ }
447
+ // Clear the active pointer only when it pointed at the deleted session.
448
+ try {
449
+ if (getActiveSessionId(home) === id)
450
+ setActiveSession(null, home);
451
+ }
452
+ catch {
453
+ // never throws; active cleanup is best-effort
454
+ }
455
+ return true;
456
+ }
457
+ // Full-record overwrite. The stored id/createdAt win when a record already
458
+ // exists on disk — id/createdAt can never be mutated through save.
459
+ // updatedAt always bumps to now.
460
+ export function saveSession(session, home) {
461
+ if (!session || !isNonEmptyString(session.id)) {
462
+ throw new Error("saveSession: session.id must be a non-empty string");
463
+ }
464
+ const disk = getSession(session.id, home);
465
+ const candidate = {
466
+ ...session,
467
+ id: disk ? disk.id : session.id,
468
+ createdAt: disk ? disk.createdAt : session.createdAt,
469
+ updatedAt: new Date().toISOString(),
470
+ };
471
+ const valid = validateSessionRecord(candidate);
472
+ if (!valid) {
473
+ throw new Error("saveSession: session record failed validation");
474
+ }
475
+ persistSession(valid, home);
476
+ return valid;
477
+ }
478
+ // Bump updatedAt to now (runtime message/assistant/tool mutations). The
479
+ // caller mutates content via updateSession/saveSession; touch only refreshes
480
+ // the recency marker so listings sort correctly.
481
+ export function touchSession(id, home) {
482
+ const current = getSession(id, home);
483
+ if (!current)
484
+ return null;
485
+ const next = { ...current, updatedAt: new Date().toISOString() };
486
+ persistSession(next, home);
487
+ return next;
488
+ }
489
+ // null clears the pointer. Unknown ids are ignored (active unchanged).
490
+ // Never throws.
491
+ export function setActiveSession(id, home) {
492
+ try {
493
+ const activePath = activeFilePath(home);
494
+ if (id === null) {
495
+ try {
496
+ if (existsSync(activePath))
497
+ unlinkSync(activePath);
498
+ }
499
+ catch {
500
+ // best-effort clear; ignore
501
+ }
502
+ return;
503
+ }
504
+ if (typeof id !== "string" || id.length === 0)
505
+ return;
506
+ if (!getSession(id, home))
507
+ return;
508
+ writeFileAtomic(activePath, id);
509
+ }
510
+ catch {
511
+ // never throws; ignore
512
+ }
513
+ }
514
+ export function getActiveSessionId(home) {
515
+ try {
516
+ const activePath = activeFilePath(home);
517
+ if (!existsSync(activePath))
518
+ return null;
519
+ const raw = readFileSync(activePath, "utf8").trim();
520
+ return raw.length > 0 ? raw : null;
521
+ }
522
+ catch {
523
+ return null;
524
+ }
525
+ }
526
+ export function getActiveSession(home) {
527
+ const id = getActiveSessionId(home);
528
+ if (!id)
529
+ return null;
530
+ return getSession(id, home);
531
+ }
532
+ // Return the active session when it still exists on disk, else create (and
533
+ // activate, when nothing is set) a new one from opts.
534
+ export function ensureActiveSession(opts = {}, home) {
535
+ const active = getActiveSession(home);
536
+ if (active)
537
+ return active;
538
+ const created = createSession(opts, home);
539
+ // createSession only claims the pointer when none is set; a dangling
540
+ // pointer must be re-pointed at the replacement session.
541
+ setActiveSession(created.id, home);
542
+ return created;
543
+ }
package/dist/system.js CHANGED
@@ -13,17 +13,93 @@
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, a long-horizon coding agent that works through tools.",
17
- "",
18
- "Loop every task: explore, plan, implement, verify, report.",
19
- "Plan 3+ step tasks with todowrite: full list up front, exactly one in_progress, mark completed immediately, never batch.",
20
- "Read files before editing them. Search existing code before writing new code. Match surrounding patterns.",
21
- "Prefer the smallest correct change. Fix root causes. Handle errors and edge cases. Remove dead code.",
22
- "Ground every claim in tool output, never in memory. Run commands to check facts.",
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.",
25
- "Keep calling tools until verified done. Never end on an unverified summary or a guess.",
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.",
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."
29
105
  ].join("\n");