@creatornader/agent-bridge 0.6.2 → 0.6.4

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.
@@ -27,12 +27,18 @@ var EDGE_SQLITE_APPLICATION_ID = 1094862661;
27
27
  var SQLITE_DATABASE_SCHEMA_VERSION = 1;
28
28
  var SQLITE_METADATA_TABLE = "agent_bridge_metadata";
29
29
  var LOCAL_SQLITE_SCHEMA_CONTRACTS = Object.freeze([
30
+ Object.freeze({ id: "current-upgraded-write-coordinator", sha256: "d8504fc1092045ac5dc50aa64b0291a68ccb57af1cfebe5d1997b79f433b2eab" }),
31
+ Object.freeze({ id: "current-upgraded-project-column-write-coordinator", sha256: "37845f7e49b4c56dffe40295ffb31320d4b33c2acc2873e48514a1b8e36a490b" }),
32
+ Object.freeze({ id: "current-upgraded-delivery-policy-write-coordinator", sha256: "a0079dfe925a529b613425f779b5d978464a4c6b23e6cb7439fbf74f70da89bb" }),
33
+ Object.freeze({ id: "current-upgraded-delivery-events-write-coordinator", sha256: "a7de84c001b5f467a10c3aefdb6a92c86c7ce621cb5889cde90bc3c9f0b2f2ab" }),
30
34
  Object.freeze({ id: "current-created-schema", sha256: "e2f978c27ea5d151a4b4b3ec349166833441e42401fedfe60dc3ba20f3d713aa" }),
31
35
  Object.freeze({ id: "current-upgraded-project-column", sha256: "cb0975bbc1ccc7a2a66d8f4d76df619804449cf81119972337afad6cdab64451" }),
32
36
  Object.freeze({ id: "current-upgraded-delivery-policy", sha256: "d4fd8905ea73057b27662454c16c038834f5a1bf017db9cfc041792104aa2e6d" }),
33
37
  Object.freeze({ id: "current-upgraded-delivery-events", sha256: "35bdab0cbdf4ce619ba1c7d36032379ae4d9692bd7509e49bb90b409bbbcf632" })
34
38
  ]);
35
39
  var EDGE_SQLITE_SCHEMA_CONTRACTS = Object.freeze([
40
+ Object.freeze({ id: "current-upgraded-write-coordinator", sha256: "973d010ff81a726d6520dc482a4450697ef2295f58f42a45dee30b6174b7b9f9" }),
41
+ Object.freeze({ id: "current-upgraded-project-column-migration-gate-write-coordinator", sha256: "06c159fe10fade817b211197dea14f178e75f54c2f55b69d63f8fafb629e731c" }),
36
42
  Object.freeze({ id: "current-created-schema", sha256: "27f22b2f4024585c87e8d6f76f8999a8df92bb1f585d46594a7a1e852fd53c4c" }),
37
43
  Object.freeze({ id: "current-upgraded-project-column", sha256: "171ae11f520b4963f517d3102de64cb8a5f45c080078ff02e20aeb17891b585c" }),
38
44
  Object.freeze({ id: "current-upgraded-project-column-migration-gate", sha256: "790b4bfed373ff776ba6700065154f7a3cbbecd94f37ea09a654768ac2a8455c" }),
@@ -115,7 +121,8 @@ var localColumns = {
115
121
  "action",
116
122
  "created_at"
117
123
  ],
118
- bridge_presence: ["workspace", "agent", "instance", "runtime_type", "capabilities", "lease_expires_at", "last_seen_at"]
124
+ bridge_presence: ["workspace", "agent", "instance", "runtime_type", "capabilities", "lease_expires_at", "last_seen_at"],
125
+ bridge_write_gates: ["gate_key", "lease_token", "lease_expires_at"]
119
126
  };
120
127
  var localObjects = /* @__PURE__ */ new Map([
121
128
  ["agent_bridge_metadata", ["table", "agent_bridge_metadata"]],
@@ -124,6 +131,7 @@ var localObjects = /* @__PURE__ */ new Map([
124
131
  ["bridge_messages", ["table", "bridge_messages"]],
125
132
  ["bridge_presence", ["table", "bridge_presence"]],
126
133
  ["bridge_receipts", ["table", "bridge_receipts"]],
134
+ ["bridge_write_gates", ["table", "bridge_write_gates"]],
127
135
  ["bridge_deliveries_claim", ["index", "bridge_deliveries"]],
128
136
  ["bridge_deliveries_publisher", ["index", "bridge_deliveries"]],
129
137
  ["bridge_deliveries_terminal", ["index", "bridge_deliveries"]],
@@ -149,7 +157,8 @@ var edgeColumns = {
149
157
  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"],
150
158
  edge_migration_gates: ["scope_key", "state", "operation_id", "lease_token", "lease_expires_at", "updated_at"],
151
159
  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"],
152
- edge_inbox: ["scope_key", "message_id", "remote_sequence", "sequence_key", "workspace", "project", "source", "type", "thread_id", "created_at", "expires_at", "message_json"]
160
+ edge_inbox: ["scope_key", "message_id", "remote_sequence", "sequence_key", "workspace", "project", "source", "type", "thread_id", "created_at", "expires_at", "message_json"],
161
+ edge_write_gates: ["gate_key", "lease_token", "lease_expires_at"]
153
162
  };
154
163
  var edgeObjects = /* @__PURE__ */ new Map([
155
164
  ["agent_bridge_metadata", ["table", "agent_bridge_metadata"]],
@@ -157,6 +166,7 @@ var edgeObjects = /* @__PURE__ */ new Map([
157
166
  ["edge_migration_gates", ["table", "edge_migration_gates"]],
158
167
  ["edge_outbox", ["table", "edge_outbox"]],
159
168
  ["edge_inbox", ["table", "edge_inbox"]],
169
+ ["edge_write_gates", ["table", "edge_write_gates"]],
160
170
  ["edge_inbox_created", ["index", "edge_inbox"]],
161
171
  ["edge_inbox_cursor", ["index", "edge_inbox"]],
162
172
  ["edge_inbox_project", ["index", "edge_inbox"]],
@@ -377,6 +387,9 @@ async function retrySqliteBusy(operation, timeoutMs) {
377
387
  }
378
388
  }
379
389
  }
390
+ function rollbackSqliteTransaction(database) {
391
+ if (database.isTransaction) database.exec("ROLLBACK");
392
+ }
380
393
 
381
394
  // src/sqlite-bridge-store.ts
382
395
  var require2 = createRequire(import.meta.url);
@@ -385,9 +398,6 @@ function openDatabase(path) {
385
398
  return new DatabaseSync(path);
386
399
  }
387
400
  var schema = `
388
- PRAGMA journal_mode = WAL;
389
- PRAGMA synchronous = FULL;
390
- PRAGMA foreign_keys = ON;
391
401
  CREATE TABLE IF NOT EXISTS bridge_messages (
392
402
  sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, workspace TEXT NOT NULL, project TEXT, source TEXT NOT NULL,
393
403
  type TEXT NOT NULL, content TEXT NOT NULL, content_type TEXT NOT NULL, data TEXT, targets TEXT NOT NULL DEFAULT '[]',
@@ -452,6 +462,7 @@ CREATE INDEX IF NOT EXISTS bridge_presence_active ON bridge_presence(workspace,
452
462
  CREATE TRIGGER IF NOT EXISTS bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END;
453
463
  CREATE TRIGGER IF NOT EXISTS bridge_messages_no_delete BEFORE DELETE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END;
454
464
  `;
465
+ var writeGateSchema = "CREATE TABLE IF NOT EXISTS bridge_write_gates (gate_key TEXT PRIMARY KEY, lease_token TEXT, lease_expires_at TEXT)";
455
466
  function stringify(value) {
456
467
  return value === void 0 ? null : JSON.stringify(value);
457
468
  }
@@ -497,6 +508,70 @@ var SQLiteBridgeStore = class {
497
508
  throw error;
498
509
  }
499
510
  }
511
+ async write(work) {
512
+ if (this.databasePath === ":memory:") return work();
513
+ const token = randomUUID();
514
+ const waitMs = process.platform === "win32" ? 6e4 : SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS;
515
+ const deadline = Date.now() + waitMs;
516
+ let pause = 2;
517
+ while (true) {
518
+ const now = /* @__PURE__ */ new Date();
519
+ const claimed = await retrySqliteBusy(() => {
520
+ this.db.exec("BEGIN IMMEDIATE");
521
+ try {
522
+ this.db.prepare("INSERT INTO bridge_write_gates (gate_key,lease_token,lease_expires_at) VALUES ('local',NULL,NULL) ON CONFLICT(gate_key) DO NOTHING").run();
523
+ const result2 = this.db.prepare("UPDATE bridge_write_gates SET lease_token=?,lease_expires_at=? WHERE gate_key='local' AND (lease_token IS NULL OR lease_expires_at<=?)").run(token, new Date(now.getTime() + 3e4).toISOString(), now.toISOString());
524
+ this.db.exec("COMMIT");
525
+ return result2.changes === 1;
526
+ } catch (error) {
527
+ rollbackSqliteTransaction(this.db);
528
+ throw error;
529
+ }
530
+ }, Math.max(1, deadline - Date.now()));
531
+ if (claimed) break;
532
+ if (Date.now() >= deadline) throw new Error("local write coordinator remained busy");
533
+ await new Promise((resolve) => setTimeout(resolve, pause));
534
+ pause = Math.min(Math.ceil(pause * 1.7), 25);
535
+ }
536
+ let completed = false;
537
+ let result;
538
+ try {
539
+ result = await work();
540
+ completed = true;
541
+ } finally {
542
+ try {
543
+ await retrySqliteBusy(() => {
544
+ this.db.exec("BEGIN IMMEDIATE");
545
+ try {
546
+ this.db.prepare("UPDATE bridge_write_gates SET lease_token=NULL,lease_expires_at=NULL WHERE gate_key='local' AND lease_token=?").run(token);
547
+ this.db.exec("COMMIT");
548
+ } catch (error) {
549
+ rollbackSqliteTransaction(this.db);
550
+ throw error;
551
+ }
552
+ }, waitMs);
553
+ } catch (error) {
554
+ if (!completed) throw error;
555
+ }
556
+ }
557
+ return result;
558
+ }
559
+ async bootstrapWriteGate(timeoutMs) {
560
+ await retrySqliteBusy(() => this.db.exec("PRAGMA journal_mode = WAL"), timeoutMs);
561
+ const exists = () => Boolean(this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='bridge_write_gates'").get());
562
+ if (exists()) return;
563
+ await retrySqliteBusy(() => {
564
+ if (exists()) return;
565
+ this.db.exec("BEGIN IMMEDIATE");
566
+ try {
567
+ if (!exists()) this.db.exec(writeGateSchema);
568
+ this.db.exec("COMMIT");
569
+ } catch (error) {
570
+ rollbackSqliteTransaction(this.db);
571
+ throw error;
572
+ }
573
+ }, timeoutMs);
574
+ }
500
575
  restrictFiles(includeSidecars = true) {
501
576
  if (this.databasePath === ":memory:") return;
502
577
  const paths = includeSidecars ? [this.databasePath, `${this.databasePath}-wal`, `${this.databasePath}-shm`] : [this.databasePath];
@@ -526,33 +601,36 @@ var SQLiteBridgeStore = class {
526
601
  if (encoded < 3051003) throw new Error("SQLite 3.51.3 or newer is required");
527
602
  assertLocalUpgradeCandidate(this.db);
528
603
  const initializationTimeoutMs = Math.max(this.busyTimeoutMs, SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS);
529
- await retrySqliteBusy(() => this.db.exec(schema), initializationTimeoutMs);
530
- await retrySqliteBusy(() => this.db.exec("BEGIN IMMEDIATE"), initializationTimeoutMs);
531
- try {
532
- const columns = this.db.prepare("PRAGMA table_info(bridge_messages)").all();
533
- if (!columns.some((column) => column.name === "project")) {
534
- this.db.exec("ALTER TABLE bridge_messages ADD COLUMN project TEXT");
535
- }
536
- const addedDeliveryPolicy = !columns.some((column) => column.name === "delivery_policy");
537
- if (addedDeliveryPolicy) {
538
- this.db.exec("DROP TRIGGER IF EXISTS bridge_messages_no_update");
539
- 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}'`);
540
- this.db.exec("CREATE TRIGGER bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END");
541
- }
542
- const legacyPolicy = addedDeliveryPolicy || Boolean(this.db.prepare(`SELECT 1 FROM bridge_messages
604
+ await this.bootstrapWriteGate(initializationTimeoutMs);
605
+ this.db.exec("PRAGMA synchronous = FULL; PRAGMA foreign_keys = ON");
606
+ await this.write(async () => {
607
+ await retrySqliteBusy(() => this.db.exec("BEGIN IMMEDIATE"), initializationTimeoutMs);
608
+ try {
609
+ this.db.exec(schema);
610
+ const columns = this.db.prepare("PRAGMA table_info(bridge_messages)").all();
611
+ if (!columns.some((column) => column.name === "project")) {
612
+ this.db.exec("ALTER TABLE bridge_messages ADD COLUMN project TEXT");
613
+ }
614
+ const addedDeliveryPolicy = !columns.some((column) => column.name === "delivery_policy");
615
+ if (addedDeliveryPolicy) {
616
+ this.db.exec("DROP TRIGGER IF EXISTS bridge_messages_no_update");
617
+ 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}'`);
618
+ this.db.exec("CREATE TRIGGER bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END");
619
+ }
620
+ const legacyPolicy = addedDeliveryPolicy || Boolean(this.db.prepare(`SELECT 1 FROM bridge_messages
543
621
  WHERE json_type(delivery_policy,'$.publisherOwned') IS NOT NULL
544
622
  OR json_type(delivery_policy,'$.baseDelayMs') IS NOT NULL
545
623
  OR json_type(delivery_policy,'$.maxDelayMs') IS NOT NULL
546
624
  OR json_type(delivery_policy,'$.jitterRatio') IS NOT NULL
547
625
  LIMIT 1`).get());
548
- if (legacyPolicy) {
549
- this.db.exec("DROP TRIGGER IF EXISTS bridge_messages_no_update");
550
- if (addedDeliveryPolicy) {
551
- this.db.exec(`UPDATE bridge_messages SET delivery_policy=CASE
626
+ if (legacyPolicy) {
627
+ this.db.exec("DROP TRIGGER IF EXISTS bridge_messages_no_update");
628
+ if (addedDeliveryPolicy) {
629
+ this.db.exec(`UPDATE bridge_messages SET delivery_policy=CASE
552
630
  WHEN json_array_length(targets)=0 THEN '{"mode":"mailbox"}'
553
631
  ELSE '{"mode":"leased","maxAttempts":5,"retryBaseDelayMs":1000,"retryMaxDelayMs":60000,"retryJitterRatio":0.2}' END`);
554
- } else {
555
- this.db.exec(`UPDATE bridge_messages SET delivery_policy=CASE
632
+ } else {
633
+ this.db.exec(`UPDATE bridge_messages SET delivery_policy=CASE
556
634
  WHEN json_extract(delivery_policy,'$.mode')='mailbox' THEN
557
635
  json_remove(delivery_policy,'$.publisherOwned','$.maxAttempts','$.baseDelayMs','$.maxDelayMs','$.jitterRatio','$.retryBaseDelayMs','$.retryMaxDelayMs','$.retryJitterRatio','$.notBefore')
558
636
  ELSE json_set(
@@ -567,21 +645,21 @@ var SQLiteBridgeStore = class {
567
645
  OR json_type(delivery_policy,'$.baseDelayMs') IS NOT NULL
568
646
  OR json_type(delivery_policy,'$.maxDelayMs') IS NOT NULL
569
647
  OR json_type(delivery_policy,'$.jitterRatio') IS NOT NULL`);
648
+ }
649
+ this.db.exec("CREATE TRIGGER bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END");
570
650
  }
571
- this.db.exec("CREATE TRIGGER bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END");
572
- }
573
- const deliveryColumns = this.db.prepare("PRAGMA table_info(bridge_deliveries)").all();
574
- if (!deliveryColumns.some((column) => column.name === "last_action")) {
575
- 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");
576
- const eventColumns = this.db.prepare("PRAGMA table_info(bridge_delivery_events)").all();
577
- this.db.exec(`CREATE TABLE bridge_delivery_events_v10 (
651
+ const deliveryColumns = this.db.prepare("PRAGMA table_info(bridge_deliveries)").all();
652
+ if (!deliveryColumns.some((column) => column.name === "last_action")) {
653
+ 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");
654
+ const eventColumns = this.db.prepare("PRAGMA table_info(bridge_delivery_events)").all();
655
+ this.db.exec(`CREATE TABLE bridge_delivery_events_v10 (
578
656
  sequence INTEGER PRIMARY KEY AUTOINCREMENT, delivery_id TEXT NOT NULL,
579
657
  message_id TEXT NOT NULL, workspace TEXT NOT NULL, recipient TEXT NOT NULL,
580
658
  from_state TEXT, to_state TEXT NOT NULL, attempt INTEGER NOT NULL,
581
659
  cycle_attempt INTEGER NOT NULL, requeue_count INTEGER NOT NULL,
582
660
  lease_owner TEXT, error TEXT, actor TEXT NOT NULL, action TEXT NOT NULL, created_at TEXT NOT NULL)`);
583
- if (eventColumns.length) {
584
- this.db.exec(`INSERT INTO bridge_delivery_events_v10
661
+ if (eventColumns.length) {
662
+ this.db.exec(`INSERT INTO bridge_delivery_events_v10
585
663
  (sequence,delivery_id,message_id,workspace,recipient,from_state,to_state,attempt,cycle_attempt,requeue_count,lease_owner,error,actor,action,created_at)
586
664
  SELECT event.sequence,event.delivery_id,event.message_id,event.workspace,event.recipient,
587
665
  event.from_state,event.to_state,event.attempt,event.attempt,0,event.lease_owner,event.error,
@@ -609,9 +687,9 @@ var SQLiteBridgeStore = class {
609
687
  ELSE 'created' END,event.created_at
610
688
  FROM bridge_delivery_events event JOIN bridge_messages message
611
689
  ON message.workspace=event.workspace AND message.id=event.message_id`);
612
- this.db.exec("DROP TABLE bridge_delivery_events");
613
- }
614
- this.db.exec(`CREATE TABLE bridge_deliveries_v10 (
690
+ this.db.exec("DROP TABLE bridge_delivery_events");
691
+ }
692
+ this.db.exec(`CREATE TABLE bridge_deliveries_v10 (
615
693
  id TEXT PRIMARY KEY, message_id TEXT NOT NULL, workspace TEXT NOT NULL, recipient TEXT NOT NULL,
616
694
  state TEXT NOT NULL, attempt INTEGER NOT NULL DEFAULT 0, cycle_attempt INTEGER NOT NULL DEFAULT 0,
617
695
  requeue_count INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, priority_rank INTEGER NOT NULL,
@@ -622,7 +700,7 @@ var SQLiteBridgeStore = class {
622
700
  CHECK(attempt>=0 AND cycle_attempt>=0 AND cycle_attempt<=attempt AND requeue_count>=0),
623
701
  CHECK(priority_rank BETWEEN 0 AND 2),
624
702
  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)))`);
625
- this.db.exec(`INSERT INTO bridge_deliveries_v10
703
+ this.db.exec(`INSERT INTO bridge_deliveries_v10
626
704
  (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)
627
705
  SELECT delivery.id,delivery.message_id,delivery.workspace,delivery.recipient,delivery.state,delivery.attempt,delivery.attempt,0,
628
706
  message.created_at,CASE message.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 ELSE 2 END,
@@ -649,13 +727,13 @@ var SQLiteBridgeStore = class {
649
727
  ELSE 'created' END
650
728
  FROM bridge_deliveries delivery JOIN bridge_messages message
651
729
  ON message.workspace=delivery.workspace AND message.id=delivery.message_id`);
652
- this.db.exec("DROP TABLE bridge_deliveries; ALTER TABLE bridge_deliveries_v10 RENAME TO bridge_deliveries");
653
- this.db.exec("ALTER TABLE bridge_delivery_events_v10 RENAME TO bridge_delivery_events");
654
- }
655
- 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)");
656
- 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;
730
+ this.db.exec("DROP TABLE bridge_deliveries; ALTER TABLE bridge_deliveries_v10 RENAME TO bridge_deliveries");
731
+ this.db.exec("ALTER TABLE bridge_delivery_events_v10 RENAME TO bridge_delivery_events");
732
+ }
733
+ 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)");
734
+ 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;
657
735
  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`);
658
- this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_messages_policy_insert BEFORE INSERT ON bridge_messages
736
+ this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_messages_policy_insert BEFORE INSERT ON bridge_messages
659
737
  WHEN NOT (
660
738
  json_valid(NEW.delivery_policy) AND json_type(NEW.delivery_policy)='object'
661
739
  AND NOT EXISTS (SELECT 1 FROM json_each(NEW.delivery_policy) WHERE key NOT IN ('mode','maxAttempts','retryBaseDelayMs','retryMaxDelayMs','retryJitterRatio','notBefore'))
@@ -676,17 +754,18 @@ var SQLiteBridgeStore = class {
676
754
  )
677
755
  )
678
756
  ) BEGIN SELECT RAISE(ABORT,'invalid delivery policy'); END`);
679
- this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_messages_domain_insert BEFORE INSERT ON bridge_messages
757
+ this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_messages_domain_insert BEFORE INSERT ON bridge_messages
680
758
  WHEN NOT (json_valid(NEW.targets) AND json_type(NEW.targets)='array' AND NEW.priority IN ('info','high','urgent'))
681
759
  BEGIN SELECT RAISE(ABORT,'invalid message domain'); END`);
682
- this.db.exec("CREATE INDEX IF NOT EXISTS bridge_messages_project ON bridge_messages(workspace, project, sequence)");
683
- installLocalAuthorityMarkers(this.db);
684
- this.db.exec("COMMIT");
685
- } catch (error) {
686
- this.db.exec("ROLLBACK");
687
- throw error;
688
- }
689
- this.restrictFiles();
760
+ this.db.exec("CREATE INDEX IF NOT EXISTS bridge_messages_project ON bridge_messages(workspace, project, sequence)");
761
+ installLocalAuthorityMarkers(this.db);
762
+ this.db.exec("COMMIT");
763
+ } catch (error) {
764
+ rollbackSqliteTransaction(this.db);
765
+ throw error;
766
+ }
767
+ this.restrictFiles();
768
+ });
690
769
  }
691
770
  async ready() {
692
771
  await this.initialize();
@@ -695,32 +774,34 @@ var SQLiteBridgeStore = class {
695
774
  await this.ready();
696
775
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
697
776
  const storedPolicy = input.deliveryPolicy ?? (input.targets.length ? { mode: "leased", maxAttempts: 5, retryBaseDelayMs: 1e3, retryMaxDelayMs: 6e4, retryJitterRatio: 0.2 } : { mode: "mailbox" });
698
- this.db.exec("BEGIN IMMEDIATE");
699
- try {
700
- if (input.idempotencyKey) {
701
- const existing = this.db.prepare("SELECT * FROM bridge_messages WHERE workspace = ? AND source = ? AND idempotency_key = ?").get(input.workspace, input.source, input.idempotencyKey);
702
- if (existing) {
703
- const replay = message(existing);
704
- assertIdempotentReplay(replay, input);
705
- this.db.exec("COMMIT");
706
- return { message: replay, created: false };
777
+ return this.write(() => {
778
+ this.db.exec("BEGIN IMMEDIATE");
779
+ try {
780
+ if (input.idempotencyKey) {
781
+ const existing = this.db.prepare("SELECT * FROM bridge_messages WHERE workspace = ? AND source = ? AND idempotency_key = ?").get(input.workspace, input.source, input.idempotencyKey);
782
+ if (existing) {
783
+ const replay = message(existing);
784
+ assertIdempotentReplay(replay, input);
785
+ this.db.exec("COMMIT");
786
+ return { message: replay, created: false };
787
+ }
707
788
  }
708
- }
709
- 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);
710
- if (storedPolicy.mode === "leased") {
711
- const availableAt = storedPolicy.notBefore && storedPolicy.notBefore > createdAt ? storedPolicy.notBefore : createdAt;
712
- const priorityRank = input.priority === "urgent" ? 0 : input.priority === "high" ? 1 : 2;
713
- for (const recipient of input.targets) {
714
- 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");
789
+ 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);
790
+ if (storedPolicy.mode === "leased") {
791
+ const availableAt = storedPolicy.notBefore && storedPolicy.notBefore > createdAt ? storedPolicy.notBefore : createdAt;
792
+ const priorityRank = input.priority === "urgent" ? 0 : input.priority === "high" ? 1 : 2;
793
+ for (const recipient of input.targets) {
794
+ 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");
795
+ }
715
796
  }
797
+ const row = this.db.prepare("SELECT * FROM bridge_messages WHERE id = ?").get(input.id);
798
+ this.db.exec("COMMIT");
799
+ return { message: message(row), created: true };
800
+ } catch (error) {
801
+ rollbackSqliteTransaction(this.db);
802
+ throw error;
716
803
  }
717
- const row = this.db.prepare("SELECT * FROM bridge_messages WHERE id = ?").get(input.id);
718
- this.db.exec("COMMIT");
719
- return { message: message(row), created: true };
720
- } catch (error) {
721
- this.db.exec("ROLLBACK");
722
- throw error;
723
- }
804
+ });
724
805
  }
725
806
  async listMessages(principal, query = {}) {
726
807
  await this.ready();
@@ -777,17 +858,19 @@ var SQLiteBridgeStore = class {
777
858
  }
778
859
  async recordReceipt(principal, messageIds, readAt = /* @__PURE__ */ new Date()) {
779
860
  await this.ready();
780
- 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=?))");
781
- let changed = 0;
782
- this.db.exec("BEGIN IMMEDIATE");
783
- try {
784
- for (const id of messageIds) changed += Number(stmt.run(principal.agent, readAt.toISOString(), principal.workspace, id, principal.agent).changes);
785
- this.db.exec("COMMIT");
786
- return changed;
787
- } catch (e) {
788
- this.db.exec("ROLLBACK");
789
- throw e;
790
- }
861
+ return this.write(() => {
862
+ 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=?))");
863
+ let changed = 0;
864
+ this.db.exec("BEGIN IMMEDIATE");
865
+ try {
866
+ for (const id of messageIds) changed += Number(stmt.run(principal.agent, readAt.toISOString(), principal.workspace, id, principal.agent).changes);
867
+ this.db.exec("COMMIT");
868
+ return changed;
869
+ } catch (e) {
870
+ rollbackSqliteTransaction(this.db);
871
+ throw e;
872
+ }
873
+ });
791
874
  }
792
875
  async claimDelivery(principal, options) {
793
876
  await this.ready();
@@ -795,63 +878,69 @@ var SQLiteBridgeStore = class {
795
878
  const nowText = now.toISOString();
796
879
  const expires = new Date(now.getTime() + options.leaseMs).toISOString();
797
880
  const owner = principal.instance ?? principal.agent;
798
- this.db.exec("BEGIN IMMEDIATE");
799
- try {
800
- 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 (? IS NULL OR message_id=?) 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, options.messageId ?? null, options.messageId ?? null, nowText);
801
- 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 (? IS NULL OR message_id=?) 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, options.messageId ?? null, options.messageId ?? null, nowText);
802
- 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 (? IS NULL OR message_id=?) 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, options.messageId ?? null, options.messageId ?? null, nowText);
803
- 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 (? IS NULL OR delivery.message_id=?) 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, options.messageId ?? null, options.messageId ?? null, nowText, nowText);
804
- if (!candidate) {
881
+ return this.write(() => {
882
+ this.db.exec("BEGIN IMMEDIATE");
883
+ try {
884
+ 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 (? IS NULL OR message_id=?) 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, options.messageId ?? null, options.messageId ?? null, nowText);
885
+ 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 (? IS NULL OR message_id=?) 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, options.messageId ?? null, options.messageId ?? null, nowText);
886
+ 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 (? IS NULL OR message_id=?) 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, options.messageId ?? null, options.messageId ?? null, nowText);
887
+ 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 (? IS NULL OR delivery.message_id=?) 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, options.messageId ?? null, options.messageId ?? null, nowText, nowText);
888
+ if (!candidate) {
889
+ this.db.exec("COMMIT");
890
+ return null;
891
+ }
892
+ const candidateId = String(candidate.id);
893
+ const token = randomUUID();
894
+ 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);
895
+ const claimed = this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(candidateId);
805
896
  this.db.exec("COMMIT");
806
- return null;
897
+ return delivery(claimed);
898
+ } catch (e) {
899
+ rollbackSqliteTransaction(this.db);
900
+ throw e;
807
901
  }
808
- const candidateId = String(candidate.id);
809
- const token = randomUUID();
810
- 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);
811
- const claimed = this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(candidateId);
812
- this.db.exec("COMMIT");
813
- return delivery(claimed);
814
- } catch (e) {
815
- this.db.exec("ROLLBACK");
816
- throw e;
817
- }
902
+ });
818
903
  }
819
904
  async renewDelivery(principal, id, token, leaseMs) {
820
905
  await this.ready();
821
- const now = /* @__PURE__ */ new Date();
822
- const expires = new Date(now.getTime() + leaseMs).toISOString();
823
- const owner = principal.instance ?? principal.agent;
824
- 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());
825
- if (!result.changes) return null;
826
- return delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
906
+ return this.write(() => {
907
+ const now = /* @__PURE__ */ new Date();
908
+ const expires = new Date(now.getTime() + leaseMs).toISOString();
909
+ const owner = principal.instance ?? principal.agent;
910
+ 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());
911
+ if (!result.changes) return null;
912
+ return delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
913
+ });
827
914
  }
828
915
  async settleDelivery(principal, id, token, state, error, _retryPolicy) {
829
916
  await this.ready();
830
917
  const now = /* @__PURE__ */ new Date();
831
918
  const owner = principal.instance ?? principal.agent;
832
- this.db.exec("BEGIN IMMEDIATE");
833
- try {
834
- 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());
835
- if (!current) {
919
+ return this.write(() => {
920
+ this.db.exec("BEGIN IMMEDIATE");
921
+ try {
922
+ 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());
923
+ if (!current) {
924
+ this.db.exec("COMMIT");
925
+ return null;
926
+ }
927
+ const policy = parse(current.delivery_policy);
928
+ const exhausted = state === "retrying" && Number(current.cycle_attempt) >= Number(policy.maxAttempts);
929
+ const nextState = exhausted ? "dead" : state;
930
+ const exponential = Math.min(policy.retryMaxDelayMs, policy.retryBaseDelayMs * 2 ** Math.max(0, Number(current.cycle_attempt) - 1));
931
+ const jitter = 1 + (Math.random() * 2 - 1) * policy.retryJitterRatio;
932
+ const delay = nextState === "retrying" ? Math.max(1, Math.round(exponential * jitter)) : 0;
933
+ const available = nextState === "retrying" ? new Date(now.getTime() + delay).toISOString() : String(current.available_at);
934
+ const action = state === "acked" ? "ack" : state === "dead" ? "nack_dead" : exhausted ? "attempts_exhausted" : "nack_retry";
935
+ 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);
936
+ const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
836
937
  this.db.exec("COMMIT");
837
- return null;
938
+ return result;
939
+ } catch (caught) {
940
+ rollbackSqliteTransaction(this.db);
941
+ throw caught;
838
942
  }
839
- const policy = parse(current.delivery_policy);
840
- const exhausted = state === "retrying" && Number(current.cycle_attempt) >= Number(policy.maxAttempts);
841
- const nextState = exhausted ? "dead" : state;
842
- const exponential = Math.min(policy.retryMaxDelayMs, policy.retryBaseDelayMs * 2 ** Math.max(0, Number(current.cycle_attempt) - 1));
843
- const jitter = 1 + (Math.random() * 2 - 1) * policy.retryJitterRatio;
844
- const delay = nextState === "retrying" ? Math.max(1, Math.round(exponential * jitter)) : 0;
845
- const available = nextState === "retrying" ? new Date(now.getTime() + delay).toISOString() : String(current.available_at);
846
- const action = state === "acked" ? "ack" : state === "dead" ? "nack_dead" : exhausted ? "attempts_exhausted" : "nack_retry";
847
- 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);
848
- const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
849
- this.db.exec("COMMIT");
850
- return result;
851
- } catch (caught) {
852
- this.db.exec("ROLLBACK");
853
- throw caught;
854
- }
943
+ });
855
944
  }
856
945
  async diagnostics(principal) {
857
946
  await this.ready();
@@ -877,31 +966,33 @@ var SQLiteBridgeStore = class {
877
966
  const now = /* @__PURE__ */ new Date();
878
967
  const nowText = now.toISOString();
879
968
  const expires = new Date(now.getTime() + leaseMs).toISOString();
880
- this.db.exec("BEGIN IMMEDIATE");
881
- try {
882
- this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, nowText);
883
- const current = this.db.prepare("SELECT 1 FROM bridge_presence WHERE workspace=? AND agent=? AND instance=?").get(principal.workspace, principal.agent, principal.instance);
884
- const agentCount = this.db.prepare("SELECT count(*) AS count FROM bridge_presence WHERE workspace=? AND agent=?").get(principal.workspace, principal.agent);
885
- const workspaceCount = this.db.prepare("SELECT count(*) AS count FROM bridge_presence WHERE workspace=?").get(principal.workspace);
886
- if (!current && (Number(agentCount.count) >= 128 || Number(workspaceCount.count) >= 4096)) {
887
- throw Object.assign(new Error("active instance limit reached"), {
888
- status: 429,
889
- code: "presence_limit"
890
- });
969
+ return this.write(() => {
970
+ this.db.exec("BEGIN IMMEDIATE");
971
+ try {
972
+ this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, nowText);
973
+ const current = this.db.prepare("SELECT 1 FROM bridge_presence WHERE workspace=? AND agent=? AND instance=?").get(principal.workspace, principal.agent, principal.instance);
974
+ const agentCount = this.db.prepare("SELECT count(*) AS count FROM bridge_presence WHERE workspace=? AND agent=?").get(principal.workspace, principal.agent);
975
+ const workspaceCount = this.db.prepare("SELECT count(*) AS count FROM bridge_presence WHERE workspace=?").get(principal.workspace);
976
+ if (!current && (Number(agentCount.count) >= 128 || Number(workspaceCount.count) >= 4096)) {
977
+ throw Object.assign(new Error("active instance limit reached"), {
978
+ status: 429,
979
+ code: "presence_limit"
980
+ });
981
+ }
982
+ 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);
983
+ const result = presence(this.db.prepare("SELECT * FROM bridge_presence WHERE workspace=? AND agent=? AND instance=?").get(principal.workspace, principal.agent, principal.instance));
984
+ this.db.exec("COMMIT");
985
+ return result;
986
+ } catch (error) {
987
+ rollbackSqliteTransaction(this.db);
988
+ throw error;
891
989
  }
892
- 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);
893
- const result = presence(this.db.prepare("SELECT * FROM bridge_presence WHERE workspace=? AND agent=? AND instance=?").get(principal.workspace, principal.agent, principal.instance));
894
- this.db.exec("COMMIT");
895
- return result;
896
- } catch (error) {
897
- this.db.exec("ROLLBACK");
898
- throw error;
899
- }
990
+ });
900
991
  }
901
992
  async listPresence(principal) {
902
993
  await this.ready();
903
994
  const now = (/* @__PURE__ */ new Date()).toISOString();
904
- this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, now);
995
+ await this.write(() => this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, now));
905
996
  const rows = this.db.prepare("SELECT * FROM bridge_presence WHERE workspace=? ORDER BY agent,instance").all(principal.workspace);
906
997
  return rows.map(presence);
907
998
  }
@@ -950,49 +1041,53 @@ var SQLiteBridgeStore = class {
950
1041
  }
951
1042
  async cancelDelivery(principal, id) {
952
1043
  await this.ready();
953
- this.db.exec("BEGIN IMMEDIATE");
954
- try {
955
- 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);
956
- if (!current) {
957
- this.db.exec("COMMIT");
958
- return null;
959
- }
960
- if (current.state === "cancelled") {
1044
+ return this.write(() => {
1045
+ this.db.exec("BEGIN IMMEDIATE");
1046
+ try {
1047
+ 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);
1048
+ if (!current) {
1049
+ this.db.exec("COMMIT");
1050
+ return null;
1051
+ }
1052
+ if (current.state === "cancelled") {
1053
+ this.db.exec("COMMIT");
1054
+ return delivery(current);
1055
+ }
1056
+ if (!["pending", "retrying", "claimed"].includes(String(current.state))) throw new DeliveryStateConflictError(`cannot cancel a ${current.state} delivery`);
1057
+ 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);
1058
+ const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
961
1059
  this.db.exec("COMMIT");
962
- return delivery(current);
1060
+ return result;
1061
+ } catch (error) {
1062
+ rollbackSqliteTransaction(this.db);
1063
+ throw error;
963
1064
  }
964
- if (!["pending", "retrying", "claimed"].includes(String(current.state))) throw new DeliveryStateConflictError(`cannot cancel a ${current.state} delivery`);
965
- 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);
966
- const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
967
- this.db.exec("COMMIT");
968
- return result;
969
- } catch (error) {
970
- this.db.exec("ROLLBACK");
971
- throw error;
972
- }
1065
+ });
973
1066
  }
974
1067
  async requeueDelivery(principal, id) {
975
1068
  await this.ready();
976
- this.db.exec("BEGIN IMMEDIATE");
977
- try {
978
- 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);
979
- if (!current) {
1069
+ return this.write(() => {
1070
+ this.db.exec("BEGIN IMMEDIATE");
1071
+ try {
1072
+ 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);
1073
+ if (!current) {
1074
+ this.db.exec("COMMIT");
1075
+ return null;
1076
+ }
1077
+ if (!["dead", "cancelled"].includes(String(current.state))) throw new DeliveryStateConflictError(`cannot requeue a ${current.state} delivery`);
1078
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1079
+ if (current.expires_at && String(current.expires_at) <= now) throw new DeliveryStateConflictError("cannot requeue an expired message");
1080
+ const policy = parse(current.delivery_policy);
1081
+ const availableAt = policy.notBefore && policy.notBefore > now ? policy.notBefore : now;
1082
+ 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);
1083
+ const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
980
1084
  this.db.exec("COMMIT");
981
- return null;
1085
+ return result;
1086
+ } catch (error) {
1087
+ rollbackSqliteTransaction(this.db);
1088
+ throw error;
982
1089
  }
983
- if (!["dead", "cancelled"].includes(String(current.state))) throw new DeliveryStateConflictError(`cannot requeue a ${current.state} delivery`);
984
- const now = (/* @__PURE__ */ new Date()).toISOString();
985
- if (current.expires_at && String(current.expires_at) <= now) throw new DeliveryStateConflictError("cannot requeue an expired message");
986
- const policy = parse(current.delivery_policy);
987
- const availableAt = policy.notBefore && policy.notBefore > now ? policy.notBefore : now;
988
- 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);
989
- const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
990
- this.db.exec("COMMIT");
991
- return result;
992
- } catch (error) {
993
- this.db.exec("ROLLBACK");
994
- throw error;
995
- }
1090
+ });
996
1091
  }
997
1092
  async close() {
998
1093
  this.db.close();
@@ -1002,6 +1097,7 @@ var SQLiteBridgeStore = class {
1002
1097
  export {
1003
1098
  SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS,
1004
1099
  retrySqliteBusy,
1100
+ rollbackSqliteTransaction,
1005
1101
  LOCAL_SQLITE_APPLICATION_ID,
1006
1102
  EDGE_SQLITE_APPLICATION_ID,
1007
1103
  SQLITE_DATABASE_SCHEMA_VERSION,
@@ -1022,4 +1118,4 @@ export {
1022
1118
  verifySqliteHealth,
1023
1119
  SQLiteBridgeStore
1024
1120
  };
1025
- //# sourceMappingURL=chunk-IVG4XWWS.js.map
1121
+ //# sourceMappingURL=chunk-NSQE6YRP.js.map