@a-dray/aglib 0.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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/dist/agent.d.ts +158 -0
  4. package/dist/agent.js +2 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/content.d.ts +45 -0
  7. package/dist/content.js +11 -0
  8. package/dist/content.js.map +1 -0
  9. package/dist/harness/adapters/acp/index.d.ts +92 -0
  10. package/dist/harness/adapters/acp/index.js +479 -0
  11. package/dist/harness/adapters/acp/index.js.map +1 -0
  12. package/dist/harness/adapters/acp/rpc.d.ts +23 -0
  13. package/dist/harness/adapters/acp/rpc.js +103 -0
  14. package/dist/harness/adapters/acp/rpc.js.map +1 -0
  15. package/dist/harness/adapters/native/compaction.d.ts +16 -0
  16. package/dist/harness/adapters/native/compaction.js +55 -0
  17. package/dist/harness/adapters/native/compaction.js.map +1 -0
  18. package/dist/harness/adapters/native/index.d.ts +2 -0
  19. package/dist/harness/adapters/native/index.js +2 -0
  20. package/dist/harness/adapters/native/index.js.map +1 -0
  21. package/dist/harness/adapters/native/loop.d.ts +25 -0
  22. package/dist/harness/adapters/native/loop.js +153 -0
  23. package/dist/harness/adapters/native/loop.js.map +1 -0
  24. package/dist/harness/harness.d.ts +129 -0
  25. package/dist/harness/harness.js +2 -0
  26. package/dist/harness/harness.js.map +1 -0
  27. package/dist/harness/index.d.ts +3 -0
  28. package/dist/harness/index.js +2 -0
  29. package/dist/harness/index.js.map +1 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +5 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/json.d.ts +4 -0
  34. package/dist/json.js +2 -0
  35. package/dist/json.js.map +1 -0
  36. package/dist/model/adapters/anthropic/index.d.ts +34 -0
  37. package/dist/model/adapters/anthropic/index.js +271 -0
  38. package/dist/model/adapters/anthropic/index.js.map +1 -0
  39. package/dist/model/adapters/fake/index.d.ts +27 -0
  40. package/dist/model/adapters/fake/index.js +37 -0
  41. package/dist/model/adapters/fake/index.js.map +1 -0
  42. package/dist/model/adapters/openai-compatible/index.d.ts +29 -0
  43. package/dist/model/adapters/openai-compatible/index.js +226 -0
  44. package/dist/model/adapters/openai-compatible/index.js.map +1 -0
  45. package/dist/model/conformance.d.ts +210 -0
  46. package/dist/model/conformance.js +427 -0
  47. package/dist/model/conformance.js.map +1 -0
  48. package/dist/model/index.d.ts +2 -0
  49. package/dist/model/index.js +2 -0
  50. package/dist/model/index.js.map +1 -0
  51. package/dist/model/model.d.ts +85 -0
  52. package/dist/model/model.js +8 -0
  53. package/dist/model/model.js.map +1 -0
  54. package/dist/result.d.ts +23 -0
  55. package/dist/result.js +3 -0
  56. package/dist/result.js.map +1 -0
  57. package/dist/run.d.ts +10 -0
  58. package/dist/run.js +477 -0
  59. package/dist/run.js.map +1 -0
  60. package/dist/sandbox/adapters/docker.d.ts +22 -0
  61. package/dist/sandbox/adapters/docker.js +443 -0
  62. package/dist/sandbox/adapters/docker.js.map +1 -0
  63. package/dist/sandbox/adapters/local.d.ts +11 -0
  64. package/dist/sandbox/adapters/local.js +284 -0
  65. package/dist/sandbox/adapters/local.js.map +1 -0
  66. package/dist/sandbox/conformance.d.ts +73 -0
  67. package/dist/sandbox/conformance.js +309 -0
  68. package/dist/sandbox/conformance.js.map +1 -0
  69. package/dist/sandbox/index.d.ts +1 -0
  70. package/dist/sandbox/index.js +2 -0
  71. package/dist/sandbox/index.js.map +1 -0
  72. package/dist/sandbox/sandbox.d.ts +172 -0
  73. package/dist/sandbox/sandbox.js +2 -0
  74. package/dist/sandbox/sandbox.js.map +1 -0
  75. package/dist/session/entry.d.ts +196 -0
  76. package/dist/session/entry.js +2 -0
  77. package/dist/session/entry.js.map +1 -0
  78. package/dist/session/index.d.ts +2 -0
  79. package/dist/session/index.js +2 -0
  80. package/dist/session/index.js.map +1 -0
  81. package/dist/session/log.d.ts +20 -0
  82. package/dist/session/log.js +24 -0
  83. package/dist/session/log.js.map +1 -0
  84. package/dist/session/messages.d.ts +42 -0
  85. package/dist/session/messages.js +99 -0
  86. package/dist/session/messages.js.map +1 -0
  87. package/dist/store/adapters/sqlite.d.ts +20 -0
  88. package/dist/store/adapters/sqlite.js +365 -0
  89. package/dist/store/adapters/sqlite.js.map +1 -0
  90. package/dist/store/conformance.d.ts +66 -0
  91. package/dist/store/conformance.js +580 -0
  92. package/dist/store/conformance.js.map +1 -0
  93. package/dist/store/index.d.ts +1 -0
  94. package/dist/store/index.js +2 -0
  95. package/dist/store/index.js.map +1 -0
  96. package/dist/store/store.d.ts +244 -0
  97. package/dist/store/store.js +2 -0
  98. package/dist/store/store.js.map +1 -0
  99. package/dist/tools/execute.d.ts +24 -0
  100. package/dist/tools/execute.js +105 -0
  101. package/dist/tools/execute.js.map +1 -0
  102. package/dist/tools/index.d.ts +2 -0
  103. package/dist/tools/index.js +2 -0
  104. package/dist/tools/index.js.map +1 -0
  105. package/dist/tools/tool.d.ts +106 -0
  106. package/dist/tools/tool.js +34 -0
  107. package/dist/tools/tool.js.map +1 -0
  108. package/package.json +119 -0
@@ -0,0 +1,365 @@
1
+ import { err, ok } from "../../result.js";
2
+ const schema = `
3
+ CREATE TABLE IF NOT EXISTS sessions (
4
+ id TEXT PRIMARY KEY,
5
+ agent_id TEXT NOT NULL,
6
+ agent_ver TEXT NOT NULL,
7
+ key TEXT,
8
+ metadata TEXT NOT NULL DEFAULT '{}',
9
+ pending TEXT NOT NULL DEFAULT '[]',
10
+ last_seq INTEGER NOT NULL DEFAULT 0,
11
+ running_since TEXT,
12
+ updated_at TEXT NOT NULL
13
+ );
14
+ CREATE TABLE IF NOT EXISTS entries (
15
+ session_id TEXT NOT NULL,
16
+ seq INTEGER NOT NULL,
17
+ at TEXT NOT NULL,
18
+ body TEXT NOT NULL,
19
+ PRIMARY KEY (session_id, seq)
20
+ );
21
+ CREATE TABLE IF NOT EXISTS deliveries (
22
+ session_id TEXT NOT NULL,
23
+ sender TEXT NOT NULL,
24
+ delivery_id TEXT NOT NULL,
25
+ at TEXT NOT NULL,
26
+ PRIMARY KEY (session_id, sender, delivery_id)
27
+ );
28
+ CREATE INDEX IF NOT EXISTS sessions_by_key ON sessions (key, updated_at DESC, id DESC);
29
+ CREATE INDEX IF NOT EXISTS sessions_runnable ON sessions (updated_at) WHERE pending <> '[]';
30
+ DROP INDEX IF EXISTS sessions_stranded; -- sessions_interrupted under its former name
31
+ CREATE INDEX IF NOT EXISTS sessions_interrupted ON sessions (updated_at) WHERE running_since IS NOT NULL;
32
+ CREATE INDEX IF NOT EXISTS deliveries_by_age ON deliveries (at);
33
+ `;
34
+ /** `updatedAt` and the session id, so a page boundary is a point and not an instant. */
35
+ const splitCursor = (cursor) => {
36
+ const cut = cursor.lastIndexOf("|");
37
+ return cut < 0 ? [cursor, ""] : [cursor.slice(0, cut), cursor.slice(cut + 1)];
38
+ };
39
+ /**
40
+ * How long a claim holds before another worker may take the session.
41
+ *
42
+ * Long enough that an ordinary turn finishes inside it, short enough that a
43
+ * worker killed mid-turn does not strand its session for an afternoon. If the
44
+ * original worker is somehow still alive when it expires, its writes fail the
45
+ * compare-and-swap, so the window governs wasted work and never correctness.
46
+ */
47
+ const DEFAULT_CLAIM_MS = 10 * 60 * 1000;
48
+ /**
49
+ * How long a delivery id is remembered.
50
+ *
51
+ * `id` means "this delivery, once" — a webhook redelivered ten seconds later,
52
+ * a retried send, a cron that fired twice all collapse to one. That has to
53
+ * outlive the queue, because the common case is a duplicate arriving after the
54
+ * first copy was already read. It cannot be remembered forever, so it is
55
+ * remembered for a window an application can set and reason about.
56
+ */
57
+ const DEFAULT_DELIVERY_MEMORY_MS = 7 * 24 * 60 * 60 * 1000;
58
+ export function createSqliteStore(input) {
59
+ const database = input.database;
60
+ const claimMs = input.claimMs ?? DEFAULT_CLAIM_MS;
61
+ const deliveryMemoryMs = input.deliveryMemoryMs ?? DEFAULT_DELIVERY_MEMORY_MS;
62
+ database.exec(schema);
63
+ const one = (sql, parameters) => database.query(sql).all(...parameters)[0];
64
+ const run = (sql, parameters) => { database.query(sql).run(...parameters); };
65
+ const notFound = (sessionId) => ({ code: "not-found", message: `No session ${sessionId}`, retryable: false });
66
+ const failed = (error) => ({ code: "failed", message: error instanceof Error ? error.message : String(error), retryable: false });
67
+ const queueOf = (row) => JSON.parse(row.pending);
68
+ /**
69
+ * Watchers, woken after a write is committed.
70
+ *
71
+ * Every call site is already past its `COMMIT`, which is what makes the change
72
+ * readable. The microtask is for the other half: a watcher that throws must
73
+ * not fail the write that woke it — it surfaces as the caller's own unhandled
74
+ * error rather than as a refused append — and one that writes back through
75
+ * this store must not do so from inside the stack of the write it is
76
+ * answering.
77
+ */
78
+ const watchers = new Set();
79
+ const woke = (changes) => {
80
+ if (!watchers.size || !changes.length)
81
+ return;
82
+ for (const watcher of [...watchers]) {
83
+ // One microtask each. A watcher that throws must not fail the write that
84
+ // woke it, and must not suppress the watchers after it in the loop — in
85
+ // its own task the throw surfaces as the caller's unhandled error and
86
+ // costs nobody else their wake. Membership is re-checked at delivery, so
87
+ // a watcher that let go between the write and this task is not called:
88
+ // `off()` says stop, and a queued wake is still a wake.
89
+ queueMicrotask(() => {
90
+ if (!watchers.has(watcher))
91
+ return;
92
+ for (const change of changes)
93
+ watcher(change);
94
+ });
95
+ }
96
+ };
97
+ return {
98
+ /** Idempotent: a session that is already here keeps what it was created with. */
99
+ async create({ sessionId, agent, key, metadata }) {
100
+ try {
101
+ const existing = one(`SELECT id FROM sessions WHERE id = ?`, [sessionId]);
102
+ if (existing)
103
+ return ok(undefined);
104
+ run(`INSERT OR IGNORE INTO sessions (id, agent_id, agent_ver, key, metadata, updated_at)
105
+ VALUES (?, ?, ?, ?, ?, ?)`, [sessionId, agent.id, agent.version, key ?? null, JSON.stringify(metadata ?? {}), new Date().toISOString()]);
106
+ // Only a session that really appeared is a change, and a new one has
107
+ // nothing waiting on it — announcing `runnable: false` for a session
108
+ // that already had input would have been wrong rather than spurious.
109
+ woke([{ sessionId, runnable: false }]);
110
+ return ok(undefined);
111
+ }
112
+ catch (error) {
113
+ return err(failed(error));
114
+ }
115
+ },
116
+ async read({ sessionId, afterSeq = 0 }) {
117
+ try {
118
+ const session = one(`SELECT * FROM sessions WHERE id = ?`, [sessionId]);
119
+ if (!session)
120
+ return err(notFound(sessionId));
121
+ const rows = database.query(`SELECT seq, at, body FROM entries WHERE session_id = ? AND seq > ? ORDER BY seq`).all(sessionId, afterSeq);
122
+ return ok({
123
+ sessionId,
124
+ agent: { id: session.agent_id, version: session.agent_ver },
125
+ seq: session.last_seq,
126
+ metadata: JSON.parse(session.metadata),
127
+ entries: rows.map((row) => ({ ...JSON.parse(row.body), seq: row.seq, at: row.at })),
128
+ pending: queueOf(session),
129
+ });
130
+ }
131
+ catch (error) {
132
+ return err(failed(error));
133
+ }
134
+ },
135
+ /**
136
+ * Entries, deliveries, metadata and the queue in one transaction.
137
+ *
138
+ * The compare-and-swap on `last_seq` decides everything else: a caller
139
+ * working from a stale position loses and is told the real one, and because
140
+ * the queue is consumed here rather than in `next`, a loser leaves the
141
+ * messages it was carrying untouched.
142
+ */
143
+ async append({ sessionId, expectedSeq, entries, enqueue, metadata, takePending }) {
144
+ const at = new Date().toISOString();
145
+ try {
146
+ // Inside the try: on a busy database this is where the failure lands,
147
+ // and a throw here escaped the `Result` the port promises.
148
+ database.exec("BEGIN IMMEDIATE");
149
+ const session = one(`SELECT * FROM sessions WHERE id = ?`, [sessionId]);
150
+ if (!session) {
151
+ database.exec("ROLLBACK");
152
+ return err(notFound(sessionId));
153
+ }
154
+ if (session.last_seq !== expectedSeq) {
155
+ database.exec("ROLLBACK");
156
+ return err({
157
+ code: "conflict", message: `Session moved to ${session.last_seq}`,
158
+ retryable: true, actualSeq: session.last_seq,
159
+ });
160
+ }
161
+ entries.forEach((entry, offset) => {
162
+ run(`INSERT INTO entries (session_id, seq, at, body) VALUES (?, ?, ?, ?)`, [sessionId, expectedSeq + offset + 1, at, JSON.stringify(entry)]);
163
+ });
164
+ // An activation is open from the entry that starts it to the entry that
165
+ // ends it. `next` reads this to keep a second one from beginning, and
166
+ // the log is the only thing that knows — so it is derived from the log
167
+ // rather than asserted by a caller.
168
+ const opens = entries.some((entry) => entry.type === "run.started");
169
+ const closes = entries.some((entry) => entry.type === "run.finished");
170
+ // Only a write that records work renews the claim. Renewing on any
171
+ // append at all meant a message *to* a session renewed the claim of the
172
+ // worker that was supposed to read it — so a dead worker's session
173
+ // stayed claimed for as long as anyone kept talking to it, invisible to
174
+ // `next` (the claim never lapsed) and to `interrupted` (the queue was not
175
+ // empty). An activation renews by doing its work, and being spoken to
176
+ // is not its work.
177
+ const worked = entries.length > 0;
178
+ // Every write by an open activation renews its claim. Without this the
179
+ // timestamp stayed at whenever the run began, so any turn outstanding
180
+ // longer than the claim window was handed to a second worker while it
181
+ // was still going — and a compare-and-swap cannot un-run a tool call.
182
+ // A worker that dies still releases, because a dead worker stops writing.
183
+ const runningSince = closes ? null : (opens || (worked && session.running_since)) ? at : session.running_since;
184
+ const merged = metadata
185
+ ? JSON.stringify({ ...JSON.parse(session.metadata), ...metadata })
186
+ : session.metadata;
187
+ const waiting = queueOf(session);
188
+ if (takePending !== undefined && takePending > waiting.length) {
189
+ // The one loss this design exists to prevent, reachable from a
190
+ // caller's off-by-one: silently dropping messages nothing recorded.
191
+ // `takePending` is only ever `claim.pending.length`, so a larger
192
+ // number means the caller is working from a queue that is not there.
193
+ database.exec("ROLLBACK");
194
+ return err({
195
+ code: "conflict",
196
+ message: `Asked to take ${takePending} deliveries from a queue of ${waiting.length}`,
197
+ retryable: false,
198
+ });
199
+ }
200
+ const queue = takePending ? waiting.slice(takePending) : waiting;
201
+ /** Which sessions this write put input on, this one included. */
202
+ const delivered = new Set();
203
+ run(`UPDATE sessions SET last_seq = ?, metadata = ?, pending = ?, running_since = ?, updated_at = ?
204
+ WHERE id = ?`, [expectedSeq + entries.length, merged, JSON.stringify(queue), runningSince, at, sessionId]);
205
+ // Swept before the ids are checked, not after: an id past its window is
206
+ // forgotten, and one still inside it is not. Sweeping afterwards left
207
+ // every expired id alive for exactly one more send.
208
+ run(`DELETE FROM deliveries WHERE at < ?`, [new Date(Date.parse(at) - deliveryMemoryMs).toISOString()]);
209
+ for (const delivery of enqueue ?? []) {
210
+ const target = one(`SELECT pending FROM sessions WHERE id = ?`, [delivery.sessionId]);
211
+ if (!target) {
212
+ database.exec("ROLLBACK");
213
+ return err(notFound(delivery.sessionId));
214
+ }
215
+ // Two deliveries with the same id from the same sender are the same
216
+ // delivery, and stay so after the first has been read — the whole
217
+ // point of an idempotency key is that it survives the queue. The
218
+ // sender is part of it because the id is the sender's own: without
219
+ // that, two agents numbering their messages from one silently
220
+ // suppress each other.
221
+ // The column is one string, so the two fields are joined into one
222
+ // here. The encoding is this adapter's own and never leaves it: no
223
+ // caller reads the column, and a store that keys the same fact some
224
+ // other way is answering the same question, which is the one the
225
+ // conformance suite asks.
226
+ if (delivery.id) {
227
+ const sender = delivery.from ? `${delivery.from.kind}:${delivery.from.id}` : "";
228
+ const seen = one(`SELECT delivery_id FROM deliveries WHERE session_id = ? AND sender = ? AND delivery_id = ?`, [delivery.sessionId, sender, delivery.id]);
229
+ if (seen)
230
+ continue;
231
+ run(`INSERT INTO deliveries (session_id, sender, delivery_id, at) VALUES (?, ?, ?, ?)`, [delivery.sessionId, sender, delivery.id, at]);
232
+ }
233
+ run(`UPDATE sessions SET pending = ?, updated_at = ? WHERE id = ?`, [JSON.stringify([...queueOf(target), delivery]), at, delivery.sessionId]);
234
+ delivered.add(delivery.sessionId);
235
+ }
236
+ database.exec("COMMIT");
237
+ // After the commit, never before: a watcher that looked on an earlier
238
+ // wake would find nothing and never be told again.
239
+ woke([
240
+ { sessionId, runnable: delivered.has(sessionId) || queue.length > 0 },
241
+ ...[...delivered].filter((id) => id !== sessionId).map((id) => ({ sessionId: id, runnable: true })),
242
+ ]);
243
+ return ok({ seq: expectedSeq + entries.length });
244
+ }
245
+ catch (error) {
246
+ database.exec("ROLLBACK");
247
+ return err(failed(error));
248
+ }
249
+ },
250
+ async list({ key, limit = 50, before }) {
251
+ try {
252
+ // The cursor carries the id as well as the timestamp, because sessions
253
+ // written in the same tick have no order without it — and a plain
254
+ // `< before` then steps over every row that shares the boundary instant.
255
+ const [beforeAt, beforeId] = before ? splitCursor(before) : [null, null];
256
+ const rows = database.query(`SELECT * FROM sessions
257
+ WHERE (? IS NULL OR key = ?)
258
+ AND (? IS NULL OR updated_at < ? OR (updated_at = ? AND id < ?))
259
+ ORDER BY updated_at DESC, id DESC LIMIT ?`).all(key ?? null, key ?? null, beforeAt, beforeAt, beforeAt, beforeId, limit);
260
+ return ok(rows.map((row) => ({
261
+ sessionId: row.id,
262
+ agent: { id: row.agent_id, version: row.agent_ver },
263
+ key: row.key,
264
+ seq: row.last_seq,
265
+ metadata: JSON.parse(row.metadata),
266
+ updatedAt: row.updated_at,
267
+ })));
268
+ }
269
+ catch (error) {
270
+ return err(failed(error));
271
+ }
272
+ },
273
+ /**
274
+ * Claim a session that has input and no activation already running.
275
+ *
276
+ * The queue is reported and left in place; the append that commits it as
277
+ * entries is what removes it. Nothing here is destructive, so a worker that
278
+ * takes this and then dies costs one expired claim and no messages.
279
+ */
280
+ async next({ signal }) {
281
+ // Nothing is owed to a caller that has given up. Answered rather than
282
+ // refused: "nothing to claim" is true, and it needs no code of its own.
283
+ if (signal?.aborted)
284
+ return ok(undefined);
285
+ const now = Date.now();
286
+ const stale = new Date(now - claimMs).toISOString();
287
+ try {
288
+ database.exec("BEGIN IMMEDIATE");
289
+ const row = one(`SELECT * FROM sessions
290
+ WHERE pending <> '[]' AND (running_since IS NULL OR running_since < ?)
291
+ ORDER BY updated_at LIMIT 1`, [stale]);
292
+ if (!row) {
293
+ database.exec("COMMIT");
294
+ return ok(undefined);
295
+ }
296
+ run(`UPDATE sessions SET running_since = ? WHERE id = ?`, [new Date(now).toISOString(), row.id]);
297
+ database.exec("COMMIT");
298
+ return ok({
299
+ sessionId: row.id,
300
+ seq: row.last_seq,
301
+ pending: queueOf(row),
302
+ metadata: JSON.parse(row.metadata),
303
+ });
304
+ }
305
+ catch (error) {
306
+ database.exec("ROLLBACK");
307
+ return err(failed(error));
308
+ }
309
+ },
310
+ /**
311
+ * Claim a session whose activation was interrupted.
312
+ *
313
+ * The mirror of `next`, over the two columns that already exist: an open
314
+ * run — `running_since` set, because the entry that would clear it never
315
+ * committed — whose claim has lapsed, and an empty queue, because the
316
+ * commit that opened the run took what made it runnable. Nothing else in
317
+ * the store can see this session, which is why the port asks for it here
318
+ * rather than leaving every application to write this query itself.
319
+ */
320
+ async interrupted({ signal }) {
321
+ if (signal?.aborted)
322
+ return ok(undefined);
323
+ const now = Date.now();
324
+ const stale = new Date(now - claimMs).toISOString();
325
+ try {
326
+ database.exec("BEGIN IMMEDIATE");
327
+ const row = one(`SELECT * FROM sessions
328
+ WHERE pending = '[]' AND running_since IS NOT NULL AND running_since < ?
329
+ ORDER BY updated_at LIMIT 1`, [stale]);
330
+ if (!row) {
331
+ database.exec("COMMIT");
332
+ return ok(undefined);
333
+ }
334
+ run(`UPDATE sessions SET running_since = ? WHERE id = ?`, [new Date(now).toISOString(), row.id]);
335
+ database.exec("COMMIT");
336
+ return ok({
337
+ sessionId: row.id,
338
+ seq: row.last_seq,
339
+ pending: [],
340
+ metadata: JSON.parse(row.metadata),
341
+ });
342
+ }
343
+ catch (error) {
344
+ database.exec("ROLLBACK");
345
+ return err(failed(error));
346
+ }
347
+ },
348
+ /**
349
+ * The feed is this store's own writes, and reaches exactly as far as they
350
+ * do: a watcher here is woken by what this instance commits, and by nothing
351
+ * else. Another process on the same file, a second store over the same
352
+ * handle, and the application's own SQL beside the log are all invisible,
353
+ * because sqlite has no channel to tell us — that is the miss the port
354
+ * requires a heartbeat for. The case that matters is covered: the surface
355
+ * that writes a delivery and the worker that reads it are the same process,
356
+ * and usually the same store.
357
+ */
358
+ watch(watcher) {
359
+ watchers.add(watcher);
360
+ return () => { watchers.delete(watcher); };
361
+ },
362
+ async close() { watchers.clear(); /* the caller owns the handle it passed in */ },
363
+ };
364
+ }
365
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../src/store/adapters/sqlite.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Store, StoreChange, StoreError, StoreConflict, SessionRead, SessionSummary, Runnable,\n} from \"../store.js\";\nimport type { Delivery, Entry, Stored } from \"../../session/entry.js\";\nimport type { JsonValue } from \"../../json.js\";\nimport { err, ok } from \"../../result.js\";\n\n/**\n * The minimum a driver must provide. Declared here rather than importing one so\n * the same file serves `node:sqlite` and `bun:sqlite`, and so an application can\n * hand in a database it already opened — which is what lets its own tables live\n * beside the log and join against it.\n */\nexport interface SqliteDatabase {\n exec(sql: string): void;\n query(sql: string): { all(...parameters: unknown[]): unknown[]; run(...parameters: unknown[]): unknown };\n}\n\nconst schema = `\n CREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL,\n agent_ver TEXT NOT NULL,\n key TEXT,\n metadata TEXT NOT NULL DEFAULT '{}',\n pending TEXT NOT NULL DEFAULT '[]',\n last_seq INTEGER NOT NULL DEFAULT 0,\n running_since TEXT,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS entries (\n session_id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n at TEXT NOT NULL,\n body TEXT NOT NULL,\n PRIMARY KEY (session_id, seq)\n );\n CREATE TABLE IF NOT EXISTS deliveries (\n session_id TEXT NOT NULL,\n sender TEXT NOT NULL,\n delivery_id TEXT NOT NULL,\n at TEXT NOT NULL,\n PRIMARY KEY (session_id, sender, delivery_id)\n );\n CREATE INDEX IF NOT EXISTS sessions_by_key ON sessions (key, updated_at DESC, id DESC);\n CREATE INDEX IF NOT EXISTS sessions_runnable ON sessions (updated_at) WHERE pending <> '[]';\n DROP INDEX IF EXISTS sessions_stranded; -- sessions_interrupted under its former name\n CREATE INDEX IF NOT EXISTS sessions_interrupted ON sessions (updated_at) WHERE running_since IS NOT NULL;\n CREATE INDEX IF NOT EXISTS deliveries_by_age ON deliveries (at);\n`;\n\ninterface SessionRow {\n id: string; agent_id: string; agent_ver: string; key: string | null;\n metadata: string; pending: string; last_seq: number; running_since: string | null; updated_at: string;\n}\n\n/** `updatedAt` and the session id, so a page boundary is a point and not an instant. */\nconst splitCursor = (cursor: string): [string, string] => {\n const cut = cursor.lastIndexOf(\"|\");\n return cut < 0 ? [cursor, \"\"] : [cursor.slice(0, cut), cursor.slice(cut + 1)];\n};\n\n/**\n * How long a claim holds before another worker may take the session.\n *\n * Long enough that an ordinary turn finishes inside it, short enough that a\n * worker killed mid-turn does not strand its session for an afternoon. If the\n * original worker is somehow still alive when it expires, its writes fail the\n * compare-and-swap, so the window governs wasted work and never correctness.\n */\nconst DEFAULT_CLAIM_MS = 10 * 60 * 1000;\n\n/**\n * How long a delivery id is remembered.\n *\n * `id` means \"this delivery, once\" — a webhook redelivered ten seconds later,\n * a retried send, a cron that fired twice all collapse to one. That has to\n * outlive the queue, because the common case is a duplicate arriving after the\n * first copy was already read. It cannot be remembered forever, so it is\n * remembered for a window an application can set and reason about.\n */\nconst DEFAULT_DELIVERY_MEMORY_MS = 7 * 24 * 60 * 60 * 1000;\n\nexport function createSqliteStore(input: {\n database: SqliteDatabase;\n claimMs?: number;\n /** How long a delivery id is remembered. Defaults to seven days. */\n deliveryMemoryMs?: number;\n}): Store {\n const database = input.database;\n const claimMs = input.claimMs ?? DEFAULT_CLAIM_MS;\n const deliveryMemoryMs = input.deliveryMemoryMs ?? DEFAULT_DELIVERY_MEMORY_MS;\n database.exec(schema);\n\n const one = <T>(sql: string, parameters: readonly unknown[]): T | undefined =>\n database.query(sql).all(...parameters)[0] as T | undefined;\n const run = (sql: string, parameters: readonly unknown[]) => { database.query(sql).run(...parameters); };\n const notFound = (sessionId: string): StoreError =>\n ({ code: \"not-found\", message: `No session ${sessionId}`, retryable: false });\n const failed = (error: unknown): StoreError =>\n ({ code: \"failed\", message: error instanceof Error ? error.message : String(error), retryable: false });\n const queueOf = (row: { pending: string }): Delivery[] => JSON.parse(row.pending) as Delivery[];\n\n /**\n * Watchers, woken after a write is committed.\n *\n * Every call site is already past its `COMMIT`, which is what makes the change\n * readable. The microtask is for the other half: a watcher that throws must\n * not fail the write that woke it — it surfaces as the caller's own unhandled\n * error rather than as a refused append — and one that writes back through\n * this store must not do so from inside the stack of the write it is\n * answering.\n */\n const watchers = new Set<(change: StoreChange) => void>();\n const woke = (changes: readonly StoreChange[]): void => {\n if (!watchers.size || !changes.length) return;\n for (const watcher of [...watchers]) {\n // One microtask each. A watcher that throws must not fail the write that\n // woke it, and must not suppress the watchers after it in the loop — in\n // its own task the throw surfaces as the caller's unhandled error and\n // costs nobody else their wake. Membership is re-checked at delivery, so\n // a watcher that let go between the write and this task is not called:\n // `off()` says stop, and a queued wake is still a wake.\n queueMicrotask(() => {\n if (!watchers.has(watcher)) return;\n for (const change of changes) watcher(change);\n });\n }\n };\n\n return {\n /** Idempotent: a session that is already here keeps what it was created with. */\n async create({ sessionId, agent, key, metadata }) {\n try {\n const existing = one<{ id: string }>(`SELECT id FROM sessions WHERE id = ?`, [sessionId]);\n if (existing) return ok(undefined);\n run(\n `INSERT OR IGNORE INTO sessions (id, agent_id, agent_ver, key, metadata, updated_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [sessionId, agent.id, agent.version, key ?? null, JSON.stringify(metadata ?? {}), new Date().toISOString()],\n );\n // Only a session that really appeared is a change, and a new one has\n // nothing waiting on it — announcing `runnable: false` for a session\n // that already had input would have been wrong rather than spurious.\n woke([{ sessionId, runnable: false }]);\n return ok(undefined);\n } catch (error) { return err(failed(error)); }\n },\n\n async read({ sessionId, afterSeq = 0 }) {\n try {\n const session = one<SessionRow>(`SELECT * FROM sessions WHERE id = ?`, [sessionId]);\n if (!session) return err(notFound(sessionId));\n const rows = database.query(\n `SELECT seq, at, body FROM entries WHERE session_id = ? AND seq > ? ORDER BY seq`,\n ).all(sessionId, afterSeq) as { seq: number; at: string; body: string }[];\n return ok<SessionRead>({\n sessionId,\n agent: { id: session.agent_id, version: session.agent_ver },\n seq: session.last_seq,\n metadata: JSON.parse(session.metadata) as JsonValue,\n entries: rows.map((row): Stored => ({ ...(JSON.parse(row.body) as Entry), seq: row.seq, at: row.at })),\n pending: queueOf(session),\n });\n } catch (error) { return err(failed(error)); }\n },\n\n /**\n * Entries, deliveries, metadata and the queue in one transaction.\n *\n * The compare-and-swap on `last_seq` decides everything else: a caller\n * working from a stale position loses and is told the real one, and because\n * the queue is consumed here rather than in `next`, a loser leaves the\n * messages it was carrying untouched.\n */\n async append({ sessionId, expectedSeq, entries, enqueue, metadata, takePending }) {\n const at = new Date().toISOString();\n try {\n // Inside the try: on a busy database this is where the failure lands,\n // and a throw here escaped the `Result` the port promises.\n database.exec(\"BEGIN IMMEDIATE\");\n const session = one<SessionRow>(`SELECT * FROM sessions WHERE id = ?`, [sessionId]);\n if (!session) { database.exec(\"ROLLBACK\"); return err(notFound(sessionId)); }\n if (session.last_seq !== expectedSeq) {\n database.exec(\"ROLLBACK\");\n return err<StoreConflict>({\n code: \"conflict\", message: `Session moved to ${session.last_seq}`,\n retryable: true, actualSeq: session.last_seq,\n });\n }\n entries.forEach((entry, offset) => {\n run(`INSERT INTO entries (session_id, seq, at, body) VALUES (?, ?, ?, ?)`,\n [sessionId, expectedSeq + offset + 1, at, JSON.stringify(entry)]);\n });\n\n // An activation is open from the entry that starts it to the entry that\n // ends it. `next` reads this to keep a second one from beginning, and\n // the log is the only thing that knows — so it is derived from the log\n // rather than asserted by a caller.\n const opens = entries.some((entry) => entry.type === \"run.started\");\n const closes = entries.some((entry) => entry.type === \"run.finished\");\n // Only a write that records work renews the claim. Renewing on any\n // append at all meant a message *to* a session renewed the claim of the\n // worker that was supposed to read it — so a dead worker's session\n // stayed claimed for as long as anyone kept talking to it, invisible to\n // `next` (the claim never lapsed) and to `interrupted` (the queue was not\n // empty). An activation renews by doing its work, and being spoken to\n // is not its work.\n const worked = entries.length > 0;\n // Every write by an open activation renews its claim. Without this the\n // timestamp stayed at whenever the run began, so any turn outstanding\n // longer than the claim window was handed to a second worker while it\n // was still going — and a compare-and-swap cannot un-run a tool call.\n // A worker that dies still releases, because a dead worker stops writing.\n const runningSince = closes ? null : (opens || (worked && session.running_since)) ? at : session.running_since;\n\n const merged = metadata\n ? JSON.stringify({ ...(JSON.parse(session.metadata) as object), ...(metadata as object) })\n : session.metadata;\n const waiting = queueOf(session);\n if (takePending !== undefined && takePending > waiting.length) {\n // The one loss this design exists to prevent, reachable from a\n // caller's off-by-one: silently dropping messages nothing recorded.\n // `takePending` is only ever `claim.pending.length`, so a larger\n // number means the caller is working from a queue that is not there.\n database.exec(\"ROLLBACK\");\n return err<StoreError>({\n code: \"conflict\",\n message: `Asked to take ${takePending} deliveries from a queue of ${waiting.length}`,\n retryable: false,\n });\n }\n const queue = takePending ? waiting.slice(takePending) : waiting;\n /** Which sessions this write put input on, this one included. */\n const delivered = new Set<string>();\n run(\n `UPDATE sessions SET last_seq = ?, metadata = ?, pending = ?, running_since = ?, updated_at = ?\n WHERE id = ?`,\n [expectedSeq + entries.length, merged, JSON.stringify(queue), runningSince, at, sessionId],\n );\n\n // Swept before the ids are checked, not after: an id past its window is\n // forgotten, and one still inside it is not. Sweeping afterwards left\n // every expired id alive for exactly one more send.\n run(`DELETE FROM deliveries WHERE at < ?`,\n [new Date(Date.parse(at) - deliveryMemoryMs).toISOString()]);\n\n for (const delivery of enqueue ?? []) {\n const target = one<{ pending: string }>(`SELECT pending FROM sessions WHERE id = ?`, [delivery.sessionId]);\n if (!target) { database.exec(\"ROLLBACK\"); return err(notFound(delivery.sessionId)); }\n\n // Two deliveries with the same id from the same sender are the same\n // delivery, and stay so after the first has been read — the whole\n // point of an idempotency key is that it survives the queue. The\n // sender is part of it because the id is the sender's own: without\n // that, two agents numbering their messages from one silently\n // suppress each other.\n // The column is one string, so the two fields are joined into one\n // here. The encoding is this adapter's own and never leaves it: no\n // caller reads the column, and a store that keys the same fact some\n // other way is answering the same question, which is the one the\n // conformance suite asks.\n if (delivery.id) {\n const sender = delivery.from ? `${delivery.from.kind}:${delivery.from.id}` : \"\";\n const seen = one<{ delivery_id: string }>(\n `SELECT delivery_id FROM deliveries WHERE session_id = ? AND sender = ? AND delivery_id = ?`,\n [delivery.sessionId, sender, delivery.id]);\n if (seen) continue;\n run(`INSERT INTO deliveries (session_id, sender, delivery_id, at) VALUES (?, ?, ?, ?)`,\n [delivery.sessionId, sender, delivery.id, at]);\n }\n\n run(`UPDATE sessions SET pending = ?, updated_at = ? WHERE id = ?`,\n [JSON.stringify([...queueOf(target), delivery]), at, delivery.sessionId]);\n delivered.add(delivery.sessionId);\n }\n database.exec(\"COMMIT\");\n // After the commit, never before: a watcher that looked on an earlier\n // wake would find nothing and never be told again.\n woke([\n { sessionId, runnable: delivered.has(sessionId) || queue.length > 0 },\n ...[...delivered].filter((id) => id !== sessionId).map((id) => ({ sessionId: id, runnable: true })),\n ]);\n return ok({ seq: expectedSeq + entries.length });\n } catch (error) {\n database.exec(\"ROLLBACK\");\n return err(failed(error));\n }\n },\n\n async list({ key, limit = 50, before }) {\n try {\n // The cursor carries the id as well as the timestamp, because sessions\n // written in the same tick have no order without it — and a plain\n // `< before` then steps over every row that shares the boundary instant.\n const [beforeAt, beforeId] = before ? splitCursor(before) : [null, null];\n const rows = database.query(\n `SELECT * FROM sessions\n WHERE (? IS NULL OR key = ?)\n AND (? IS NULL OR updated_at < ? OR (updated_at = ? AND id < ?))\n ORDER BY updated_at DESC, id DESC LIMIT ?`,\n ).all(key ?? null, key ?? null, beforeAt, beforeAt, beforeAt, beforeId, limit) as SessionRow[];\n return ok(rows.map((row): SessionSummary => ({\n sessionId: row.id,\n agent: { id: row.agent_id, version: row.agent_ver },\n key: row.key,\n seq: row.last_seq,\n metadata: JSON.parse(row.metadata) as JsonValue,\n updatedAt: row.updated_at,\n })));\n } catch (error) { return err(failed(error)); }\n },\n\n /**\n * Claim a session that has input and no activation already running.\n *\n * The queue is reported and left in place; the append that commits it as\n * entries is what removes it. Nothing here is destructive, so a worker that\n * takes this and then dies costs one expired claim and no messages.\n */\n async next({ signal }) {\n // Nothing is owed to a caller that has given up. Answered rather than\n // refused: \"nothing to claim\" is true, and it needs no code of its own.\n if (signal?.aborted) return ok(undefined);\n const now = Date.now();\n const stale = new Date(now - claimMs).toISOString();\n try {\n database.exec(\"BEGIN IMMEDIATE\");\n const row = one<SessionRow>(\n `SELECT * FROM sessions\n WHERE pending <> '[]' AND (running_since IS NULL OR running_since < ?)\n ORDER BY updated_at LIMIT 1`, [stale]);\n if (!row) { database.exec(\"COMMIT\"); return ok(undefined); }\n run(`UPDATE sessions SET running_since = ? WHERE id = ?`, [new Date(now).toISOString(), row.id]);\n database.exec(\"COMMIT\");\n return ok<Runnable>({\n sessionId: row.id,\n seq: row.last_seq,\n pending: queueOf(row),\n metadata: JSON.parse(row.metadata) as JsonValue,\n });\n } catch (error) {\n database.exec(\"ROLLBACK\");\n return err(failed(error));\n }\n },\n\n /**\n * Claim a session whose activation was interrupted.\n *\n * The mirror of `next`, over the two columns that already exist: an open\n * run — `running_since` set, because the entry that would clear it never\n * committed — whose claim has lapsed, and an empty queue, because the\n * commit that opened the run took what made it runnable. Nothing else in\n * the store can see this session, which is why the port asks for it here\n * rather than leaving every application to write this query itself.\n */\n async interrupted({ signal }) {\n if (signal?.aborted) return ok(undefined);\n const now = Date.now();\n const stale = new Date(now - claimMs).toISOString();\n try {\n database.exec(\"BEGIN IMMEDIATE\");\n const row = one<SessionRow>(\n `SELECT * FROM sessions\n WHERE pending = '[]' AND running_since IS NOT NULL AND running_since < ?\n ORDER BY updated_at LIMIT 1`, [stale]);\n if (!row) { database.exec(\"COMMIT\"); return ok(undefined); }\n run(`UPDATE sessions SET running_since = ? WHERE id = ?`, [new Date(now).toISOString(), row.id]);\n database.exec(\"COMMIT\");\n return ok<Runnable>({\n sessionId: row.id,\n seq: row.last_seq,\n pending: [],\n metadata: JSON.parse(row.metadata) as JsonValue,\n });\n } catch (error) {\n database.exec(\"ROLLBACK\");\n return err(failed(error));\n }\n },\n\n /**\n * The feed is this store's own writes, and reaches exactly as far as they\n * do: a watcher here is woken by what this instance commits, and by nothing\n * else. Another process on the same file, a second store over the same\n * handle, and the application's own SQL beside the log are all invisible,\n * because sqlite has no channel to tell us — that is the miss the port\n * requires a heartbeat for. The case that matters is covered: the surface\n * that writes a delivery and the worker that reads it are the same process,\n * and usually the same store.\n */\n watch(watcher) {\n watchers.add(watcher);\n return () => { watchers.delete(watcher); };\n },\n\n async close() { watchers.clear(); /* the caller owns the handle it passed in */ },\n };\n}\n"]}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * What any implementation of the store port must do.
3
+ *
4
+ * The port is seven methods and two mechanisms, and the mechanisms are the part
5
+ * that goes wrong. `expectedSeq` is correctness and the claim is exclusion; one
6
+ * does not imply the other; and the failure from confusing them is not a
7
+ * rejected write but a log a provider will not accept, discovered in
8
+ * production. Every store written from the interface alone re-derives those
9
+ * rules, and a store that gets them subtly wrong looks exactly like one that
10
+ * does not until two workers meet.
11
+ *
12
+ * So the contract is executable. These cases say what the interface means —
13
+ * that a claim does not consume, that a queue survives a lost race, that an
14
+ * open activation renews by writing rather than by having started, that a
15
+ * delivery id outlives the queue — and an adapter runs them rather than reading
16
+ * them.
17
+ *
18
+ * Inert on purpose. Each case is a name and a function that throws, so the
19
+ * suite drags no test framework into the package and an adapter runs it under
20
+ * whichever one it already has:
21
+ *
22
+ * ```ts
23
+ * for (const item of defineStoreConformance({ open })) test(item.name, item.run);
24
+ * ```
25
+ */
26
+ import type { Store } from "./store.js";
27
+ /** One case: a name, and a function that throws when the contract is broken. */
28
+ export interface ConformanceCase {
29
+ name: string;
30
+ run(): Promise<void>;
31
+ }
32
+ export interface StoreUnderTest {
33
+ /**
34
+ * A store with no sessions in it, whose claim lapses after `claimMs`.
35
+ *
36
+ * Called once per case, so nothing leaks between them. The window is the
37
+ * suite's because two cases turn on a claim expiring, and waiting out a
38
+ * production default would take ten minutes.
39
+ */
40
+ open(input: {
41
+ claimMs: number;
42
+ }): Promise<Store> | Store;
43
+ /** Release whatever `open` provisioned. `Store.close` is called first. */
44
+ release?(store: Store): Promise<void> | void;
45
+ /**
46
+ * How far this store's change feed reaches.
47
+ *
48
+ * Declared rather than inferred from the method being there, so a store that
49
+ * has a push channel and never wired it up fails here instead of quietly
50
+ * costing every worker built on it a poll — and so that "reach is the store's
51
+ * own to state" has somewhere to be stated and held to.
52
+ *
53
+ * `"process"` wakes watchers on this instance: the HTTP surface writes and
54
+ * the worker beside it hears, which is most deployments. `"deployment"` wakes
55
+ * a watcher on another connection too, and owes a `peer` to prove it.
56
+ */
57
+ changes: "none" | "process" | "deployment";
58
+ /**
59
+ * A second store on the same data, for a feed claiming `"deployment"`. Not
60
+ * `open`, which is entitled to start from nothing.
61
+ */
62
+ peer?(input: {
63
+ claimMs: number;
64
+ }): Promise<Store> | Store;
65
+ }
66
+ export declare function defineStoreConformance(subject: StoreUnderTest): readonly ConformanceCase[];