@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.
Files changed (35) hide show
  1. package/dist/cli.js +126 -284
  2. package/dist/cli.js.map +4 -4
  3. package/dist/daemon-active-turn.d.ts +1 -0
  4. package/dist/daemon-active-turn.d.ts.map +1 -1
  5. package/dist/daemon-active-turn.js +1 -0
  6. package/dist/daemon-active-turn.js.map +1 -1
  7. package/dist/daemon-turn-registry.d.ts +2 -1
  8. package/dist/daemon-turn-registry.d.ts.map +1 -1
  9. package/dist/daemon-turn-registry.js +32 -34
  10. package/dist/daemon-turn-registry.js.map +1 -1
  11. package/dist/index.js +116 -281
  12. package/dist/index.js.map +4 -4
  13. package/dist/native-sessions/live-history-page.d.ts.map +1 -1
  14. package/dist/native-sessions/live-history-page.js +2 -0
  15. package/dist/native-sessions/live-history-page.js.map +1 -1
  16. package/dist/relay-connection/run-relay-connection.js +1 -1
  17. package/dist/relay-connection/run-relay-connection.js.map +1 -1
  18. package/dist/relay-connection/session-event-publisher.d.ts +3 -4
  19. package/dist/relay-connection/session-event-publisher.d.ts.map +1 -1
  20. package/dist/relay-connection/session-event-publisher.js +14 -22
  21. package/dist/relay-connection/session-event-publisher.js.map +1 -1
  22. package/dist/relay-connection/session-event-queue.d.ts +14 -0
  23. package/dist/relay-connection/session-event-queue.d.ts.map +1 -0
  24. package/dist/relay-connection/session-event-queue.js +75 -0
  25. package/dist/relay-connection/session-event-queue.js.map +1 -0
  26. package/dist/run-daemon.d.ts.map +1 -1
  27. package/dist/run-daemon.js +1 -3
  28. package/dist/run-daemon.js.map +1 -1
  29. package/dist/runtime.js +118 -283
  30. package/dist/runtime.js.map +4 -4
  31. package/package.json +10 -3
  32. package/dist/relay-connection/session-event-outbox.d.ts +0 -20
  33. package/dist/relay-connection/session-event-outbox.d.ts.map +0 -1
  34. package/dist/relay-connection/session-event-outbox.js +0 -243
  35. package/dist/relay-connection/session-event-outbox.js.map +0 -1
package/dist/index.js CHANGED
@@ -20990,6 +20990,11 @@ var assistantHistoryMessageSchema = external_exports.strictObject({
20990
20990
  }).readonly();
20991
20991
  var historyMessage = external_exports.discriminatedUnion("role", [userHistoryMessage, assistantHistoryMessageSchema]);
20992
20992
  var availableSessionHistoryObjectSchema = external_exports.strictObject({
20993
+ streamPosition: external_exports.strictObject({
20994
+ commandId: identifierStringSchema,
20995
+ epoch: identifierStringSchema,
20996
+ sequence: external_exports.number().int().nonnegative()
20997
+ }).readonly().optional(),
20993
20998
  status: external_exports.literal("available"),
20994
20999
  messages: external_exports.array(historyMessage).max(SESSION_HISTORY_MESSAGE_LIMIT).readonly(),
20995
21000
  truncated: external_exports.boolean(),
@@ -39111,6 +39116,7 @@ var ActiveDaemonTurn = class {
39111
39116
  commandId: decodeIdentifier(this.command.commandId),
39112
39117
  epoch: this.#epoch,
39113
39118
  messages: this.timelineMessages(),
39119
+ sequence: this.#sequence,
39114
39120
  state: "running",
39115
39121
  truncated: false
39116
39122
  };
@@ -39138,7 +39144,7 @@ var ActiveDaemonTurn = class {
39138
39144
  };
39139
39145
 
39140
39146
  // src/daemon-turn-registry.ts
39141
- var COMPLETED_TURN_RECOVERY_LIMIT = 32;
39147
+ var MAX_SESSIONS_WITH_COMPLETED_RECOVERY = 32;
39142
39148
  var waitForTurnCompletion = (turn, waitSignal) => {
39143
39149
  waitSignal.throwIfAborted();
39144
39150
  return new Promise((resolve9, reject) => {
@@ -39164,46 +39170,46 @@ var waitForTurnCompletion = (turn, waitSignal) => {
39164
39170
  });
39165
39171
  };
39166
39172
  var DaemonTurnRegistry = class {
39167
- #turns = /* @__PURE__ */ new Map();
39168
- #completed = /* @__PURE__ */ new Map();
39173
+ #inFlightByCommandId = /* @__PURE__ */ new Map();
39174
+ #latestCompletedBySessionId = /* @__PURE__ */ new Map();
39169
39175
  #operations;
39170
39176
  constructor(operations) {
39171
39177
  this.#operations = operations;
39172
39178
  }
39173
39179
  start = (command, attemptId) => {
39174
39180
  const turn = new ActiveDaemonTurn(command, attemptId);
39175
- this.#turns.set(command.commandId, turn);
39181
+ this.#inFlightByCommandId.set(command.commandId, turn);
39176
39182
  return turn;
39177
39183
  };
39178
- get = (commandId) => this.#turns.get(commandId);
39184
+ get = (commandId) => this.#inFlightByCommandId.get(commandId);
39179
39185
  finish = (turn) => {
39180
39186
  const recovery = turn.recovery();
39181
- this.#turns.delete(turn.command.commandId);
39187
+ this.#inFlightByCommandId.delete(turn.command.commandId);
39182
39188
  if (recovery !== null) {
39183
- this.#completed.delete(turn.command.sessionId);
39184
- this.#completed.set(turn.command.sessionId, {
39185
- command: turn.command,
39186
- recovery: { ...recovery, state: "completed" }
39187
- });
39188
- while (this.#completed.size > COMPLETED_TURN_RECOVERY_LIMIT) {
39189
- const oldestSessionId = this.#completed.keys().next().value;
39190
- if (oldestSessionId === void 0) {
39191
- break;
39192
- }
39193
- this.#completed.delete(oldestSessionId);
39194
- }
39189
+ this.retainLatestCompletedRecovery(turn.command, { ...recovery, state: "completed" });
39195
39190
  }
39196
39191
  turn.finish();
39197
39192
  };
39193
+ retainLatestCompletedRecovery = (command, recovery) => {
39194
+ this.#latestCompletedBySessionId.delete(command.sessionId);
39195
+ this.#latestCompletedBySessionId.set(command.sessionId, { command, recovery });
39196
+ while (this.#latestCompletedBySessionId.size > MAX_SESSIONS_WITH_COMPLETED_RECOVERY) {
39197
+ const oldestSessionId = this.#latestCompletedBySessionId.keys().next().value;
39198
+ if (oldestSessionId === void 0) {
39199
+ break;
39200
+ }
39201
+ this.#latestCompletedBySessionId.delete(oldestSessionId);
39202
+ }
39203
+ };
39198
39204
  recordEvent = (event) => {
39199
39205
  if ("commandId" in event) {
39200
- const turn = this.#turns.get(event.commandId);
39206
+ const turn = this.#inFlightByCommandId.get(event.commandId);
39201
39207
  if (turn !== void 0 && turn.command.sessionId === event.sessionId) {
39202
39208
  return turn.recordEvent(event);
39203
39209
  }
39204
39210
  }
39205
39211
  if (event.kind === SESSION_PERMISSION_RESOLVED_KIND || event.kind === SESSION_ELICITATION_RESOLVED_KIND) {
39206
- for (const turn of this.#turns.values()) {
39212
+ for (const turn of this.#inFlightByCommandId.values()) {
39207
39213
  if (turn.matchesInteraction(event.interactionId, event.sessionId)) {
39208
39214
  return turn.recordEvent(event);
39209
39215
  }
@@ -39212,17 +39218,17 @@ var DaemonTurnRegistry = class {
39212
39218
  return event;
39213
39219
  };
39214
39220
  recordSteeringPrompt = (command) => {
39215
- const target = this.#turns.get(command.targetCommandId);
39221
+ const target = this.#inFlightByCommandId.get(command.targetCommandId);
39216
39222
  return target?.recordSteeringPrompt(command) ?? false;
39217
39223
  };
39218
39224
  executionSnapshot = async (matches) => {
39219
39225
  const matching = [];
39220
- for (const turn of this.#turns.values()) {
39226
+ for (const turn of this.#inFlightByCommandId.values()) {
39221
39227
  if (await matches(turn.command)) {
39222
39228
  matching.push(turn);
39223
39229
  }
39224
39230
  }
39225
- const current = matching.filter((turn) => this.#turns.get(turn.command.commandId) === turn);
39231
+ const current = matching.filter((turn) => this.#inFlightByCommandId.get(turn.command.commandId) === turn);
39226
39232
  return {
39227
39233
  turn: current.map((turn) => turn.snapshot()).find((snapshot) => snapshot !== null) ?? null,
39228
39234
  queued: current.filter((turn) => turn.isQueued()).map(({ command }) => ({
@@ -39233,7 +39239,7 @@ var DaemonTurnRegistry = class {
39233
39239
  };
39234
39240
  };
39235
39241
  snapshot = (matches) => {
39236
- for (const turn of this.#turns.values()) {
39242
+ for (const turn of this.#inFlightByCommandId.values()) {
39237
39243
  if (!matches(turn.command)) {
39238
39244
  continue;
39239
39245
  }
@@ -39245,7 +39251,7 @@ var DaemonTurnRegistry = class {
39245
39251
  return null;
39246
39252
  };
39247
39253
  recovery = (matches) => {
39248
- for (const turn of this.#turns.values()) {
39254
+ for (const turn of this.#inFlightByCommandId.values()) {
39249
39255
  if (!matches(turn.command)) {
39250
39256
  continue;
39251
39257
  }
@@ -39254,7 +39260,7 @@ var DaemonTurnRegistry = class {
39254
39260
  return recovery;
39255
39261
  }
39256
39262
  }
39257
- for (const completed of this.#completed.values()) {
39263
+ for (const completed of this.#latestCompletedBySessionId.values()) {
39258
39264
  if (matches(completed.command)) {
39259
39265
  return completed.recovery;
39260
39266
  }
@@ -39262,17 +39268,17 @@ var DaemonTurnRegistry = class {
39262
39268
  return null;
39263
39269
  };
39264
39270
  close = async () => {
39265
- const turns = [...this.#turns.values()];
39271
+ const turns = [...this.#inFlightByCommandId.values()];
39266
39272
  for (const turn of turns) {
39267
39273
  turn.abortDaemon();
39268
39274
  }
39269
39275
  await Promise.all(turns.map((turn) => turn.completion));
39270
- this.#completed.clear();
39276
+ this.#latestCompletedBySessionId.clear();
39271
39277
  };
39272
39278
  admitAndWait = async (turn, waitSignal, onQueued) => {
39273
39279
  const command = turn.command;
39274
39280
  waitSignal.throwIfAborted();
39275
- const head = () => [...this.#turns.values()].find(
39281
+ const head = () => [...this.#inFlightByCommandId.values()].find(
39276
39282
  (turn2) => turn2.command.sessionId === command.sessionId && turn2.command.disposition !== SessionPromptDisposition.Steer
39277
39283
  );
39278
39284
  if (command.disposition === SessionPromptDisposition.Queue) {
@@ -41842,6 +41848,7 @@ function paginateActiveTurn(history, activeTurn, sessionId, endIndex, limit) {
41842
41848
  historicalCharacterLimit
41843
41849
  ) : null;
41844
41850
  return {
41851
+ streamPosition: { commandId: activeTurn.commandId, epoch: activeTurn.epoch, sequence: activeTurn.sequence },
41845
41852
  coveredCommandIds: historical?.coveredCommandIds ?? [],
41846
41853
  coveredMessageIds: [
41847
41854
  ...historical?.coveredMessageIds ?? [],
@@ -41874,6 +41881,7 @@ function mergeLiveSessionHistory(history, activeTurn, sessionId) {
41874
41881
  }
41875
41882
  return {
41876
41883
  coveredCommandIds: page.coveredCommandIds,
41884
+ ...page.streamPosition === void 0 ? {} : { streamPosition: page.streamPosition },
41877
41885
  coveredMessageIds: page.coveredMessageIds,
41878
41886
  messages: page.messages,
41879
41887
  status: "available",
@@ -43469,7 +43477,7 @@ async function runRelayConnection({
43469
43477
  return;
43470
43478
  }
43471
43479
  if (kind === SESSION_EVENT_REJECTED_KIND) {
43472
- publisher.quarantine(message);
43480
+ publisher.reject(message);
43473
43481
  return;
43474
43482
  }
43475
43483
  commands.handle(message);
@@ -43496,250 +43504,84 @@ async function runRelayConnection({
43496
43504
  }
43497
43505
  }
43498
43506
 
43499
- // src/relay-connection/session-event-outbox.ts
43507
+ // src/relay-connection/session-event-publisher.ts
43508
+ import { WebSocket as WebSocket9 } from "undici";
43509
+
43510
+ // src/relay-connection/session-event-queue.ts
43500
43511
  import { randomUUID as randomUUID7 } from "node:crypto";
43501
- import { mkdirSync as mkdirSync3 } from "node:fs";
43502
- import { dirname as dirname6 } from "node:path";
43503
- import { DatabaseSync } from "node:sqlite";
43504
- var DEFAULT_STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
43505
- var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
43506
- var MAX_DEAD_LETTERS = 1024;
43507
- var DEFAULT_LIMITS = {
43508
- criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
43509
- standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
43510
- };
43511
- var SessionEventOutboxCapacityError = class extends Error {
43512
- code = "session_event_outbox_full";
43512
+ var STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
43513
+ var CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
43514
+ var SessionEventQueueCapacityError = class extends Error {
43515
+ code = "session_event_queue_full";
43513
43516
  constructor() {
43514
- super("The persistent Session event outbox is full");
43515
- this.name = "SessionEventOutboxCapacityError";
43517
+ super("The in-memory Session event queue is full");
43518
+ this.name = "SessionEventQueueCapacityError";
43516
43519
  }
43517
43520
  };
43518
- function rowNumber(row, field) {
43519
- const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
43520
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
43521
- throw new Error(`SQLite returned an invalid ${field} value`);
43522
- }
43523
- return value;
43524
- }
43525
- function rowString(row, field) {
43526
- const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
43527
- if (typeof value !== "string" || value.length === 0) {
43528
- throw new Error(`SQLite returned an invalid ${field} value`);
43529
- }
43530
- return value;
43531
- }
43532
- function validateLimits(limits) {
43533
- if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
43534
- throw new Error("Session event outbox limits are invalid");
43535
- }
43536
- }
43537
- var SessionEventOutbox = class {
43538
- #database;
43539
- #limits;
43540
- constructor(path2, limits = DEFAULT_LIMITS) {
43541
- validateLimits(limits);
43542
- mkdirSync3(dirname6(path2), { mode: 448, recursive: true });
43543
- this.#limits = limits;
43544
- this.#database = new DatabaseSync(path2);
43545
- this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
43546
- this.#database.exec(`
43547
- CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
43548
- ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
43549
- event_id TEXT NOT NULL UNIQUE,
43550
- session_id TEXT NOT NULL,
43551
- source_seq INTEGER NOT NULL UNIQUE,
43552
- byte_length INTEGER NOT NULL,
43553
- envelope_json TEXT NOT NULL
43554
- ) STRICT;
43555
- CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
43556
- ON relay_session_event_outbox(source_seq);
43557
- CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
43558
- singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
43559
- stream_id TEXT NOT NULL,
43560
- next_source_seq INTEGER NOT NULL,
43561
- total_bytes INTEGER NOT NULL
43562
- ) STRICT;
43563
- CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
43564
- ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
43565
- event_id TEXT NOT NULL UNIQUE,
43566
- session_id TEXT NOT NULL,
43567
- rejection_code TEXT NOT NULL,
43568
- envelope_json TEXT NOT NULL
43569
- ) STRICT;
43570
- `);
43571
- this.#database.prepare(
43572
- `INSERT OR IGNORE INTO relay_session_event_outbox_state (
43573
- singleton, stream_id, next_source_seq, total_bytes
43574
- ) VALUES (1, ?, 1, 0)`
43575
- ).run(randomUUID7());
43576
- }
43577
- acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
43578
- close = () => {
43579
- this.#database.close();
43580
- };
43581
- enqueue = (sessionId, payload, critical) => {
43582
- const envelope2 = this.enqueueBatch(sessionId, [payload], critical)[0];
43583
- if (envelope2 === void 0) {
43584
- throw new Error("The Session event outbox did not enqueue the event");
43585
- }
43586
- return envelope2;
43587
- };
43521
+ var SessionEventQueue = class {
43522
+ #streamId = randomUUID7();
43523
+ #events = /* @__PURE__ */ new Map();
43524
+ #nextSourceSeq = 1;
43525
+ #totalBytes = 0;
43588
43526
  enqueueBatch = (sessionId, payloads, critical) => {
43589
43527
  if (payloads.length < 1 || payloads.length > 32) {
43590
43528
  throw new Error("A Session event batch must contain between 1 and 32 events");
43591
43529
  }
43592
- this.#database.exec("BEGIN IMMEDIATE");
43593
- try {
43594
- const state = this.#database.prepare(
43595
- `SELECT stream_id, next_source_seq, total_bytes
43596
- FROM relay_session_event_outbox_state
43597
- WHERE singleton = 1`
43598
- ).get();
43599
- const sourceSeq = rowNumber(state, "next_source_seq");
43600
- if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + payloads.length)) {
43601
- throw new Error("The Session event source sequence is exhausted");
43602
- }
43603
- const streamId = rowString(state, "stream_id");
43604
- const stored = payloads.map((payload, index) => {
43605
- const envelope2 = {
43606
- delivery: {
43607
- eventId: randomUUID7(),
43608
- kind: "reliable",
43609
- sourceSeq: (sourceSeq + index).toString(),
43610
- streamId
43611
- },
43612
- kind: SESSION_EVENT_KIND,
43613
- payload,
43614
- sessionId
43615
- };
43616
- const encoded = encodeSessionEventEnvelope(envelope2);
43617
- return { byteLength: Buffer.byteLength(encoded, "utf8"), encoded, envelope: envelope2 };
43618
- });
43619
- const byteLength = stored.reduce((total, entry) => total + entry.byteLength, 0);
43620
- const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
43621
- if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
43622
- throw new SessionEventOutboxCapacityError();
43623
- }
43624
- const insert = this.#database.prepare(
43625
- `INSERT INTO relay_session_event_outbox (
43626
- event_id, session_id, source_seq, byte_length, envelope_json
43627
- ) VALUES (?, ?, ?, ?, ?)`
43628
- );
43629
- for (const entry of stored) {
43630
- insert.run(
43631
- entry.envelope.delivery.eventId,
43632
- sessionId,
43633
- Number(entry.envelope.delivery.sourceSeq),
43634
- entry.byteLength,
43635
- entry.encoded
43636
- );
43637
- }
43638
- this.#database.prepare(
43639
- `UPDATE relay_session_event_outbox_state
43640
- SET next_source_seq = next_source_seq + ?,
43641
- total_bytes = total_bytes + ?
43642
- WHERE singleton = 1`
43643
- ).run(stored.length, byteLength);
43644
- this.#database.exec("COMMIT");
43645
- return stored.map((entry) => entry.envelope);
43646
- } catch (error62) {
43647
- this.#database.exec("ROLLBACK");
43648
- throw error62;
43530
+ if (!Number.isSafeInteger(this.#nextSourceSeq + payloads.length)) {
43531
+ throw new Error("The Session event source sequence is exhausted");
43649
43532
  }
43650
- };
43651
- pending = (limit, excludedEventIds) => {
43652
- if (!Number.isSafeInteger(limit) || limit < 1) {
43653
- throw new Error("Session event replay limit must be a positive integer");
43654
- }
43655
- const exclusion = excludedEventIds.length === 0 ? "" : `WHERE event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`;
43656
- return this.#database.prepare(
43657
- `SELECT envelope_json
43658
- FROM relay_session_event_outbox
43659
- ${exclusion}
43660
- ORDER BY source_seq
43661
- LIMIT ?`
43662
- ).all(...excludedEventIds, limit).map((row) => {
43663
- const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
43664
- if (!("delivery" in envelope2)) {
43665
- throw new Error("SQLite returned an invalid reliable Session event envelope");
43666
- }
43667
- return envelope2;
43533
+ const entries = payloads.map((payload, index) => {
43534
+ const envelope2 = {
43535
+ delivery: {
43536
+ eventId: randomUUID7(),
43537
+ kind: "reliable",
43538
+ sourceSeq: (this.#nextSourceSeq + index).toString(),
43539
+ streamId: this.#streamId
43540
+ },
43541
+ kind: SESSION_EVENT_KIND,
43542
+ payload,
43543
+ sessionId
43544
+ };
43545
+ return { envelope: envelope2, byteLength: Buffer.byteLength(encodeSessionEventEnvelope(envelope2), "utf8") };
43668
43546
  });
43547
+ const byteLength = entries.reduce((total, entry) => total + entry.byteLength, 0);
43548
+ if (this.#totalBytes + byteLength > (critical ? CRITICAL_EVENT_BYTES : STANDARD_EVENT_BYTES)) {
43549
+ throw new SessionEventQueueCapacityError();
43550
+ }
43551
+ for (const entry of entries) {
43552
+ this.#events.set(entry.envelope.delivery.eventId, entry);
43553
+ }
43554
+ this.#nextSourceSeq += entries.length;
43555
+ this.#totalBytes += byteLength;
43669
43556
  };
43670
- quarantine = (rejection) => {
43671
- this.#database.exec("BEGIN IMMEDIATE");
43672
- try {
43673
- const stored = this.#database.prepare(
43674
- `SELECT byte_length, envelope_json
43675
- FROM relay_session_event_outbox
43676
- WHERE event_id = ? AND session_id = ?`
43677
- ).get(rejection.eventId, rejection.sessionId);
43678
- if (stored === void 0) {
43679
- this.#database.exec("COMMIT");
43680
- return false;
43557
+ pending = (limit, excludedEventIds) => {
43558
+ const result = [];
43559
+ for (const [eventId, entry] of this.#events) {
43560
+ if (!excludedEventIds.has(eventId)) {
43561
+ result.push(entry.envelope);
43562
+ if (result.length === limit) {
43563
+ break;
43564
+ }
43681
43565
  }
43682
- this.#database.prepare(
43683
- `INSERT INTO relay_session_event_dead_letters (
43684
- event_id, session_id, rejection_code, envelope_json
43685
- ) VALUES (?, ?, ?, ?)`
43686
- ).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
43687
- this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
43688
- this.#database.prepare(
43689
- `DELETE FROM relay_session_event_dead_letters
43690
- WHERE ordinal NOT IN (
43691
- SELECT ordinal FROM relay_session_event_dead_letters
43692
- ORDER BY ordinal DESC LIMIT ?
43693
- )`
43694
- ).run(MAX_DEAD_LETTERS);
43695
- this.#database.exec("COMMIT");
43696
- return true;
43697
- } catch (error62) {
43698
- this.#database.exec("ROLLBACK");
43699
- throw error62;
43700
43566
  }
43567
+ return result;
43701
43568
  };
43702
- #deleteStored = (eventId, sessionId, byteLength) => {
43703
- const deletion = this.#database.prepare(
43704
- `DELETE FROM relay_session_event_outbox
43705
- WHERE event_id = ? AND session_id = ?`
43706
- ).run(eventId, sessionId);
43707
- if (deletion.changes !== 1) {
43708
- throw new Error("The Session event could not be removed from the outbox");
43709
- }
43710
- const state = this.#database.prepare(
43711
- `UPDATE relay_session_event_outbox_state
43712
- SET total_bytes = total_bytes - ?
43713
- WHERE singleton = 1 AND total_bytes >= ?`
43714
- ).run(byteLength, byteLength);
43715
- if (state.changes !== 1) {
43716
- throw new Error("The Session event outbox capacity state is inconsistent");
43569
+ remove = (eventId, sessionId) => {
43570
+ const entry = this.#events.get(eventId);
43571
+ if (entry === void 0 || entry.envelope.sessionId !== sessionId) {
43572
+ return false;
43717
43573
  }
43574
+ this.#events.delete(eventId);
43575
+ this.#totalBytes -= entry.byteLength;
43576
+ return true;
43718
43577
  };
43719
- #remove = (eventId, sessionId) => {
43720
- this.#database.exec("BEGIN IMMEDIATE");
43721
- try {
43722
- const stored = this.#database.prepare(
43723
- `SELECT byte_length
43724
- FROM relay_session_event_outbox
43725
- WHERE event_id = ? AND session_id = ?`
43726
- ).get(eventId, sessionId);
43727
- if (stored === void 0) {
43728
- this.#database.exec("COMMIT");
43729
- return false;
43730
- }
43731
- this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
43732
- this.#database.exec("COMMIT");
43733
- return true;
43734
- } catch (error62) {
43735
- this.#database.exec("ROLLBACK");
43736
- throw error62;
43737
- }
43578
+ clear = () => {
43579
+ this.#events.clear();
43580
+ this.#totalBytes = 0;
43738
43581
  };
43739
43582
  };
43740
43583
 
43741
43584
  // src/relay-connection/session-event-publisher.ts
43742
- import { WebSocket as WebSocket9 } from "undici";
43743
43585
  var SESSION_EVENT_REPLAY_WINDOW = 16;
43744
43586
  var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
43745
43587
  function reservesSessionEventCapacity(event) {
@@ -43750,18 +43592,17 @@ function reservesSessionEventCapacity(event) {
43750
43592
  }
43751
43593
  var DaemonSessionEventPublisher = class {
43752
43594
  #inFlight = /* @__PURE__ */ new Map();
43753
- #outbox;
43595
+ #queue = new SessionEventQueue();
43754
43596
  #reportedCapacityKinds = /* @__PURE__ */ new Set();
43755
43597
  #timer;
43756
43598
  #connection = { kind: "disconnected" };
43757
43599
  #deferredReplay = null;
43758
43600
  #inFlightBytes = 0;
43759
- constructor(outbox, timer) {
43760
- this.#outbox = outbox;
43601
+ constructor(timer) {
43761
43602
  this.#timer = timer;
43762
43603
  }
43763
43604
  acknowledge = (acknowledgement) => {
43764
- if (!this.#outbox.acknowledge(acknowledgement)) {
43605
+ if (!this.#queue.remove(acknowledgement.eventId, acknowledgement.sessionId)) {
43765
43606
  return;
43766
43607
  }
43767
43608
  this.#removeInFlight(acknowledgement.eventId);
@@ -43772,7 +43613,7 @@ var DaemonSessionEventPublisher = class {
43772
43613
  this.#deferredReplay = null;
43773
43614
  this.#connection = { kind: "disconnected" };
43774
43615
  this.#clearInFlight();
43775
- this.#outbox.close();
43616
+ this.#queue.clear();
43776
43617
  };
43777
43618
  connect = (socket, delivery) => {
43778
43619
  this.#connection = { delivery, kind: "connected", socket };
@@ -43801,16 +43642,18 @@ var DaemonSessionEventPublisher = class {
43801
43642
  };
43802
43643
  publish = (sessionId, event) => {
43803
43644
  try {
43804
- this.#outbox.enqueue(sessionId, encodeRecordedSessionEvent(event), reservesSessionEventCapacity(event));
43645
+ this.#queue.enqueueBatch(
43646
+ sessionId,
43647
+ [encodeRecordedSessionEvent(event)],
43648
+ reservesSessionEventCapacity(event)
43649
+ );
43805
43650
  } catch (error62) {
43806
- if (!(error62 instanceof SessionEventOutboxCapacityError)) {
43651
+ if (!(error62 instanceof SessionEventQueueCapacityError)) {
43807
43652
  throw error62;
43808
43653
  }
43809
43654
  if (!this.#reportedCapacityKinds.has(event.kind)) {
43810
43655
  this.#reportedCapacityKinds.add(event.kind);
43811
- console.warn(
43812
- `Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
43813
- );
43656
+ console.warn(`Mobius daemon could not buffer "${event.kind}" because the Session event queue is full`);
43814
43657
  }
43815
43658
  return;
43816
43659
  }
@@ -43818,11 +43661,11 @@ var DaemonSessionEventPublisher = class {
43818
43661
  };
43819
43662
  publishBatch = (sessionId, events) => {
43820
43663
  const critical = events.some(reservesSessionEventCapacity);
43821
- this.#outbox.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
43664
+ this.#queue.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
43822
43665
  this.#publishNextBatch();
43823
43666
  };
43824
- quarantine = (rejection) => {
43825
- if (!this.#outbox.quarantine(rejection)) {
43667
+ reject = (rejection) => {
43668
+ if (!this.#queue.remove(rejection.eventId, rejection.sessionId)) {
43826
43669
  return;
43827
43670
  }
43828
43671
  this.#removeInFlight(rejection.eventId);
@@ -43838,9 +43681,10 @@ var DaemonSessionEventPublisher = class {
43838
43681
  return;
43839
43682
  }
43840
43683
  while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
43841
- const batch = this.#outbox.pending(SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size, [
43842
- ...this.#inFlight.keys()
43843
- ]);
43684
+ const batch = this.#queue.pending(
43685
+ SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size,
43686
+ new Set(this.#inFlight.keys())
43687
+ );
43844
43688
  let sent = 0;
43845
43689
  for (const envelope2 of batch) {
43846
43690
  const encoded = encodeDaemonToServiceMessage(
@@ -43859,12 +43703,7 @@ var DaemonSessionEventPublisher = class {
43859
43703
  return;
43860
43704
  }
43861
43705
  if (this.#connection.delivery === "ephemeral") {
43862
- this.#outbox.acknowledge({
43863
- eventId: envelope2.delivery.eventId,
43864
- kind: "session.event.ack",
43865
- outcome: { kind: "relayed" },
43866
- sessionId: envelope2.sessionId
43867
- });
43706
+ this.#queue.remove(envelope2.delivery.eventId, envelope2.sessionId);
43868
43707
  sent += 1;
43869
43708
  continue;
43870
43709
  }
@@ -43977,11 +43816,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
43977
43816
  join18(stateDirectory, "sessions", `${createHash6("sha256").update(config2.channelId).digest("hex")}.sqlite`)
43978
43817
  );
43979
43818
  const operations = new DaemonOperationState();
43980
- const channelHash = createHash6("sha256").update(config2.channelId).digest("hex");
43981
- const publisher = new DaemonSessionEventPublisher(
43982
- new SessionEventOutbox(join18(stateDirectory, "events", `${channelHash}.sqlite`)),
43983
- timer
43984
- );
43819
+ const publisher = new DaemonSessionEventPublisher(timer);
43985
43820
  const activeTurns = new DaemonTurnRegistry(operations);
43986
43821
  const recordSessionEvent = (sessionId, event) => {
43987
43822
  publisher.publish(sessionId, activeTurns.recordEvent(event));