@moxt-ai/mobius 0.0.152 → 0.0.154
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/dist/cli.js +126 -284
- package/dist/cli.js.map +4 -4
- package/dist/daemon-active-turn.d.ts +1 -0
- package/dist/daemon-active-turn.d.ts.map +1 -1
- package/dist/daemon-active-turn.js +1 -0
- package/dist/daemon-active-turn.js.map +1 -1
- package/dist/daemon-turn-registry.d.ts +2 -1
- package/dist/daemon-turn-registry.d.ts.map +1 -1
- package/dist/daemon-turn-registry.js +32 -34
- package/dist/daemon-turn-registry.js.map +1 -1
- package/dist/index.js +116 -281
- package/dist/index.js.map +4 -4
- package/dist/native-sessions/live-history-page.d.ts.map +1 -1
- package/dist/native-sessions/live-history-page.js +2 -0
- package/dist/native-sessions/live-history-page.js.map +1 -1
- package/dist/relay-connection/run-relay-connection.js +1 -1
- package/dist/relay-connection/run-relay-connection.js.map +1 -1
- package/dist/relay-connection/session-event-publisher.d.ts +3 -4
- package/dist/relay-connection/session-event-publisher.d.ts.map +1 -1
- package/dist/relay-connection/session-event-publisher.js +14 -22
- package/dist/relay-connection/session-event-publisher.js.map +1 -1
- package/dist/relay-connection/session-event-queue.d.ts +14 -0
- package/dist/relay-connection/session-event-queue.d.ts.map +1 -0
- package/dist/relay-connection/session-event-queue.js +75 -0
- package/dist/relay-connection/session-event-queue.js.map +1 -0
- package/dist/run-daemon.d.ts.map +1 -1
- package/dist/run-daemon.js +1 -3
- package/dist/run-daemon.js.map +1 -1
- package/dist/runtime.js +118 -283
- package/dist/runtime.js.map +4 -4
- package/package.json +10 -3
- package/dist/relay-connection/session-event-outbox.d.ts +0 -20
- package/dist/relay-connection/session-event-outbox.d.ts.map +0 -1
- package/dist/relay-connection/session-event-outbox.js +0 -243
- package/dist/relay-connection/session-event-outbox.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -3381,7 +3381,7 @@ var program = new Command();
|
|
|
3381
3381
|
// package.json
|
|
3382
3382
|
var package_default = {
|
|
3383
3383
|
name: "@moxt-ai/mobius",
|
|
3384
|
-
version: "0.0.
|
|
3384
|
+
version: "0.0.154",
|
|
3385
3385
|
description: "Mobius CLI for pairing and running local AI agent services.",
|
|
3386
3386
|
type: "module",
|
|
3387
3387
|
bin: {
|
|
@@ -3416,7 +3416,9 @@ var package_default = {
|
|
|
3416
3416
|
dev: "node --watch src/cli.ts",
|
|
3417
3417
|
prepack: "pnpm build",
|
|
3418
3418
|
start: "node dist/cli.js",
|
|
3419
|
-
typecheck: "tsc --project tsconfig.json"
|
|
3419
|
+
typecheck: "tsc --project tsconfig.json && tsc --project tests/tsconfig.json",
|
|
3420
|
+
"test:controlled": "vitest run tests/sessions tests/runtime --maxWorkers=4",
|
|
3421
|
+
"test:agents": "vitest run tests/agents --maxWorkers=2"
|
|
3420
3422
|
},
|
|
3421
3423
|
dependencies: {
|
|
3422
3424
|
"@agentclientprotocol/sdk": "^1.4.0",
|
|
@@ -3430,7 +3432,12 @@ var package_default = {
|
|
|
3430
3432
|
"@types/better-sqlite3": "7.6.13",
|
|
3431
3433
|
"@types/node": "^24.13.5",
|
|
3432
3434
|
"drizzle-orm": "^0.45.2",
|
|
3433
|
-
esbuild: "0.28.2"
|
|
3435
|
+
esbuild: "0.28.2",
|
|
3436
|
+
"@moxt-ai/mobius-sdk": "workspace:*",
|
|
3437
|
+
"@moxt-ai/mobius-test": "workspace:*",
|
|
3438
|
+
valibot: "1.5.0",
|
|
3439
|
+
ws: "^8.21.3",
|
|
3440
|
+
"@types/ws": "^8.18.1"
|
|
3434
3441
|
},
|
|
3435
3442
|
main: "dist/index.js",
|
|
3436
3443
|
keywords: [],
|
|
@@ -29554,6 +29561,11 @@ var assistantHistoryMessageSchema = external_exports.strictObject({
|
|
|
29554
29561
|
}).readonly();
|
|
29555
29562
|
var historyMessage = external_exports.discriminatedUnion("role", [userHistoryMessage, assistantHistoryMessageSchema]);
|
|
29556
29563
|
var availableSessionHistoryObjectSchema = external_exports.strictObject({
|
|
29564
|
+
streamPosition: external_exports.strictObject({
|
|
29565
|
+
commandId: identifierStringSchema,
|
|
29566
|
+
epoch: identifierStringSchema,
|
|
29567
|
+
sequence: external_exports.number().int().nonnegative()
|
|
29568
|
+
}).readonly().optional(),
|
|
29557
29569
|
status: external_exports.literal("available"),
|
|
29558
29570
|
messages: external_exports.array(historyMessage).max(SESSION_HISTORY_MESSAGE_LIMIT).readonly(),
|
|
29559
29571
|
truncated: external_exports.boolean(),
|
|
@@ -42587,6 +42599,7 @@ var ActiveDaemonTurn = class {
|
|
|
42587
42599
|
commandId: decodeIdentifier(this.command.commandId),
|
|
42588
42600
|
epoch: this.#epoch,
|
|
42589
42601
|
messages: this.timelineMessages(),
|
|
42602
|
+
sequence: this.#sequence,
|
|
42590
42603
|
state: "running",
|
|
42591
42604
|
truncated: false
|
|
42592
42605
|
};
|
|
@@ -42614,7 +42627,7 @@ var ActiveDaemonTurn = class {
|
|
|
42614
42627
|
};
|
|
42615
42628
|
|
|
42616
42629
|
// src/daemon-turn-registry.ts
|
|
42617
|
-
var
|
|
42630
|
+
var MAX_SESSIONS_WITH_COMPLETED_RECOVERY = 32;
|
|
42618
42631
|
var waitForTurnCompletion = (turn, waitSignal) => {
|
|
42619
42632
|
waitSignal.throwIfAborted();
|
|
42620
42633
|
return new Promise((resolve9, reject) => {
|
|
@@ -42640,46 +42653,46 @@ var waitForTurnCompletion = (turn, waitSignal) => {
|
|
|
42640
42653
|
});
|
|
42641
42654
|
};
|
|
42642
42655
|
var DaemonTurnRegistry = class {
|
|
42643
|
-
#
|
|
42644
|
-
#
|
|
42656
|
+
#inFlightByCommandId = /* @__PURE__ */ new Map();
|
|
42657
|
+
#latestCompletedBySessionId = /* @__PURE__ */ new Map();
|
|
42645
42658
|
#operations;
|
|
42646
42659
|
constructor(operations) {
|
|
42647
42660
|
this.#operations = operations;
|
|
42648
42661
|
}
|
|
42649
42662
|
start = (command, attemptId) => {
|
|
42650
42663
|
const turn = new ActiveDaemonTurn(command, attemptId);
|
|
42651
|
-
this.#
|
|
42664
|
+
this.#inFlightByCommandId.set(command.commandId, turn);
|
|
42652
42665
|
return turn;
|
|
42653
42666
|
};
|
|
42654
|
-
get = (commandId) => this.#
|
|
42667
|
+
get = (commandId) => this.#inFlightByCommandId.get(commandId);
|
|
42655
42668
|
finish = (turn) => {
|
|
42656
42669
|
const recovery = turn.recovery();
|
|
42657
|
-
this.#
|
|
42670
|
+
this.#inFlightByCommandId.delete(turn.command.commandId);
|
|
42658
42671
|
if (recovery !== null) {
|
|
42659
|
-
this
|
|
42660
|
-
this.#completed.set(turn.command.sessionId, {
|
|
42661
|
-
command: turn.command,
|
|
42662
|
-
recovery: { ...recovery, state: "completed" }
|
|
42663
|
-
});
|
|
42664
|
-
while (this.#completed.size > COMPLETED_TURN_RECOVERY_LIMIT) {
|
|
42665
|
-
const oldestSessionId = this.#completed.keys().next().value;
|
|
42666
|
-
if (oldestSessionId === void 0) {
|
|
42667
|
-
break;
|
|
42668
|
-
}
|
|
42669
|
-
this.#completed.delete(oldestSessionId);
|
|
42670
|
-
}
|
|
42672
|
+
this.retainLatestCompletedRecovery(turn.command, { ...recovery, state: "completed" });
|
|
42671
42673
|
}
|
|
42672
42674
|
turn.finish();
|
|
42673
42675
|
};
|
|
42676
|
+
retainLatestCompletedRecovery = (command, recovery) => {
|
|
42677
|
+
this.#latestCompletedBySessionId.delete(command.sessionId);
|
|
42678
|
+
this.#latestCompletedBySessionId.set(command.sessionId, { command, recovery });
|
|
42679
|
+
while (this.#latestCompletedBySessionId.size > MAX_SESSIONS_WITH_COMPLETED_RECOVERY) {
|
|
42680
|
+
const oldestSessionId = this.#latestCompletedBySessionId.keys().next().value;
|
|
42681
|
+
if (oldestSessionId === void 0) {
|
|
42682
|
+
break;
|
|
42683
|
+
}
|
|
42684
|
+
this.#latestCompletedBySessionId.delete(oldestSessionId);
|
|
42685
|
+
}
|
|
42686
|
+
};
|
|
42674
42687
|
recordEvent = (event) => {
|
|
42675
42688
|
if ("commandId" in event) {
|
|
42676
|
-
const turn = this.#
|
|
42689
|
+
const turn = this.#inFlightByCommandId.get(event.commandId);
|
|
42677
42690
|
if (turn !== void 0 && turn.command.sessionId === event.sessionId) {
|
|
42678
42691
|
return turn.recordEvent(event);
|
|
42679
42692
|
}
|
|
42680
42693
|
}
|
|
42681
42694
|
if (event.kind === SESSION_PERMISSION_RESOLVED_KIND || event.kind === SESSION_ELICITATION_RESOLVED_KIND) {
|
|
42682
|
-
for (const turn of this.#
|
|
42695
|
+
for (const turn of this.#inFlightByCommandId.values()) {
|
|
42683
42696
|
if (turn.matchesInteraction(event.interactionId, event.sessionId)) {
|
|
42684
42697
|
return turn.recordEvent(event);
|
|
42685
42698
|
}
|
|
@@ -42688,17 +42701,17 @@ var DaemonTurnRegistry = class {
|
|
|
42688
42701
|
return event;
|
|
42689
42702
|
};
|
|
42690
42703
|
recordSteeringPrompt = (command) => {
|
|
42691
|
-
const target = this.#
|
|
42704
|
+
const target = this.#inFlightByCommandId.get(command.targetCommandId);
|
|
42692
42705
|
return target?.recordSteeringPrompt(command) ?? false;
|
|
42693
42706
|
};
|
|
42694
42707
|
executionSnapshot = async (matches) => {
|
|
42695
42708
|
const matching = [];
|
|
42696
|
-
for (const turn of this.#
|
|
42709
|
+
for (const turn of this.#inFlightByCommandId.values()) {
|
|
42697
42710
|
if (await matches(turn.command)) {
|
|
42698
42711
|
matching.push(turn);
|
|
42699
42712
|
}
|
|
42700
42713
|
}
|
|
42701
|
-
const current = matching.filter((turn) => this.#
|
|
42714
|
+
const current = matching.filter((turn) => this.#inFlightByCommandId.get(turn.command.commandId) === turn);
|
|
42702
42715
|
return {
|
|
42703
42716
|
turn: current.map((turn) => turn.snapshot()).find((snapshot) => snapshot !== null) ?? null,
|
|
42704
42717
|
queued: current.filter((turn) => turn.isQueued()).map(({ command }) => ({
|
|
@@ -42709,7 +42722,7 @@ var DaemonTurnRegistry = class {
|
|
|
42709
42722
|
};
|
|
42710
42723
|
};
|
|
42711
42724
|
snapshot = (matches) => {
|
|
42712
|
-
for (const turn of this.#
|
|
42725
|
+
for (const turn of this.#inFlightByCommandId.values()) {
|
|
42713
42726
|
if (!matches(turn.command)) {
|
|
42714
42727
|
continue;
|
|
42715
42728
|
}
|
|
@@ -42721,7 +42734,7 @@ var DaemonTurnRegistry = class {
|
|
|
42721
42734
|
return null;
|
|
42722
42735
|
};
|
|
42723
42736
|
recovery = (matches) => {
|
|
42724
|
-
for (const turn of this.#
|
|
42737
|
+
for (const turn of this.#inFlightByCommandId.values()) {
|
|
42725
42738
|
if (!matches(turn.command)) {
|
|
42726
42739
|
continue;
|
|
42727
42740
|
}
|
|
@@ -42730,7 +42743,7 @@ var DaemonTurnRegistry = class {
|
|
|
42730
42743
|
return recovery;
|
|
42731
42744
|
}
|
|
42732
42745
|
}
|
|
42733
|
-
for (const completed of this.#
|
|
42746
|
+
for (const completed of this.#latestCompletedBySessionId.values()) {
|
|
42734
42747
|
if (matches(completed.command)) {
|
|
42735
42748
|
return completed.recovery;
|
|
42736
42749
|
}
|
|
@@ -42738,17 +42751,17 @@ var DaemonTurnRegistry = class {
|
|
|
42738
42751
|
return null;
|
|
42739
42752
|
};
|
|
42740
42753
|
close = async () => {
|
|
42741
|
-
const turns = [...this.#
|
|
42754
|
+
const turns = [...this.#inFlightByCommandId.values()];
|
|
42742
42755
|
for (const turn of turns) {
|
|
42743
42756
|
turn.abortDaemon();
|
|
42744
42757
|
}
|
|
42745
42758
|
await Promise.all(turns.map((turn) => turn.completion));
|
|
42746
|
-
this.#
|
|
42759
|
+
this.#latestCompletedBySessionId.clear();
|
|
42747
42760
|
};
|
|
42748
42761
|
admitAndWait = async (turn, waitSignal, onQueued) => {
|
|
42749
42762
|
const command = turn.command;
|
|
42750
42763
|
waitSignal.throwIfAborted();
|
|
42751
|
-
const head = () => [...this.#
|
|
42764
|
+
const head = () => [...this.#inFlightByCommandId.values()].find(
|
|
42752
42765
|
(turn2) => turn2.command.sessionId === command.sessionId && turn2.command.disposition !== SessionPromptDisposition.Steer
|
|
42753
42766
|
);
|
|
42754
42767
|
if (command.disposition === SessionPromptDisposition.Queue) {
|
|
@@ -45318,6 +45331,7 @@ function paginateActiveTurn(history, activeTurn, sessionId, endIndex, limit) {
|
|
|
45318
45331
|
historicalCharacterLimit
|
|
45319
45332
|
) : null;
|
|
45320
45333
|
return {
|
|
45334
|
+
streamPosition: { commandId: activeTurn.commandId, epoch: activeTurn.epoch, sequence: activeTurn.sequence },
|
|
45321
45335
|
coveredCommandIds: historical?.coveredCommandIds ?? [],
|
|
45322
45336
|
coveredMessageIds: [
|
|
45323
45337
|
...historical?.coveredMessageIds ?? [],
|
|
@@ -45350,6 +45364,7 @@ function mergeLiveSessionHistory(history, activeTurn, sessionId) {
|
|
|
45350
45364
|
}
|
|
45351
45365
|
return {
|
|
45352
45366
|
coveredCommandIds: page.coveredCommandIds,
|
|
45367
|
+
...page.streamPosition === void 0 ? {} : { streamPosition: page.streamPosition },
|
|
45353
45368
|
coveredMessageIds: page.coveredMessageIds,
|
|
45354
45369
|
messages: page.messages,
|
|
45355
45370
|
status: "available",
|
|
@@ -46965,7 +46980,7 @@ async function runRelayConnection({
|
|
|
46965
46980
|
return;
|
|
46966
46981
|
}
|
|
46967
46982
|
if (kind === SESSION_EVENT_REJECTED_KIND) {
|
|
46968
|
-
publisher.
|
|
46983
|
+
publisher.reject(message);
|
|
46969
46984
|
return;
|
|
46970
46985
|
}
|
|
46971
46986
|
commands.handle(message);
|
|
@@ -46992,250 +47007,84 @@ async function runRelayConnection({
|
|
|
46992
47007
|
}
|
|
46993
47008
|
}
|
|
46994
47009
|
|
|
46995
|
-
// src/relay-connection/session-event-
|
|
47010
|
+
// src/relay-connection/session-event-publisher.ts
|
|
47011
|
+
import { WebSocket as WebSocket9 } from "undici";
|
|
47012
|
+
|
|
47013
|
+
// src/relay-connection/session-event-queue.ts
|
|
46996
47014
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
46997
|
-
|
|
46998
|
-
|
|
46999
|
-
|
|
47000
|
-
|
|
47001
|
-
var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
|
|
47002
|
-
var MAX_DEAD_LETTERS = 1024;
|
|
47003
|
-
var DEFAULT_LIMITS = {
|
|
47004
|
-
criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
|
|
47005
|
-
standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
|
|
47006
|
-
};
|
|
47007
|
-
var SessionEventOutboxCapacityError = class extends Error {
|
|
47008
|
-
code = "session_event_outbox_full";
|
|
47015
|
+
var STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
|
|
47016
|
+
var CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
|
|
47017
|
+
var SessionEventQueueCapacityError = class extends Error {
|
|
47018
|
+
code = "session_event_queue_full";
|
|
47009
47019
|
constructor() {
|
|
47010
|
-
super("The
|
|
47011
|
-
this.name = "
|
|
47020
|
+
super("The in-memory Session event queue is full");
|
|
47021
|
+
this.name = "SessionEventQueueCapacityError";
|
|
47012
47022
|
}
|
|
47013
47023
|
};
|
|
47014
|
-
|
|
47015
|
-
|
|
47016
|
-
|
|
47017
|
-
|
|
47018
|
-
|
|
47019
|
-
return value;
|
|
47020
|
-
}
|
|
47021
|
-
function rowString(row, field) {
|
|
47022
|
-
const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
|
|
47023
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
47024
|
-
throw new Error(`SQLite returned an invalid ${field} value`);
|
|
47025
|
-
}
|
|
47026
|
-
return value;
|
|
47027
|
-
}
|
|
47028
|
-
function validateLimits(limits) {
|
|
47029
|
-
if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
|
|
47030
|
-
throw new Error("Session event outbox limits are invalid");
|
|
47031
|
-
}
|
|
47032
|
-
}
|
|
47033
|
-
var SessionEventOutbox = class {
|
|
47034
|
-
#database;
|
|
47035
|
-
#limits;
|
|
47036
|
-
constructor(path3, limits = DEFAULT_LIMITS) {
|
|
47037
|
-
validateLimits(limits);
|
|
47038
|
-
mkdirSync3(dirname6(path3), { mode: 448, recursive: true });
|
|
47039
|
-
this.#limits = limits;
|
|
47040
|
-
this.#database = new DatabaseSync(path3);
|
|
47041
|
-
this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
|
|
47042
|
-
this.#database.exec(`
|
|
47043
|
-
CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
|
|
47044
|
-
ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47045
|
-
event_id TEXT NOT NULL UNIQUE,
|
|
47046
|
-
session_id TEXT NOT NULL,
|
|
47047
|
-
source_seq INTEGER NOT NULL UNIQUE,
|
|
47048
|
-
byte_length INTEGER NOT NULL,
|
|
47049
|
-
envelope_json TEXT NOT NULL
|
|
47050
|
-
) STRICT;
|
|
47051
|
-
CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
|
|
47052
|
-
ON relay_session_event_outbox(source_seq);
|
|
47053
|
-
CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
|
|
47054
|
-
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
|
|
47055
|
-
stream_id TEXT NOT NULL,
|
|
47056
|
-
next_source_seq INTEGER NOT NULL,
|
|
47057
|
-
total_bytes INTEGER NOT NULL
|
|
47058
|
-
) STRICT;
|
|
47059
|
-
CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
|
|
47060
|
-
ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47061
|
-
event_id TEXT NOT NULL UNIQUE,
|
|
47062
|
-
session_id TEXT NOT NULL,
|
|
47063
|
-
rejection_code TEXT NOT NULL,
|
|
47064
|
-
envelope_json TEXT NOT NULL
|
|
47065
|
-
) STRICT;
|
|
47066
|
-
`);
|
|
47067
|
-
this.#database.prepare(
|
|
47068
|
-
`INSERT OR IGNORE INTO relay_session_event_outbox_state (
|
|
47069
|
-
singleton, stream_id, next_source_seq, total_bytes
|
|
47070
|
-
) VALUES (1, ?, 1, 0)`
|
|
47071
|
-
).run(randomUUID7());
|
|
47072
|
-
}
|
|
47073
|
-
acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
|
|
47074
|
-
close = () => {
|
|
47075
|
-
this.#database.close();
|
|
47076
|
-
};
|
|
47077
|
-
enqueue = (sessionId, payload, critical) => {
|
|
47078
|
-
const envelope2 = this.enqueueBatch(sessionId, [payload], critical)[0];
|
|
47079
|
-
if (envelope2 === void 0) {
|
|
47080
|
-
throw new Error("The Session event outbox did not enqueue the event");
|
|
47081
|
-
}
|
|
47082
|
-
return envelope2;
|
|
47083
|
-
};
|
|
47024
|
+
var SessionEventQueue = class {
|
|
47025
|
+
#streamId = randomUUID7();
|
|
47026
|
+
#events = /* @__PURE__ */ new Map();
|
|
47027
|
+
#nextSourceSeq = 1;
|
|
47028
|
+
#totalBytes = 0;
|
|
47084
47029
|
enqueueBatch = (sessionId, payloads, critical) => {
|
|
47085
47030
|
if (payloads.length < 1 || payloads.length > 32) {
|
|
47086
47031
|
throw new Error("A Session event batch must contain between 1 and 32 events");
|
|
47087
47032
|
}
|
|
47088
|
-
this.#
|
|
47089
|
-
|
|
47090
|
-
const state = this.#database.prepare(
|
|
47091
|
-
`SELECT stream_id, next_source_seq, total_bytes
|
|
47092
|
-
FROM relay_session_event_outbox_state
|
|
47093
|
-
WHERE singleton = 1`
|
|
47094
|
-
).get();
|
|
47095
|
-
const sourceSeq = rowNumber(state, "next_source_seq");
|
|
47096
|
-
if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + payloads.length)) {
|
|
47097
|
-
throw new Error("The Session event source sequence is exhausted");
|
|
47098
|
-
}
|
|
47099
|
-
const streamId = rowString(state, "stream_id");
|
|
47100
|
-
const stored = payloads.map((payload, index) => {
|
|
47101
|
-
const envelope2 = {
|
|
47102
|
-
delivery: {
|
|
47103
|
-
eventId: randomUUID7(),
|
|
47104
|
-
kind: "reliable",
|
|
47105
|
-
sourceSeq: (sourceSeq + index).toString(),
|
|
47106
|
-
streamId
|
|
47107
|
-
},
|
|
47108
|
-
kind: SESSION_EVENT_KIND,
|
|
47109
|
-
payload,
|
|
47110
|
-
sessionId
|
|
47111
|
-
};
|
|
47112
|
-
const encoded = encodeSessionEventEnvelope(envelope2);
|
|
47113
|
-
return { byteLength: Buffer.byteLength(encoded, "utf8"), encoded, envelope: envelope2 };
|
|
47114
|
-
});
|
|
47115
|
-
const byteLength = stored.reduce((total, entry) => total + entry.byteLength, 0);
|
|
47116
|
-
const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
|
|
47117
|
-
if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
|
|
47118
|
-
throw new SessionEventOutboxCapacityError();
|
|
47119
|
-
}
|
|
47120
|
-
const insert = this.#database.prepare(
|
|
47121
|
-
`INSERT INTO relay_session_event_outbox (
|
|
47122
|
-
event_id, session_id, source_seq, byte_length, envelope_json
|
|
47123
|
-
) VALUES (?, ?, ?, ?, ?)`
|
|
47124
|
-
);
|
|
47125
|
-
for (const entry of stored) {
|
|
47126
|
-
insert.run(
|
|
47127
|
-
entry.envelope.delivery.eventId,
|
|
47128
|
-
sessionId,
|
|
47129
|
-
Number(entry.envelope.delivery.sourceSeq),
|
|
47130
|
-
entry.byteLength,
|
|
47131
|
-
entry.encoded
|
|
47132
|
-
);
|
|
47133
|
-
}
|
|
47134
|
-
this.#database.prepare(
|
|
47135
|
-
`UPDATE relay_session_event_outbox_state
|
|
47136
|
-
SET next_source_seq = next_source_seq + ?,
|
|
47137
|
-
total_bytes = total_bytes + ?
|
|
47138
|
-
WHERE singleton = 1`
|
|
47139
|
-
).run(stored.length, byteLength);
|
|
47140
|
-
this.#database.exec("COMMIT");
|
|
47141
|
-
return stored.map((entry) => entry.envelope);
|
|
47142
|
-
} catch (error62) {
|
|
47143
|
-
this.#database.exec("ROLLBACK");
|
|
47144
|
-
throw error62;
|
|
47033
|
+
if (!Number.isSafeInteger(this.#nextSourceSeq + payloads.length)) {
|
|
47034
|
+
throw new Error("The Session event source sequence is exhausted");
|
|
47145
47035
|
}
|
|
47146
|
-
|
|
47147
|
-
|
|
47148
|
-
|
|
47149
|
-
|
|
47150
|
-
|
|
47151
|
-
|
|
47152
|
-
|
|
47153
|
-
|
|
47154
|
-
|
|
47155
|
-
|
|
47156
|
-
|
|
47157
|
-
|
|
47158
|
-
|
|
47159
|
-
const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
|
|
47160
|
-
if (!("delivery" in envelope2)) {
|
|
47161
|
-
throw new Error("SQLite returned an invalid reliable Session event envelope");
|
|
47162
|
-
}
|
|
47163
|
-
return envelope2;
|
|
47036
|
+
const entries = payloads.map((payload, index) => {
|
|
47037
|
+
const envelope2 = {
|
|
47038
|
+
delivery: {
|
|
47039
|
+
eventId: randomUUID7(),
|
|
47040
|
+
kind: "reliable",
|
|
47041
|
+
sourceSeq: (this.#nextSourceSeq + index).toString(),
|
|
47042
|
+
streamId: this.#streamId
|
|
47043
|
+
},
|
|
47044
|
+
kind: SESSION_EVENT_KIND,
|
|
47045
|
+
payload,
|
|
47046
|
+
sessionId
|
|
47047
|
+
};
|
|
47048
|
+
return { envelope: envelope2, byteLength: Buffer.byteLength(encodeSessionEventEnvelope(envelope2), "utf8") };
|
|
47164
47049
|
});
|
|
47050
|
+
const byteLength = entries.reduce((total, entry) => total + entry.byteLength, 0);
|
|
47051
|
+
if (this.#totalBytes + byteLength > (critical ? CRITICAL_EVENT_BYTES : STANDARD_EVENT_BYTES)) {
|
|
47052
|
+
throw new SessionEventQueueCapacityError();
|
|
47053
|
+
}
|
|
47054
|
+
for (const entry of entries) {
|
|
47055
|
+
this.#events.set(entry.envelope.delivery.eventId, entry);
|
|
47056
|
+
}
|
|
47057
|
+
this.#nextSourceSeq += entries.length;
|
|
47058
|
+
this.#totalBytes += byteLength;
|
|
47165
47059
|
};
|
|
47166
|
-
|
|
47167
|
-
|
|
47168
|
-
|
|
47169
|
-
|
|
47170
|
-
|
|
47171
|
-
|
|
47172
|
-
|
|
47173
|
-
|
|
47174
|
-
if (stored === void 0) {
|
|
47175
|
-
this.#database.exec("COMMIT");
|
|
47176
|
-
return false;
|
|
47060
|
+
pending = (limit, excludedEventIds) => {
|
|
47061
|
+
const result = [];
|
|
47062
|
+
for (const [eventId, entry] of this.#events) {
|
|
47063
|
+
if (!excludedEventIds.has(eventId)) {
|
|
47064
|
+
result.push(entry.envelope);
|
|
47065
|
+
if (result.length === limit) {
|
|
47066
|
+
break;
|
|
47067
|
+
}
|
|
47177
47068
|
}
|
|
47178
|
-
this.#database.prepare(
|
|
47179
|
-
`INSERT INTO relay_session_event_dead_letters (
|
|
47180
|
-
event_id, session_id, rejection_code, envelope_json
|
|
47181
|
-
) VALUES (?, ?, ?, ?)`
|
|
47182
|
-
).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
|
|
47183
|
-
this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
|
|
47184
|
-
this.#database.prepare(
|
|
47185
|
-
`DELETE FROM relay_session_event_dead_letters
|
|
47186
|
-
WHERE ordinal NOT IN (
|
|
47187
|
-
SELECT ordinal FROM relay_session_event_dead_letters
|
|
47188
|
-
ORDER BY ordinal DESC LIMIT ?
|
|
47189
|
-
)`
|
|
47190
|
-
).run(MAX_DEAD_LETTERS);
|
|
47191
|
-
this.#database.exec("COMMIT");
|
|
47192
|
-
return true;
|
|
47193
|
-
} catch (error62) {
|
|
47194
|
-
this.#database.exec("ROLLBACK");
|
|
47195
|
-
throw error62;
|
|
47196
47069
|
}
|
|
47070
|
+
return result;
|
|
47197
47071
|
};
|
|
47198
|
-
|
|
47199
|
-
const
|
|
47200
|
-
|
|
47201
|
-
|
|
47202
|
-
).run(eventId, sessionId);
|
|
47203
|
-
if (deletion.changes !== 1) {
|
|
47204
|
-
throw new Error("The Session event could not be removed from the outbox");
|
|
47205
|
-
}
|
|
47206
|
-
const state = this.#database.prepare(
|
|
47207
|
-
`UPDATE relay_session_event_outbox_state
|
|
47208
|
-
SET total_bytes = total_bytes - ?
|
|
47209
|
-
WHERE singleton = 1 AND total_bytes >= ?`
|
|
47210
|
-
).run(byteLength, byteLength);
|
|
47211
|
-
if (state.changes !== 1) {
|
|
47212
|
-
throw new Error("The Session event outbox capacity state is inconsistent");
|
|
47072
|
+
remove = (eventId, sessionId) => {
|
|
47073
|
+
const entry = this.#events.get(eventId);
|
|
47074
|
+
if (entry === void 0 || entry.envelope.sessionId !== sessionId) {
|
|
47075
|
+
return false;
|
|
47213
47076
|
}
|
|
47077
|
+
this.#events.delete(eventId);
|
|
47078
|
+
this.#totalBytes -= entry.byteLength;
|
|
47079
|
+
return true;
|
|
47214
47080
|
};
|
|
47215
|
-
|
|
47216
|
-
this.#
|
|
47217
|
-
|
|
47218
|
-
const stored = this.#database.prepare(
|
|
47219
|
-
`SELECT byte_length
|
|
47220
|
-
FROM relay_session_event_outbox
|
|
47221
|
-
WHERE event_id = ? AND session_id = ?`
|
|
47222
|
-
).get(eventId, sessionId);
|
|
47223
|
-
if (stored === void 0) {
|
|
47224
|
-
this.#database.exec("COMMIT");
|
|
47225
|
-
return false;
|
|
47226
|
-
}
|
|
47227
|
-
this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
|
|
47228
|
-
this.#database.exec("COMMIT");
|
|
47229
|
-
return true;
|
|
47230
|
-
} catch (error62) {
|
|
47231
|
-
this.#database.exec("ROLLBACK");
|
|
47232
|
-
throw error62;
|
|
47233
|
-
}
|
|
47081
|
+
clear = () => {
|
|
47082
|
+
this.#events.clear();
|
|
47083
|
+
this.#totalBytes = 0;
|
|
47234
47084
|
};
|
|
47235
47085
|
};
|
|
47236
47086
|
|
|
47237
47087
|
// src/relay-connection/session-event-publisher.ts
|
|
47238
|
-
import { WebSocket as WebSocket9 } from "undici";
|
|
47239
47088
|
var SESSION_EVENT_REPLAY_WINDOW = 16;
|
|
47240
47089
|
var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
|
|
47241
47090
|
function reservesSessionEventCapacity(event) {
|
|
@@ -47246,18 +47095,17 @@ function reservesSessionEventCapacity(event) {
|
|
|
47246
47095
|
}
|
|
47247
47096
|
var DaemonSessionEventPublisher = class {
|
|
47248
47097
|
#inFlight = /* @__PURE__ */ new Map();
|
|
47249
|
-
#
|
|
47098
|
+
#queue = new SessionEventQueue();
|
|
47250
47099
|
#reportedCapacityKinds = /* @__PURE__ */ new Set();
|
|
47251
47100
|
#timer;
|
|
47252
47101
|
#connection = { kind: "disconnected" };
|
|
47253
47102
|
#deferredReplay = null;
|
|
47254
47103
|
#inFlightBytes = 0;
|
|
47255
|
-
constructor(
|
|
47256
|
-
this.#outbox = outbox;
|
|
47104
|
+
constructor(timer) {
|
|
47257
47105
|
this.#timer = timer;
|
|
47258
47106
|
}
|
|
47259
47107
|
acknowledge = (acknowledgement) => {
|
|
47260
|
-
if (!this.#
|
|
47108
|
+
if (!this.#queue.remove(acknowledgement.eventId, acknowledgement.sessionId)) {
|
|
47261
47109
|
return;
|
|
47262
47110
|
}
|
|
47263
47111
|
this.#removeInFlight(acknowledgement.eventId);
|
|
@@ -47268,7 +47116,7 @@ var DaemonSessionEventPublisher = class {
|
|
|
47268
47116
|
this.#deferredReplay = null;
|
|
47269
47117
|
this.#connection = { kind: "disconnected" };
|
|
47270
47118
|
this.#clearInFlight();
|
|
47271
|
-
this.#
|
|
47119
|
+
this.#queue.clear();
|
|
47272
47120
|
};
|
|
47273
47121
|
connect = (socket, delivery) => {
|
|
47274
47122
|
this.#connection = { delivery, kind: "connected", socket };
|
|
@@ -47297,16 +47145,18 @@ var DaemonSessionEventPublisher = class {
|
|
|
47297
47145
|
};
|
|
47298
47146
|
publish = (sessionId, event) => {
|
|
47299
47147
|
try {
|
|
47300
|
-
this.#
|
|
47148
|
+
this.#queue.enqueueBatch(
|
|
47149
|
+
sessionId,
|
|
47150
|
+
[encodeRecordedSessionEvent(event)],
|
|
47151
|
+
reservesSessionEventCapacity(event)
|
|
47152
|
+
);
|
|
47301
47153
|
} catch (error62) {
|
|
47302
|
-
if (!(error62 instanceof
|
|
47154
|
+
if (!(error62 instanceof SessionEventQueueCapacityError)) {
|
|
47303
47155
|
throw error62;
|
|
47304
47156
|
}
|
|
47305
47157
|
if (!this.#reportedCapacityKinds.has(event.kind)) {
|
|
47306
47158
|
this.#reportedCapacityKinds.add(event.kind);
|
|
47307
|
-
console.warn(
|
|
47308
|
-
`Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
|
|
47309
|
-
);
|
|
47159
|
+
console.warn(`Mobius daemon could not buffer "${event.kind}" because the Session event queue is full`);
|
|
47310
47160
|
}
|
|
47311
47161
|
return;
|
|
47312
47162
|
}
|
|
@@ -47314,11 +47164,11 @@ var DaemonSessionEventPublisher = class {
|
|
|
47314
47164
|
};
|
|
47315
47165
|
publishBatch = (sessionId, events) => {
|
|
47316
47166
|
const critical = events.some(reservesSessionEventCapacity);
|
|
47317
|
-
this.#
|
|
47167
|
+
this.#queue.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
|
|
47318
47168
|
this.#publishNextBatch();
|
|
47319
47169
|
};
|
|
47320
|
-
|
|
47321
|
-
if (!this.#
|
|
47170
|
+
reject = (rejection) => {
|
|
47171
|
+
if (!this.#queue.remove(rejection.eventId, rejection.sessionId)) {
|
|
47322
47172
|
return;
|
|
47323
47173
|
}
|
|
47324
47174
|
this.#removeInFlight(rejection.eventId);
|
|
@@ -47334,9 +47184,10 @@ var DaemonSessionEventPublisher = class {
|
|
|
47334
47184
|
return;
|
|
47335
47185
|
}
|
|
47336
47186
|
while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
|
|
47337
|
-
const batch = this.#
|
|
47338
|
-
|
|
47339
|
-
|
|
47187
|
+
const batch = this.#queue.pending(
|
|
47188
|
+
SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size,
|
|
47189
|
+
new Set(this.#inFlight.keys())
|
|
47190
|
+
);
|
|
47340
47191
|
let sent = 0;
|
|
47341
47192
|
for (const envelope2 of batch) {
|
|
47342
47193
|
const encoded = encodeDaemonToServiceMessage(
|
|
@@ -47355,12 +47206,7 @@ var DaemonSessionEventPublisher = class {
|
|
|
47355
47206
|
return;
|
|
47356
47207
|
}
|
|
47357
47208
|
if (this.#connection.delivery === "ephemeral") {
|
|
47358
|
-
this.#
|
|
47359
|
-
eventId: envelope2.delivery.eventId,
|
|
47360
|
-
kind: "session.event.ack",
|
|
47361
|
-
outcome: { kind: "relayed" },
|
|
47362
|
-
sessionId: envelope2.sessionId
|
|
47363
|
-
});
|
|
47209
|
+
this.#queue.remove(envelope2.delivery.eventId, envelope2.sessionId);
|
|
47364
47210
|
sent += 1;
|
|
47365
47211
|
continue;
|
|
47366
47212
|
}
|
|
@@ -47473,11 +47319,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
|
|
|
47473
47319
|
join18(stateDirectory, "sessions", `${createHash6("sha256").update(config2.channelId).digest("hex")}.sqlite`)
|
|
47474
47320
|
);
|
|
47475
47321
|
const operations = new DaemonOperationState();
|
|
47476
|
-
const
|
|
47477
|
-
const publisher = new DaemonSessionEventPublisher(
|
|
47478
|
-
new SessionEventOutbox(join18(stateDirectory, "events", `${channelHash}.sqlite`)),
|
|
47479
|
-
timer
|
|
47480
|
-
);
|
|
47322
|
+
const publisher = new DaemonSessionEventPublisher(timer);
|
|
47481
47323
|
const activeTurns = new DaemonTurnRegistry(operations);
|
|
47482
47324
|
const recordSessionEvent = (sessionId, event) => {
|
|
47483
47325
|
publisher.publish(sessionId, activeTurns.recordEvent(event));
|