@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,1013 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ preparePrivateSqliteLocation,
4
+ securePrivatePath,
5
+ verifyPrivatePathAccess
6
+ } from "./chunk-NCO7RLBM.js";
7
+ import {
8
+ assertIdempotentReplay
9
+ } from "./chunk-VOQIDTCJ.js";
10
+ import {
11
+ DeliveryStateConflictError,
12
+ cursorScope,
13
+ decodeCursor,
14
+ decodeScopedCursor,
15
+ encodeCursor,
16
+ encodeScopedCursor,
17
+ scopedCursorScope,
18
+ validateDeliveryCursorPosition,
19
+ validateEventCursorPosition
20
+ } from "./chunk-BZLMF5O5.js";
21
+
22
+ // src/sqlite-database-contract.ts
23
+ import { createHash } from "crypto";
24
+ var LOCAL_SQLITE_APPLICATION_ID = 1094862642;
25
+ var EDGE_SQLITE_APPLICATION_ID = 1094862661;
26
+ var SQLITE_DATABASE_SCHEMA_VERSION = 1;
27
+ var SQLITE_METADATA_TABLE = "agent_bridge_metadata";
28
+ var LOCAL_SQLITE_SCHEMA_CONTRACTS = Object.freeze([
29
+ Object.freeze({ id: "current-created-schema", sha256: "e2f978c27ea5d151a4b4b3ec349166833441e42401fedfe60dc3ba20f3d713aa" }),
30
+ Object.freeze({ id: "current-upgraded-project-column", sha256: "cb0975bbc1ccc7a2a66d8f4d76df619804449cf81119972337afad6cdab64451" }),
31
+ Object.freeze({ id: "current-upgraded-delivery-policy", sha256: "d4fd8905ea73057b27662454c16c038834f5a1bf017db9cfc041792104aa2e6d" }),
32
+ Object.freeze({ id: "current-upgraded-delivery-events", sha256: "35bdab0cbdf4ce619ba1c7d36032379ae4d9692bd7509e49bb90b409bbbcf632" })
33
+ ]);
34
+ var EDGE_SQLITE_SCHEMA_CONTRACTS = Object.freeze([
35
+ Object.freeze({ id: "current-created-schema", sha256: "27f22b2f4024585c87e8d6f76f8999a8df92bb1f585d46594a7a1e852fd53c4c" }),
36
+ Object.freeze({ id: "current-upgraded-project-column", sha256: "171ae11f520b4963f517d3102de64cb8a5f45c080078ff02e20aeb17891b585c" })
37
+ ]);
38
+ var localContractHashes = new Set(LOCAL_SQLITE_SCHEMA_CONTRACTS.map((contract) => contract.sha256));
39
+ var edgeContractHashes = new Set(EDGE_SQLITE_SCHEMA_CONTRACTS.map((contract) => contract.sha256));
40
+ var isSupportedLocalSqliteSchemaContract = (hash) => localContractHashes.has(hash);
41
+ var isSupportedEdgeSqliteSchemaContract = (hash) => edgeContractHashes.has(hash);
42
+ var localMetadata = {
43
+ database_kind: "local-authority",
44
+ schema_name: "agent-bridge-local-v2",
45
+ schema_version: SQLITE_DATABASE_SCHEMA_VERSION
46
+ };
47
+ var edgeMetadata = {
48
+ database_kind: "edge-cache",
49
+ schema_name: "agent-bridge-edge-v1",
50
+ schema_version: SQLITE_DATABASE_SCHEMA_VERSION
51
+ };
52
+ var localColumns = {
53
+ agent_bridge_metadata: ["singleton", "database_kind", "schema_name", "schema_version"],
54
+ bridge_messages: [
55
+ "sequence",
56
+ "id",
57
+ "workspace",
58
+ "project",
59
+ "source",
60
+ "type",
61
+ "content",
62
+ "content_type",
63
+ "data",
64
+ "targets",
65
+ "thread_id",
66
+ "reply_to_id",
67
+ "correlation_id",
68
+ "causation_id",
69
+ "priority",
70
+ "expires_at",
71
+ "idempotency_key",
72
+ "atrib_receipt_id",
73
+ "informed_by",
74
+ "metadata",
75
+ "delivery_policy",
76
+ "created_at"
77
+ ],
78
+ bridge_receipts: ["workspace", "message_id", "principal", "read_at"],
79
+ bridge_deliveries: [
80
+ "id",
81
+ "message_id",
82
+ "workspace",
83
+ "recipient",
84
+ "state",
85
+ "attempt",
86
+ "cycle_attempt",
87
+ "requeue_count",
88
+ "created_at",
89
+ "priority_rank",
90
+ "available_at",
91
+ "lease_token",
92
+ "lease_owner",
93
+ "lease_expires_at",
94
+ "last_error",
95
+ "last_actor",
96
+ "last_action"
97
+ ],
98
+ bridge_delivery_events: [
99
+ "sequence",
100
+ "delivery_id",
101
+ "message_id",
102
+ "workspace",
103
+ "recipient",
104
+ "from_state",
105
+ "to_state",
106
+ "attempt",
107
+ "cycle_attempt",
108
+ "requeue_count",
109
+ "lease_owner",
110
+ "error",
111
+ "actor",
112
+ "action",
113
+ "created_at"
114
+ ],
115
+ bridge_presence: ["workspace", "agent", "instance", "runtime_type", "capabilities", "lease_expires_at", "last_seen_at"]
116
+ };
117
+ var localObjects = /* @__PURE__ */ new Map([
118
+ ["agent_bridge_metadata", ["table", "agent_bridge_metadata"]],
119
+ ["bridge_deliveries", ["table", "bridge_deliveries"]],
120
+ ["bridge_delivery_events", ["table", "bridge_delivery_events"]],
121
+ ["bridge_messages", ["table", "bridge_messages"]],
122
+ ["bridge_presence", ["table", "bridge_presence"]],
123
+ ["bridge_receipts", ["table", "bridge_receipts"]],
124
+ ["bridge_deliveries_claim", ["index", "bridge_deliveries"]],
125
+ ["bridge_deliveries_publisher", ["index", "bridge_deliveries"]],
126
+ ["bridge_deliveries_terminal", ["index", "bridge_deliveries"]],
127
+ ["bridge_delivery_events_lookup", ["index", "bridge_delivery_events"]],
128
+ ["bridge_messages_created", ["index", "bridge_messages"]],
129
+ ["bridge_messages_cursor", ["index", "bridge_messages"]],
130
+ ["bridge_messages_idempotency", ["index", "bridge_messages"]],
131
+ ["bridge_messages_project", ["index", "bridge_messages"]],
132
+ ["bridge_messages_source", ["index", "bridge_messages"]],
133
+ ["bridge_messages_thread", ["index", "bridge_messages"]],
134
+ ["bridge_presence_active", ["index", "bridge_presence"]],
135
+ ["bridge_delivery_events_insert", ["trigger", "bridge_deliveries"]],
136
+ ["bridge_delivery_events_update", ["trigger", "bridge_deliveries"]],
137
+ ["bridge_messages_no_delete", ["trigger", "bridge_messages"]],
138
+ ["bridge_messages_no_update", ["trigger", "bridge_messages"]],
139
+ ["bridge_messages_policy_insert", ["trigger", "bridge_messages"]],
140
+ ["bridge_messages_domain_insert", ["trigger", "bridge_messages"]],
141
+ ["agent_bridge_metadata_no_delete", ["trigger", "agent_bridge_metadata"]],
142
+ ["agent_bridge_metadata_no_update", ["trigger", "agent_bridge_metadata"]]
143
+ ]);
144
+ var edgeColumns = {
145
+ agent_bridge_metadata: ["singleton", "database_kind", "schema_name", "schema_version"],
146
+ edge_scopes: ["scope_key", "endpoint_hash", "workspace", "agent", "pull_cursor", "last_sync_at", "last_error", "last_outbound_sync_at", "last_inbound_sync_at", "last_attempt_at", "cache_contract"],
147
+ edge_outbox: ["position", "scope_key", "message_id", "idempotency_key", "payload_hash", "draft_json", "state", "attempts", "available_at", "lease_token", "lease_expires_at", "last_error", "blocked_at", "created_at"],
148
+ edge_inbox: ["scope_key", "message_id", "remote_sequence", "sequence_key", "workspace", "project", "source", "type", "thread_id", "created_at", "expires_at", "message_json"]
149
+ };
150
+ var edgeObjects = /* @__PURE__ */ new Map([
151
+ ["agent_bridge_metadata", ["table", "agent_bridge_metadata"]],
152
+ ["edge_scopes", ["table", "edge_scopes"]],
153
+ ["edge_outbox", ["table", "edge_outbox"]],
154
+ ["edge_inbox", ["table", "edge_inbox"]],
155
+ ["edge_inbox_created", ["index", "edge_inbox"]],
156
+ ["edge_inbox_cursor", ["index", "edge_inbox"]],
157
+ ["edge_inbox_project", ["index", "edge_inbox"]],
158
+ ["edge_inbox_source", ["index", "edge_inbox"]],
159
+ ["edge_inbox_thread", ["index", "edge_inbox"]],
160
+ ["edge_outbox_due", ["index", "edge_outbox"]],
161
+ ["agent_bridge_metadata_no_delete", ["trigger", "agent_bridge_metadata"]],
162
+ ["agent_bridge_metadata_no_update", ["trigger", "agent_bridge_metadata"]]
163
+ ]);
164
+ var SQLiteDatabaseContractError = class extends Error {
165
+ };
166
+ function normalizeSql(sql) {
167
+ let output = "";
168
+ let quote = "";
169
+ let pendingSpace = false;
170
+ for (let index = 0; index < sql.length; index += 1) {
171
+ const character = sql[index];
172
+ if (quote) {
173
+ output += character;
174
+ if (quote === "]" && character === "]" || quote !== "]" && character === quote) {
175
+ if (sql[index + 1] === character && quote !== "]") {
176
+ output += sql[++index];
177
+ } else quote = "";
178
+ }
179
+ continue;
180
+ }
181
+ if (/\s/u.test(character)) {
182
+ pendingSpace = output.length > 0;
183
+ continue;
184
+ }
185
+ if (pendingSpace && !/[(),;]/u.test(character) && !/[(),;]/u.test(output[output.length - 1] ?? "")) output += " ";
186
+ pendingSpace = false;
187
+ if (character === "'" || character === '"' || character === "`") quote = character;
188
+ else if (character === "[") quote = "]";
189
+ output += quote ? character : character.toLowerCase();
190
+ }
191
+ return output.trim();
192
+ }
193
+ function stableRows(rows) {
194
+ return rows.map((row) => Object.fromEntries(Object.keys(row).sort().map((key) => {
195
+ const value = row[key];
196
+ return [key, value === null ? null : typeof value === "number" ? value : String(value)];
197
+ })));
198
+ }
199
+ function quoteIdentifier(value) {
200
+ return `"${value.split('"').join('""')}"`;
201
+ }
202
+ function sqliteSchemaContractHash(db) {
203
+ const objects = db.prepare("SELECT type,name,tbl_name,sql FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' ORDER BY type,name").all().map((row) => ({ type: String(row.type), name: String(row.name), table: String(row.tbl_name), sql: normalizeSql(String(row.sql)) }));
204
+ const tables = objects.filter((object) => object.type === "table").map((object) => ({
205
+ name: object.name,
206
+ columns: stableRows(db.prepare(`PRAGMA table_info(${quoteIdentifier(object.name)})`).all()),
207
+ foreignKeys: stableRows(db.prepare(`PRAGMA foreign_key_list(${quoteIdentifier(object.name)})`).all()),
208
+ indexes: db.prepare(`PRAGMA index_list(${quoteIdentifier(object.name)})`).all().sort((left, right) => String(left.name).localeCompare(String(right.name))).map((row) => ({
209
+ ...Object.fromEntries(Object.keys(row).filter((key) => key !== "seq").sort().map((key) => [key, row[key] === null ? null : typeof row[key] === "number" ? row[key] : String(row[key])])),
210
+ columns: stableRows(db.prepare(`PRAGMA index_xinfo(${quoteIdentifier(String(row.name))})`).all())
211
+ }))
212
+ }));
213
+ return createHash("sha256").update(JSON.stringify({ objects, tables })).digest("hex");
214
+ }
215
+ function assertUpgradeCandidate(db, allowed, oppositePrefix, label) {
216
+ if (objectNames(db, oppositePrefix).length) throw new SQLiteDatabaseContractError(`${label} database kind is invalid`);
217
+ const rows = db.prepare("SELECT type,name,tbl_name FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' ORDER BY name").all();
218
+ for (const row of rows) {
219
+ const expected = allowed.get(String(row.name));
220
+ if (!expected || expected[0] !== String(row.type) || expected[1] !== String(row.tbl_name)) {
221
+ throw new SQLiteDatabaseContractError(`${label} database has an unexpected pre-migration schema object`);
222
+ }
223
+ }
224
+ }
225
+ function assertLocalUpgradeCandidate(db) {
226
+ assertUpgradeCandidate(db, localObjects, "edge_", "local authority");
227
+ }
228
+ function assertEdgeUpgradeCandidate(db) {
229
+ assertUpgradeCandidate(db, edgeObjects, "bridge_", "edge");
230
+ }
231
+ function scalar(db, pragma) {
232
+ const row = db.prepare(pragma).get();
233
+ return Number(Object.values(row)[0]);
234
+ }
235
+ function objectNames(db, prefix) {
236
+ return db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE ? ORDER BY name").all(`${prefix}%`).map((row) => String(row.name));
237
+ }
238
+ function metadata(db) {
239
+ const exists = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(SQLITE_METADATA_TABLE);
240
+ if (!exists) return {};
241
+ const row = db.prepare(`SELECT singleton,database_kind,schema_name,schema_version FROM ${SQLITE_METADATA_TABLE}`).get();
242
+ if (!row || Number(row.singleton) !== 1) return {};
243
+ return { database_kind: String(row.database_kind), schema_name: String(row.schema_name), schema_version: Number(row.schema_version) };
244
+ }
245
+ function installMarkers(db, applicationId, expected) {
246
+ const currentApplicationId = scalar(db, "PRAGMA application_id");
247
+ if (currentApplicationId !== 0 && currentApplicationId !== applicationId) {
248
+ throw new SQLiteDatabaseContractError("SQLite database kind marker does not match the selected store");
249
+ }
250
+ const currentVersion = scalar(db, "PRAGMA user_version");
251
+ if (currentVersion !== 0 && currentVersion !== SQLITE_DATABASE_SCHEMA_VERSION) {
252
+ throw new SQLiteDatabaseContractError("SQLite database schema version is unsupported");
253
+ }
254
+ db.exec(`CREATE TABLE IF NOT EXISTS ${SQLITE_METADATA_TABLE} (
255
+ singleton INTEGER PRIMARY KEY CHECK(singleton=1), database_kind TEXT NOT NULL,
256
+ schema_name TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version>=1)
257
+ ) WITHOUT ROWID`);
258
+ const existing = metadata(db);
259
+ if (Object.keys(existing).length && Object.entries(expected).some(([key, value]) => existing[key] !== value)) {
260
+ throw new SQLiteDatabaseContractError("SQLite database metadata does not match the selected store");
261
+ }
262
+ db.prepare(`INSERT OR IGNORE INTO ${SQLITE_METADATA_TABLE}(singleton,database_kind,schema_name,schema_version) VALUES (1,?,?,?)`).run(expected.database_kind, expected.schema_name, expected.schema_version);
263
+ db.exec(`CREATE TRIGGER IF NOT EXISTS agent_bridge_metadata_no_update BEFORE UPDATE ON ${SQLITE_METADATA_TABLE}
264
+ BEGIN SELECT RAISE(ABORT,'database metadata is immutable'); END;
265
+ CREATE TRIGGER IF NOT EXISTS agent_bridge_metadata_no_delete BEFORE DELETE ON ${SQLITE_METADATA_TABLE}
266
+ BEGIN SELECT RAISE(ABORT,'database metadata is immutable'); END`);
267
+ db.exec(`PRAGMA application_id = ${applicationId}; PRAGMA user_version = ${SQLITE_DATABASE_SCHEMA_VERSION}`);
268
+ }
269
+ function installLocalAuthorityMarkers(db) {
270
+ if (objectNames(db, "edge_").length) throw new SQLiteDatabaseContractError("edge databases cannot be opened as local authority stores");
271
+ installMarkers(db, LOCAL_SQLITE_APPLICATION_ID, localMetadata);
272
+ assertLocalAuthorityDatabase(db);
273
+ }
274
+ function installEdgeMarkers(db) {
275
+ if (objectNames(db, "bridge_").length) throw new SQLiteDatabaseContractError("local authority databases cannot be opened as edge stores");
276
+ installMarkers(db, EDGE_SQLITE_APPLICATION_ID, edgeMetadata);
277
+ assertEdgeDatabase(db);
278
+ }
279
+ function identifySqliteDatabase(db) {
280
+ const applicationId = scalar(db, "PRAGMA application_id");
281
+ const declared = metadata(db).database_kind;
282
+ if (applicationId === LOCAL_SQLITE_APPLICATION_ID && declared === "edge-cache" || applicationId === EDGE_SQLITE_APPLICATION_ID && declared === "local-authority") {
283
+ throw new SQLiteDatabaseContractError("SQLite application and metadata kind markers conflict");
284
+ }
285
+ if (applicationId === LOCAL_SQLITE_APPLICATION_ID || declared === "local-authority") return "local-authority";
286
+ if (applicationId === EDGE_SQLITE_APPLICATION_ID || declared === "edge-cache") return "edge-cache";
287
+ if (objectNames(db, "edge_").length) return "legacy-edge";
288
+ if (objectNames(db, "bridge_").length) return "legacy-local";
289
+ return "unknown";
290
+ }
291
+ function assertEdgeDatabase(db) {
292
+ if (identifySqliteDatabase(db) !== "edge-cache") throw new SQLiteDatabaseContractError("database is not a marked edge store");
293
+ if (scalar(db, "PRAGMA application_id") !== EDGE_SQLITE_APPLICATION_ID || scalar(db, "PRAGMA user_version") !== SQLITE_DATABASE_SCHEMA_VERSION) throw new SQLiteDatabaseContractError("edge database markers are invalid");
294
+ const actualMetadata = metadata(db);
295
+ if (Object.keys(actualMetadata).length !== Object.keys(edgeMetadata).length || Object.entries(edgeMetadata).some(([key, value]) => actualMetadata[key] !== value)) throw new SQLiteDatabaseContractError("edge database metadata is invalid");
296
+ const rows = db.prepare("SELECT type,name,tbl_name FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' ORDER BY name").all();
297
+ if (rows.length !== edgeObjects.size) throw new SQLiteDatabaseContractError("edge database has an unexpected schema object set");
298
+ for (const row of rows) {
299
+ const expected = edgeObjects.get(String(row.name));
300
+ if (!expected || expected[0] !== String(row.type) || expected[1] !== String(row.tbl_name)) throw new SQLiteDatabaseContractError("edge database has an unexpected schema object");
301
+ }
302
+ for (const [table, expected] of Object.entries(edgeColumns)) {
303
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all().map((row) => String(row.name));
304
+ const ordered = [...columns].sort();
305
+ const expectedOrdered = [...expected].sort();
306
+ if (ordered.length !== expectedOrdered.length || ordered.some((column, index) => column !== expectedOrdered[index])) throw new SQLiteDatabaseContractError("edge database column contract is invalid");
307
+ }
308
+ const contract = sqliteSchemaContractHash(db);
309
+ if (!isSupportedEdgeSqliteSchemaContract(contract)) throw new SQLiteDatabaseContractError("edge database schema contract hash is invalid");
310
+ }
311
+ function assertLocalAuthorityDatabase(db) {
312
+ if (identifySqliteDatabase(db) !== "local-authority") throw new SQLiteDatabaseContractError("database is not a marked local authority store");
313
+ if (scalar(db, "PRAGMA application_id") !== LOCAL_SQLITE_APPLICATION_ID || scalar(db, "PRAGMA user_version") !== SQLITE_DATABASE_SCHEMA_VERSION) {
314
+ throw new SQLiteDatabaseContractError("local authority database markers are invalid");
315
+ }
316
+ const actualMetadata = metadata(db);
317
+ if (Object.keys(actualMetadata).length !== Object.keys(localMetadata).length || Object.entries(localMetadata).some(([key, value]) => actualMetadata[key] !== value)) {
318
+ throw new SQLiteDatabaseContractError("local authority database metadata is invalid");
319
+ }
320
+ const rows = db.prepare("SELECT type,name,tbl_name FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' ORDER BY name").all();
321
+ if (rows.length !== localObjects.size) throw new SQLiteDatabaseContractError("local authority database has an unexpected schema object set");
322
+ for (const row of rows) {
323
+ const expected = localObjects.get(String(row.name));
324
+ if (!expected || expected[0] !== String(row.type) || expected[1] !== String(row.tbl_name)) {
325
+ throw new SQLiteDatabaseContractError("local authority database has an unexpected schema object");
326
+ }
327
+ }
328
+ for (const [table, expected] of Object.entries(localColumns)) {
329
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all().map((row) => String(row.name));
330
+ const ordered = [...columns].sort();
331
+ const expectedOrdered = [...expected].sort();
332
+ if (ordered.length !== expectedOrdered.length || ordered.some((column, index) => column !== expectedOrdered[index])) {
333
+ throw new SQLiteDatabaseContractError("local authority database column contract is invalid");
334
+ }
335
+ }
336
+ const contract = sqliteSchemaContractHash(db);
337
+ if (!isSupportedLocalSqliteSchemaContract(contract)) throw new SQLiteDatabaseContractError("local authority database schema contract hash is invalid");
338
+ }
339
+ function verifySqliteHealth(db) {
340
+ const integrity = db.prepare("PRAGMA integrity_check").all();
341
+ if (integrity.length !== 1 || String(Object.values(integrity[0])[0]) !== "ok") {
342
+ throw new SQLiteDatabaseContractError("SQLite integrity check failed");
343
+ }
344
+ const foreignKeys = db.prepare("PRAGMA foreign_key_check").all();
345
+ if (foreignKeys.length) throw new SQLiteDatabaseContractError("SQLite foreign key check failed");
346
+ }
347
+
348
+ // src/sqlite-bridge-store.ts
349
+ import { randomUUID } from "crypto";
350
+ import { existsSync, lstatSync } from "fs";
351
+ import { createRequire } from "module";
352
+
353
+ // src/sqlite-retry.ts
354
+ function isBusy(error) {
355
+ if (!(error instanceof Error)) return false;
356
+ const sqlite = error;
357
+ return sqlite.errcode === 5 || /database is (?:locked|busy)/i.test(error.message);
358
+ }
359
+ async function retrySqliteBusy(operation, timeoutMs) {
360
+ const deadline = Date.now() + Math.max(1, Math.trunc(timeoutMs));
361
+ let delayMs = 5;
362
+ while (true) {
363
+ try {
364
+ return operation();
365
+ } catch (error) {
366
+ if (!isBusy(error) || Date.now() >= deadline) throw error;
367
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
368
+ delayMs = Math.min(delayMs * 2, 50);
369
+ }
370
+ }
371
+ }
372
+
373
+ // src/sqlite-bridge-store.ts
374
+ var require2 = createRequire(import.meta.url);
375
+ function openDatabase(path) {
376
+ const { DatabaseSync } = require2("node:sqlite");
377
+ return new DatabaseSync(path);
378
+ }
379
+ var schema = `
380
+ PRAGMA journal_mode = WAL;
381
+ PRAGMA synchronous = FULL;
382
+ PRAGMA foreign_keys = ON;
383
+ CREATE TABLE IF NOT EXISTS bridge_messages (
384
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, workspace TEXT NOT NULL, project TEXT, source TEXT NOT NULL,
385
+ type TEXT NOT NULL, content TEXT NOT NULL, content_type TEXT NOT NULL, data TEXT, targets TEXT NOT NULL DEFAULT '[]',
386
+ thread_id TEXT, reply_to_id TEXT, correlation_id TEXT, causation_id TEXT, priority TEXT NOT NULL, expires_at TEXT,
387
+ idempotency_key TEXT, atrib_receipt_id TEXT, informed_by TEXT, metadata TEXT, delivery_policy TEXT NOT NULL, created_at TEXT NOT NULL,
388
+ UNIQUE(workspace, id), CHECK(json_valid(targets) AND json_type(targets) = 'array'),
389
+ CHECK(priority IN ('info', 'high', 'urgent')),
390
+ CHECK(json_valid(delivery_policy) AND json_type(delivery_policy)='object'),
391
+ CHECK(
392
+ (json_extract(delivery_policy,'$.mode')='mailbox'
393
+ AND json_type(delivery_policy,'$.maxAttempts') IS NULL
394
+ AND json_type(delivery_policy,'$.retryBaseDelayMs') IS NULL
395
+ AND json_type(delivery_policy,'$.retryMaxDelayMs') IS NULL
396
+ AND json_type(delivery_policy,'$.retryJitterRatio') IS NULL
397
+ AND json_type(delivery_policy,'$.notBefore') IS NULL)
398
+ OR
399
+ (json_extract(delivery_policy,'$.mode')='leased'
400
+ AND json_array_length(targets)>0
401
+ AND json_type(delivery_policy,'$.maxAttempts')='integer'
402
+ AND json_extract(delivery_policy,'$.maxAttempts') BETWEEN 1 AND 100
403
+ AND json_type(delivery_policy,'$.retryBaseDelayMs')='integer'
404
+ AND json_extract(delivery_policy,'$.retryBaseDelayMs') BETWEEN 1 AND 3600000
405
+ AND json_type(delivery_policy,'$.retryMaxDelayMs')='integer'
406
+ AND json_extract(delivery_policy,'$.retryMaxDelayMs') BETWEEN json_extract(delivery_policy,'$.retryBaseDelayMs') AND 86400000
407
+ AND json_type(delivery_policy,'$.retryJitterRatio') IN ('integer','real')
408
+ AND json_extract(delivery_policy,'$.retryJitterRatio') BETWEEN 0 AND 1
409
+ AND (json_type(delivery_policy,'$.notBefore') IS NULL OR (
410
+ json_type(delivery_policy,'$.notBefore')='text'
411
+ AND julianday(json_extract(delivery_policy,'$.notBefore')) IS NOT NULL
412
+ AND (expires_at IS NULL OR julianday(json_extract(delivery_policy,'$.notBefore'))<julianday(expires_at))
413
+ )))
414
+ )
415
+ );
416
+ CREATE UNIQUE INDEX IF NOT EXISTS bridge_messages_idempotency ON bridge_messages(workspace, source, idempotency_key) WHERE idempotency_key IS NOT NULL;
417
+ CREATE INDEX IF NOT EXISTS bridge_messages_cursor ON bridge_messages(workspace, sequence);
418
+ CREATE INDEX IF NOT EXISTS bridge_messages_source ON bridge_messages(workspace, source, sequence);
419
+ CREATE INDEX IF NOT EXISTS bridge_messages_thread ON bridge_messages(workspace, thread_id, sequence) WHERE thread_id IS NOT NULL;
420
+ CREATE INDEX IF NOT EXISTS bridge_messages_created ON bridge_messages(workspace, created_at, sequence);
421
+ CREATE TABLE IF NOT EXISTS bridge_receipts (workspace TEXT NOT NULL, message_id TEXT NOT NULL, principal TEXT NOT NULL, read_at TEXT NOT NULL, PRIMARY KEY(workspace, message_id, principal), FOREIGN KEY(workspace, message_id) REFERENCES bridge_messages(workspace, id) ON DELETE CASCADE);
422
+ CREATE TABLE IF NOT EXISTS bridge_deliveries (
423
+ id TEXT PRIMARY KEY, message_id TEXT NOT NULL, workspace TEXT NOT NULL, recipient TEXT NOT NULL,
424
+ state TEXT NOT NULL, attempt INTEGER NOT NULL DEFAULT 0, cycle_attempt INTEGER NOT NULL DEFAULT 0, requeue_count INTEGER NOT NULL DEFAULT 0,
425
+ created_at TEXT NOT NULL, priority_rank INTEGER NOT NULL, available_at TEXT NOT NULL,
426
+ lease_token TEXT, lease_owner TEXT, lease_expires_at TEXT, last_error TEXT, last_actor TEXT, last_action TEXT NOT NULL,
427
+ UNIQUE(message_id, recipient), FOREIGN KEY(workspace, message_id) REFERENCES bridge_messages(workspace, id) ON DELETE CASCADE,
428
+ CHECK(state IN ('pending', 'claimed', 'acked', 'retrying', 'dead', 'cancelled')), CHECK(attempt >= 0 AND cycle_attempt >= 0 AND cycle_attempt <= attempt AND requeue_count >= 0),
429
+ CHECK(priority_rank BETWEEN 0 AND 2),
430
+ CHECK((state = 'claimed' AND lease_token IS NOT NULL AND lease_expires_at IS NOT NULL) OR (state <> 'claimed' AND lease_token IS NULL AND lease_owner IS NULL AND lease_expires_at IS NULL))
431
+ );
432
+ CREATE TABLE IF NOT EXISTS bridge_delivery_events (
433
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT, delivery_id TEXT NOT NULL,
434
+ message_id TEXT NOT NULL, workspace TEXT NOT NULL, recipient TEXT NOT NULL,
435
+ from_state TEXT, to_state TEXT NOT NULL, attempt INTEGER NOT NULL, cycle_attempt INTEGER NOT NULL,
436
+ requeue_count INTEGER NOT NULL, lease_owner TEXT, error TEXT, actor TEXT NOT NULL, action TEXT NOT NULL, created_at TEXT NOT NULL
437
+ );
438
+ CREATE TABLE IF NOT EXISTS bridge_presence (
439
+ workspace TEXT NOT NULL, agent TEXT NOT NULL, instance TEXT NOT NULL, runtime_type TEXT,
440
+ capabilities TEXT NOT NULL DEFAULT '[]', lease_expires_at TEXT NOT NULL, last_seen_at TEXT NOT NULL,
441
+ PRIMARY KEY(workspace, agent, instance), CHECK(json_valid(capabilities) AND json_type(capabilities)='array')
442
+ );
443
+ CREATE INDEX IF NOT EXISTS bridge_presence_active ON bridge_presence(workspace, lease_expires_at);
444
+ CREATE TRIGGER IF NOT EXISTS bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END;
445
+ CREATE TRIGGER IF NOT EXISTS bridge_messages_no_delete BEFORE DELETE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END;
446
+ `;
447
+ function stringify(value) {
448
+ return value === void 0 ? null : JSON.stringify(value);
449
+ }
450
+ function parse(value) {
451
+ return typeof value === "string" ? JSON.parse(value) : void 0;
452
+ }
453
+ function optionalString(value) {
454
+ return value == null ? void 0 : String(value);
455
+ }
456
+ function message(row) {
457
+ const policy = parse(row.delivery_policy);
458
+ if (policy?.notBefore == null) delete policy?.notBefore;
459
+ return { id: String(row.id), workspace: String(row.workspace), project: optionalString(row.project), source: String(row.source), sequence: String(row.sequence), type: String(row.type), content: String(row.content), contentType: String(row.content_type), data: parse(row.data), targets: parse(row.targets) ?? [], threadId: optionalString(row.thread_id), replyToId: optionalString(row.reply_to_id), correlationId: optionalString(row.correlation_id), causationId: optionalString(row.causation_id), priority: row.priority, expiresAt: optionalString(row.expires_at), idempotencyKey: optionalString(row.idempotency_key), atribReceiptId: optionalString(row.atrib_receipt_id), informedBy: parse(row.informed_by), metadata: parse(row.metadata), deliveryPolicy: policy, createdAt: String(row.created_at) };
460
+ }
461
+ function delivery(row) {
462
+ return { id: String(row.id), messageId: String(row.message_id), workspace: String(row.workspace), recipient: String(row.recipient), state: row.state, attempt: Number(row.attempt), cycleAttempt: Number(row.cycle_attempt), requeueCount: Number(row.requeue_count), createdAt: String(row.created_at), priorityRank: Number(row.priority_rank), availableAt: String(row.available_at), leaseToken: optionalString(row.lease_token), leaseOwner: optionalString(row.lease_owner), leaseExpiresAt: optionalString(row.lease_expires_at), lastError: optionalString(row.last_error), lastActor: optionalString(row.last_actor), lastAction: row.last_action };
463
+ }
464
+ function presence(row) {
465
+ return { workspace: String(row.workspace), agent: String(row.agent), instance: String(row.instance), runtimeType: optionalString(row.runtime_type), capabilities: parse(row.capabilities) ?? [], leaseExpiresAt: String(row.lease_expires_at), lastSeenAt: String(row.last_seen_at) };
466
+ }
467
+ function deliveryEvent(row) {
468
+ return { sequence: String(row.sequence), deliveryId: String(row.delivery_id), messageId: String(row.message_id), workspace: String(row.workspace), recipient: String(row.recipient), fromState: row.from_state == null ? void 0 : row.from_state, toState: row.to_state, attempt: Number(row.attempt), cycleAttempt: Number(row.cycle_attempt), requeueCount: Number(row.requeue_count), leaseOwner: optionalString(row.lease_owner), error: optionalString(row.error), actor: String(row.actor), action: row.action, createdAt: String(row.created_at) };
469
+ }
470
+ var SQLiteBridgeStore = class {
471
+ constructor(path = ":memory:", busyTimeoutMs = 2e3) {
472
+ this.path = path;
473
+ this.busyTimeoutMs = busyTimeoutMs;
474
+ this.initialized = false;
475
+ const selected = preparePrivateSqliteLocation(path);
476
+ this.databasePath = selected;
477
+ this.preexistingFiles = new Set(selected === ":memory:" ? [] : [selected, `${selected}-wal`, `${selected}-shm`].filter(existsSync));
478
+ const before = selected === ":memory:" || !existsSync(selected) ? void 0 : lstatSync(selected);
479
+ this.db = openDatabase(selected);
480
+ try {
481
+ this.db.exec(`PRAGMA busy_timeout = ${Math.max(1, Math.trunc(this.busyTimeoutMs))}`);
482
+ this.restrictFiles();
483
+ if (before) {
484
+ const after = lstatSync(selected);
485
+ if (after.dev !== before.dev || after.ino !== before.ino) throw new Error("SQLite database path identity changed while opening");
486
+ }
487
+ } catch (error) {
488
+ this.db.close();
489
+ throw error;
490
+ }
491
+ }
492
+ restrictFiles() {
493
+ if (this.databasePath === ":memory:") return;
494
+ for (const path of [this.databasePath, `${this.databasePath}-wal`, `${this.databasePath}-shm`]) {
495
+ if (!existsSync(path)) continue;
496
+ if (path === this.databasePath && this.preexistingFiles.has(path)) verifyPrivatePathAccess(path, "file");
497
+ else securePrivatePath(path, "file");
498
+ }
499
+ }
500
+ async initialize() {
501
+ if (this.initialized) return;
502
+ if (!this.initialization) {
503
+ this.initialization = this.initializeOnce().then(() => {
504
+ this.initialized = true;
505
+ }).catch((error) => {
506
+ this.initialization = void 0;
507
+ throw error;
508
+ });
509
+ }
510
+ return this.initialization;
511
+ }
512
+ async initializeOnce() {
513
+ const versionRow = this.db.prepare("SELECT sqlite_version() AS version").get();
514
+ const parts = String(versionRow.version).split(".").map(Number);
515
+ const encoded = (parts[0] ?? 0) * 1e6 + (parts[1] ?? 0) * 1e3 + (parts[2] ?? 0);
516
+ if (encoded < 3051003) throw new Error("SQLite 3.51.3 or newer is required");
517
+ assertLocalUpgradeCandidate(this.db);
518
+ await retrySqliteBusy(() => this.db.exec(schema), this.busyTimeoutMs);
519
+ await retrySqliteBusy(() => this.db.exec("BEGIN IMMEDIATE"), this.busyTimeoutMs);
520
+ try {
521
+ const columns = this.db.prepare("PRAGMA table_info(bridge_messages)").all();
522
+ if (!columns.some((column) => column.name === "project")) {
523
+ this.db.exec("ALTER TABLE bridge_messages ADD COLUMN project TEXT");
524
+ }
525
+ const addedDeliveryPolicy = !columns.some((column) => column.name === "delivery_policy");
526
+ if (addedDeliveryPolicy) {
527
+ this.db.exec("DROP TRIGGER IF EXISTS bridge_messages_no_update");
528
+ this.db.exec(`ALTER TABLE bridge_messages ADD COLUMN delivery_policy TEXT NOT NULL DEFAULT '{"mode":"leased","maxAttempts":5,"retryBaseDelayMs":1000,"retryMaxDelayMs":60000,"retryJitterRatio":0.2}'`);
529
+ this.db.exec("CREATE TRIGGER bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END");
530
+ }
531
+ const legacyPolicy = addedDeliveryPolicy || Boolean(this.db.prepare(`SELECT 1 FROM bridge_messages
532
+ WHERE json_type(delivery_policy,'$.publisherOwned') IS NOT NULL
533
+ OR json_type(delivery_policy,'$.baseDelayMs') IS NOT NULL
534
+ OR json_type(delivery_policy,'$.maxDelayMs') IS NOT NULL
535
+ OR json_type(delivery_policy,'$.jitterRatio') IS NOT NULL
536
+ LIMIT 1`).get());
537
+ if (legacyPolicy) {
538
+ this.db.exec("DROP TRIGGER IF EXISTS bridge_messages_no_update");
539
+ if (addedDeliveryPolicy) {
540
+ this.db.exec(`UPDATE bridge_messages SET delivery_policy=CASE
541
+ WHEN json_array_length(targets)=0 THEN '{"mode":"mailbox"}'
542
+ ELSE '{"mode":"leased","maxAttempts":5,"retryBaseDelayMs":1000,"retryMaxDelayMs":60000,"retryJitterRatio":0.2}' END`);
543
+ } else {
544
+ this.db.exec(`UPDATE bridge_messages SET delivery_policy=CASE
545
+ WHEN json_extract(delivery_policy,'$.mode')='mailbox' THEN
546
+ json_remove(delivery_policy,'$.publisherOwned','$.maxAttempts','$.baseDelayMs','$.maxDelayMs','$.jitterRatio','$.retryBaseDelayMs','$.retryMaxDelayMs','$.retryJitterRatio','$.notBefore')
547
+ ELSE json_set(
548
+ json_remove(delivery_policy,'$.publisherOwned','$.baseDelayMs','$.maxDelayMs','$.jitterRatio'),
549
+ '$.mode','leased',
550
+ '$.maxAttempts',coalesce(json_extract(delivery_policy,'$.maxAttempts'),5),
551
+ '$.retryBaseDelayMs',coalesce(json_extract(delivery_policy,'$.retryBaseDelayMs'),json_extract(delivery_policy,'$.baseDelayMs'),1000),
552
+ '$.retryMaxDelayMs',coalesce(json_extract(delivery_policy,'$.retryMaxDelayMs'),json_extract(delivery_policy,'$.maxDelayMs'),60000),
553
+ '$.retryJitterRatio',coalesce(json_extract(delivery_policy,'$.retryJitterRatio'),json_extract(delivery_policy,'$.jitterRatio'),0.2)
554
+ ) END
555
+ WHERE json_type(delivery_policy,'$.publisherOwned') IS NOT NULL
556
+ OR json_type(delivery_policy,'$.baseDelayMs') IS NOT NULL
557
+ OR json_type(delivery_policy,'$.maxDelayMs') IS NOT NULL
558
+ OR json_type(delivery_policy,'$.jitterRatio') IS NOT NULL`);
559
+ }
560
+ this.db.exec("CREATE TRIGGER bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END");
561
+ }
562
+ const deliveryColumns = this.db.prepare("PRAGMA table_info(bridge_deliveries)").all();
563
+ if (!deliveryColumns.some((column) => column.name === "last_action")) {
564
+ this.db.exec("DROP TRIGGER IF EXISTS bridge_delivery_events_insert; DROP TRIGGER IF EXISTS bridge_delivery_events_update; DROP INDEX IF EXISTS bridge_deliveries_claim");
565
+ const eventColumns = this.db.prepare("PRAGMA table_info(bridge_delivery_events)").all();
566
+ this.db.exec(`CREATE TABLE bridge_delivery_events_v10 (
567
+ sequence INTEGER PRIMARY KEY AUTOINCREMENT, delivery_id TEXT NOT NULL,
568
+ message_id TEXT NOT NULL, workspace TEXT NOT NULL, recipient TEXT NOT NULL,
569
+ from_state TEXT, to_state TEXT NOT NULL, attempt INTEGER NOT NULL,
570
+ cycle_attempt INTEGER NOT NULL, requeue_count INTEGER NOT NULL,
571
+ lease_owner TEXT, error TEXT, actor TEXT NOT NULL, action TEXT NOT NULL, created_at TEXT NOT NULL)`);
572
+ if (eventColumns.length) {
573
+ this.db.exec(`INSERT INTO bridge_delivery_events_v10
574
+ (sequence,delivery_id,message_id,workspace,recipient,from_state,to_state,attempt,cycle_attempt,requeue_count,lease_owner,error,actor,action,created_at)
575
+ SELECT event.sequence,event.delivery_id,event.message_id,event.workspace,event.recipient,
576
+ event.from_state,event.to_state,event.attempt,event.attempt,0,event.lease_owner,event.error,
577
+ CASE
578
+ WHEN event.from_state IS NULL THEN message.source
579
+ WHEN event.to_state='dead' AND event.error IN ('message expired','maximum attempts reached') THEN 'agent-bridge'
580
+ WHEN event.to_state IN ('acked','retrying','dead') THEN coalesce(
581
+ event.lease_owner,
582
+ (SELECT claim.lease_owner FROM bridge_delivery_events claim
583
+ WHERE claim.delivery_id=event.delivery_id AND claim.to_state='claimed'
584
+ AND claim.attempt=event.attempt AND claim.sequence<event.sequence
585
+ AND claim.lease_owner IS NOT NULL
586
+ ORDER BY claim.sequence DESC LIMIT 1),
587
+ event.recipient)
588
+ ELSE coalesce(event.lease_owner,event.recipient)
589
+ END,CASE
590
+ WHEN event.from_state IS NULL THEN 'created'
591
+ WHEN event.to_state='claimed' THEN 'claim'
592
+ WHEN event.to_state='acked' THEN 'ack'
593
+ WHEN event.to_state='retrying' THEN 'nack_retry'
594
+ WHEN event.to_state='dead' AND event.error='message expired' THEN 'message_expired'
595
+ WHEN event.to_state='dead' AND event.error='maximum attempts reached' THEN 'attempts_exhausted'
596
+ WHEN event.to_state='dead' THEN 'nack_dead'
597
+ WHEN event.to_state='cancelled' THEN 'cancel'
598
+ ELSE 'created' END,event.created_at
599
+ FROM bridge_delivery_events event JOIN bridge_messages message
600
+ ON message.workspace=event.workspace AND message.id=event.message_id`);
601
+ this.db.exec("DROP TABLE bridge_delivery_events");
602
+ }
603
+ this.db.exec(`CREATE TABLE bridge_deliveries_v10 (
604
+ id TEXT PRIMARY KEY, message_id TEXT NOT NULL, workspace TEXT NOT NULL, recipient TEXT NOT NULL,
605
+ state TEXT NOT NULL, attempt INTEGER NOT NULL DEFAULT 0, cycle_attempt INTEGER NOT NULL DEFAULT 0,
606
+ requeue_count INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, priority_rank INTEGER NOT NULL,
607
+ available_at TEXT NOT NULL, lease_token TEXT, lease_owner TEXT,
608
+ lease_expires_at TEXT, last_error TEXT, last_actor TEXT, last_action TEXT NOT NULL, UNIQUE(message_id, recipient),
609
+ FOREIGN KEY(workspace,message_id) REFERENCES bridge_messages(workspace,id) ON DELETE CASCADE,
610
+ CHECK(state IN ('pending','claimed','acked','retrying','dead','cancelled')),
611
+ CHECK(attempt>=0 AND cycle_attempt>=0 AND cycle_attempt<=attempt AND requeue_count>=0),
612
+ CHECK(priority_rank BETWEEN 0 AND 2),
613
+ CHECK((state='claimed' AND lease_token IS NOT NULL AND lease_expires_at IS NOT NULL) OR (state<>'claimed' AND lease_token IS NULL AND lease_owner IS NULL AND lease_expires_at IS NULL)))`);
614
+ this.db.exec(`INSERT INTO bridge_deliveries_v10
615
+ (id,message_id,workspace,recipient,state,attempt,cycle_attempt,requeue_count,created_at,priority_rank,available_at,lease_token,lease_owner,lease_expires_at,last_error,last_actor,last_action)
616
+ SELECT delivery.id,delivery.message_id,delivery.workspace,delivery.recipient,delivery.state,delivery.attempt,delivery.attempt,0,
617
+ message.created_at,CASE message.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 ELSE 2 END,
618
+ delivery.available_at,delivery.lease_token,delivery.lease_owner,delivery.lease_expires_at,delivery.last_error,
619
+ CASE
620
+ WHEN delivery.state='pending' AND delivery.attempt=0 THEN message.source
621
+ WHEN delivery.state='dead' AND delivery.last_error IN ('message expired','maximum attempts reached') THEN 'agent-bridge'
622
+ ELSE coalesce(
623
+ delivery.lease_owner,
624
+ (SELECT event.lease_owner FROM bridge_delivery_events_v10 event
625
+ WHERE event.delivery_id=delivery.id AND event.to_state='claimed'
626
+ AND event.attempt=delivery.attempt AND event.lease_owner IS NOT NULL
627
+ ORDER BY event.sequence DESC LIMIT 1),
628
+ delivery.recipient)
629
+ END,CASE
630
+ WHEN delivery.state='pending' AND delivery.attempt=0 THEN 'created'
631
+ WHEN delivery.state='claimed' THEN 'claim'
632
+ WHEN delivery.state='acked' THEN 'ack'
633
+ WHEN delivery.state='retrying' THEN 'nack_retry'
634
+ WHEN delivery.state='dead' AND delivery.last_error='message expired' THEN 'message_expired'
635
+ WHEN delivery.state='dead' AND delivery.last_error='maximum attempts reached' THEN 'attempts_exhausted'
636
+ WHEN delivery.state='dead' THEN 'nack_dead'
637
+ WHEN delivery.state='cancelled' THEN 'cancel'
638
+ ELSE 'created' END
639
+ FROM bridge_deliveries delivery JOIN bridge_messages message
640
+ ON message.workspace=delivery.workspace AND message.id=delivery.message_id`);
641
+ this.db.exec("DROP TABLE bridge_deliveries; ALTER TABLE bridge_deliveries_v10 RENAME TO bridge_deliveries");
642
+ this.db.exec("ALTER TABLE bridge_delivery_events_v10 RENAME TO bridge_delivery_events");
643
+ }
644
+ this.db.exec("CREATE INDEX IF NOT EXISTS bridge_deliveries_claim ON bridge_deliveries(workspace,recipient,priority_rank,available_at,created_at,id) WHERE state IN ('pending','retrying','claimed'); CREATE INDEX IF NOT EXISTS bridge_deliveries_publisher ON bridge_deliveries(workspace,message_id,created_at,id); CREATE INDEX IF NOT EXISTS bridge_deliveries_terminal ON bridge_deliveries(workspace,recipient,state,created_at,id) WHERE state IN ('dead','cancelled'); CREATE INDEX IF NOT EXISTS bridge_delivery_events_lookup ON bridge_delivery_events(workspace,delivery_id,sequence)");
645
+ this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_delivery_events_insert AFTER INSERT ON bridge_deliveries BEGIN INSERT INTO bridge_delivery_events (delivery_id,message_id,workspace,recipient,from_state,to_state,attempt,cycle_attempt,requeue_count,lease_owner,error,actor,action,created_at) VALUES (NEW.id,NEW.message_id,NEW.workspace,NEW.recipient,NULL,NEW.state,NEW.attempt,NEW.cycle_attempt,NEW.requeue_count,NEW.lease_owner,NEW.last_error,coalesce(NEW.last_actor,NEW.recipient),NEW.last_action,strftime('%Y-%m-%dT%H:%M:%fZ','now')); END;
646
+ CREATE TRIGGER IF NOT EXISTS bridge_delivery_events_update AFTER UPDATE ON bridge_deliveries WHEN OLD.state IS NOT NEW.state OR OLD.attempt IS NOT NEW.attempt OR OLD.requeue_count IS NOT NEW.requeue_count OR OLD.last_action IS NOT NEW.last_action BEGIN INSERT INTO bridge_delivery_events (delivery_id,message_id,workspace,recipient,from_state,to_state,attempt,cycle_attempt,requeue_count,lease_owner,error,actor,action,created_at) VALUES (NEW.id,NEW.message_id,NEW.workspace,NEW.recipient,OLD.state,NEW.state,NEW.attempt,NEW.cycle_attempt,NEW.requeue_count,NEW.lease_owner,NEW.last_error,coalesce(NEW.last_actor,NEW.recipient),NEW.last_action,strftime('%Y-%m-%dT%H:%M:%fZ','now')); END`);
647
+ this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_messages_policy_insert BEFORE INSERT ON bridge_messages
648
+ WHEN NOT (
649
+ json_valid(NEW.delivery_policy) AND json_type(NEW.delivery_policy)='object'
650
+ AND NOT EXISTS (SELECT 1 FROM json_each(NEW.delivery_policy) WHERE key NOT IN ('mode','maxAttempts','retryBaseDelayMs','retryMaxDelayMs','retryJitterRatio','notBefore'))
651
+ AND (
652
+ (json_extract(NEW.delivery_policy,'$.mode')='mailbox' AND json_array_length(NEW.targets)>=0
653
+ AND (SELECT count(*) FROM json_each(NEW.delivery_policy))=1)
654
+ OR
655
+ (json_extract(NEW.delivery_policy,'$.mode')='leased' AND json_array_length(NEW.targets)>0
656
+ AND json_type(NEW.delivery_policy,'$.maxAttempts')='integer' AND json_extract(NEW.delivery_policy,'$.maxAttempts') BETWEEN 1 AND 100
657
+ AND json_type(NEW.delivery_policy,'$.retryBaseDelayMs')='integer' AND json_extract(NEW.delivery_policy,'$.retryBaseDelayMs') BETWEEN 1 AND 3600000
658
+ AND json_type(NEW.delivery_policy,'$.retryMaxDelayMs')='integer' AND json_extract(NEW.delivery_policy,'$.retryMaxDelayMs') BETWEEN json_extract(NEW.delivery_policy,'$.retryBaseDelayMs') AND 86400000
659
+ AND json_type(NEW.delivery_policy,'$.retryJitterRatio') IN ('integer','real') AND json_extract(NEW.delivery_policy,'$.retryJitterRatio') BETWEEN 0 AND 1
660
+ AND (json_type(NEW.delivery_policy,'$.notBefore') IS NULL OR (
661
+ json_type(NEW.delivery_policy,'$.notBefore')='text'
662
+ AND julianday(json_extract(NEW.delivery_policy,'$.notBefore')) IS NOT NULL
663
+ AND (NEW.expires_at IS NULL OR julianday(json_extract(NEW.delivery_policy,'$.notBefore'))<julianday(NEW.expires_at))
664
+ ))
665
+ )
666
+ )
667
+ ) BEGIN SELECT RAISE(ABORT,'invalid delivery policy'); END`);
668
+ this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_messages_domain_insert BEFORE INSERT ON bridge_messages
669
+ WHEN NOT (json_valid(NEW.targets) AND json_type(NEW.targets)='array' AND NEW.priority IN ('info','high','urgent'))
670
+ BEGIN SELECT RAISE(ABORT,'invalid message domain'); END`);
671
+ this.db.exec("CREATE INDEX IF NOT EXISTS bridge_messages_project ON bridge_messages(workspace, project, sequence)");
672
+ installLocalAuthorityMarkers(this.db);
673
+ this.db.exec("COMMIT");
674
+ } catch (error) {
675
+ this.db.exec("ROLLBACK");
676
+ throw error;
677
+ }
678
+ this.restrictFiles();
679
+ }
680
+ async ready() {
681
+ await this.initialize();
682
+ }
683
+ async insertMessage(input) {
684
+ await this.ready();
685
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
686
+ const storedPolicy = input.deliveryPolicy ?? (input.targets.length ? { mode: "leased", maxAttempts: 5, retryBaseDelayMs: 1e3, retryMaxDelayMs: 6e4, retryJitterRatio: 0.2 } : { mode: "mailbox" });
687
+ this.db.exec("BEGIN IMMEDIATE");
688
+ try {
689
+ if (input.idempotencyKey) {
690
+ const existing = this.db.prepare("SELECT * FROM bridge_messages WHERE workspace = ? AND source = ? AND idempotency_key = ?").get(input.workspace, input.source, input.idempotencyKey);
691
+ if (existing) {
692
+ const replay = message(existing);
693
+ assertIdempotentReplay(replay, input);
694
+ this.db.exec("COMMIT");
695
+ return { message: replay, created: false };
696
+ }
697
+ }
698
+ this.db.prepare(`INSERT INTO bridge_messages (id,workspace,project,source,type,content,content_type,data,targets,thread_id,reply_to_id,correlation_id,causation_id,priority,expires_at,idempotency_key,atrib_receipt_id,informed_by,metadata,delivery_policy,created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(input.id, input.workspace, input.project ?? null, input.source, input.type, input.content, input.contentType, stringify(input.data), JSON.stringify(input.targets), input.threadId ?? null, input.replyToId ?? null, input.correlationId ?? null, input.causationId ?? null, input.priority, input.expiresAt ?? null, input.idempotencyKey ?? null, input.atribReceiptId ?? null, stringify(input.informedBy), stringify(input.metadata), JSON.stringify(storedPolicy), createdAt);
699
+ if (storedPolicy.mode === "leased") {
700
+ const availableAt = storedPolicy.notBefore && storedPolicy.notBefore > createdAt ? storedPolicy.notBefore : createdAt;
701
+ const priorityRank = input.priority === "urgent" ? 0 : input.priority === "high" ? 1 : 2;
702
+ for (const recipient of input.targets) {
703
+ this.db.prepare("INSERT INTO bridge_deliveries (id,message_id,workspace,recipient,state,created_at,priority_rank,available_at,last_actor,last_action) VALUES (?,?,?,?,?,?,?,?,?,?)").run(randomUUID(), input.id, input.workspace, recipient, "pending", createdAt, priorityRank, availableAt, input.source, "created");
704
+ }
705
+ }
706
+ const row = this.db.prepare("SELECT * FROM bridge_messages WHERE id = ?").get(input.id);
707
+ this.db.exec("COMMIT");
708
+ return { message: message(row), created: true };
709
+ } catch (error) {
710
+ this.db.exec("ROLLBACK");
711
+ throw error;
712
+ }
713
+ }
714
+ async listMessages(principal, query = {}) {
715
+ await this.ready();
716
+ const scope = cursorScope(principal, query);
717
+ const cursor = decodeCursor(query.cursor, scope);
718
+ const limit = Math.min(Math.max(Math.trunc(query.limit ?? 50), 1), 200);
719
+ const now = (/* @__PURE__ */ new Date()).toISOString();
720
+ const highWaterRow = query.latest ? void 0 : this.db.prepare("SELECT max(sequence) AS sequence FROM bridge_messages WHERE workspace=?").get(principal.workspace);
721
+ const highWater = highWaterRow?.sequence === null || highWaterRow?.sequence === void 0 ? void 0 : String(highWaterRow.sequence);
722
+ const mailbox = query.mailbox ?? "inbox";
723
+ const visibility = mailbox === "sent" ? "source = ?" : mailbox === "all" ? "(source = ? OR targets = '[]' OR EXISTS (SELECT 1 FROM json_each(targets) WHERE value = ?))" : "(targets = '[]' OR EXISTS (SELECT 1 FROM json_each(targets) WHERE value = ?))";
724
+ const clauses = ["workspace = ?", query.latest ? "1=1" : "sequence > ?", visibility];
725
+ const args = query.latest ? [principal.workspace] : [principal.workspace, cursor ?? "0"];
726
+ if (mailbox === "all") args.push(principal.agent, principal.agent);
727
+ else args.push(principal.agent);
728
+ if (highWater) {
729
+ clauses.push("sequence <= ?");
730
+ args.push(highWater);
731
+ }
732
+ if (!query.includeExpired) {
733
+ clauses.push("(expires_at IS NULL OR expires_at > ?)");
734
+ args.push(now);
735
+ }
736
+ if (query.types?.length) {
737
+ clauses.push(`type IN (${query.types.map(() => "?").join(",")})`);
738
+ args.push(...query.types);
739
+ }
740
+ if (query.source) {
741
+ clauses.push("source = ?");
742
+ args.push(query.source);
743
+ }
744
+ if (query.project) {
745
+ clauses.push("project = ?");
746
+ args.push(query.project);
747
+ }
748
+ if (query.since) {
749
+ clauses.push("created_at >= ?");
750
+ args.push(query.since);
751
+ }
752
+ if (query.receiptState && query.receiptState !== "any") {
753
+ clauses.push(`${query.receiptState === "unread" ? "NOT " : ""}EXISTS (SELECT 1 FROM bridge_receipts receipt WHERE receipt.workspace=bridge_messages.workspace AND receipt.message_id=bridge_messages.id AND receipt.principal=?)`);
754
+ args.push(principal.agent);
755
+ }
756
+ if (query.threadId) {
757
+ clauses.push("thread_id = ?");
758
+ args.push(query.threadId);
759
+ }
760
+ const rows = this.db.prepare(`SELECT * FROM bridge_messages WHERE ${clauses.join(" AND ")} ORDER BY sequence ${query.latest ? "DESC" : "ASC"} LIMIT ?`).all(...args, limit);
761
+ const messages = rows.map(message);
762
+ const last = messages[messages.length - 1];
763
+ if (query.latest) return { messages, cursor: messages[0] ? encodeCursor(messages[0].sequence, scope) : query.cursor };
764
+ if (messages.length === limit) return { messages, cursor: encodeCursor(last.sequence, scope) };
765
+ return { messages, cursor: highWater ? encodeCursor(highWater, scope) : query.cursor };
766
+ }
767
+ async recordReceipt(principal, messageIds, readAt = /* @__PURE__ */ new Date()) {
768
+ await this.ready();
769
+ const stmt = this.db.prepare("INSERT OR IGNORE INTO bridge_receipts (workspace,message_id,principal,read_at) SELECT workspace,id,?,? FROM bridge_messages WHERE workspace=? AND id=? AND (targets='[]' OR EXISTS (SELECT 1 FROM json_each(targets) WHERE value=?))");
770
+ let changed = 0;
771
+ this.db.exec("BEGIN IMMEDIATE");
772
+ try {
773
+ for (const id of messageIds) changed += Number(stmt.run(principal.agent, readAt.toISOString(), principal.workspace, id, principal.agent).changes);
774
+ this.db.exec("COMMIT");
775
+ return changed;
776
+ } catch (e) {
777
+ this.db.exec("ROLLBACK");
778
+ throw e;
779
+ }
780
+ }
781
+ async claimDelivery(principal, options) {
782
+ await this.ready();
783
+ const now = options.now ?? /* @__PURE__ */ new Date();
784
+ const nowText = now.toISOString();
785
+ const expires = new Date(now.getTime() + options.leaseMs).toISOString();
786
+ const owner = principal.instance ?? principal.agent;
787
+ this.db.exec("BEGIN IMMEDIATE");
788
+ try {
789
+ this.db.prepare("UPDATE bridge_deliveries SET state='dead',last_error='message expired',lease_token=NULL,lease_owner=NULL,lease_expires_at=NULL,last_actor='agent-bridge',last_action='message_expired' WHERE workspace=? AND recipient=? AND state IN ('pending','retrying','claimed') AND EXISTS (SELECT 1 FROM bridge_messages message WHERE message.workspace=bridge_deliveries.workspace AND message.id=bridge_deliveries.message_id AND message.expires_at IS NOT NULL AND message.expires_at<=?)").run(principal.workspace, principal.agent, nowText);
790
+ this.db.prepare("UPDATE bridge_deliveries SET state='dead',last_error='maximum attempts reached',lease_token=NULL,lease_owner=NULL,lease_expires_at=NULL,last_actor='agent-bridge',last_action='attempts_exhausted' WHERE workspace=? AND recipient=? AND cycle_attempt>=(SELECT json_extract(delivery_policy,'$.maxAttempts') FROM bridge_messages WHERE workspace=bridge_deliveries.workspace AND id=bridge_deliveries.message_id) AND (state IN ('pending','retrying') OR (state='claimed' AND lease_expires_at<=?))").run(principal.workspace, principal.agent, nowText);
791
+ this.db.prepare("UPDATE bridge_deliveries SET state='retrying',available_at=?,last_error='lease expired',lease_token=NULL,lease_owner=NULL,lease_expires_at=NULL,last_actor='agent-bridge',last_action='lease_expired' WHERE workspace=? AND recipient=? AND state='claimed' AND lease_expires_at<=? AND cycle_attempt<(SELECT json_extract(delivery_policy,'$.maxAttempts') FROM bridge_messages WHERE workspace=bridge_deliveries.workspace AND id=bridge_deliveries.message_id)").run(nowText, principal.workspace, principal.agent, nowText);
792
+ const candidate = this.db.prepare("SELECT delivery.id FROM bridge_deliveries delivery JOIN bridge_messages message ON message.workspace=delivery.workspace AND message.id=delivery.message_id WHERE delivery.workspace=? AND delivery.recipient=? AND delivery.state IN ('pending','retrying') AND delivery.available_at<=? AND delivery.cycle_attempt<json_extract(message.delivery_policy,'$.maxAttempts') AND (message.expires_at IS NULL OR message.expires_at>?) ORDER BY delivery.priority_rank,delivery.available_at,delivery.created_at,delivery.id LIMIT 1").get(principal.workspace, principal.agent, nowText, nowText);
793
+ if (!candidate) {
794
+ this.db.exec("COMMIT");
795
+ return null;
796
+ }
797
+ const candidateId = String(candidate.id);
798
+ const token = randomUUID();
799
+ this.db.prepare("UPDATE bridge_deliveries SET state='claimed',attempt=attempt+1,cycle_attempt=cycle_attempt+1,lease_token=?,lease_owner=?,lease_expires_at=?,last_error=NULL,last_actor=?,last_action='claim' WHERE id=?").run(token, owner, expires, principal.agent, candidateId);
800
+ const claimed = this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(candidateId);
801
+ this.db.exec("COMMIT");
802
+ return delivery(claimed);
803
+ } catch (e) {
804
+ this.db.exec("ROLLBACK");
805
+ throw e;
806
+ }
807
+ }
808
+ async renewDelivery(principal, id, token, leaseMs) {
809
+ await this.ready();
810
+ const now = /* @__PURE__ */ new Date();
811
+ const expires = new Date(now.getTime() + leaseMs).toISOString();
812
+ const owner = principal.instance ?? principal.agent;
813
+ const result = this.db.prepare("UPDATE bridge_deliveries SET lease_expires_at=? WHERE workspace=? AND recipient=? AND lease_owner=? AND id=? AND lease_token=? AND state='claimed' AND lease_expires_at > ?").run(expires, principal.workspace, principal.agent, owner, id, token, now.toISOString());
814
+ if (!result.changes) return null;
815
+ return delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
816
+ }
817
+ async settleDelivery(principal, id, token, state, error, _retryPolicy) {
818
+ await this.ready();
819
+ const now = /* @__PURE__ */ new Date();
820
+ const owner = principal.instance ?? principal.agent;
821
+ this.db.exec("BEGIN IMMEDIATE");
822
+ try {
823
+ const current = this.db.prepare("SELECT delivery.*,message.delivery_policy FROM bridge_deliveries delivery JOIN bridge_messages message ON message.workspace=delivery.workspace AND message.id=delivery.message_id WHERE delivery.workspace=? AND delivery.recipient=? AND delivery.lease_owner=? AND delivery.id=? AND delivery.lease_token=? AND delivery.state='claimed' AND delivery.lease_expires_at>?").get(principal.workspace, principal.agent, owner, id, token, now.toISOString());
824
+ if (!current) {
825
+ this.db.exec("COMMIT");
826
+ return null;
827
+ }
828
+ const policy = parse(current.delivery_policy);
829
+ const exhausted = state === "retrying" && Number(current.cycle_attempt) >= Number(policy.maxAttempts);
830
+ const nextState = exhausted ? "dead" : state;
831
+ const exponential = Math.min(policy.retryMaxDelayMs, policy.retryBaseDelayMs * 2 ** Math.max(0, Number(current.cycle_attempt) - 1));
832
+ const jitter = 1 + (Math.random() * 2 - 1) * policy.retryJitterRatio;
833
+ const delay = nextState === "retrying" ? Math.max(1, Math.round(exponential * jitter)) : 0;
834
+ const available = nextState === "retrying" ? new Date(now.getTime() + delay).toISOString() : String(current.available_at);
835
+ const action = state === "acked" ? "ack" : state === "dead" ? "nack_dead" : exhausted ? "attempts_exhausted" : "nack_retry";
836
+ this.db.prepare("UPDATE bridge_deliveries SET state=?,available_at=?,last_error=?,lease_token=NULL,lease_owner=NULL,lease_expires_at=NULL,last_actor=?,last_action=? WHERE id=?").run(nextState, available, error?.slice(0, 1024) ?? null, principal.agent, action, id);
837
+ const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
838
+ this.db.exec("COMMIT");
839
+ return result;
840
+ } catch (caught) {
841
+ this.db.exec("ROLLBACK");
842
+ throw caught;
843
+ }
844
+ }
845
+ async diagnostics(principal) {
846
+ await this.ready();
847
+ const now = /* @__PURE__ */ new Date();
848
+ const nowText = now.toISOString();
849
+ const queue = this.db.prepare(`SELECT
850
+ sum(CASE WHEN state='pending' THEN 1 ELSE 0 END) AS pending,
851
+ sum(CASE WHEN state='claimed' THEN 1 ELSE 0 END) AS claimed,
852
+ sum(CASE WHEN state='retrying' THEN 1 ELSE 0 END) AS retrying,
853
+ sum(CASE WHEN state='dead' THEN 1 ELSE 0 END) AS dead,
854
+ sum(CASE WHEN state='cancelled' THEN 1 ELSE 0 END) AS cancelled,
855
+ min(CASE WHEN state IN ('pending','retrying') THEN available_at END) AS oldest_available,
856
+ sum(CASE WHEN state IN ('pending','retrying') AND available_at<=? THEN 1 ELSE 0 END) AS due,
857
+ sum(CASE WHEN state IN ('pending','retrying') AND available_at>? THEN 1 ELSE 0 END) AS scheduled,
858
+ sum(CASE WHEN state='claimed' AND lease_expires_at<=? THEN 1 ELSE 0 END) AS expired_leases,
859
+ min(CASE WHEN state IN ('pending','retrying') AND available_at<=? THEN available_at END) AS oldest_due
860
+ FROM bridge_deliveries WHERE workspace=? AND recipient=?`).get(nowText, nowText, nowText, nowText, principal.workspace, principal.agent);
861
+ const oldestDueAt = queue.oldest_due ? String(queue.oldest_due) : void 0;
862
+ return { schemaVersion: "local-v2", deliverySupported: true, pending: Number(queue.pending ?? 0), claimed: Number(queue.claimed ?? 0), retrying: Number(queue.retrying ?? 0), dead: Number(queue.dead ?? 0), cancelled: Number(queue.cancelled ?? 0), oldestAvailableAt: queue.oldest_available ? String(queue.oldest_available) : void 0, due: Number(queue.due ?? 0), scheduled: Number(queue.scheduled ?? 0), expiredLeases: Number(queue.expired_leases ?? 0), oldestDueAt, queueLagMs: oldestDueAt ? Math.max(0, now.getTime() - Date.parse(oldestDueAt)) : 0 };
863
+ }
864
+ async heartbeat(principal, leaseMs, runtimeType, capabilities = []) {
865
+ await this.ready();
866
+ const now = /* @__PURE__ */ new Date();
867
+ const nowText = now.toISOString();
868
+ const expires = new Date(now.getTime() + leaseMs).toISOString();
869
+ this.db.exec("BEGIN IMMEDIATE");
870
+ try {
871
+ this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, nowText);
872
+ const current = this.db.prepare("SELECT 1 FROM bridge_presence WHERE workspace=? AND agent=? AND instance=?").get(principal.workspace, principal.agent, principal.instance);
873
+ const agentCount = this.db.prepare("SELECT count(*) AS count FROM bridge_presence WHERE workspace=? AND agent=?").get(principal.workspace, principal.agent);
874
+ const workspaceCount = this.db.prepare("SELECT count(*) AS count FROM bridge_presence WHERE workspace=?").get(principal.workspace);
875
+ if (!current && (Number(agentCount.count) >= 128 || Number(workspaceCount.count) >= 4096)) {
876
+ throw Object.assign(new Error("active instance limit reached"), {
877
+ status: 429,
878
+ code: "presence_limit"
879
+ });
880
+ }
881
+ this.db.prepare("INSERT INTO bridge_presence (workspace,agent,instance,runtime_type,capabilities,lease_expires_at,last_seen_at) VALUES (?,?,?,?,?,?,?) 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").run(principal.workspace, principal.agent, principal.instance, runtimeType ?? null, JSON.stringify(capabilities), expires, nowText);
882
+ const result = presence(this.db.prepare("SELECT * FROM bridge_presence WHERE workspace=? AND agent=? AND instance=?").get(principal.workspace, principal.agent, principal.instance));
883
+ this.db.exec("COMMIT");
884
+ return result;
885
+ } catch (error) {
886
+ this.db.exec("ROLLBACK");
887
+ throw error;
888
+ }
889
+ }
890
+ async listPresence(principal) {
891
+ await this.ready();
892
+ const now = (/* @__PURE__ */ new Date()).toISOString();
893
+ this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, now);
894
+ const rows = this.db.prepare("SELECT * FROM bridge_presence WHERE workspace=? ORDER BY agent,instance").all(principal.workspace);
895
+ return rows.map(presence);
896
+ }
897
+ async listDeliveries(principal, query = {}) {
898
+ await this.ready();
899
+ const filters = { role: query.role ?? "all", states: [...query.states ?? []].sort(), messageId: query.messageId ?? null, recipient: query.recipient ?? null };
900
+ const scope = scopedCursorScope("deliveries", principal, filters);
901
+ const position = validateDeliveryCursorPosition(decodeScopedCursor(query.cursor, scope));
902
+ const limit = query.limit ?? 50;
903
+ const clauses = ["delivery.workspace=?"];
904
+ const args = [principal.workspace];
905
+ const role = query.role ?? "all";
906
+ clauses.push(role === "recipient" ? "delivery.recipient=?" : role === "publisher" ? "message.source=?" : "(delivery.recipient=? OR message.source=?)");
907
+ args.push(principal.agent);
908
+ if (role === "all") args.push(principal.agent);
909
+ if (position) {
910
+ clauses.push("(delivery.created_at>? OR (delivery.created_at=? AND delivery.id>?))");
911
+ args.push(position.createdAt, position.createdAt, position.id);
912
+ }
913
+ if (query.messageId) {
914
+ clauses.push("delivery.message_id=?");
915
+ args.push(query.messageId);
916
+ }
917
+ if (query.recipient) {
918
+ clauses.push("delivery.recipient=?");
919
+ args.push(query.recipient);
920
+ }
921
+ if (query.states?.length) {
922
+ clauses.push(`delivery.state IN (${query.states.map(() => "?").join(",")})`);
923
+ args.push(...query.states);
924
+ }
925
+ const rows = this.db.prepare(`SELECT delivery.* FROM bridge_deliveries delivery JOIN bridge_messages message ON message.workspace=delivery.workspace AND message.id=delivery.message_id WHERE ${clauses.join(" AND ")} ORDER BY delivery.created_at,delivery.id LIMIT ?`).all(...args, limit);
926
+ const deliveries = rows.map(delivery);
927
+ const last = deliveries[deliveries.length - 1];
928
+ return { deliveries, cursor: deliveries.length === limit && last ? encodeScopedCursor(scope, { createdAt: last.createdAt, id: last.id }) : void 0 };
929
+ }
930
+ async listDeliveryEvents(principal, deliveryId, query = {}) {
931
+ await this.ready();
932
+ const limit = query.limit ?? 50;
933
+ const scope = scopedCursorScope("delivery-events", principal, { deliveryId });
934
+ const position = validateEventCursorPosition(decodeScopedCursor(query.cursor, scope));
935
+ const rows = this.db.prepare("SELECT event.* FROM bridge_delivery_events event JOIN bridge_messages message ON message.workspace=event.workspace AND message.id=event.message_id WHERE event.workspace=? AND event.delivery_id=? AND event.sequence>? AND (event.recipient=? OR message.source=?) ORDER BY event.sequence LIMIT ?").all(principal.workspace, deliveryId, position ?? "0", principal.agent, principal.agent, limit);
936
+ const events = rows.map(deliveryEvent);
937
+ const last = events[events.length - 1];
938
+ return { events, cursor: events.length === limit && last ? encodeScopedCursor(scope, last.sequence) : void 0 };
939
+ }
940
+ async cancelDelivery(principal, id) {
941
+ await this.ready();
942
+ this.db.exec("BEGIN IMMEDIATE");
943
+ try {
944
+ const current = this.db.prepare("SELECT delivery.* FROM bridge_deliveries delivery JOIN bridge_messages message ON message.workspace=delivery.workspace AND message.id=delivery.message_id WHERE delivery.id=? AND delivery.workspace=? AND message.source=?").get(id, principal.workspace, principal.agent);
945
+ if (!current) {
946
+ this.db.exec("COMMIT");
947
+ return null;
948
+ }
949
+ if (current.state === "cancelled") {
950
+ this.db.exec("COMMIT");
951
+ return delivery(current);
952
+ }
953
+ if (!["pending", "retrying", "claimed"].includes(String(current.state))) throw new DeliveryStateConflictError(`cannot cancel a ${current.state} delivery`);
954
+ this.db.prepare("UPDATE bridge_deliveries SET state='cancelled',lease_token=NULL,lease_owner=NULL,lease_expires_at=NULL,last_error=NULL,last_actor=?,last_action='cancel' WHERE id=?").run(principal.agent, id);
955
+ const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
956
+ this.db.exec("COMMIT");
957
+ return result;
958
+ } catch (error) {
959
+ this.db.exec("ROLLBACK");
960
+ throw error;
961
+ }
962
+ }
963
+ async requeueDelivery(principal, id) {
964
+ await this.ready();
965
+ this.db.exec("BEGIN IMMEDIATE");
966
+ try {
967
+ const current = this.db.prepare("SELECT delivery.*,message.expires_at,message.delivery_policy FROM bridge_deliveries delivery JOIN bridge_messages message ON message.workspace=delivery.workspace AND message.id=delivery.message_id WHERE delivery.id=? AND delivery.workspace=? AND message.source=?").get(id, principal.workspace, principal.agent);
968
+ if (!current) {
969
+ this.db.exec("COMMIT");
970
+ return null;
971
+ }
972
+ if (!["dead", "cancelled"].includes(String(current.state))) throw new DeliveryStateConflictError(`cannot requeue a ${current.state} delivery`);
973
+ const now = (/* @__PURE__ */ new Date()).toISOString();
974
+ if (current.expires_at && String(current.expires_at) <= now) throw new DeliveryStateConflictError("cannot requeue an expired message");
975
+ const policy = parse(current.delivery_policy);
976
+ const availableAt = policy.notBefore && policy.notBefore > now ? policy.notBefore : now;
977
+ this.db.prepare("UPDATE bridge_deliveries SET state='pending',available_at=?,cycle_attempt=0,requeue_count=requeue_count+1,lease_token=NULL,lease_owner=NULL,lease_expires_at=NULL,last_error=NULL,last_actor=?,last_action='requeue' WHERE id=?").run(availableAt, principal.agent, id);
978
+ const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
979
+ this.db.exec("COMMIT");
980
+ return result;
981
+ } catch (error) {
982
+ this.db.exec("ROLLBACK");
983
+ throw error;
984
+ }
985
+ }
986
+ async close() {
987
+ this.db.close();
988
+ }
989
+ };
990
+
991
+ export {
992
+ retrySqliteBusy,
993
+ LOCAL_SQLITE_APPLICATION_ID,
994
+ EDGE_SQLITE_APPLICATION_ID,
995
+ SQLITE_DATABASE_SCHEMA_VERSION,
996
+ SQLITE_METADATA_TABLE,
997
+ LOCAL_SQLITE_SCHEMA_CONTRACTS,
998
+ EDGE_SQLITE_SCHEMA_CONTRACTS,
999
+ isSupportedLocalSqliteSchemaContract,
1000
+ isSupportedEdgeSqliteSchemaContract,
1001
+ SQLiteDatabaseContractError,
1002
+ sqliteSchemaContractHash,
1003
+ assertLocalUpgradeCandidate,
1004
+ assertEdgeUpgradeCandidate,
1005
+ installLocalAuthorityMarkers,
1006
+ installEdgeMarkers,
1007
+ identifySqliteDatabase,
1008
+ assertEdgeDatabase,
1009
+ assertLocalAuthorityDatabase,
1010
+ verifySqliteHealth,
1011
+ SQLiteBridgeStore
1012
+ };
1013
+ //# sourceMappingURL=chunk-5GMB3A77.js.map