@creatornader/agent-bridge 0.6.1 → 0.6.3
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.
- package/CHANGELOG.md +27 -1
- package/README.md +6 -3
- package/ROADMAP.md +16 -2
- package/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +2 -2
- package/dist/{chunk-IVG4XWWS.js → chunk-7PYY3S27.js} +281 -189
- package/dist/chunk-7PYY3S27.js.map +1 -0
- package/dist/{chunk-IF5OKPBK.js → chunk-7Q5THTJH.js} +2 -2
- package/dist/{chunk-JIRNR75D.js → chunk-DHGJN6NA.js} +2 -2
- package/dist/{chunk-IXNQG4SB.js → chunk-TZHXTMPJ.js} +3 -3
- package/dist/{chunk-ZUZ7TCIH.js → chunk-U4M67GFU.js} +323 -226
- package/dist/chunk-U4M67GFU.js.map +1 -0
- package/dist/cli.js +55 -13
- package/dist/cli.js.map +1 -1
- package/dist/client-runtime.d.ts +1 -1
- package/dist/client-runtime.js +3 -3
- package/dist/dr.d.ts +18 -0
- package/dist/dr.js +2 -2
- package/dist/index.js +4 -4
- package/dist/server.js +4 -4
- package/dist/{sqlite-edge-store-BAFUeysk.d.ts → sqlite-edge-store-C-pVhdgx.d.ts} +5 -1
- package/dist/sqlite.d.ts +4 -2
- package/dist/sqlite.js +2 -2
- package/docs/architecture-v2.md +6 -0
- package/docs/deployment.md +6 -3
- package/docs/troubleshooting.md +17 -0
- package/package.json +5 -2
- package/dist/chunk-IVG4XWWS.js.map +0 -1
- package/dist/chunk-ZUZ7TCIH.js.map +0 -1
- /package/dist/{chunk-IF5OKPBK.js.map → chunk-7Q5THTJH.js.map} +0 -0
- /package/dist/{chunk-JIRNR75D.js.map → chunk-DHGJN6NA.js.map} +0 -0
- /package/dist/{chunk-IXNQG4SB.js.map → chunk-TZHXTMPJ.js.map} +0 -0
|
@@ -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"]],
|
|
@@ -385,9 +395,6 @@ function openDatabase(path) {
|
|
|
385
395
|
return new DatabaseSync(path);
|
|
386
396
|
}
|
|
387
397
|
var schema = `
|
|
388
|
-
PRAGMA journal_mode = WAL;
|
|
389
|
-
PRAGMA synchronous = FULL;
|
|
390
|
-
PRAGMA foreign_keys = ON;
|
|
391
398
|
CREATE TABLE IF NOT EXISTS bridge_messages (
|
|
392
399
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, workspace TEXT NOT NULL, project TEXT, source TEXT NOT NULL,
|
|
393
400
|
type TEXT NOT NULL, content TEXT NOT NULL, content_type TEXT NOT NULL, data TEXT, targets TEXT NOT NULL DEFAULT '[]',
|
|
@@ -452,6 +459,7 @@ CREATE INDEX IF NOT EXISTS bridge_presence_active ON bridge_presence(workspace,
|
|
|
452
459
|
CREATE TRIGGER IF NOT EXISTS bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END;
|
|
453
460
|
CREATE TRIGGER IF NOT EXISTS bridge_messages_no_delete BEFORE DELETE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END;
|
|
454
461
|
`;
|
|
462
|
+
var writeGateSchema = "CREATE TABLE IF NOT EXISTS bridge_write_gates (gate_key TEXT PRIMARY KEY, lease_token TEXT, lease_expires_at TEXT)";
|
|
455
463
|
function stringify(value) {
|
|
456
464
|
return value === void 0 ? null : JSON.stringify(value);
|
|
457
465
|
}
|
|
@@ -497,6 +505,70 @@ var SQLiteBridgeStore = class {
|
|
|
497
505
|
throw error;
|
|
498
506
|
}
|
|
499
507
|
}
|
|
508
|
+
async write(work) {
|
|
509
|
+
if (this.databasePath === ":memory:") return work();
|
|
510
|
+
const token = randomUUID();
|
|
511
|
+
const waitMs = process.platform === "win32" ? 6e4 : SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS;
|
|
512
|
+
const deadline = Date.now() + waitMs;
|
|
513
|
+
let pause = 2;
|
|
514
|
+
while (true) {
|
|
515
|
+
const now = /* @__PURE__ */ new Date();
|
|
516
|
+
const claimed = await retrySqliteBusy(() => {
|
|
517
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
518
|
+
try {
|
|
519
|
+
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();
|
|
520
|
+
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());
|
|
521
|
+
this.db.exec("COMMIT");
|
|
522
|
+
return result2.changes === 1;
|
|
523
|
+
} catch (error) {
|
|
524
|
+
this.db.exec("ROLLBACK");
|
|
525
|
+
throw error;
|
|
526
|
+
}
|
|
527
|
+
}, Math.max(1, deadline - Date.now()));
|
|
528
|
+
if (claimed) break;
|
|
529
|
+
if (Date.now() >= deadline) throw new Error("local write coordinator remained busy");
|
|
530
|
+
await new Promise((resolve) => setTimeout(resolve, pause));
|
|
531
|
+
pause = Math.min(Math.ceil(pause * 1.7), 25);
|
|
532
|
+
}
|
|
533
|
+
let completed = false;
|
|
534
|
+
let result;
|
|
535
|
+
try {
|
|
536
|
+
result = await work();
|
|
537
|
+
completed = true;
|
|
538
|
+
} finally {
|
|
539
|
+
try {
|
|
540
|
+
await retrySqliteBusy(() => {
|
|
541
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
542
|
+
try {
|
|
543
|
+
this.db.prepare("UPDATE bridge_write_gates SET lease_token=NULL,lease_expires_at=NULL WHERE gate_key='local' AND lease_token=?").run(token);
|
|
544
|
+
this.db.exec("COMMIT");
|
|
545
|
+
} catch (error) {
|
|
546
|
+
this.db.exec("ROLLBACK");
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
}, waitMs);
|
|
550
|
+
} catch (error) {
|
|
551
|
+
if (!completed) throw error;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return result;
|
|
555
|
+
}
|
|
556
|
+
async bootstrapWriteGate(timeoutMs) {
|
|
557
|
+
await retrySqliteBusy(() => this.db.exec("PRAGMA journal_mode = WAL"), timeoutMs);
|
|
558
|
+
const exists = () => Boolean(this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='bridge_write_gates'").get());
|
|
559
|
+
if (exists()) return;
|
|
560
|
+
await retrySqliteBusy(() => {
|
|
561
|
+
if (exists()) return;
|
|
562
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
563
|
+
try {
|
|
564
|
+
if (!exists()) this.db.exec(writeGateSchema);
|
|
565
|
+
this.db.exec("COMMIT");
|
|
566
|
+
} catch (error) {
|
|
567
|
+
this.db.exec("ROLLBACK");
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
}, timeoutMs);
|
|
571
|
+
}
|
|
500
572
|
restrictFiles(includeSidecars = true) {
|
|
501
573
|
if (this.databasePath === ":memory:") return;
|
|
502
574
|
const paths = includeSidecars ? [this.databasePath, `${this.databasePath}-wal`, `${this.databasePath}-shm`] : [this.databasePath];
|
|
@@ -526,33 +598,36 @@ var SQLiteBridgeStore = class {
|
|
|
526
598
|
if (encoded < 3051003) throw new Error("SQLite 3.51.3 or newer is required");
|
|
527
599
|
assertLocalUpgradeCandidate(this.db);
|
|
528
600
|
const initializationTimeoutMs = Math.max(this.busyTimeoutMs, SQLITE_INITIALIZATION_BUSY_TIMEOUT_MS);
|
|
529
|
-
await
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
this.db.exec(
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
601
|
+
await this.bootstrapWriteGate(initializationTimeoutMs);
|
|
602
|
+
this.db.exec("PRAGMA synchronous = FULL; PRAGMA foreign_keys = ON");
|
|
603
|
+
await this.write(async () => {
|
|
604
|
+
await retrySqliteBusy(() => this.db.exec("BEGIN IMMEDIATE"), initializationTimeoutMs);
|
|
605
|
+
try {
|
|
606
|
+
this.db.exec(schema);
|
|
607
|
+
const columns = this.db.prepare("PRAGMA table_info(bridge_messages)").all();
|
|
608
|
+
if (!columns.some((column) => column.name === "project")) {
|
|
609
|
+
this.db.exec("ALTER TABLE bridge_messages ADD COLUMN project TEXT");
|
|
610
|
+
}
|
|
611
|
+
const addedDeliveryPolicy = !columns.some((column) => column.name === "delivery_policy");
|
|
612
|
+
if (addedDeliveryPolicy) {
|
|
613
|
+
this.db.exec("DROP TRIGGER IF EXISTS bridge_messages_no_update");
|
|
614
|
+
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}'`);
|
|
615
|
+
this.db.exec("CREATE TRIGGER bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END");
|
|
616
|
+
}
|
|
617
|
+
const legacyPolicy = addedDeliveryPolicy || Boolean(this.db.prepare(`SELECT 1 FROM bridge_messages
|
|
543
618
|
WHERE json_type(delivery_policy,'$.publisherOwned') IS NOT NULL
|
|
544
619
|
OR json_type(delivery_policy,'$.baseDelayMs') IS NOT NULL
|
|
545
620
|
OR json_type(delivery_policy,'$.maxDelayMs') IS NOT NULL
|
|
546
621
|
OR json_type(delivery_policy,'$.jitterRatio') IS NOT NULL
|
|
547
622
|
LIMIT 1`).get());
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
623
|
+
if (legacyPolicy) {
|
|
624
|
+
this.db.exec("DROP TRIGGER IF EXISTS bridge_messages_no_update");
|
|
625
|
+
if (addedDeliveryPolicy) {
|
|
626
|
+
this.db.exec(`UPDATE bridge_messages SET delivery_policy=CASE
|
|
552
627
|
WHEN json_array_length(targets)=0 THEN '{"mode":"mailbox"}'
|
|
553
628
|
ELSE '{"mode":"leased","maxAttempts":5,"retryBaseDelayMs":1000,"retryMaxDelayMs":60000,"retryJitterRatio":0.2}' END`);
|
|
554
|
-
|
|
555
|
-
|
|
629
|
+
} else {
|
|
630
|
+
this.db.exec(`UPDATE bridge_messages SET delivery_policy=CASE
|
|
556
631
|
WHEN json_extract(delivery_policy,'$.mode')='mailbox' THEN
|
|
557
632
|
json_remove(delivery_policy,'$.publisherOwned','$.maxAttempts','$.baseDelayMs','$.maxDelayMs','$.jitterRatio','$.retryBaseDelayMs','$.retryMaxDelayMs','$.retryJitterRatio','$.notBefore')
|
|
558
633
|
ELSE json_set(
|
|
@@ -567,21 +642,21 @@ var SQLiteBridgeStore = class {
|
|
|
567
642
|
OR json_type(delivery_policy,'$.baseDelayMs') IS NOT NULL
|
|
568
643
|
OR json_type(delivery_policy,'$.maxDelayMs') IS NOT NULL
|
|
569
644
|
OR json_type(delivery_policy,'$.jitterRatio') IS NOT NULL`);
|
|
645
|
+
}
|
|
646
|
+
this.db.exec("CREATE TRIGGER bridge_messages_no_update BEFORE UPDATE ON bridge_messages BEGIN SELECT RAISE(ABORT, 'bridge messages are immutable'); END");
|
|
570
647
|
}
|
|
571
|
-
this.db.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
const eventColumns = this.db.prepare("PRAGMA table_info(bridge_delivery_events)").all();
|
|
577
|
-
this.db.exec(`CREATE TABLE bridge_delivery_events_v10 (
|
|
648
|
+
const deliveryColumns = this.db.prepare("PRAGMA table_info(bridge_deliveries)").all();
|
|
649
|
+
if (!deliveryColumns.some((column) => column.name === "last_action")) {
|
|
650
|
+
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");
|
|
651
|
+
const eventColumns = this.db.prepare("PRAGMA table_info(bridge_delivery_events)").all();
|
|
652
|
+
this.db.exec(`CREATE TABLE bridge_delivery_events_v10 (
|
|
578
653
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT, delivery_id TEXT NOT NULL,
|
|
579
654
|
message_id TEXT NOT NULL, workspace TEXT NOT NULL, recipient TEXT NOT NULL,
|
|
580
655
|
from_state TEXT, to_state TEXT NOT NULL, attempt INTEGER NOT NULL,
|
|
581
656
|
cycle_attempt INTEGER NOT NULL, requeue_count INTEGER NOT NULL,
|
|
582
657
|
lease_owner TEXT, error TEXT, actor TEXT NOT NULL, action TEXT NOT NULL, created_at TEXT NOT NULL)`);
|
|
583
|
-
|
|
584
|
-
|
|
658
|
+
if (eventColumns.length) {
|
|
659
|
+
this.db.exec(`INSERT INTO bridge_delivery_events_v10
|
|
585
660
|
(sequence,delivery_id,message_id,workspace,recipient,from_state,to_state,attempt,cycle_attempt,requeue_count,lease_owner,error,actor,action,created_at)
|
|
586
661
|
SELECT event.sequence,event.delivery_id,event.message_id,event.workspace,event.recipient,
|
|
587
662
|
event.from_state,event.to_state,event.attempt,event.attempt,0,event.lease_owner,event.error,
|
|
@@ -609,9 +684,9 @@ var SQLiteBridgeStore = class {
|
|
|
609
684
|
ELSE 'created' END,event.created_at
|
|
610
685
|
FROM bridge_delivery_events event JOIN bridge_messages message
|
|
611
686
|
ON message.workspace=event.workspace AND message.id=event.message_id`);
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
687
|
+
this.db.exec("DROP TABLE bridge_delivery_events");
|
|
688
|
+
}
|
|
689
|
+
this.db.exec(`CREATE TABLE bridge_deliveries_v10 (
|
|
615
690
|
id TEXT PRIMARY KEY, message_id TEXT NOT NULL, workspace TEXT NOT NULL, recipient TEXT NOT NULL,
|
|
616
691
|
state TEXT NOT NULL, attempt INTEGER NOT NULL DEFAULT 0, cycle_attempt INTEGER NOT NULL DEFAULT 0,
|
|
617
692
|
requeue_count INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, priority_rank INTEGER NOT NULL,
|
|
@@ -622,7 +697,7 @@ var SQLiteBridgeStore = class {
|
|
|
622
697
|
CHECK(attempt>=0 AND cycle_attempt>=0 AND cycle_attempt<=attempt AND requeue_count>=0),
|
|
623
698
|
CHECK(priority_rank BETWEEN 0 AND 2),
|
|
624
699
|
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
|
-
|
|
700
|
+
this.db.exec(`INSERT INTO bridge_deliveries_v10
|
|
626
701
|
(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
702
|
SELECT delivery.id,delivery.message_id,delivery.workspace,delivery.recipient,delivery.state,delivery.attempt,delivery.attempt,0,
|
|
628
703
|
message.created_at,CASE message.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 ELSE 2 END,
|
|
@@ -649,13 +724,13 @@ var SQLiteBridgeStore = class {
|
|
|
649
724
|
ELSE 'created' END
|
|
650
725
|
FROM bridge_deliveries delivery JOIN bridge_messages message
|
|
651
726
|
ON message.workspace=delivery.workspace AND message.id=delivery.message_id`);
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
727
|
+
this.db.exec("DROP TABLE bridge_deliveries; ALTER TABLE bridge_deliveries_v10 RENAME TO bridge_deliveries");
|
|
728
|
+
this.db.exec("ALTER TABLE bridge_delivery_events_v10 RENAME TO bridge_delivery_events");
|
|
729
|
+
}
|
|
730
|
+
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)");
|
|
731
|
+
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
732
|
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
|
-
|
|
733
|
+
this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_messages_policy_insert BEFORE INSERT ON bridge_messages
|
|
659
734
|
WHEN NOT (
|
|
660
735
|
json_valid(NEW.delivery_policy) AND json_type(NEW.delivery_policy)='object'
|
|
661
736
|
AND NOT EXISTS (SELECT 1 FROM json_each(NEW.delivery_policy) WHERE key NOT IN ('mode','maxAttempts','retryBaseDelayMs','retryMaxDelayMs','retryJitterRatio','notBefore'))
|
|
@@ -676,17 +751,18 @@ var SQLiteBridgeStore = class {
|
|
|
676
751
|
)
|
|
677
752
|
)
|
|
678
753
|
) BEGIN SELECT RAISE(ABORT,'invalid delivery policy'); END`);
|
|
679
|
-
|
|
754
|
+
this.db.exec(`CREATE TRIGGER IF NOT EXISTS bridge_messages_domain_insert BEFORE INSERT ON bridge_messages
|
|
680
755
|
WHEN NOT (json_valid(NEW.targets) AND json_type(NEW.targets)='array' AND NEW.priority IN ('info','high','urgent'))
|
|
681
756
|
BEGIN SELECT RAISE(ABORT,'invalid message domain'); END`);
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
757
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS bridge_messages_project ON bridge_messages(workspace, project, sequence)");
|
|
758
|
+
installLocalAuthorityMarkers(this.db);
|
|
759
|
+
this.db.exec("COMMIT");
|
|
760
|
+
} catch (error) {
|
|
761
|
+
this.db.exec("ROLLBACK");
|
|
762
|
+
throw error;
|
|
763
|
+
}
|
|
764
|
+
this.restrictFiles();
|
|
765
|
+
});
|
|
690
766
|
}
|
|
691
767
|
async ready() {
|
|
692
768
|
await this.initialize();
|
|
@@ -695,32 +771,34 @@ var SQLiteBridgeStore = class {
|
|
|
695
771
|
await this.ready();
|
|
696
772
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
697
773
|
const storedPolicy = input.deliveryPolicy ?? (input.targets.length ? { mode: "leased", maxAttempts: 5, retryBaseDelayMs: 1e3, retryMaxDelayMs: 6e4, retryJitterRatio: 0.2 } : { mode: "mailbox" });
|
|
698
|
-
this.
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
774
|
+
return this.write(() => {
|
|
775
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
776
|
+
try {
|
|
777
|
+
if (input.idempotencyKey) {
|
|
778
|
+
const existing = this.db.prepare("SELECT * FROM bridge_messages WHERE workspace = ? AND source = ? AND idempotency_key = ?").get(input.workspace, input.source, input.idempotencyKey);
|
|
779
|
+
if (existing) {
|
|
780
|
+
const replay = message(existing);
|
|
781
|
+
assertIdempotentReplay(replay, input);
|
|
782
|
+
this.db.exec("COMMIT");
|
|
783
|
+
return { message: replay, created: false };
|
|
784
|
+
}
|
|
707
785
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
786
|
+
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);
|
|
787
|
+
if (storedPolicy.mode === "leased") {
|
|
788
|
+
const availableAt = storedPolicy.notBefore && storedPolicy.notBefore > createdAt ? storedPolicy.notBefore : createdAt;
|
|
789
|
+
const priorityRank = input.priority === "urgent" ? 0 : input.priority === "high" ? 1 : 2;
|
|
790
|
+
for (const recipient of input.targets) {
|
|
791
|
+
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");
|
|
792
|
+
}
|
|
715
793
|
}
|
|
794
|
+
const row = this.db.prepare("SELECT * FROM bridge_messages WHERE id = ?").get(input.id);
|
|
795
|
+
this.db.exec("COMMIT");
|
|
796
|
+
return { message: message(row), created: true };
|
|
797
|
+
} catch (error) {
|
|
798
|
+
this.db.exec("ROLLBACK");
|
|
799
|
+
throw error;
|
|
716
800
|
}
|
|
717
|
-
|
|
718
|
-
this.db.exec("COMMIT");
|
|
719
|
-
return { message: message(row), created: true };
|
|
720
|
-
} catch (error) {
|
|
721
|
-
this.db.exec("ROLLBACK");
|
|
722
|
-
throw error;
|
|
723
|
-
}
|
|
801
|
+
});
|
|
724
802
|
}
|
|
725
803
|
async listMessages(principal, query = {}) {
|
|
726
804
|
await this.ready();
|
|
@@ -777,17 +855,19 @@ var SQLiteBridgeStore = class {
|
|
|
777
855
|
}
|
|
778
856
|
async recordReceipt(principal, messageIds, readAt = /* @__PURE__ */ new Date()) {
|
|
779
857
|
await this.ready();
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
858
|
+
return this.write(() => {
|
|
859
|
+
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=?))");
|
|
860
|
+
let changed = 0;
|
|
861
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
862
|
+
try {
|
|
863
|
+
for (const id of messageIds) changed += Number(stmt.run(principal.agent, readAt.toISOString(), principal.workspace, id, principal.agent).changes);
|
|
864
|
+
this.db.exec("COMMIT");
|
|
865
|
+
return changed;
|
|
866
|
+
} catch (e) {
|
|
867
|
+
this.db.exec("ROLLBACK");
|
|
868
|
+
throw e;
|
|
869
|
+
}
|
|
870
|
+
});
|
|
791
871
|
}
|
|
792
872
|
async claimDelivery(principal, options) {
|
|
793
873
|
await this.ready();
|
|
@@ -795,63 +875,69 @@ var SQLiteBridgeStore = class {
|
|
|
795
875
|
const nowText = now.toISOString();
|
|
796
876
|
const expires = new Date(now.getTime() + options.leaseMs).toISOString();
|
|
797
877
|
const owner = principal.instance ?? principal.agent;
|
|
798
|
-
this.
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
878
|
+
return this.write(() => {
|
|
879
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
880
|
+
try {
|
|
881
|
+
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);
|
|
882
|
+
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);
|
|
883
|
+
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);
|
|
884
|
+
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);
|
|
885
|
+
if (!candidate) {
|
|
886
|
+
this.db.exec("COMMIT");
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
const candidateId = String(candidate.id);
|
|
890
|
+
const token = randomUUID();
|
|
891
|
+
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);
|
|
892
|
+
const claimed = this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(candidateId);
|
|
805
893
|
this.db.exec("COMMIT");
|
|
806
|
-
return
|
|
894
|
+
return delivery(claimed);
|
|
895
|
+
} catch (e) {
|
|
896
|
+
this.db.exec("ROLLBACK");
|
|
897
|
+
throw e;
|
|
807
898
|
}
|
|
808
|
-
|
|
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
|
-
}
|
|
899
|
+
});
|
|
818
900
|
}
|
|
819
901
|
async renewDelivery(principal, id, token, leaseMs) {
|
|
820
902
|
await this.ready();
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
903
|
+
return this.write(() => {
|
|
904
|
+
const now = /* @__PURE__ */ new Date();
|
|
905
|
+
const expires = new Date(now.getTime() + leaseMs).toISOString();
|
|
906
|
+
const owner = principal.instance ?? principal.agent;
|
|
907
|
+
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());
|
|
908
|
+
if (!result.changes) return null;
|
|
909
|
+
return delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
|
|
910
|
+
});
|
|
827
911
|
}
|
|
828
912
|
async settleDelivery(principal, id, token, state, error, _retryPolicy) {
|
|
829
913
|
await this.ready();
|
|
830
914
|
const now = /* @__PURE__ */ new Date();
|
|
831
915
|
const owner = principal.instance ?? principal.agent;
|
|
832
|
-
this.
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
916
|
+
return this.write(() => {
|
|
917
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
918
|
+
try {
|
|
919
|
+
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());
|
|
920
|
+
if (!current) {
|
|
921
|
+
this.db.exec("COMMIT");
|
|
922
|
+
return null;
|
|
923
|
+
}
|
|
924
|
+
const policy = parse(current.delivery_policy);
|
|
925
|
+
const exhausted = state === "retrying" && Number(current.cycle_attempt) >= Number(policy.maxAttempts);
|
|
926
|
+
const nextState = exhausted ? "dead" : state;
|
|
927
|
+
const exponential = Math.min(policy.retryMaxDelayMs, policy.retryBaseDelayMs * 2 ** Math.max(0, Number(current.cycle_attempt) - 1));
|
|
928
|
+
const jitter = 1 + (Math.random() * 2 - 1) * policy.retryJitterRatio;
|
|
929
|
+
const delay = nextState === "retrying" ? Math.max(1, Math.round(exponential * jitter)) : 0;
|
|
930
|
+
const available = nextState === "retrying" ? new Date(now.getTime() + delay).toISOString() : String(current.available_at);
|
|
931
|
+
const action = state === "acked" ? "ack" : state === "dead" ? "nack_dead" : exhausted ? "attempts_exhausted" : "nack_retry";
|
|
932
|
+
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);
|
|
933
|
+
const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
|
|
836
934
|
this.db.exec("COMMIT");
|
|
837
|
-
return
|
|
935
|
+
return result;
|
|
936
|
+
} catch (caught) {
|
|
937
|
+
this.db.exec("ROLLBACK");
|
|
938
|
+
throw caught;
|
|
838
939
|
}
|
|
839
|
-
|
|
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
|
-
}
|
|
940
|
+
});
|
|
855
941
|
}
|
|
856
942
|
async diagnostics(principal) {
|
|
857
943
|
await this.ready();
|
|
@@ -877,31 +963,33 @@ var SQLiteBridgeStore = class {
|
|
|
877
963
|
const now = /* @__PURE__ */ new Date();
|
|
878
964
|
const nowText = now.toISOString();
|
|
879
965
|
const expires = new Date(now.getTime() + leaseMs).toISOString();
|
|
880
|
-
this.
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
966
|
+
return this.write(() => {
|
|
967
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
968
|
+
try {
|
|
969
|
+
this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, nowText);
|
|
970
|
+
const current = this.db.prepare("SELECT 1 FROM bridge_presence WHERE workspace=? AND agent=? AND instance=?").get(principal.workspace, principal.agent, principal.instance);
|
|
971
|
+
const agentCount = this.db.prepare("SELECT count(*) AS count FROM bridge_presence WHERE workspace=? AND agent=?").get(principal.workspace, principal.agent);
|
|
972
|
+
const workspaceCount = this.db.prepare("SELECT count(*) AS count FROM bridge_presence WHERE workspace=?").get(principal.workspace);
|
|
973
|
+
if (!current && (Number(agentCount.count) >= 128 || Number(workspaceCount.count) >= 4096)) {
|
|
974
|
+
throw Object.assign(new Error("active instance limit reached"), {
|
|
975
|
+
status: 429,
|
|
976
|
+
code: "presence_limit"
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
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);
|
|
980
|
+
const result = presence(this.db.prepare("SELECT * FROM bridge_presence WHERE workspace=? AND agent=? AND instance=?").get(principal.workspace, principal.agent, principal.instance));
|
|
981
|
+
this.db.exec("COMMIT");
|
|
982
|
+
return result;
|
|
983
|
+
} catch (error) {
|
|
984
|
+
this.db.exec("ROLLBACK");
|
|
985
|
+
throw error;
|
|
891
986
|
}
|
|
892
|
-
|
|
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
|
-
}
|
|
987
|
+
});
|
|
900
988
|
}
|
|
901
989
|
async listPresence(principal) {
|
|
902
990
|
await this.ready();
|
|
903
991
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
904
|
-
this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, now);
|
|
992
|
+
await this.write(() => this.db.prepare("DELETE FROM bridge_presence WHERE workspace=? AND lease_expires_at<=?").run(principal.workspace, now));
|
|
905
993
|
const rows = this.db.prepare("SELECT * FROM bridge_presence WHERE workspace=? ORDER BY agent,instance").all(principal.workspace);
|
|
906
994
|
return rows.map(presence);
|
|
907
995
|
}
|
|
@@ -950,49 +1038,53 @@ var SQLiteBridgeStore = class {
|
|
|
950
1038
|
}
|
|
951
1039
|
async cancelDelivery(principal, id) {
|
|
952
1040
|
await this.ready();
|
|
953
|
-
this.
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1041
|
+
return this.write(() => {
|
|
1042
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
1043
|
+
try {
|
|
1044
|
+
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);
|
|
1045
|
+
if (!current) {
|
|
1046
|
+
this.db.exec("COMMIT");
|
|
1047
|
+
return null;
|
|
1048
|
+
}
|
|
1049
|
+
if (current.state === "cancelled") {
|
|
1050
|
+
this.db.exec("COMMIT");
|
|
1051
|
+
return delivery(current);
|
|
1052
|
+
}
|
|
1053
|
+
if (!["pending", "retrying", "claimed"].includes(String(current.state))) throw new DeliveryStateConflictError(`cannot cancel a ${current.state} delivery`);
|
|
1054
|
+
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);
|
|
1055
|
+
const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
|
|
961
1056
|
this.db.exec("COMMIT");
|
|
962
|
-
return
|
|
1057
|
+
return result;
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
this.db.exec("ROLLBACK");
|
|
1060
|
+
throw error;
|
|
963
1061
|
}
|
|
964
|
-
|
|
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
|
-
}
|
|
1062
|
+
});
|
|
973
1063
|
}
|
|
974
1064
|
async requeueDelivery(principal, id) {
|
|
975
1065
|
await this.ready();
|
|
976
|
-
this.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1066
|
+
return this.write(() => {
|
|
1067
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
1068
|
+
try {
|
|
1069
|
+
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);
|
|
1070
|
+
if (!current) {
|
|
1071
|
+
this.db.exec("COMMIT");
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
if (!["dead", "cancelled"].includes(String(current.state))) throw new DeliveryStateConflictError(`cannot requeue a ${current.state} delivery`);
|
|
1075
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1076
|
+
if (current.expires_at && String(current.expires_at) <= now) throw new DeliveryStateConflictError("cannot requeue an expired message");
|
|
1077
|
+
const policy = parse(current.delivery_policy);
|
|
1078
|
+
const availableAt = policy.notBefore && policy.notBefore > now ? policy.notBefore : now;
|
|
1079
|
+
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);
|
|
1080
|
+
const result = delivery(this.db.prepare("SELECT * FROM bridge_deliveries WHERE id=?").get(id));
|
|
980
1081
|
this.db.exec("COMMIT");
|
|
981
|
-
return
|
|
1082
|
+
return result;
|
|
1083
|
+
} catch (error) {
|
|
1084
|
+
this.db.exec("ROLLBACK");
|
|
1085
|
+
throw error;
|
|
982
1086
|
}
|
|
983
|
-
|
|
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
|
-
}
|
|
1087
|
+
});
|
|
996
1088
|
}
|
|
997
1089
|
async close() {
|
|
998
1090
|
this.db.close();
|
|
@@ -1022,4 +1114,4 @@ export {
|
|
|
1022
1114
|
verifySqliteHealth,
|
|
1023
1115
|
SQLiteBridgeStore
|
|
1024
1116
|
};
|
|
1025
|
-
//# sourceMappingURL=chunk-
|
|
1117
|
+
//# sourceMappingURL=chunk-7PYY3S27.js.map
|