@moxt-ai/mobius 0.0.153 → 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/index.js CHANGED
@@ -43477,7 +43477,7 @@ async function runRelayConnection({
43477
43477
  return;
43478
43478
  }
43479
43479
  if (kind === SESSION_EVENT_REJECTED_KIND) {
43480
- publisher.quarantine(message);
43480
+ publisher.reject(message);
43481
43481
  return;
43482
43482
  }
43483
43483
  commands.handle(message);
@@ -43504,250 +43504,84 @@ async function runRelayConnection({
43504
43504
  }
43505
43505
  }
43506
43506
 
43507
- // 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
43508
43511
  import { randomUUID as randomUUID7 } from "node:crypto";
43509
- import { mkdirSync as mkdirSync3 } from "node:fs";
43510
- import { dirname as dirname6 } from "node:path";
43511
- import { DatabaseSync } from "node:sqlite";
43512
- var DEFAULT_STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
43513
- var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
43514
- var MAX_DEAD_LETTERS = 1024;
43515
- var DEFAULT_LIMITS = {
43516
- criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
43517
- standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
43518
- };
43519
- var SessionEventOutboxCapacityError = class extends Error {
43520
- 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";
43521
43516
  constructor() {
43522
- super("The persistent Session event outbox is full");
43523
- this.name = "SessionEventOutboxCapacityError";
43517
+ super("The in-memory Session event queue is full");
43518
+ this.name = "SessionEventQueueCapacityError";
43524
43519
  }
43525
43520
  };
43526
- function rowNumber(row, field) {
43527
- const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
43528
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
43529
- throw new Error(`SQLite returned an invalid ${field} value`);
43530
- }
43531
- return value;
43532
- }
43533
- function rowString(row, field) {
43534
- const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
43535
- if (typeof value !== "string" || value.length === 0) {
43536
- throw new Error(`SQLite returned an invalid ${field} value`);
43537
- }
43538
- return value;
43539
- }
43540
- function validateLimits(limits) {
43541
- if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
43542
- throw new Error("Session event outbox limits are invalid");
43543
- }
43544
- }
43545
- var SessionEventOutbox = class {
43546
- #database;
43547
- #limits;
43548
- constructor(path2, limits = DEFAULT_LIMITS) {
43549
- validateLimits(limits);
43550
- mkdirSync3(dirname6(path2), { mode: 448, recursive: true });
43551
- this.#limits = limits;
43552
- this.#database = new DatabaseSync(path2);
43553
- this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
43554
- this.#database.exec(`
43555
- CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
43556
- ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
43557
- event_id TEXT NOT NULL UNIQUE,
43558
- session_id TEXT NOT NULL,
43559
- source_seq INTEGER NOT NULL UNIQUE,
43560
- byte_length INTEGER NOT NULL,
43561
- envelope_json TEXT NOT NULL
43562
- ) STRICT;
43563
- CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
43564
- ON relay_session_event_outbox(source_seq);
43565
- CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
43566
- singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
43567
- stream_id TEXT NOT NULL,
43568
- next_source_seq INTEGER NOT NULL,
43569
- total_bytes INTEGER NOT NULL
43570
- ) STRICT;
43571
- CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
43572
- ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
43573
- event_id TEXT NOT NULL UNIQUE,
43574
- session_id TEXT NOT NULL,
43575
- rejection_code TEXT NOT NULL,
43576
- envelope_json TEXT NOT NULL
43577
- ) STRICT;
43578
- `);
43579
- this.#database.prepare(
43580
- `INSERT OR IGNORE INTO relay_session_event_outbox_state (
43581
- singleton, stream_id, next_source_seq, total_bytes
43582
- ) VALUES (1, ?, 1, 0)`
43583
- ).run(randomUUID7());
43584
- }
43585
- acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
43586
- close = () => {
43587
- this.#database.close();
43588
- };
43589
- enqueue = (sessionId, payload, critical) => {
43590
- const envelope2 = this.enqueueBatch(sessionId, [payload], critical)[0];
43591
- if (envelope2 === void 0) {
43592
- throw new Error("The Session event outbox did not enqueue the event");
43593
- }
43594
- return envelope2;
43595
- };
43521
+ var SessionEventQueue = class {
43522
+ #streamId = randomUUID7();
43523
+ #events = /* @__PURE__ */ new Map();
43524
+ #nextSourceSeq = 1;
43525
+ #totalBytes = 0;
43596
43526
  enqueueBatch = (sessionId, payloads, critical) => {
43597
43527
  if (payloads.length < 1 || payloads.length > 32) {
43598
43528
  throw new Error("A Session event batch must contain between 1 and 32 events");
43599
43529
  }
43600
- this.#database.exec("BEGIN IMMEDIATE");
43601
- try {
43602
- const state = this.#database.prepare(
43603
- `SELECT stream_id, next_source_seq, total_bytes
43604
- FROM relay_session_event_outbox_state
43605
- WHERE singleton = 1`
43606
- ).get();
43607
- const sourceSeq = rowNumber(state, "next_source_seq");
43608
- if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + payloads.length)) {
43609
- throw new Error("The Session event source sequence is exhausted");
43610
- }
43611
- const streamId = rowString(state, "stream_id");
43612
- const stored = payloads.map((payload, index) => {
43613
- const envelope2 = {
43614
- delivery: {
43615
- eventId: randomUUID7(),
43616
- kind: "reliable",
43617
- sourceSeq: (sourceSeq + index).toString(),
43618
- streamId
43619
- },
43620
- kind: SESSION_EVENT_KIND,
43621
- payload,
43622
- sessionId
43623
- };
43624
- const encoded = encodeSessionEventEnvelope(envelope2);
43625
- return { byteLength: Buffer.byteLength(encoded, "utf8"), encoded, envelope: envelope2 };
43626
- });
43627
- const byteLength = stored.reduce((total, entry) => total + entry.byteLength, 0);
43628
- const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
43629
- if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
43630
- throw new SessionEventOutboxCapacityError();
43631
- }
43632
- const insert = this.#database.prepare(
43633
- `INSERT INTO relay_session_event_outbox (
43634
- event_id, session_id, source_seq, byte_length, envelope_json
43635
- ) VALUES (?, ?, ?, ?, ?)`
43636
- );
43637
- for (const entry of stored) {
43638
- insert.run(
43639
- entry.envelope.delivery.eventId,
43640
- sessionId,
43641
- Number(entry.envelope.delivery.sourceSeq),
43642
- entry.byteLength,
43643
- entry.encoded
43644
- );
43645
- }
43646
- this.#database.prepare(
43647
- `UPDATE relay_session_event_outbox_state
43648
- SET next_source_seq = next_source_seq + ?,
43649
- total_bytes = total_bytes + ?
43650
- WHERE singleton = 1`
43651
- ).run(stored.length, byteLength);
43652
- this.#database.exec("COMMIT");
43653
- return stored.map((entry) => entry.envelope);
43654
- } catch (error62) {
43655
- this.#database.exec("ROLLBACK");
43656
- throw error62;
43530
+ if (!Number.isSafeInteger(this.#nextSourceSeq + payloads.length)) {
43531
+ throw new Error("The Session event source sequence is exhausted");
43657
43532
  }
43658
- };
43659
- pending = (limit, excludedEventIds) => {
43660
- if (!Number.isSafeInteger(limit) || limit < 1) {
43661
- throw new Error("Session event replay limit must be a positive integer");
43662
- }
43663
- const exclusion = excludedEventIds.length === 0 ? "" : `WHERE event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`;
43664
- return this.#database.prepare(
43665
- `SELECT envelope_json
43666
- FROM relay_session_event_outbox
43667
- ${exclusion}
43668
- ORDER BY source_seq
43669
- LIMIT ?`
43670
- ).all(...excludedEventIds, limit).map((row) => {
43671
- const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
43672
- if (!("delivery" in envelope2)) {
43673
- throw new Error("SQLite returned an invalid reliable Session event envelope");
43674
- }
43675
- 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") };
43676
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;
43677
43556
  };
43678
- quarantine = (rejection) => {
43679
- this.#database.exec("BEGIN IMMEDIATE");
43680
- try {
43681
- const stored = this.#database.prepare(
43682
- `SELECT byte_length, envelope_json
43683
- FROM relay_session_event_outbox
43684
- WHERE event_id = ? AND session_id = ?`
43685
- ).get(rejection.eventId, rejection.sessionId);
43686
- if (stored === void 0) {
43687
- this.#database.exec("COMMIT");
43688
- 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
+ }
43689
43565
  }
43690
- this.#database.prepare(
43691
- `INSERT INTO relay_session_event_dead_letters (
43692
- event_id, session_id, rejection_code, envelope_json
43693
- ) VALUES (?, ?, ?, ?)`
43694
- ).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
43695
- this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
43696
- this.#database.prepare(
43697
- `DELETE FROM relay_session_event_dead_letters
43698
- WHERE ordinal NOT IN (
43699
- SELECT ordinal FROM relay_session_event_dead_letters
43700
- ORDER BY ordinal DESC LIMIT ?
43701
- )`
43702
- ).run(MAX_DEAD_LETTERS);
43703
- this.#database.exec("COMMIT");
43704
- return true;
43705
- } catch (error62) {
43706
- this.#database.exec("ROLLBACK");
43707
- throw error62;
43708
43566
  }
43567
+ return result;
43709
43568
  };
43710
- #deleteStored = (eventId, sessionId, byteLength) => {
43711
- const deletion = this.#database.prepare(
43712
- `DELETE FROM relay_session_event_outbox
43713
- WHERE event_id = ? AND session_id = ?`
43714
- ).run(eventId, sessionId);
43715
- if (deletion.changes !== 1) {
43716
- throw new Error("The Session event could not be removed from the outbox");
43717
- }
43718
- const state = this.#database.prepare(
43719
- `UPDATE relay_session_event_outbox_state
43720
- SET total_bytes = total_bytes - ?
43721
- WHERE singleton = 1 AND total_bytes >= ?`
43722
- ).run(byteLength, byteLength);
43723
- if (state.changes !== 1) {
43724
- 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;
43725
43573
  }
43574
+ this.#events.delete(eventId);
43575
+ this.#totalBytes -= entry.byteLength;
43576
+ return true;
43726
43577
  };
43727
- #remove = (eventId, sessionId) => {
43728
- this.#database.exec("BEGIN IMMEDIATE");
43729
- try {
43730
- const stored = this.#database.prepare(
43731
- `SELECT byte_length
43732
- FROM relay_session_event_outbox
43733
- WHERE event_id = ? AND session_id = ?`
43734
- ).get(eventId, sessionId);
43735
- if (stored === void 0) {
43736
- this.#database.exec("COMMIT");
43737
- return false;
43738
- }
43739
- this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
43740
- this.#database.exec("COMMIT");
43741
- return true;
43742
- } catch (error62) {
43743
- this.#database.exec("ROLLBACK");
43744
- throw error62;
43745
- }
43578
+ clear = () => {
43579
+ this.#events.clear();
43580
+ this.#totalBytes = 0;
43746
43581
  };
43747
43582
  };
43748
43583
 
43749
43584
  // src/relay-connection/session-event-publisher.ts
43750
- import { WebSocket as WebSocket9 } from "undici";
43751
43585
  var SESSION_EVENT_REPLAY_WINDOW = 16;
43752
43586
  var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
43753
43587
  function reservesSessionEventCapacity(event) {
@@ -43758,18 +43592,17 @@ function reservesSessionEventCapacity(event) {
43758
43592
  }
43759
43593
  var DaemonSessionEventPublisher = class {
43760
43594
  #inFlight = /* @__PURE__ */ new Map();
43761
- #outbox;
43595
+ #queue = new SessionEventQueue();
43762
43596
  #reportedCapacityKinds = /* @__PURE__ */ new Set();
43763
43597
  #timer;
43764
43598
  #connection = { kind: "disconnected" };
43765
43599
  #deferredReplay = null;
43766
43600
  #inFlightBytes = 0;
43767
- constructor(outbox, timer) {
43768
- this.#outbox = outbox;
43601
+ constructor(timer) {
43769
43602
  this.#timer = timer;
43770
43603
  }
43771
43604
  acknowledge = (acknowledgement) => {
43772
- if (!this.#outbox.acknowledge(acknowledgement)) {
43605
+ if (!this.#queue.remove(acknowledgement.eventId, acknowledgement.sessionId)) {
43773
43606
  return;
43774
43607
  }
43775
43608
  this.#removeInFlight(acknowledgement.eventId);
@@ -43780,7 +43613,7 @@ var DaemonSessionEventPublisher = class {
43780
43613
  this.#deferredReplay = null;
43781
43614
  this.#connection = { kind: "disconnected" };
43782
43615
  this.#clearInFlight();
43783
- this.#outbox.close();
43616
+ this.#queue.clear();
43784
43617
  };
43785
43618
  connect = (socket, delivery) => {
43786
43619
  this.#connection = { delivery, kind: "connected", socket };
@@ -43809,16 +43642,18 @@ var DaemonSessionEventPublisher = class {
43809
43642
  };
43810
43643
  publish = (sessionId, event) => {
43811
43644
  try {
43812
- this.#outbox.enqueue(sessionId, encodeRecordedSessionEvent(event), reservesSessionEventCapacity(event));
43645
+ this.#queue.enqueueBatch(
43646
+ sessionId,
43647
+ [encodeRecordedSessionEvent(event)],
43648
+ reservesSessionEventCapacity(event)
43649
+ );
43813
43650
  } catch (error62) {
43814
- if (!(error62 instanceof SessionEventOutboxCapacityError)) {
43651
+ if (!(error62 instanceof SessionEventQueueCapacityError)) {
43815
43652
  throw error62;
43816
43653
  }
43817
43654
  if (!this.#reportedCapacityKinds.has(event.kind)) {
43818
43655
  this.#reportedCapacityKinds.add(event.kind);
43819
- console.warn(
43820
- `Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
43821
- );
43656
+ console.warn(`Mobius daemon could not buffer "${event.kind}" because the Session event queue is full`);
43822
43657
  }
43823
43658
  return;
43824
43659
  }
@@ -43826,11 +43661,11 @@ var DaemonSessionEventPublisher = class {
43826
43661
  };
43827
43662
  publishBatch = (sessionId, events) => {
43828
43663
  const critical = events.some(reservesSessionEventCapacity);
43829
- this.#outbox.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
43664
+ this.#queue.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
43830
43665
  this.#publishNextBatch();
43831
43666
  };
43832
- quarantine = (rejection) => {
43833
- if (!this.#outbox.quarantine(rejection)) {
43667
+ reject = (rejection) => {
43668
+ if (!this.#queue.remove(rejection.eventId, rejection.sessionId)) {
43834
43669
  return;
43835
43670
  }
43836
43671
  this.#removeInFlight(rejection.eventId);
@@ -43846,9 +43681,10 @@ var DaemonSessionEventPublisher = class {
43846
43681
  return;
43847
43682
  }
43848
43683
  while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
43849
- const batch = this.#outbox.pending(SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size, [
43850
- ...this.#inFlight.keys()
43851
- ]);
43684
+ const batch = this.#queue.pending(
43685
+ SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size,
43686
+ new Set(this.#inFlight.keys())
43687
+ );
43852
43688
  let sent = 0;
43853
43689
  for (const envelope2 of batch) {
43854
43690
  const encoded = encodeDaemonToServiceMessage(
@@ -43867,12 +43703,7 @@ var DaemonSessionEventPublisher = class {
43867
43703
  return;
43868
43704
  }
43869
43705
  if (this.#connection.delivery === "ephemeral") {
43870
- this.#outbox.acknowledge({
43871
- eventId: envelope2.delivery.eventId,
43872
- kind: "session.event.ack",
43873
- outcome: { kind: "relayed" },
43874
- sessionId: envelope2.sessionId
43875
- });
43706
+ this.#queue.remove(envelope2.delivery.eventId, envelope2.sessionId);
43876
43707
  sent += 1;
43877
43708
  continue;
43878
43709
  }
@@ -43985,11 +43816,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
43985
43816
  join18(stateDirectory, "sessions", `${createHash6("sha256").update(config2.channelId).digest("hex")}.sqlite`)
43986
43817
  );
43987
43818
  const operations = new DaemonOperationState();
43988
- const channelHash = createHash6("sha256").update(config2.channelId).digest("hex");
43989
- const publisher = new DaemonSessionEventPublisher(
43990
- new SessionEventOutbox(join18(stateDirectory, "events", `${channelHash}.sqlite`)),
43991
- timer
43992
- );
43819
+ const publisher = new DaemonSessionEventPublisher(timer);
43993
43820
  const activeTurns = new DaemonTurnRegistry(operations);
43994
43821
  const recordSessionEvent = (sessionId, event) => {
43995
43822
  publisher.publish(sessionId, activeTurns.recordEvent(event));