@creatornader/agent-bridge 0.2.0 → 0.3.1

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 (115) hide show
  1. package/CHANGELOG.md +86 -4
  2. package/README.md +567 -55
  3. package/ROADMAP.md +114 -0
  4. package/SECURITY.md +90 -0
  5. package/SKILL.md +68 -2
  6. package/bin/agent-bridge +2 -2
  7. package/clients/claude-desktop.json +3 -1
  8. package/dist/archive.d.ts +163 -0
  9. package/dist/archive.js +50 -0
  10. package/dist/archive.js.map +1 -0
  11. package/dist/bridge-domain-CBX3IVqC.d.ts +153 -0
  12. package/dist/{bridge-service-CfqMcMAn.d.ts → bridge-service-hX1ZQp4W.d.ts} +15 -5
  13. package/dist/bridge-store-DPVSCwfs.d.ts +102 -0
  14. package/dist/bridge.d.ts +76 -17
  15. package/dist/bridge.js +52 -24
  16. package/dist/chunk-2DFNOLMO.js +618 -0
  17. package/dist/chunk-2DFNOLMO.js.map +1 -0
  18. package/dist/{chunk-KVR4JBRF.js → chunk-2EVLG3YA.js} +2 -2
  19. package/dist/{chunk-KVR4JBRF.js.map → chunk-2EVLG3YA.js.map} +1 -1
  20. package/dist/chunk-45BZGB77.js +709 -0
  21. package/dist/chunk-45BZGB77.js.map +1 -0
  22. package/dist/chunk-4VB44KJU.js +230 -0
  23. package/dist/chunk-4VB44KJU.js.map +1 -0
  24. package/dist/chunk-5GMB3A77.js +1013 -0
  25. package/dist/chunk-5GMB3A77.js.map +1 -0
  26. package/dist/chunk-A6W4QALF.js +1258 -0
  27. package/dist/chunk-A6W4QALF.js.map +1 -0
  28. package/dist/chunk-BZLMF5O5.js +328 -0
  29. package/dist/chunk-BZLMF5O5.js.map +1 -0
  30. package/dist/{chunk-AXL2I7FH.js → chunk-CEXBMOI5.js} +240 -79
  31. package/dist/chunk-CEXBMOI5.js.map +1 -0
  32. package/dist/chunk-GADH244D.js +527 -0
  33. package/dist/chunk-GADH244D.js.map +1 -0
  34. package/dist/chunk-MBPZXTJA.js +2428 -0
  35. package/dist/chunk-MBPZXTJA.js.map +1 -0
  36. package/dist/chunk-NCO7RLBM.js +146 -0
  37. package/dist/chunk-NCO7RLBM.js.map +1 -0
  38. package/dist/chunk-Q6PPMXJC.js +1050 -0
  39. package/dist/chunk-Q6PPMXJC.js.map +1 -0
  40. package/dist/{chunk-4KAOBC5X.js → chunk-SPSMZOCJ.js} +190 -165
  41. package/dist/chunk-SPSMZOCJ.js.map +1 -0
  42. package/dist/chunk-UAHMP6GC.js +54 -0
  43. package/dist/chunk-UAHMP6GC.js.map +1 -0
  44. package/dist/{chunk-YD6YJATN.js → chunk-VOQIDTCJ.js} +4 -2
  45. package/dist/chunk-VOQIDTCJ.js.map +1 -0
  46. package/dist/chunk-WAI2IYIQ.js +554 -0
  47. package/dist/chunk-WAI2IYIQ.js.map +1 -0
  48. package/dist/cli.d.ts +2 -1
  49. package/dist/cli.js +3555 -214
  50. package/dist/cli.js.map +1 -1
  51. package/dist/client-config.d.ts +1 -1
  52. package/dist/client-config.js +1 -1
  53. package/dist/client-runtime.d.ts +10 -6
  54. package/dist/client-runtime.js +9 -6
  55. package/dist/contracts/generate.d.ts +2 -0
  56. package/dist/contracts/generate.js +247 -0
  57. package/dist/contracts/generate.js.map +1 -0
  58. package/dist/dr.d.ts +324 -0
  59. package/dist/dr.js +124 -0
  60. package/dist/dr.js.map +1 -0
  61. package/dist/gateway-CggQ8vTC.d.ts +78 -0
  62. package/dist/gateway-main.js +190 -8
  63. package/dist/gateway-main.js.map +1 -1
  64. package/dist/gateway.d.ts +4 -3
  65. package/dist/gateway.js +4 -3
  66. package/dist/index.js +11 -8
  67. package/dist/index.js.map +1 -1
  68. package/dist/postgres-bridge-store-Bfd3hRId.d.ts +49 -0
  69. package/dist/server.d.ts +3 -2
  70. package/dist/server.js +13 -8
  71. package/dist/sqlite.d.ts +94 -12
  72. package/dist/sqlite.js +7 -4
  73. package/docs/architecture-v2.md +394 -0
  74. package/docs/decisions/0001-protocol-layers-and-acknowledgment-semantics.md +85 -0
  75. package/docs/decisions/0002-canonical-operation-contract-registry.md +46 -0
  76. package/docs/decisions/0003-host-adapters-and-consumer-instance-keys.md +88 -0
  77. package/docs/ecosystem.md +86 -0
  78. package/docs/postmortems/2026-07-08-wrapper-source-drift.md +82 -0
  79. package/docs/troubleshooting.md +89 -0
  80. package/llms.txt +107 -6
  81. package/openapi/agent-bridge-v2.openapi.json +9610 -0
  82. package/package.json +24 -4
  83. package/schemas/agent-bridge-v2.capabilities.json +1829 -0
  84. package/schemas/agent-bridge-v2.mcp.json +2074 -0
  85. package/schemas/agent-bridge-v2.schema.json +4560 -0
  86. package/sql/migrations/008_message_projects.sql +36 -0
  87. package/sql/migrations/009_mailbox_query_indexes.sql +28 -0
  88. package/sql/migrations/010_publisher_delivery_policy.sql +210 -0
  89. package/sql/migrations/011_credential_security.sql +875 -0
  90. package/sql/migrations/012_request_authority.sql +177 -0
  91. package/sql/migrations/013_row_isolation.sql +495 -0
  92. package/sql/migrations/014_owner_control_plane.sql +1641 -0
  93. package/sql/migrations/015_portable_archives.sql +1813 -0
  94. package/sql/migrations/016_native_dr_fence.sql +1005 -0
  95. package/bin/CLAUDE.md +0 -11
  96. package/dist/bridge-domain-BcnBeHOn.d.ts +0 -97
  97. package/dist/bridge-store-BuGjxTn4.d.ts +0 -61
  98. package/dist/chunk-4KAOBC5X.js.map +0 -1
  99. package/dist/chunk-6MAZ6YLZ.js +0 -146
  100. package/dist/chunk-6MAZ6YLZ.js.map +0 -1
  101. package/dist/chunk-AXL2I7FH.js.map +0 -1
  102. package/dist/chunk-BV6W4AJK.js +0 -211
  103. package/dist/chunk-BV6W4AJK.js.map +0 -1
  104. package/dist/chunk-CKSKUBDQ.js +0 -153
  105. package/dist/chunk-CKSKUBDQ.js.map +0 -1
  106. package/dist/chunk-JOUBDRYX.js +0 -272
  107. package/dist/chunk-JOUBDRYX.js.map +0 -1
  108. package/dist/chunk-JTC5YURG.js +0 -49
  109. package/dist/chunk-JTC5YURG.js.map +0 -1
  110. package/dist/chunk-O26W5QHS.js +0 -409
  111. package/dist/chunk-O26W5QHS.js.map +0 -1
  112. package/dist/chunk-WEULPMUY.js +0 -988
  113. package/dist/chunk-WEULPMUY.js.map +0 -1
  114. package/dist/chunk-YD6YJATN.js.map +0 -1
  115. package/dist/gateway-BULozv3K.d.ts +0 -54
@@ -0,0 +1,618 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ assertIdempotentReplay
4
+ } from "./chunk-VOQIDTCJ.js";
5
+ import {
6
+ DeliveryStateConflictError,
7
+ cursorScope,
8
+ decodeCursor,
9
+ decodeScopedCursor,
10
+ encodeCursor,
11
+ encodeScopedCursor,
12
+ scopedCursorScope,
13
+ validateDeliveryCursorPosition,
14
+ validateEventCursorPosition
15
+ } from "./chunk-BZLMF5O5.js";
16
+
17
+ // src/postgres-bridge-store.ts
18
+ import { randomUUID } from "crypto";
19
+ function json(value) {
20
+ return value === void 0 ? null : JSON.stringify(value);
21
+ }
22
+ function parse(value) {
23
+ return typeof value === "string" ? JSON.parse(value) : value;
24
+ }
25
+ function asTimestamp(value) {
26
+ if (value === void 0 || value === null) return void 0;
27
+ if (value instanceof Date) return value.toISOString();
28
+ return String(value);
29
+ }
30
+ function asMessage(row) {
31
+ const deliveryPolicy = row.delivery_mode === "mailbox" ? { mode: "mailbox" } : {
32
+ mode: "leased",
33
+ maxAttempts: Number(row.delivery_max_attempts),
34
+ retryBaseDelayMs: Number(row.delivery_retry_base_delay_ms),
35
+ retryMaxDelayMs: Number(row.delivery_retry_max_delay_ms),
36
+ retryJitterRatio: Number(row.delivery_retry_jitter_ratio),
37
+ ...row.delivery_not_before ? { notBefore: asTimestamp(row.delivery_not_before) } : {}
38
+ };
39
+ return {
40
+ id: String(row.id),
41
+ workspace: String(row.workspace),
42
+ project: row.project ?? void 0,
43
+ source: String(row.source),
44
+ sequence: String(row.sequence),
45
+ type: String(row.type),
46
+ content: String(row.content),
47
+ contentType: String(row.content_type),
48
+ data: parse(row.data),
49
+ targets: parse(row.targets) ?? [],
50
+ threadId: row.thread_id ?? void 0,
51
+ replyToId: row.reply_to_id ?? void 0,
52
+ correlationId: row.correlation_id ?? void 0,
53
+ causationId: row.causation_id ?? void 0,
54
+ priority: row.priority,
55
+ expiresAt: asTimestamp(row.expires_at),
56
+ idempotencyKey: row.idempotency_key ?? void 0,
57
+ atribReceiptId: row.atrib_receipt_id ?? void 0,
58
+ informedBy: parse(row.informed_by),
59
+ metadata: parse(row.metadata),
60
+ deliveryPolicy,
61
+ createdAt: asTimestamp(row.created_at) ?? ""
62
+ };
63
+ }
64
+ function asDelivery(row) {
65
+ return {
66
+ id: String(row.id),
67
+ messageId: String(row.message_id),
68
+ workspace: String(row.workspace),
69
+ recipient: String(row.recipient),
70
+ state: row.state,
71
+ attempt: Number(row.attempt),
72
+ cycleAttempt: Number(row.cycle_attempt ?? row.attempt),
73
+ requeueCount: Number(row.requeue_count ?? 0),
74
+ createdAt: asTimestamp(row.created_at) ?? "",
75
+ priorityRank: Number(row.priority_rank),
76
+ availableAt: asTimestamp(row.available_at) ?? "",
77
+ leaseToken: row.lease_token ?? void 0,
78
+ leaseOwner: row.lease_owner ?? void 0,
79
+ leaseExpiresAt: asTimestamp(row.lease_expires_at),
80
+ lastError: row.last_error ?? void 0,
81
+ lastActor: row.last_actor ?? void 0,
82
+ lastAction: row.last_action
83
+ };
84
+ }
85
+ function asPresence(row) {
86
+ return {
87
+ workspace: String(row.workspace),
88
+ agent: String(row.agent),
89
+ instance: String(row.instance),
90
+ runtimeType: row.runtime_type ?? void 0,
91
+ capabilities: parse(row.capabilities) ?? [],
92
+ leaseExpiresAt: asTimestamp(row.lease_expires_at) ?? "",
93
+ lastSeenAt: asTimestamp(row.last_seen_at) ?? ""
94
+ };
95
+ }
96
+ function asDeliveryEvent(row) {
97
+ return {
98
+ sequence: String(row.sequence),
99
+ deliveryId: String(row.delivery_id),
100
+ messageId: String(row.message_id),
101
+ workspace: String(row.workspace),
102
+ recipient: String(row.recipient),
103
+ fromState: row.from_state == null ? void 0 : row.from_state,
104
+ toState: row.to_state,
105
+ attempt: Number(row.attempt),
106
+ cycleAttempt: Number(row.cycle_attempt),
107
+ requeueCount: Number(row.requeue_count),
108
+ leaseOwner: row.lease_owner ?? void 0,
109
+ error: row.error ?? void 0,
110
+ actor: String(row.actor),
111
+ action: row.action,
112
+ createdAt: asTimestamp(row.created_at) ?? ""
113
+ };
114
+ }
115
+ async function transaction(db, work) {
116
+ if (db.inTransaction) return work(db);
117
+ const borrowed = !db.release && Boolean(db.connect);
118
+ const client = borrowed ? await db.connect() : db;
119
+ try {
120
+ await client.query("BEGIN");
121
+ const result = await work(client);
122
+ await client.query("COMMIT");
123
+ return result;
124
+ } catch (error) {
125
+ await client.query("ROLLBACK").catch(() => void 0);
126
+ throw error;
127
+ } finally {
128
+ if (borrowed && "release" in client && typeof client.release === "function") client.release();
129
+ }
130
+ }
131
+ var PostgresBridgeStore = class {
132
+ constructor(db) {
133
+ this.db = db;
134
+ }
135
+ async initialize() {
136
+ }
137
+ async insertMessage(input) {
138
+ const values = [
139
+ input.id,
140
+ input.workspace,
141
+ input.project ?? null,
142
+ input.source,
143
+ input.type,
144
+ input.content,
145
+ input.contentType,
146
+ json(input.data),
147
+ json(input.targets),
148
+ input.threadId ?? null,
149
+ input.replyToId ?? null,
150
+ input.correlationId ?? null,
151
+ input.causationId ?? null,
152
+ input.priority,
153
+ input.expiresAt ?? null,
154
+ input.idempotencyKey ?? null,
155
+ input.atribReceiptId ?? null,
156
+ json(input.informedBy),
157
+ json(input.metadata),
158
+ input.deliveryPolicy.mode,
159
+ input.deliveryPolicy.mode === "leased" ? input.deliveryPolicy.maxAttempts : null,
160
+ input.deliveryPolicy.mode === "leased" ? input.deliveryPolicy.retryBaseDelayMs : null,
161
+ input.deliveryPolicy.mode === "leased" ? input.deliveryPolicy.retryMaxDelayMs : null,
162
+ input.deliveryPolicy.mode === "leased" ? input.deliveryPolicy.retryJitterRatio : null,
163
+ input.deliveryPolicy.mode === "leased" ? input.deliveryPolicy.notBefore ?? null : null
164
+ ];
165
+ const result = await this.db.query(
166
+ `WITH inserted AS (
167
+ INSERT INTO agent_bridge.messages (
168
+ id, workspace, project, source, type, content, content_type, data, targets,
169
+ thread_id, reply_to_id, correlation_id, causation_id, priority,
170
+ expires_at, idempotency_key, atrib_receipt_id, informed_by, metadata,
171
+ delivery_mode, delivery_max_attempts, delivery_retry_base_delay_ms,
172
+ delivery_retry_max_delay_ms, delivery_retry_jitter_ratio, delivery_not_before
173
+ )
174
+ VALUES (
175
+ $1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9::jsonb,
176
+ $10, $11, $12, $13, $14, $15, $16, $17, $18::jsonb, $19::jsonb,
177
+ $20, $21, $22, $23, $24, $25
178
+ )
179
+ ON CONFLICT (workspace, source, idempotency_key)
180
+ WHERE idempotency_key IS NOT NULL
181
+ DO NOTHING
182
+ RETURNING *, true AS created
183
+ ), selected AS (
184
+ SELECT * FROM inserted
185
+ UNION ALL
186
+ SELECT existing.*, false AS created
187
+ FROM agent_bridge.messages existing
188
+ WHERE $16::text IS NOT NULL
189
+ AND existing.workspace = $2
190
+ AND existing.source = $4
191
+ AND existing.idempotency_key = $16
192
+ AND NOT EXISTS (SELECT 1 FROM inserted)
193
+ LIMIT 1
194
+ ), delivery_rows AS (
195
+ INSERT INTO agent_bridge.deliveries (
196
+ id, message_id, workspace, publisher, recipient, state, available_at,
197
+ created_at, priority_rank, last_actor, last_action
198
+ )
199
+ SELECT gen_random_uuid(), inserted.id, inserted.workspace, inserted.source, target, 'pending',
200
+ greatest(now(), coalesce(inserted.delivery_not_before, now())),
201
+ inserted.created_at,
202
+ case inserted.priority when 'urgent' then 0 when 'high' then 1 else 2 end,
203
+ inserted.source, 'created'
204
+ FROM inserted
205
+ CROSS JOIN LATERAL jsonb_array_elements_text(inserted.targets) AS target
206
+ WHERE inserted.delivery_mode = 'leased'
207
+ ON CONFLICT (message_id, recipient) DO NOTHING
208
+ )
209
+ SELECT * FROM selected`,
210
+ values
211
+ );
212
+ let row = result.rows[0];
213
+ if (!row && input.idempotencyKey) {
214
+ const existing = await this.db.query(
215
+ `SELECT *, false AS created
216
+ FROM agent_bridge.messages
217
+ WHERE workspace=$1 AND source=$2 AND idempotency_key=$3`,
218
+ [input.workspace, input.source, input.idempotencyKey]
219
+ );
220
+ row = existing.rows[0];
221
+ }
222
+ if (!row) throw new Error("message insert did not return a row");
223
+ const message = asMessage(row);
224
+ if (row.created !== true) assertIdempotentReplay(message, input);
225
+ return { message, created: row.created === true };
226
+ }
227
+ async listMessages(principal, query = {}) {
228
+ const scope = cursorScope(principal, query);
229
+ const cursor = decodeCursor(query.cursor, scope) ?? "0";
230
+ const limit = Math.min(Math.max(Math.trunc(query.limit ?? 50), 1), 200);
231
+ const boundary = query.latest ? void 0 : await this.db.query(
232
+ "SELECT max(sequence)::text AS sequence FROM agent_bridge.messages WHERE workspace=$1",
233
+ [principal.workspace]
234
+ );
235
+ const highWater = boundary?.rows[0]?.sequence;
236
+ const values = query.latest ? [principal.workspace, principal.agent] : [principal.workspace, cursor, principal.agent];
237
+ const mailbox = query.mailbox ?? "inbox";
238
+ const agentParam = query.latest ? 2 : 3;
239
+ const visibility = mailbox === "sent" ? `source=$${agentParam}` : mailbox === "all" ? `(source=$${agentParam} OR targets='[]'::jsonb OR targets ? $${agentParam})` : `(targets='[]'::jsonb OR targets ? $${agentParam})`;
240
+ let sql = `SELECT * FROM agent_bridge.messages WHERE workspace=$1 ${query.latest ? "" : "AND sequence>$2::bigint "}AND ${visibility}`;
241
+ if (highWater) {
242
+ values.push(highWater);
243
+ sql += ` AND sequence <= $${values.length}::bigint`;
244
+ }
245
+ if (!query.includeExpired) {
246
+ sql += " AND (expires_at IS NULL OR expires_at > now())";
247
+ }
248
+ if (query.types?.length) {
249
+ values.push(query.types);
250
+ sql += ` AND type = ANY($${values.length})`;
251
+ }
252
+ if (query.source) {
253
+ values.push(query.source);
254
+ sql += ` AND source = $${values.length}`;
255
+ }
256
+ if (query.project) {
257
+ values.push(query.project);
258
+ sql += ` AND project = $${values.length}`;
259
+ }
260
+ if (query.since) {
261
+ values.push(query.since);
262
+ sql += ` AND created_at >= $${values.length}::timestamptz`;
263
+ }
264
+ if (query.receiptState && query.receiptState !== "any") {
265
+ sql += ` AND ${query.receiptState === "unread" ? "NOT " : ""}EXISTS (
266
+ SELECT 1 FROM agent_bridge.receipts receipt
267
+ WHERE receipt.workspace=agent_bridge.messages.workspace
268
+ AND receipt.message_id=agent_bridge.messages.id
269
+ AND receipt.principal=$${agentParam}
270
+ )`;
271
+ }
272
+ if (query.threadId) {
273
+ values.push(query.threadId);
274
+ sql += ` AND thread_id = $${values.length}`;
275
+ }
276
+ values.push(limit);
277
+ const result = await this.db.query(
278
+ `${sql} ORDER BY sequence ${query.latest ? "DESC" : "ASC"} LIMIT $${values.length}`,
279
+ values
280
+ );
281
+ const messages = result.rows.map(asMessage);
282
+ const last = messages[messages.length - 1];
283
+ if (query.latest) return { messages, cursor: messages[0] ? encodeCursor(messages[0].sequence, scope) : query.cursor };
284
+ if (messages.length === limit) return { messages, cursor: encodeCursor(last.sequence, scope) };
285
+ return { messages, cursor: highWater ? encodeCursor(highWater, scope) : query.cursor };
286
+ }
287
+ async recordReceipt(principal, ids, readAt = /* @__PURE__ */ new Date()) {
288
+ const result = await this.db.query(
289
+ `INSERT INTO agent_bridge.receipts (workspace, message_id, principal, read_at)
290
+ SELECT message.workspace, message.id, $3, $4
291
+ FROM agent_bridge.messages message
292
+ WHERE message.workspace = $1 AND message.id = ANY($2::uuid[])
293
+ AND (message.targets='[]'::jsonb OR message.targets ? $3)
294
+ ON CONFLICT DO NOTHING`,
295
+ [principal.workspace, ids, principal.agent, readAt]
296
+ );
297
+ return result.rowCount ?? 0;
298
+ }
299
+ async claimDelivery(principal, options) {
300
+ return transaction(this.db, async (db) => {
301
+ await db.query(
302
+ `UPDATE agent_bridge.deliveries delivery
303
+ SET state='dead', last_error='message expired', lease_token=NULL,
304
+ lease_owner=NULL, lease_expires_at=NULL,
305
+ last_actor='agent-bridge', last_action='message_expired'
306
+ FROM agent_bridge.messages message
307
+ WHERE delivery.workspace=$1 AND delivery.recipient=$2
308
+ AND delivery.state IN ('pending','retrying','claimed')
309
+ AND message.workspace=delivery.workspace AND message.id=delivery.message_id
310
+ AND message.expires_at IS NOT NULL AND message.expires_at<=now()`,
311
+ [principal.workspace, principal.agent]
312
+ );
313
+ await db.query(
314
+ `UPDATE agent_bridge.deliveries delivery
315
+ SET state='dead', last_error='maximum attempts reached', lease_token=NULL,
316
+ lease_owner=NULL, lease_expires_at=NULL,
317
+ last_actor='agent-bridge', last_action='attempts_exhausted'
318
+ FROM agent_bridge.messages message
319
+ WHERE delivery.workspace=$1 AND delivery.recipient=$2
320
+ AND message.workspace=delivery.workspace AND message.id=delivery.message_id
321
+ AND delivery.cycle_attempt>=message.delivery_max_attempts
322
+ AND (delivery.state IN ('pending','retrying')
323
+ OR (delivery.state='claimed' AND delivery.lease_expires_at<=now()))`,
324
+ [principal.workspace, principal.agent]
325
+ );
326
+ await db.query(
327
+ `UPDATE agent_bridge.deliveries delivery
328
+ SET state='retrying', available_at=now(), lease_token=NULL,
329
+ lease_owner=NULL, lease_expires_at=NULL,
330
+ last_error='lease expired', last_actor='agent-bridge', last_action='lease_expired'
331
+ FROM agent_bridge.messages message
332
+ WHERE delivery.workspace=$1 AND delivery.recipient=$2
333
+ AND message.workspace=delivery.workspace AND message.id=delivery.message_id
334
+ AND delivery.state='claimed' AND delivery.lease_expires_at<=now()
335
+ AND delivery.cycle_attempt<message.delivery_max_attempts`,
336
+ [principal.workspace, principal.agent]
337
+ );
338
+ const candidate = await db.query(
339
+ `SELECT delivery.id
340
+ FROM agent_bridge.deliveries delivery
341
+ JOIN agent_bridge.messages message
342
+ ON message.workspace=delivery.workspace AND message.id=delivery.message_id
343
+ WHERE delivery.workspace=$1 AND delivery.recipient=$2
344
+ AND delivery.state IN ('pending','retrying')
345
+ AND delivery.available_at<=now()
346
+ AND delivery.cycle_attempt<message.delivery_max_attempts
347
+ AND (message.expires_at IS NULL OR message.expires_at>now())
348
+ ORDER BY delivery.priority_rank, delivery.available_at, delivery.created_at, delivery.id
349
+ FOR UPDATE OF delivery SKIP LOCKED
350
+ LIMIT 1`,
351
+ [principal.workspace, principal.agent]
352
+ );
353
+ if (!candidate.rows[0]) return null;
354
+ const token = randomUUID();
355
+ const owner = principal.instance ?? principal.agent;
356
+ const claimed = await db.query(
357
+ `UPDATE agent_bridge.deliveries
358
+ SET state='claimed', attempt=attempt+1, cycle_attempt=cycle_attempt+1,
359
+ lease_token=$1, lease_owner=$2,
360
+ lease_expires_at=now()+($3*interval '1 millisecond'),
361
+ last_error=NULL, last_actor=$4, last_action='claim'
362
+ WHERE id=$5
363
+ RETURNING *`,
364
+ [token, owner, options.leaseMs, principal.agent, candidate.rows[0].id]
365
+ );
366
+ return asDelivery(claimed.rows[0]);
367
+ });
368
+ }
369
+ async renewDelivery(principal, id, token, leaseMs) {
370
+ const result = await this.db.query(
371
+ `UPDATE agent_bridge.deliveries
372
+ SET lease_expires_at=now() + ($1 * interval '1 millisecond')
373
+ WHERE workspace=$2 AND recipient=$3 AND lease_owner=$4 AND id=$5 AND lease_token=$6
374
+ AND state='claimed' AND lease_expires_at>now()
375
+ RETURNING *`,
376
+ [leaseMs, principal.workspace, principal.agent, principal.instance ?? principal.agent, id, token]
377
+ );
378
+ return result.rows[0] ? asDelivery(result.rows[0]) : null;
379
+ }
380
+ async settleDelivery(principal, id, token, state, error, _retryPolicy) {
381
+ const result = await this.db.query(
382
+ `UPDATE agent_bridge.deliveries delivery
383
+ SET state=CASE
384
+ WHEN $1='retrying' AND cycle_attempt>=message.delivery_max_attempts THEN 'dead'
385
+ ELSE $1
386
+ END,
387
+ available_at=CASE
388
+ WHEN $1='retrying' AND cycle_attempt<message.delivery_max_attempts THEN
389
+ now() + (
390
+ LEAST(message.delivery_retry_max_delay_ms::double precision,
391
+ message.delivery_retry_base_delay_ms::double precision * power(2, GREATEST(cycle_attempt - 1, 0)))
392
+ * (1 + ((random() * 2) - 1) * message.delivery_retry_jitter_ratio)
393
+ * interval '1 millisecond'
394
+ )
395
+ ELSE available_at
396
+ END,
397
+ last_error=$2, lease_token=NULL, lease_owner=NULL, lease_expires_at=NULL,
398
+ last_actor=$4,
399
+ last_action=CASE
400
+ WHEN $1='acked' THEN 'ack'
401
+ WHEN $1='dead' THEN 'nack_dead'
402
+ WHEN cycle_attempt>=message.delivery_max_attempts THEN 'attempts_exhausted'
403
+ ELSE 'nack_retry'
404
+ END
405
+ FROM agent_bridge.messages message
406
+ WHERE delivery.workspace=$3 AND delivery.recipient=$4 AND delivery.lease_owner=$5
407
+ AND message.workspace=delivery.workspace AND message.id=delivery.message_id
408
+ AND delivery.id=$6 AND delivery.lease_token=$7 AND delivery.state='claimed' AND delivery.lease_expires_at>now()
409
+ RETURNING delivery.*`,
410
+ [
411
+ state,
412
+ error?.slice(0, 1024) ?? null,
413
+ principal.workspace,
414
+ principal.agent,
415
+ principal.instance ?? principal.agent,
416
+ id,
417
+ token
418
+ ]
419
+ );
420
+ return result.rows[0] ? asDelivery(result.rows[0]) : null;
421
+ }
422
+ async listDeliveries(principal, query = {}) {
423
+ const filters = {
424
+ role: query.role ?? "all",
425
+ states: [...query.states ?? []].sort(),
426
+ messageId: query.messageId ?? null,
427
+ recipient: query.recipient ?? null
428
+ };
429
+ const scope = scopedCursorScope("deliveries", principal, filters);
430
+ const boundary = validateDeliveryCursorPosition(decodeScopedCursor(query.cursor, scope));
431
+ const createdAt = boundary?.createdAt;
432
+ const id = boundary?.id;
433
+ const limit = query.limit ?? 50;
434
+ const values = [principal.workspace, principal.agent];
435
+ const clauses = ["delivery.workspace=$1"];
436
+ const role = query.role ?? "all";
437
+ clauses.push(role === "recipient" ? "delivery.recipient=$2" : role === "publisher" ? "message.source=$2" : "(delivery.recipient=$2 OR message.source=$2)");
438
+ if (createdAt && id) {
439
+ values.push(createdAt, id);
440
+ clauses.push(`(delivery.created_at,delivery.id)>($${values.length - 1}::timestamptz,$${values.length}::uuid)`);
441
+ }
442
+ if (query.messageId) {
443
+ values.push(query.messageId);
444
+ clauses.push(`delivery.message_id=$${values.length}`);
445
+ }
446
+ if (query.recipient) {
447
+ values.push(query.recipient);
448
+ clauses.push(`delivery.recipient=$${values.length}`);
449
+ }
450
+ if (query.states?.length) {
451
+ values.push(query.states);
452
+ clauses.push(`delivery.state=ANY($${values.length}::text[])`);
453
+ }
454
+ values.push(limit);
455
+ const result = await this.db.query(
456
+ `SELECT delivery.* FROM agent_bridge.deliveries delivery
457
+ JOIN agent_bridge.messages message ON message.workspace=delivery.workspace AND message.id=delivery.message_id
458
+ WHERE ${clauses.join(" AND ")}
459
+ ORDER BY delivery.created_at,delivery.id LIMIT $${values.length}`,
460
+ values
461
+ );
462
+ const deliveries = result.rows.map(asDelivery);
463
+ const last = deliveries[deliveries.length - 1];
464
+ return { deliveries, cursor: deliveries.length === limit && last ? encodeScopedCursor(scope, { createdAt: last.createdAt, id: last.id }) : void 0 };
465
+ }
466
+ async listDeliveryEvents(principal, id, query = {}) {
467
+ const limit = Math.min(Math.max(query.limit ?? 50, 1), 200);
468
+ const scope = scopedCursorScope("delivery-events", principal, { deliveryId: id });
469
+ const position = validateEventCursorPosition(decodeScopedCursor(query.cursor, scope));
470
+ const result = await this.db.query(
471
+ `SELECT event.* FROM agent_bridge.delivery_events event
472
+ JOIN agent_bridge.messages message ON message.workspace=event.workspace AND message.id=event.message_id
473
+ WHERE event.workspace=$1 AND event.delivery_id=$2 AND event.sequence>$3::bigint
474
+ AND (event.recipient=$4 OR message.source=$4)
475
+ ORDER BY event.sequence LIMIT $5`,
476
+ [principal.workspace, id, position ?? "0", principal.agent, limit]
477
+ );
478
+ const events = result.rows.map(asDeliveryEvent);
479
+ const last = events[events.length - 1];
480
+ return { events, cursor: events.length === limit && last ? encodeScopedCursor(scope, last.sequence) : void 0 };
481
+ }
482
+ async cancelDelivery(principal, id) {
483
+ return transaction(this.db, async (db) => {
484
+ const visible = await db.query(
485
+ `SELECT delivery.* FROM agent_bridge.deliveries delivery
486
+ JOIN agent_bridge.messages message ON message.workspace=delivery.workspace AND message.id=delivery.message_id
487
+ WHERE delivery.id=$1 AND delivery.workspace=$2 AND message.source=$3 FOR UPDATE OF delivery`,
488
+ [id, principal.workspace, principal.agent]
489
+ );
490
+ const current = visible.rows[0];
491
+ if (!current) return null;
492
+ if (current.state === "cancelled") return asDelivery(current);
493
+ if (!["pending", "retrying", "claimed"].includes(current.state)) {
494
+ throw new DeliveryStateConflictError(`cannot cancel a ${current.state} delivery`);
495
+ }
496
+ const result = await db.query(
497
+ `UPDATE agent_bridge.deliveries SET state='cancelled', lease_token=NULL,
498
+ lease_owner=NULL, lease_expires_at=NULL, last_error=NULL,
499
+ last_actor=$1, last_action='cancel' WHERE id=$2 RETURNING *`,
500
+ [principal.agent, id]
501
+ );
502
+ return asDelivery(result.rows[0]);
503
+ });
504
+ }
505
+ async requeueDelivery(principal, id) {
506
+ return transaction(this.db, async (db) => {
507
+ const visible = await db.query(
508
+ `SELECT delivery.*,message.expires_at,message.delivery_not_before
509
+ FROM agent_bridge.deliveries delivery
510
+ JOIN agent_bridge.messages message ON message.workspace=delivery.workspace AND message.id=delivery.message_id
511
+ WHERE delivery.id=$1 AND delivery.workspace=$2 AND message.source=$3 FOR UPDATE OF delivery`,
512
+ [id, principal.workspace, principal.agent]
513
+ );
514
+ const current = visible.rows[0];
515
+ if (!current) return null;
516
+ if (!["dead", "cancelled"].includes(current.state)) {
517
+ throw new DeliveryStateConflictError(`cannot requeue a ${current.state} delivery`);
518
+ }
519
+ if (current.expires_at && new Date(current.expires_at).getTime() <= Date.now()) {
520
+ throw new DeliveryStateConflictError("cannot requeue an expired message");
521
+ }
522
+ const result = await db.query(
523
+ `UPDATE agent_bridge.deliveries SET state='pending',
524
+ available_at=greatest(now(),coalesce($1::timestamptz,now())),
525
+ cycle_attempt=0,requeue_count=requeue_count+1,
526
+ lease_token=NULL,lease_owner=NULL,lease_expires_at=NULL,last_error=NULL,
527
+ last_actor=$2,last_action='requeue' WHERE id=$3 RETURNING *`,
528
+ [current.delivery_not_before, principal.agent, id]
529
+ );
530
+ return asDelivery(result.rows[0]);
531
+ });
532
+ }
533
+ async diagnostics(principal) {
534
+ const result = await this.db.query(
535
+ `SELECT
536
+ count(*) FILTER (WHERE state='pending')::integer AS pending,
537
+ count(*) FILTER (WHERE state='claimed')::integer AS claimed,
538
+ count(*) FILTER (WHERE state='retrying')::integer AS retrying,
539
+ count(*) FILTER (WHERE state='dead')::integer AS dead,
540
+ count(*) FILTER (WHERE state='cancelled')::integer AS cancelled,
541
+ min(available_at) FILTER (WHERE state IN ('pending','retrying')) AS oldest_available,
542
+ count(*) FILTER (WHERE state IN ('pending','retrying') AND available_at<=now())::integer AS due,
543
+ count(*) FILTER (WHERE state IN ('pending','retrying') AND available_at>now())::integer AS scheduled,
544
+ count(*) FILTER (WHERE state='claimed' AND lease_expires_at<=now())::integer AS expired_leases,
545
+ min(available_at) FILTER (WHERE state IN ('pending','retrying') AND available_at<=now()) AS oldest_due,
546
+ coalesce(greatest(0,extract(epoch FROM (now()-min(available_at) FILTER (WHERE state IN ('pending','retrying') AND available_at<=now())))*1000),0)::bigint AS queue_lag_ms
547
+ FROM agent_bridge.deliveries WHERE workspace=$1 AND recipient=$2`,
548
+ [principal.workspace, principal.agent]
549
+ );
550
+ const queue = result.rows[0] ?? {};
551
+ return {
552
+ schemaVersion: "postgres-v2",
553
+ deliverySupported: true,
554
+ pending: Number(queue.pending ?? 0),
555
+ claimed: Number(queue.claimed ?? 0),
556
+ retrying: Number(queue.retrying ?? 0),
557
+ dead: Number(queue.dead ?? 0),
558
+ cancelled: Number(queue.cancelled ?? 0),
559
+ oldestAvailableAt: asTimestamp(queue.oldest_available),
560
+ due: Number(queue.due ?? 0),
561
+ scheduled: Number(queue.scheduled ?? 0),
562
+ expiredLeases: Number(queue.expired_leases ?? 0),
563
+ oldestDueAt: asTimestamp(queue.oldest_due),
564
+ queueLagMs: Number(queue.queue_lag_ms ?? 0)
565
+ };
566
+ }
567
+ async heartbeat(principal, leaseMs, runtimeType, capabilities = []) {
568
+ await this.db.query(
569
+ "DELETE FROM agent_bridge.agent_instances WHERE workspace=$1 AND lease_expires_at<=now()",
570
+ [principal.workspace]
571
+ );
572
+ const result = await this.db.query(
573
+ `WITH locked AS (
574
+ SELECT pg_advisory_xact_lock(hashtextextended($1, 0))
575
+ )
576
+ INSERT INTO agent_bridge.agent_instances (workspace,agent,instance,runtime_type,capabilities,lease_expires_at,last_seen_at)
577
+ SELECT $1,$2,$3,$4,$5::jsonb,now() + ($6 * interval '1 millisecond'),now()
578
+ FROM locked
579
+ WHERE EXISTS (
580
+ SELECT 1 FROM agent_bridge.agent_instances
581
+ WHERE workspace=$1 AND agent=$2 AND instance=$3
582
+ ) OR ((
583
+ SELECT count(*) < 128 FROM agent_bridge.agent_instances
584
+ WHERE workspace=$1 AND agent=$2
585
+ ) AND (
586
+ SELECT count(*) < 4096 FROM agent_bridge.agent_instances
587
+ WHERE workspace=$1
588
+ )
589
+ )
590
+ ON CONFLICT (workspace,agent,instance) DO UPDATE SET runtime_type=excluded.runtime_type,capabilities=excluded.capabilities,lease_expires_at=excluded.lease_expires_at,last_seen_at=excluded.last_seen_at
591
+ RETURNING *`,
592
+ [principal.workspace, principal.agent, principal.instance, runtimeType ?? null, JSON.stringify(capabilities), leaseMs]
593
+ );
594
+ if (!result.rows[0]) {
595
+ throw Object.assign(new Error("active instance limit reached"), {
596
+ status: 429,
597
+ code: "presence_limit"
598
+ });
599
+ }
600
+ return asPresence(result.rows[0]);
601
+ }
602
+ async listPresence(principal) {
603
+ await this.db.query(
604
+ "DELETE FROM agent_bridge.agent_instances WHERE workspace=$1 AND lease_expires_at<=now()",
605
+ [principal.workspace]
606
+ );
607
+ const result = await this.db.query(
608
+ "SELECT * FROM agent_bridge.agent_instances WHERE workspace=$1 ORDER BY agent,instance",
609
+ [principal.workspace]
610
+ );
611
+ return result.rows.map(asPresence);
612
+ }
613
+ };
614
+
615
+ export {
616
+ PostgresBridgeStore
617
+ };
618
+ //# sourceMappingURL=chunk-2DFNOLMO.js.map