@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.
- package/CHANGELOG.md +24 -1
- package/README.md +3 -2
- package/ROADMAP.md +12 -0
- package/dist/bridge.d.ts +1 -1
- package/dist/bridge.js +2 -2
- package/dist/{chunk-JIRNR75D.js → chunk-2UBJ6DHC.js} +2 -2
- package/dist/{chunk-IXNQG4SB.js → chunk-DBQ5PNQW.js} +3 -3
- package/dist/{chunk-IF5OKPBK.js → chunk-H2UAY6AV.js} +2 -2
- package/dist/{chunk-ZUZ7TCIH.js → chunk-HIYXPNBE.js} +325 -227
- package/dist/chunk-HIYXPNBE.js.map +1 -0
- package/dist/{chunk-IVG4XWWS.js → chunk-NSQE6YRP.js} +285 -189
- package/dist/chunk-NSQE6YRP.js.map +1 -0
- package/dist/cli.js +51 -9
- 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/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-JIRNR75D.js.map → chunk-2UBJ6DHC.js.map} +0 -0
- /package/dist/{chunk-IXNQG4SB.js.map → chunk-DBQ5PNQW.js.map} +0 -0
- /package/dist/{chunk-IF5OKPBK.js.map → chunk-H2UAY6AV.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"]],
|
|
@@ -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
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
this.db.exec(
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
555
|
-
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
584
|
-
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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.
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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.
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
|
897
|
+
return delivery(claimed);
|
|
898
|
+
} catch (e) {
|
|
899
|
+
rollbackSqliteTransaction(this.db);
|
|
900
|
+
throw e;
|
|
807
901
|
}
|
|
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
|
-
}
|
|
902
|
+
});
|
|
818
903
|
}
|
|
819
904
|
async renewDelivery(principal, id, token, leaseMs) {
|
|
820
905
|
await this.ready();
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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.
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
|
938
|
+
return result;
|
|
939
|
+
} catch (caught) {
|
|
940
|
+
rollbackSqliteTransaction(this.db);
|
|
941
|
+
throw caught;
|
|
838
942
|
}
|
|
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
|
-
}
|
|
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.
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
|
|
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.
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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
|
|
1060
|
+
return result;
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
rollbackSqliteTransaction(this.db);
|
|
1063
|
+
throw error;
|
|
963
1064
|
}
|
|
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
|
-
}
|
|
1065
|
+
});
|
|
973
1066
|
}
|
|
974
1067
|
async requeueDelivery(principal, id) {
|
|
975
1068
|
await this.ready();
|
|
976
|
-
this.
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
|
1085
|
+
return result;
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
rollbackSqliteTransaction(this.db);
|
|
1088
|
+
throw error;
|
|
982
1089
|
}
|
|
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
|
-
}
|
|
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-
|
|
1121
|
+
//# sourceMappingURL=chunk-NSQE6YRP.js.map
|