@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/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.153",
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: {
@@ -46980,7 +46980,7 @@ async function runRelayConnection({
46980
46980
  return;
46981
46981
  }
46982
46982
  if (kind === SESSION_EVENT_REJECTED_KIND) {
46983
- publisher.quarantine(message);
46983
+ publisher.reject(message);
46984
46984
  return;
46985
46985
  }
46986
46986
  commands.handle(message);
@@ -47007,250 +47007,84 @@ async function runRelayConnection({
47007
47007
  }
47008
47008
  }
47009
47009
 
47010
- // src/relay-connection/session-event-outbox.ts
47010
+ // src/relay-connection/session-event-publisher.ts
47011
+ import { WebSocket as WebSocket9 } from "undici";
47012
+
47013
+ // src/relay-connection/session-event-queue.ts
47011
47014
  import { randomUUID as randomUUID7 } from "node:crypto";
47012
- import { mkdirSync as mkdirSync3 } from "node:fs";
47013
- import { dirname as dirname6 } from "node:path";
47014
- import { DatabaseSync } from "node:sqlite";
47015
- var DEFAULT_STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
47016
- var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
47017
- var MAX_DEAD_LETTERS = 1024;
47018
- var DEFAULT_LIMITS = {
47019
- criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
47020
- standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
47021
- };
47022
- var SessionEventOutboxCapacityError = class extends Error {
47023
- 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";
47024
47019
  constructor() {
47025
- super("The persistent Session event outbox is full");
47026
- this.name = "SessionEventOutboxCapacityError";
47020
+ super("The in-memory Session event queue is full");
47021
+ this.name = "SessionEventQueueCapacityError";
47027
47022
  }
47028
47023
  };
47029
- function rowNumber(row, field) {
47030
- const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
47031
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
47032
- throw new Error(`SQLite returned an invalid ${field} value`);
47033
- }
47034
- return value;
47035
- }
47036
- function rowString(row, field) {
47037
- const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
47038
- if (typeof value !== "string" || value.length === 0) {
47039
- throw new Error(`SQLite returned an invalid ${field} value`);
47040
- }
47041
- return value;
47042
- }
47043
- function validateLimits(limits) {
47044
- if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
47045
- throw new Error("Session event outbox limits are invalid");
47046
- }
47047
- }
47048
- var SessionEventOutbox = class {
47049
- #database;
47050
- #limits;
47051
- constructor(path3, limits = DEFAULT_LIMITS) {
47052
- validateLimits(limits);
47053
- mkdirSync3(dirname6(path3), { mode: 448, recursive: true });
47054
- this.#limits = limits;
47055
- this.#database = new DatabaseSync(path3);
47056
- this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
47057
- this.#database.exec(`
47058
- CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
47059
- ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
47060
- event_id TEXT NOT NULL UNIQUE,
47061
- session_id TEXT NOT NULL,
47062
- source_seq INTEGER NOT NULL UNIQUE,
47063
- byte_length INTEGER NOT NULL,
47064
- envelope_json TEXT NOT NULL
47065
- ) STRICT;
47066
- CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
47067
- ON relay_session_event_outbox(source_seq);
47068
- CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
47069
- singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
47070
- stream_id TEXT NOT NULL,
47071
- next_source_seq INTEGER NOT NULL,
47072
- total_bytes INTEGER NOT NULL
47073
- ) STRICT;
47074
- CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
47075
- ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
47076
- event_id TEXT NOT NULL UNIQUE,
47077
- session_id TEXT NOT NULL,
47078
- rejection_code TEXT NOT NULL,
47079
- envelope_json TEXT NOT NULL
47080
- ) STRICT;
47081
- `);
47082
- this.#database.prepare(
47083
- `INSERT OR IGNORE INTO relay_session_event_outbox_state (
47084
- singleton, stream_id, next_source_seq, total_bytes
47085
- ) VALUES (1, ?, 1, 0)`
47086
- ).run(randomUUID7());
47087
- }
47088
- acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
47089
- close = () => {
47090
- this.#database.close();
47091
- };
47092
- enqueue = (sessionId, payload, critical) => {
47093
- const envelope2 = this.enqueueBatch(sessionId, [payload], critical)[0];
47094
- if (envelope2 === void 0) {
47095
- throw new Error("The Session event outbox did not enqueue the event");
47096
- }
47097
- return envelope2;
47098
- };
47024
+ var SessionEventQueue = class {
47025
+ #streamId = randomUUID7();
47026
+ #events = /* @__PURE__ */ new Map();
47027
+ #nextSourceSeq = 1;
47028
+ #totalBytes = 0;
47099
47029
  enqueueBatch = (sessionId, payloads, critical) => {
47100
47030
  if (payloads.length < 1 || payloads.length > 32) {
47101
47031
  throw new Error("A Session event batch must contain between 1 and 32 events");
47102
47032
  }
47103
- this.#database.exec("BEGIN IMMEDIATE");
47104
- try {
47105
- const state = this.#database.prepare(
47106
- `SELECT stream_id, next_source_seq, total_bytes
47107
- FROM relay_session_event_outbox_state
47108
- WHERE singleton = 1`
47109
- ).get();
47110
- const sourceSeq = rowNumber(state, "next_source_seq");
47111
- if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + payloads.length)) {
47112
- throw new Error("The Session event source sequence is exhausted");
47113
- }
47114
- const streamId = rowString(state, "stream_id");
47115
- const stored = payloads.map((payload, index) => {
47116
- const envelope2 = {
47117
- delivery: {
47118
- eventId: randomUUID7(),
47119
- kind: "reliable",
47120
- sourceSeq: (sourceSeq + index).toString(),
47121
- streamId
47122
- },
47123
- kind: SESSION_EVENT_KIND,
47124
- payload,
47125
- sessionId
47126
- };
47127
- const encoded = encodeSessionEventEnvelope(envelope2);
47128
- return { byteLength: Buffer.byteLength(encoded, "utf8"), encoded, envelope: envelope2 };
47129
- });
47130
- const byteLength = stored.reduce((total, entry) => total + entry.byteLength, 0);
47131
- const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
47132
- if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
47133
- throw new SessionEventOutboxCapacityError();
47134
- }
47135
- const insert = this.#database.prepare(
47136
- `INSERT INTO relay_session_event_outbox (
47137
- event_id, session_id, source_seq, byte_length, envelope_json
47138
- ) VALUES (?, ?, ?, ?, ?)`
47139
- );
47140
- for (const entry of stored) {
47141
- insert.run(
47142
- entry.envelope.delivery.eventId,
47143
- sessionId,
47144
- Number(entry.envelope.delivery.sourceSeq),
47145
- entry.byteLength,
47146
- entry.encoded
47147
- );
47148
- }
47149
- this.#database.prepare(
47150
- `UPDATE relay_session_event_outbox_state
47151
- SET next_source_seq = next_source_seq + ?,
47152
- total_bytes = total_bytes + ?
47153
- WHERE singleton = 1`
47154
- ).run(stored.length, byteLength);
47155
- this.#database.exec("COMMIT");
47156
- return stored.map((entry) => entry.envelope);
47157
- } catch (error62) {
47158
- this.#database.exec("ROLLBACK");
47159
- throw error62;
47033
+ if (!Number.isSafeInteger(this.#nextSourceSeq + payloads.length)) {
47034
+ throw new Error("The Session event source sequence is exhausted");
47160
47035
  }
47161
- };
47162
- pending = (limit, excludedEventIds) => {
47163
- if (!Number.isSafeInteger(limit) || limit < 1) {
47164
- throw new Error("Session event replay limit must be a positive integer");
47165
- }
47166
- const exclusion = excludedEventIds.length === 0 ? "" : `WHERE event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`;
47167
- return this.#database.prepare(
47168
- `SELECT envelope_json
47169
- FROM relay_session_event_outbox
47170
- ${exclusion}
47171
- ORDER BY source_seq
47172
- LIMIT ?`
47173
- ).all(...excludedEventIds, limit).map((row) => {
47174
- const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
47175
- if (!("delivery" in envelope2)) {
47176
- throw new Error("SQLite returned an invalid reliable Session event envelope");
47177
- }
47178
- 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") };
47179
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;
47180
47059
  };
47181
- quarantine = (rejection) => {
47182
- this.#database.exec("BEGIN IMMEDIATE");
47183
- try {
47184
- const stored = this.#database.prepare(
47185
- `SELECT byte_length, envelope_json
47186
- FROM relay_session_event_outbox
47187
- WHERE event_id = ? AND session_id = ?`
47188
- ).get(rejection.eventId, rejection.sessionId);
47189
- if (stored === void 0) {
47190
- this.#database.exec("COMMIT");
47191
- 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
+ }
47192
47068
  }
47193
- this.#database.prepare(
47194
- `INSERT INTO relay_session_event_dead_letters (
47195
- event_id, session_id, rejection_code, envelope_json
47196
- ) VALUES (?, ?, ?, ?)`
47197
- ).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
47198
- this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
47199
- this.#database.prepare(
47200
- `DELETE FROM relay_session_event_dead_letters
47201
- WHERE ordinal NOT IN (
47202
- SELECT ordinal FROM relay_session_event_dead_letters
47203
- ORDER BY ordinal DESC LIMIT ?
47204
- )`
47205
- ).run(MAX_DEAD_LETTERS);
47206
- this.#database.exec("COMMIT");
47207
- return true;
47208
- } catch (error62) {
47209
- this.#database.exec("ROLLBACK");
47210
- throw error62;
47211
47069
  }
47070
+ return result;
47212
47071
  };
47213
- #deleteStored = (eventId, sessionId, byteLength) => {
47214
- const deletion = this.#database.prepare(
47215
- `DELETE FROM relay_session_event_outbox
47216
- WHERE event_id = ? AND session_id = ?`
47217
- ).run(eventId, sessionId);
47218
- if (deletion.changes !== 1) {
47219
- throw new Error("The Session event could not be removed from the outbox");
47220
- }
47221
- const state = this.#database.prepare(
47222
- `UPDATE relay_session_event_outbox_state
47223
- SET total_bytes = total_bytes - ?
47224
- WHERE singleton = 1 AND total_bytes >= ?`
47225
- ).run(byteLength, byteLength);
47226
- if (state.changes !== 1) {
47227
- 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;
47228
47076
  }
47077
+ this.#events.delete(eventId);
47078
+ this.#totalBytes -= entry.byteLength;
47079
+ return true;
47229
47080
  };
47230
- #remove = (eventId, sessionId) => {
47231
- this.#database.exec("BEGIN IMMEDIATE");
47232
- try {
47233
- const stored = this.#database.prepare(
47234
- `SELECT byte_length
47235
- FROM relay_session_event_outbox
47236
- WHERE event_id = ? AND session_id = ?`
47237
- ).get(eventId, sessionId);
47238
- if (stored === void 0) {
47239
- this.#database.exec("COMMIT");
47240
- return false;
47241
- }
47242
- this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
47243
- this.#database.exec("COMMIT");
47244
- return true;
47245
- } catch (error62) {
47246
- this.#database.exec("ROLLBACK");
47247
- throw error62;
47248
- }
47081
+ clear = () => {
47082
+ this.#events.clear();
47083
+ this.#totalBytes = 0;
47249
47084
  };
47250
47085
  };
47251
47086
 
47252
47087
  // src/relay-connection/session-event-publisher.ts
47253
- import { WebSocket as WebSocket9 } from "undici";
47254
47088
  var SESSION_EVENT_REPLAY_WINDOW = 16;
47255
47089
  var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
47256
47090
  function reservesSessionEventCapacity(event) {
@@ -47261,18 +47095,17 @@ function reservesSessionEventCapacity(event) {
47261
47095
  }
47262
47096
  var DaemonSessionEventPublisher = class {
47263
47097
  #inFlight = /* @__PURE__ */ new Map();
47264
- #outbox;
47098
+ #queue = new SessionEventQueue();
47265
47099
  #reportedCapacityKinds = /* @__PURE__ */ new Set();
47266
47100
  #timer;
47267
47101
  #connection = { kind: "disconnected" };
47268
47102
  #deferredReplay = null;
47269
47103
  #inFlightBytes = 0;
47270
- constructor(outbox, timer) {
47271
- this.#outbox = outbox;
47104
+ constructor(timer) {
47272
47105
  this.#timer = timer;
47273
47106
  }
47274
47107
  acknowledge = (acknowledgement) => {
47275
- if (!this.#outbox.acknowledge(acknowledgement)) {
47108
+ if (!this.#queue.remove(acknowledgement.eventId, acknowledgement.sessionId)) {
47276
47109
  return;
47277
47110
  }
47278
47111
  this.#removeInFlight(acknowledgement.eventId);
@@ -47283,7 +47116,7 @@ var DaemonSessionEventPublisher = class {
47283
47116
  this.#deferredReplay = null;
47284
47117
  this.#connection = { kind: "disconnected" };
47285
47118
  this.#clearInFlight();
47286
- this.#outbox.close();
47119
+ this.#queue.clear();
47287
47120
  };
47288
47121
  connect = (socket, delivery) => {
47289
47122
  this.#connection = { delivery, kind: "connected", socket };
@@ -47312,16 +47145,18 @@ var DaemonSessionEventPublisher = class {
47312
47145
  };
47313
47146
  publish = (sessionId, event) => {
47314
47147
  try {
47315
- this.#outbox.enqueue(sessionId, encodeRecordedSessionEvent(event), reservesSessionEventCapacity(event));
47148
+ this.#queue.enqueueBatch(
47149
+ sessionId,
47150
+ [encodeRecordedSessionEvent(event)],
47151
+ reservesSessionEventCapacity(event)
47152
+ );
47316
47153
  } catch (error62) {
47317
- if (!(error62 instanceof SessionEventOutboxCapacityError)) {
47154
+ if (!(error62 instanceof SessionEventQueueCapacityError)) {
47318
47155
  throw error62;
47319
47156
  }
47320
47157
  if (!this.#reportedCapacityKinds.has(event.kind)) {
47321
47158
  this.#reportedCapacityKinds.add(event.kind);
47322
- console.warn(
47323
- `Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
47324
- );
47159
+ console.warn(`Mobius daemon could not buffer "${event.kind}" because the Session event queue is full`);
47325
47160
  }
47326
47161
  return;
47327
47162
  }
@@ -47329,11 +47164,11 @@ var DaemonSessionEventPublisher = class {
47329
47164
  };
47330
47165
  publishBatch = (sessionId, events) => {
47331
47166
  const critical = events.some(reservesSessionEventCapacity);
47332
- this.#outbox.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
47167
+ this.#queue.enqueueBatch(sessionId, events.map(encodeRecordedSessionEvent), critical);
47333
47168
  this.#publishNextBatch();
47334
47169
  };
47335
- quarantine = (rejection) => {
47336
- if (!this.#outbox.quarantine(rejection)) {
47170
+ reject = (rejection) => {
47171
+ if (!this.#queue.remove(rejection.eventId, rejection.sessionId)) {
47337
47172
  return;
47338
47173
  }
47339
47174
  this.#removeInFlight(rejection.eventId);
@@ -47349,9 +47184,10 @@ var DaemonSessionEventPublisher = class {
47349
47184
  return;
47350
47185
  }
47351
47186
  while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
47352
- const batch = this.#outbox.pending(SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size, [
47353
- ...this.#inFlight.keys()
47354
- ]);
47187
+ const batch = this.#queue.pending(
47188
+ SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size,
47189
+ new Set(this.#inFlight.keys())
47190
+ );
47355
47191
  let sent = 0;
47356
47192
  for (const envelope2 of batch) {
47357
47193
  const encoded = encodeDaemonToServiceMessage(
@@ -47370,12 +47206,7 @@ var DaemonSessionEventPublisher = class {
47370
47206
  return;
47371
47207
  }
47372
47208
  if (this.#connection.delivery === "ephemeral") {
47373
- this.#outbox.acknowledge({
47374
- eventId: envelope2.delivery.eventId,
47375
- kind: "session.event.ack",
47376
- outcome: { kind: "relayed" },
47377
- sessionId: envelope2.sessionId
47378
- });
47209
+ this.#queue.remove(envelope2.delivery.eventId, envelope2.sessionId);
47379
47210
  sent += 1;
47380
47211
  continue;
47381
47212
  }
@@ -47488,11 +47319,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
47488
47319
  join18(stateDirectory, "sessions", `${createHash6("sha256").update(config2.channelId).digest("hex")}.sqlite`)
47489
47320
  );
47490
47321
  const operations = new DaemonOperationState();
47491
- const channelHash = createHash6("sha256").update(config2.channelId).digest("hex");
47492
- const publisher = new DaemonSessionEventPublisher(
47493
- new SessionEventOutbox(join18(stateDirectory, "events", `${channelHash}.sqlite`)),
47494
- timer
47495
- );
47322
+ const publisher = new DaemonSessionEventPublisher(timer);
47496
47323
  const activeTurns = new DaemonTurnRegistry(operations);
47497
47324
  const recordSessionEvent = (sessionId, event) => {
47498
47325
  publisher.publish(sessionId, activeTurns.recordEvent(event));