@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/runtime.js CHANGED
@@ -42619,7 +42619,7 @@ async function runRelayConnection({
42619
42619
  return;
42620
42620
  }
42621
42621
  if (kind === SESSION_EVENT_REJECTED_KIND) {
42622
- publisher.quarantine(message);
42622
+ publisher.reject(message);
42623
42623
  return;
42624
42624
  }
42625
42625
  commands.handle(message);
@@ -42646,250 +42646,84 @@ async function runRelayConnection({
42646
42646
  }
42647
42647
  }
42648
42648
 
42649
- // src/relay-connection/session-event-outbox.ts
42649
+ // src/relay-connection/session-event-publisher.ts
42650
+ import { WebSocket as WebSocket9 } from "undici";
42651
+
42652
+ // src/relay-connection/session-event-queue.ts
42650
42653
  import { randomUUID as randomUUID6 } from "node:crypto";
42651
- import { mkdirSync as mkdirSync3 } from "node:fs";
42652
- import { dirname as dirname5 } from "node:path";
42653
- import { DatabaseSync } from "node:sqlite";
42654
- var DEFAULT_STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
42655
- var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
42656
- var MAX_DEAD_LETTERS = 1024;
42657
- var DEFAULT_LIMITS = {
42658
- criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
42659
- standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
42660
- };
42661
- var SessionEventOutboxCapacityError = class extends Error {
42662
- code = "session_event_outbox_full";
42654
+ var STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
42655
+ var CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
42656
+ var SessionEventQueueCapacityError = class extends Error {
42657
+ code = "session_event_queue_full";
42663
42658
  constructor() {
42664
- super("The persistent Session event outbox is full");
42665
- this.name = "SessionEventOutboxCapacityError";
42659
+ super("The in-memory Session event queue is full");
42660
+ this.name = "SessionEventQueueCapacityError";
42666
42661
  }
42667
42662
  };
42668
- function rowNumber(row, field) {
42669
- const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
42670
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
42671
- throw new Error(`SQLite returned an invalid ${field} value`);
42672
- }
42673
- return value;
42674
- }
42675
- function rowString(row, field) {
42676
- const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
42677
- if (typeof value !== "string" || value.length === 0) {
42678
- throw new Error(`SQLite returned an invalid ${field} value`);
42679
- }
42680
- return value;
42681
- }
42682
- function validateLimits(limits) {
42683
- if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
42684
- throw new Error("Session event outbox limits are invalid");
42685
- }
42686
- }
42687
- var SessionEventOutbox = class {
42688
- #database;
42689
- #limits;
42690
- constructor(path2, limits = DEFAULT_LIMITS) {
42691
- validateLimits(limits);
42692
- mkdirSync3(dirname5(path2), { mode: 448, recursive: true });
42693
- this.#limits = limits;
42694
- this.#database = new DatabaseSync(path2);
42695
- this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
42696
- this.#database.exec(`
42697
- CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
42698
- ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
42699
- event_id TEXT NOT NULL UNIQUE,
42700
- session_id TEXT NOT NULL,
42701
- source_seq INTEGER NOT NULL UNIQUE,
42702
- byte_length INTEGER NOT NULL,
42703
- envelope_json TEXT NOT NULL
42704
- ) STRICT;
42705
- CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
42706
- ON relay_session_event_outbox(source_seq);
42707
- CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
42708
- singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
42709
- stream_id TEXT NOT NULL,
42710
- next_source_seq INTEGER NOT NULL,
42711
- total_bytes INTEGER NOT NULL
42712
- ) STRICT;
42713
- CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
42714
- ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
42715
- event_id TEXT NOT NULL UNIQUE,
42716
- session_id TEXT NOT NULL,
42717
- rejection_code TEXT NOT NULL,
42718
- envelope_json TEXT NOT NULL
42719
- ) STRICT;
42720
- `);
42721
- this.#database.prepare(
42722
- `INSERT OR IGNORE INTO relay_session_event_outbox_state (
42723
- singleton, stream_id, next_source_seq, total_bytes
42724
- ) VALUES (1, ?, 1, 0)`
42725
- ).run(randomUUID6());
42726
- }
42727
- acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
42728
- close = () => {
42729
- this.#database.close();
42730
- };
42731
- enqueue = (sessionId, payload, critical) => {
42732
- const envelope2 = this.enqueueBatch(sessionId, [payload], critical)[0];
42733
- if (envelope2 === void 0) {
42734
- throw new Error("The Session event outbox did not enqueue the event");
42735
- }
42736
- return envelope2;
42737
- };
42663
+ var SessionEventQueue = class {
42664
+ #streamId = randomUUID6();
42665
+ #events = /* @__PURE__ */ new Map();
42666
+ #nextSourceSeq = 1;
42667
+ #totalBytes = 0;
42738
42668
  enqueueBatch = (sessionId, payloads, critical) => {
42739
42669
  if (payloads.length < 1 || payloads.length > 32) {
42740
42670
  throw new Error("A Session event batch must contain between 1 and 32 events");
42741
42671
  }
42742
- this.#database.exec("BEGIN IMMEDIATE");
42743
- try {
42744
- const state = this.#database.prepare(
42745
- `SELECT stream_id, next_source_seq, total_bytes
42746
- FROM relay_session_event_outbox_state
42747
- WHERE singleton = 1`
42748
- ).get();
42749
- const sourceSeq = rowNumber(state, "next_source_seq");
42750
- if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + payloads.length)) {
42751
- throw new Error("The Session event source sequence is exhausted");
42752
- }
42753
- const streamId = rowString(state, "stream_id");
42754
- const stored = payloads.map((payload, index) => {
42755
- const envelope2 = {
42756
- delivery: {
42757
- eventId: randomUUID6(),
42758
- kind: "reliable",
42759
- sourceSeq: (sourceSeq + index).toString(),
42760
- streamId
42761
- },
42762
- kind: SESSION_EVENT_KIND,
42763
- payload,
42764
- sessionId
42765
- };
42766
- const encoded = encodeSessionEventEnvelope(envelope2);
42767
- return { byteLength: Buffer.byteLength(encoded, "utf8"), encoded, envelope: envelope2 };
42768
- });
42769
- const byteLength = stored.reduce((total, entry) => total + entry.byteLength, 0);
42770
- const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
42771
- if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
42772
- throw new SessionEventOutboxCapacityError();
42773
- }
42774
- const insert = this.#database.prepare(
42775
- `INSERT INTO relay_session_event_outbox (
42776
- event_id, session_id, source_seq, byte_length, envelope_json
42777
- ) VALUES (?, ?, ?, ?, ?)`
42778
- );
42779
- for (const entry of stored) {
42780
- insert.run(
42781
- entry.envelope.delivery.eventId,
42782
- sessionId,
42783
- Number(entry.envelope.delivery.sourceSeq),
42784
- entry.byteLength,
42785
- entry.encoded
42786
- );
42787
- }
42788
- this.#database.prepare(
42789
- `UPDATE relay_session_event_outbox_state
42790
- SET next_source_seq = next_source_seq + ?,
42791
- total_bytes = total_bytes + ?
42792
- WHERE singleton = 1`
42793
- ).run(stored.length, byteLength);
42794
- this.#database.exec("COMMIT");
42795
- return stored.map((entry) => entry.envelope);
42796
- } catch (error62) {
42797
- this.#database.exec("ROLLBACK");
42798
- throw error62;
42672
+ if (!Number.isSafeInteger(this.#nextSourceSeq + payloads.length)) {
42673
+ throw new Error("The Session event source sequence is exhausted");
42799
42674
  }
42800
- };
42801
- pending = (limit, excludedEventIds) => {
42802
- if (!Number.isSafeInteger(limit) || limit < 1) {
42803
- throw new Error("Session event replay limit must be a positive integer");
42804
- }
42805
- const exclusion = excludedEventIds.length === 0 ? "" : `WHERE event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`;
42806
- return this.#database.prepare(
42807
- `SELECT envelope_json
42808
- FROM relay_session_event_outbox
42809
- ${exclusion}
42810
- ORDER BY source_seq
42811
- LIMIT ?`
42812
- ).all(...excludedEventIds, limit).map((row) => {
42813
- const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
42814
- if (!("delivery" in envelope2)) {
42815
- throw new Error("SQLite returned an invalid reliable Session event envelope");
42816
- }
42817
- return envelope2;
42675
+ const entries = payloads.map((payload, index) => {
42676
+ const envelope2 = {
42677
+ delivery: {
42678
+ eventId: randomUUID6(),
42679
+ kind: "reliable",
42680
+ sourceSeq: (this.#nextSourceSeq + index).toString(),
42681
+ streamId: this.#streamId
42682
+ },
42683
+ kind: SESSION_EVENT_KIND,
42684
+ payload,
42685
+ sessionId
42686
+ };
42687
+ return { envelope: envelope2, byteLength: Buffer.byteLength(encodeSessionEventEnvelope(envelope2), "utf8") };
42818
42688
  });
42689
+ const byteLength = entries.reduce((total, entry) => total + entry.byteLength, 0);
42690
+ if (this.#totalBytes + byteLength > (critical ? CRITICAL_EVENT_BYTES : STANDARD_EVENT_BYTES)) {
42691
+ throw new SessionEventQueueCapacityError();
42692
+ }
42693
+ for (const entry of entries) {
42694
+ this.#events.set(entry.envelope.delivery.eventId, entry);
42695
+ }
42696
+ this.#nextSourceSeq += entries.length;
42697
+ this.#totalBytes += byteLength;
42819
42698
  };
42820
- quarantine = (rejection) => {
42821
- this.#database.exec("BEGIN IMMEDIATE");
42822
- try {
42823
- const stored = this.#database.prepare(
42824
- `SELECT byte_length, envelope_json
42825
- FROM relay_session_event_outbox
42826
- WHERE event_id = ? AND session_id = ?`
42827
- ).get(rejection.eventId, rejection.sessionId);
42828
- if (stored === void 0) {
42829
- this.#database.exec("COMMIT");
42830
- return false;
42699
+ pending = (limit, excludedEventIds) => {
42700
+ const result = [];
42701
+ for (const [eventId, entry] of this.#events) {
42702
+ if (!excludedEventIds.has(eventId)) {
42703
+ result.push(entry.envelope);
42704
+ if (result.length === limit) {
42705
+ break;
42706
+ }
42831
42707
  }
42832
- this.#database.prepare(
42833
- `INSERT INTO relay_session_event_dead_letters (
42834
- event_id, session_id, rejection_code, envelope_json
42835
- ) VALUES (?, ?, ?, ?)`
42836
- ).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
42837
- this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
42838
- this.#database.prepare(
42839
- `DELETE FROM relay_session_event_dead_letters
42840
- WHERE ordinal NOT IN (
42841
- SELECT ordinal FROM relay_session_event_dead_letters
42842
- ORDER BY ordinal DESC LIMIT ?
42843
- )`
42844
- ).run(MAX_DEAD_LETTERS);
42845
- this.#database.exec("COMMIT");
42846
- return true;
42847
- } catch (error62) {
42848
- this.#database.exec("ROLLBACK");
42849
- throw error62;
42850
42708
  }
42709
+ return result;
42851
42710
  };
42852
- #deleteStored = (eventId, sessionId, byteLength) => {
42853
- const deletion = this.#database.prepare(
42854
- `DELETE FROM relay_session_event_outbox
42855
- WHERE event_id = ? AND session_id = ?`
42856
- ).run(eventId, sessionId);
42857
- if (deletion.changes !== 1) {
42858
- throw new Error("The Session event could not be removed from the outbox");
42859
- }
42860
- const state = this.#database.prepare(
42861
- `UPDATE relay_session_event_outbox_state
42862
- SET total_bytes = total_bytes - ?
42863
- WHERE singleton = 1 AND total_bytes >= ?`
42864
- ).run(byteLength, byteLength);
42865
- if (state.changes !== 1) {
42866
- throw new Error("The Session event outbox capacity state is inconsistent");
42711
+ remove = (eventId, sessionId) => {
42712
+ const entry = this.#events.get(eventId);
42713
+ if (entry === void 0 || entry.envelope.sessionId !== sessionId) {
42714
+ return false;
42867
42715
  }
42716
+ this.#events.delete(eventId);
42717
+ this.#totalBytes -= entry.byteLength;
42718
+ return true;
42868
42719
  };
42869
- #remove = (eventId, sessionId) => {
42870
- this.#database.exec("BEGIN IMMEDIATE");
42871
- try {
42872
- const stored = this.#database.prepare(
42873
- `SELECT byte_length
42874
- FROM relay_session_event_outbox
42875
- WHERE event_id = ? AND session_id = ?`
42876
- ).get(eventId, sessionId);
42877
- if (stored === void 0) {
42878
- this.#database.exec("COMMIT");
42879
- return false;
42880
- }
42881
- this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
42882
- this.#database.exec("COMMIT");
42883
- return true;
42884
- } catch (error62) {
42885
- this.#database.exec("ROLLBACK");
42886
- throw error62;
42887
- }
42720
+ clear = () => {
42721
+ this.#events.clear();
42722
+ this.#totalBytes = 0;
42888
42723
  };
42889
42724
  };
42890
42725
 
42891
42726
  // src/relay-connection/session-event-publisher.ts
42892
- import { WebSocket as WebSocket9 } from "undici";
42893
42727
  var SESSION_EVENT_REPLAY_WINDOW = 16;
42894
42728
  var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
42895
42729
  function reservesSessionEventCapacity(event) {
@@ -42900,18 +42734,17 @@ function reservesSessionEventCapacity(event) {
42900
42734
  }
42901
42735
  var DaemonSessionEventPublisher = class {
42902
42736
  #inFlight = /* @__PURE__ */ new Map();
42903
- #outbox;
42737
+ #queue = new SessionEventQueue();
42904
42738
  #reportedCapacityKinds = /* @__PURE__ */ new Set();
42905
42739
  #timer;
42906
42740
  #connection = { kind: "disconnected" };
42907
42741
  #deferredReplay = null;
42908
42742
  #inFlightBytes = 0;
42909
- constructor(outbox, timer) {
42910
- this.#outbox = outbox;
42743
+ constructor(timer) {
42911
42744
  this.#timer = timer;
42912
42745
  }
42913
42746
  acknowledge = (acknowledgement) => {
42914
- if (!this.#outbox.acknowledge(acknowledgement)) {
42747
+ if (!this.#queue.remove(acknowledgement.eventId, acknowledgement.sessionId)) {
42915
42748
  return;
42916
42749
  }
42917
42750
  this.#removeInFlight(acknowledgement.eventId);
@@ -42922,7 +42755,7 @@ var DaemonSessionEventPublisher = class {
42922
42755
  this.#deferredReplay = null;
42923
42756
  this.#connection = { kind: "disconnected" };
42924
42757
  this.#clearInFlight();
42925
- this.#outbox.close();
42758
+ this.#queue.clear();
42926
42759
  };
42927
42760
  connect = (socket, delivery) => {
42928
42761
  this.#connection = { delivery, kind: "connected", socket };
@@ -42951,16 +42784,18 @@ var DaemonSessionEventPublisher = class {
42951
42784
  };
42952
42785
  publish = (sessionId, event) => {
42953
42786
  try {
42954
- this.#outbox.enqueue(sessionId, encodeRecordedSessionEvent(event), reservesSessionEventCapacity(event));
42787
+ this.#queue.enqueueBatch(
42788
+ sessionId,
42789
+ [encodeRecordedSessionEvent(event)],
42790
+ reservesSessionEventCapacity(event)
42791
+ );
42955
42792
  } catch (error62) {
42956
- if (!(error62 instanceof SessionEventOutboxCapacityError)) {
42793
+ if (!(error62 instanceof SessionEventQueueCapacityError)) {
42957
42794
  throw error62;
42958
42795
  }
42959
42796
  if (!this.#reportedCapacityKinds.has(event.kind)) {
42960
42797
  this.#reportedCapacityKinds.add(event.kind);
42961
- console.warn(
42962
- `Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
42963
- );
42798
+ console.warn(`Mobius daemon could not buffer "${event.kind}" because the Session event queue is full`);
42964
42799
  }
42965
42800
  return;
42966
42801
  }
@@ -42968,11 +42803,11 @@ var DaemonSessionEventPublisher = class {
42968
42803
  };
42969
42804
  publishBatch = (sessionId, events) => {
42970
42805
  const critical = events.some(reservesSessionEventCapacity);
42971
- this.#outbox.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
42806
+ this.#queue.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
42972
42807
  this.#publishNextBatch();
42973
42808
  };
42974
- quarantine = (rejection) => {
42975
- if (!this.#outbox.quarantine(rejection)) {
42809
+ reject = (rejection) => {
42810
+ if (!this.#queue.remove(rejection.eventId, rejection.sessionId)) {
42976
42811
  return;
42977
42812
  }
42978
42813
  this.#removeInFlight(rejection.eventId);
@@ -42988,9 +42823,10 @@ var DaemonSessionEventPublisher = class {
42988
42823
  return;
42989
42824
  }
42990
42825
  while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
42991
- const batch = this.#outbox.pending(SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size, [
42992
- ...this.#inFlight.keys()
42993
- ]);
42826
+ const batch = this.#queue.pending(
42827
+ SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size,
42828
+ new Set(this.#inFlight.keys())
42829
+ );
42994
42830
  let sent = 0;
42995
42831
  for (const envelope2 of batch) {
42996
42832
  const encoded = encodeDaemonToServiceMessage(
@@ -43009,12 +42845,7 @@ var DaemonSessionEventPublisher = class {
43009
42845
  return;
43010
42846
  }
43011
42847
  if (this.#connection.delivery === "ephemeral") {
43012
- this.#outbox.acknowledge({
43013
- eventId: envelope2.delivery.eventId,
43014
- kind: "session.event.ack",
43015
- outcome: { kind: "relayed" },
43016
- sessionId: envelope2.sessionId
43017
- });
42848
+ this.#queue.remove(envelope2.delivery.eventId, envelope2.sessionId);
43018
42849
  sent += 1;
43019
42850
  continue;
43020
42851
  }
@@ -43127,11 +42958,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
43127
42958
  join17(stateDirectory, "sessions", `${createHash5("sha256").update(config2.channelId).digest("hex")}.sqlite`)
43128
42959
  );
43129
42960
  const operations = new DaemonOperationState();
43130
- const channelHash = createHash5("sha256").update(config2.channelId).digest("hex");
43131
- const publisher = new DaemonSessionEventPublisher(
43132
- new SessionEventOutbox(join17(stateDirectory, "events", `${channelHash}.sqlite`)),
43133
- timer
43134
- );
42961
+ const publisher = new DaemonSessionEventPublisher(timer);
43135
42962
  const activeTurns = new DaemonTurnRegistry(operations);
43136
42963
  const recordSessionEvent = (sessionId, event) => {
43137
42964
  publisher.publish(sessionId, activeTurns.recordEvent(event));
@@ -43325,7 +43152,7 @@ import { fetch as fetch4 } from "undici";
43325
43152
  // src/storage/runtime-installation-store.ts
43326
43153
  import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
43327
43154
  import { mkdir as mkdir4, open as open5, readdir as readdir3, rename as rename3, unlink, writeFile as writeFile3 } from "node:fs/promises";
43328
- import { dirname as dirname6, join as join18, resolve as resolve6 } from "node:path";
43155
+ import { dirname as dirname5, join as join18, resolve as resolve6 } from "node:path";
43329
43156
  var APPLICATION_ID_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+){1,31}$/;
43330
43157
  var CONNECTION_ID_PATTERN = /^mcn_[a-f0-9]{32}$/;
43331
43158
  var INSTALLATION_ID_PATTERN = /^min_[a-f0-9]{32}$/;
@@ -43567,7 +43394,7 @@ async function readJsonFile(path2, signal, missingCode) {
43567
43394
  }
43568
43395
  async function writeJsonAtomically(path2, value, signal) {
43569
43396
  signal.throwIfAborted();
43570
- const directory = dirname6(path2);
43397
+ const directory = dirname5(path2);
43571
43398
  const temporaryPath = join18(directory, `.${randomUUID8()}.tmp`);
43572
43399
  try {
43573
43400
  await mkdir4(directory, { mode: 448, recursive: true });