@moxt-ai/mobius 0.0.149 → 0.0.150

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.
@@ -19,19 +19,6 @@ export declare class DaemonTurnRegistry {
19
19
  epoch: string;
20
20
  hasOlder: boolean;
21
21
  interaction: Readonly<{
22
- commandId: string;
23
- interactionId: string;
24
- kind: "session.permission.requested";
25
- options: readonly Readonly<{
26
- kind: "allow_always" | "allow_once" | "reject_always" | "reject_once";
27
- name: string;
28
- optionId: string;
29
- }>[];
30
- sessionId: string;
31
- title: string;
32
- toolCallId: string;
33
- toolKind: string;
34
- }> | Readonly<{
35
22
  accepted: boolean;
36
23
  commandId: string;
37
24
  elicitationId: string;
@@ -63,6 +50,19 @@ export declare class DaemonTurnRegistry {
63
50
  mode: "form" | "url";
64
51
  sessionId: string;
65
52
  url: string;
53
+ }> | Readonly<{
54
+ commandId: string;
55
+ interactionId: string;
56
+ kind: "session.permission.requested";
57
+ options: readonly Readonly<{
58
+ kind: "allow_always" | "allow_once" | "reject_always" | "reject_once";
59
+ name: string;
60
+ optionId: string;
61
+ }>[];
62
+ sessionId: string;
63
+ title: string;
64
+ toolCallId: string;
65
+ toolKind: string;
66
66
  }> | null;
67
67
  messages: readonly (Readonly<{
68
68
  id: string;
package/dist/index.js CHANGED
@@ -20766,9 +20766,17 @@ function decodeSessionQueuedEvent(encoded) {
20766
20766
  }
20767
20767
  function decodeRuntimeLivenessReadyEvent(encoded) {
20768
20768
  const value = parseJson2(encoded);
20769
- assertExactKeys(value, ["kind"]);
20770
20769
  assertKind(value, RUNTIME_LIVENESS_READY_KIND);
20771
- return { kind: RUNTIME_LIVENESS_READY_KIND };
20770
+ if (value["sessionEventDelivery"] === void 0) {
20771
+ assertExactKeys(value, ["kind"]);
20772
+ return { kind: RUNTIME_LIVENESS_READY_KIND };
20773
+ }
20774
+ assertExactKeys(value, ["kind", "sessionEventDelivery"]);
20775
+ const sessionEventDelivery = readString(value, "sessionEventDelivery", 32);
20776
+ if (sessionEventDelivery !== "reliable-v1") {
20777
+ throw new ProtocolMessageError("Unsupported Session event delivery mode");
20778
+ }
20779
+ return { kind: RUNTIME_LIVENESS_READY_KIND, sessionEventDelivery };
20772
20780
  }
20773
20781
  function decodeRuntimeHeartbeatEvent(encoded) {
20774
20782
  const value = parseJson2(encoded);
@@ -21218,12 +21226,59 @@ var encodeNativeSessionCancel = (value) => JSON.stringify(nativeSessionCancelSch
21218
21226
 
21219
21227
  // ../protocol/src/session-events.ts
21220
21228
  var SESSION_EVENT_KIND = "session.event";
21229
+ var SESSION_EVENT_ACK_KIND = "session.event.ack";
21230
+ var SESSION_EVENT_DEFERRED_KIND = "session.event.deferred";
21231
+ var SESSION_EVENT_REJECTED_KIND = "session.event.rejected";
21232
+ var SessionEventRejectionCode = {
21233
+ IdempotencyConflict: "idempotency_conflict",
21234
+ InvalidEvent: "invalid_event",
21235
+ RecordingRejected: "recording_rejected"
21236
+ };
21221
21237
  var MAX_RECORDED_EVENT_LENGTH = 8 * 1024 * 1024;
21222
- var eventEnvelopeSchema = external_exports.strictObject({
21238
+ var ephemeralEventEnvelopeSchema = external_exports.strictObject({
21223
21239
  kind: external_exports.literal(SESSION_EVENT_KIND),
21224
21240
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
21225
21241
  sessionId: identifierStringSchema
21226
21242
  }).readonly();
21243
+ var reliableDeliverySchema = external_exports.strictObject({
21244
+ eventId: identifierStringSchema,
21245
+ kind: external_exports.literal("reliable"),
21246
+ sourceSeq: external_exports.string().regex(/^(?:0|[1-9][0-9]*)$/u).max(40),
21247
+ streamId: identifierStringSchema
21248
+ }).readonly();
21249
+ var reliableEventEnvelopeSchema = external_exports.strictObject({
21250
+ delivery: reliableDeliverySchema,
21251
+ kind: external_exports.literal(SESSION_EVENT_KIND),
21252
+ payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
21253
+ sessionId: identifierStringSchema
21254
+ }).readonly();
21255
+ var eventEnvelopeSchema = external_exports.union([ephemeralEventEnvelopeSchema, reliableEventEnvelopeSchema]).readonly();
21256
+ var acknowledgementOutcomeSchema = external_exports.discriminatedUnion("kind", [
21257
+ external_exports.strictObject({ kind: external_exports.literal("relayed") }).readonly(),
21258
+ external_exports.strictObject({
21259
+ kind: external_exports.literal("recorded"),
21260
+ providerId: identifierStringSchema,
21261
+ recordingEpoch: identifierStringSchema
21262
+ }).readonly()
21263
+ ]);
21264
+ var eventAcknowledgementSchema = external_exports.strictObject({
21265
+ eventId: identifierStringSchema,
21266
+ kind: external_exports.literal(SESSION_EVENT_ACK_KIND),
21267
+ outcome: acknowledgementOutcomeSchema,
21268
+ sessionId: identifierStringSchema
21269
+ }).readonly();
21270
+ var eventDeferredSchema = external_exports.strictObject({
21271
+ eventId: identifierStringSchema,
21272
+ kind: external_exports.literal(SESSION_EVENT_DEFERRED_KIND),
21273
+ retryAfterMilliseconds: external_exports.number().int().min(100).max(6e4),
21274
+ sessionId: identifierStringSchema
21275
+ }).readonly();
21276
+ var eventRejectedSchema = external_exports.strictObject({
21277
+ code: external_exports.enum(SessionEventRejectionCode),
21278
+ eventId: identifierStringSchema,
21279
+ kind: external_exports.literal(SESSION_EVENT_REJECTED_KIND),
21280
+ sessionId: identifierStringSchema
21281
+ }).readonly();
21227
21282
  var SESSION_QUEUE_CHANGED_KIND = "session.queue.changed";
21228
21283
  var sessionQueueChangedSchema = external_exports.strictObject({ kind: external_exports.literal(SESSION_QUEUE_CHANGED_KIND), sessionId: identifierStringSchema }).readonly();
21229
21284
  function decode5(encoded, schema) {
@@ -21252,6 +21307,12 @@ var encode4 = (message, schema) => {
21252
21307
  };
21253
21308
  var decodeSessionEventEnvelope = (encoded) => decode5(encoded, eventEnvelopeSchema);
21254
21309
  var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
21310
+ var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
21311
+ var encodeSessionEventAcknowledgement = (message) => encode4(message, eventAcknowledgementSchema);
21312
+ var decodeSessionEventDeferred = (encoded) => decode5(encoded, eventDeferredSchema);
21313
+ var encodeSessionEventDeferred = (message) => encode4(message, eventDeferredSchema);
21314
+ var decodeSessionEventRejected = (encoded) => decode5(encoded, eventRejectedSchema);
21315
+ var encodeSessionEventRejected = (message) => encode4(message, eventRejectedSchema);
21255
21316
  function decodeRecordedSessionEvent(encoded) {
21256
21317
  const kind = decodeMessageKind(encoded);
21257
21318
  switch (kind) {
@@ -21423,6 +21484,21 @@ var serviceToDaemonSignatures = {
21423
21484
  encodeSessionElicitationRespondCommand,
21424
21485
  decodeSessionElicitationRespondCommand
21425
21486
  ),
21487
+ [SESSION_EVENT_ACK_KIND]: defineMessageSignature(
21488
+ SESSION_EVENT_ACK_KIND,
21489
+ encodeSessionEventAcknowledgement,
21490
+ decodeSessionEventAcknowledgement
21491
+ ),
21492
+ [SESSION_EVENT_DEFERRED_KIND]: defineMessageSignature(
21493
+ SESSION_EVENT_DEFERRED_KIND,
21494
+ encodeSessionEventDeferred,
21495
+ decodeSessionEventDeferred
21496
+ ),
21497
+ [SESSION_EVENT_REJECTED_KIND]: defineMessageSignature(
21498
+ SESSION_EVENT_REJECTED_KIND,
21499
+ encodeSessionEventRejected,
21500
+ decodeSessionEventRejected
21501
+ ),
21426
21502
  [SESSION_LIVE_HISTORY_LOAD_KIND]: defineMessageSignature(
21427
21503
  SESSION_LIVE_HISTORY_LOAD_KIND,
21428
21504
  encodeSessionLiveHistoryLoad,
@@ -37515,7 +37591,7 @@ function isRuntimeRelayRevocation(closeCode, closeReason) {
37515
37591
  }
37516
37592
 
37517
37593
  // src/run-daemon.ts
37518
- import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
37594
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
37519
37595
  import { join as join18 } from "node:path";
37520
37596
  import { styleText } from "node:util";
37521
37597
 
@@ -43098,6 +43174,7 @@ async function runRelayConnection({
43098
43174
  }) {
43099
43175
  const relayUrl = new URL(config2.relayUrl);
43100
43176
  relayUrl.searchParams.set("liveness", "1");
43177
+ relayUrl.searchParams.set("sessionEventDelivery", "reliable-v1");
43101
43178
  const socket = await connectRelay(relayUrl, signal);
43102
43179
  diagnostics.connectionOpened(connectionEpoch);
43103
43180
  const connectionController = new AbortController();
@@ -43217,6 +43294,10 @@ async function runRelayConnection({
43217
43294
  const message = decodeServiceToDaemonMessage(encoded);
43218
43295
  const kind = message.kind;
43219
43296
  if (kind === RUNTIME_LIVENESS_READY_KIND) {
43297
+ publisher.connect(
43298
+ socket,
43299
+ message.sessionEventDelivery === "reliable-v1" ? "reliable" : "ephemeral"
43300
+ );
43220
43301
  connectionLiveness.start();
43221
43302
  return;
43222
43303
  }
@@ -43224,6 +43305,18 @@ async function runRelayConnection({
43224
43305
  connectionLiveness.acknowledge(message);
43225
43306
  return;
43226
43307
  }
43308
+ if (kind === SESSION_EVENT_ACK_KIND) {
43309
+ publisher.acknowledge(message);
43310
+ return;
43311
+ }
43312
+ if (kind === SESSION_EVENT_DEFERRED_KIND) {
43313
+ publisher.defer(message);
43314
+ return;
43315
+ }
43316
+ if (kind === SESSION_EVENT_REJECTED_KIND) {
43317
+ publisher.quarantine(message);
43318
+ return;
43319
+ }
43227
43320
  commands.handle(message);
43228
43321
  } catch (error62) {
43229
43322
  console.error("Mobius daemon rejected a message", error62);
@@ -43238,7 +43331,6 @@ async function runRelayConnection({
43238
43331
  socket.addEventListener("close", handleClose);
43239
43332
  socket.addEventListener("error", handleError);
43240
43333
  socket.addEventListener("message", handleMessage);
43241
- publisher.connect(socket);
43242
43334
  if (signal.aborted) {
43243
43335
  stop();
43244
43336
  }
@@ -43249,54 +43341,377 @@ async function runRelayConnection({
43249
43341
  }
43250
43342
  }
43251
43343
 
43344
+ // src/relay-connection/session-event-outbox.ts
43345
+ import { randomUUID as randomUUID7 } from "node:crypto";
43346
+ import { mkdirSync as mkdirSync3 } from "node:fs";
43347
+ import { dirname as dirname6 } from "node:path";
43348
+ import { DatabaseSync } from "node:sqlite";
43349
+ var DEFAULT_STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
43350
+ var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
43351
+ var MAX_DEAD_LETTERS = 1024;
43352
+ var DEFAULT_LIMITS = {
43353
+ criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
43354
+ standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
43355
+ };
43356
+ var SessionEventOutboxCapacityError = class extends Error {
43357
+ code = "session_event_outbox_full";
43358
+ constructor() {
43359
+ super("The persistent Session event outbox is full");
43360
+ this.name = "SessionEventOutboxCapacityError";
43361
+ }
43362
+ };
43363
+ function rowNumber(row, field) {
43364
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
43365
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
43366
+ throw new Error(`SQLite returned an invalid ${field} value`);
43367
+ }
43368
+ return value;
43369
+ }
43370
+ function rowString(row, field) {
43371
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
43372
+ if (typeof value !== "string" || value.length === 0) {
43373
+ throw new Error(`SQLite returned an invalid ${field} value`);
43374
+ }
43375
+ return value;
43376
+ }
43377
+ function validateLimits(limits) {
43378
+ if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
43379
+ throw new Error("Session event outbox limits are invalid");
43380
+ }
43381
+ }
43382
+ var SessionEventOutbox = class {
43383
+ #database;
43384
+ #limits;
43385
+ constructor(path2, limits = DEFAULT_LIMITS) {
43386
+ validateLimits(limits);
43387
+ mkdirSync3(dirname6(path2), { mode: 448, recursive: true });
43388
+ this.#limits = limits;
43389
+ this.#database = new DatabaseSync(path2);
43390
+ this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
43391
+ this.#database.exec(`
43392
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
43393
+ ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
43394
+ event_id TEXT NOT NULL UNIQUE,
43395
+ session_id TEXT NOT NULL,
43396
+ source_seq INTEGER NOT NULL UNIQUE,
43397
+ byte_length INTEGER NOT NULL,
43398
+ envelope_json TEXT NOT NULL
43399
+ ) STRICT;
43400
+ CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
43401
+ ON relay_session_event_outbox(source_seq);
43402
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
43403
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
43404
+ stream_id TEXT NOT NULL,
43405
+ next_source_seq INTEGER NOT NULL,
43406
+ total_bytes INTEGER NOT NULL
43407
+ ) STRICT;
43408
+ CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
43409
+ ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
43410
+ event_id TEXT NOT NULL UNIQUE,
43411
+ session_id TEXT NOT NULL,
43412
+ rejection_code TEXT NOT NULL,
43413
+ envelope_json TEXT NOT NULL
43414
+ ) STRICT;
43415
+ `);
43416
+ this.#database.prepare(
43417
+ `INSERT OR IGNORE INTO relay_session_event_outbox_state (
43418
+ singleton, stream_id, next_source_seq, total_bytes
43419
+ ) VALUES (1, ?, 1, 0)`
43420
+ ).run(randomUUID7());
43421
+ }
43422
+ acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
43423
+ close = () => {
43424
+ this.#database.close();
43425
+ };
43426
+ enqueue = (sessionId, payload, critical) => {
43427
+ this.#database.exec("BEGIN IMMEDIATE");
43428
+ try {
43429
+ const state = this.#database.prepare(
43430
+ `SELECT stream_id, next_source_seq, total_bytes
43431
+ FROM relay_session_event_outbox_state
43432
+ WHERE singleton = 1`
43433
+ ).get();
43434
+ const sourceSeq = rowNumber(state, "next_source_seq");
43435
+ if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + 1)) {
43436
+ throw new Error("The Session event source sequence is exhausted");
43437
+ }
43438
+ const envelope2 = {
43439
+ delivery: {
43440
+ eventId: randomUUID7(),
43441
+ kind: "reliable",
43442
+ sourceSeq: sourceSeq.toString(),
43443
+ streamId: rowString(state, "stream_id")
43444
+ },
43445
+ kind: SESSION_EVENT_KIND,
43446
+ payload,
43447
+ sessionId
43448
+ };
43449
+ const encoded = encodeSessionEventEnvelope(envelope2);
43450
+ const byteLength = Buffer.byteLength(encoded, "utf8");
43451
+ const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
43452
+ if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
43453
+ throw new SessionEventOutboxCapacityError();
43454
+ }
43455
+ this.#database.prepare(
43456
+ `INSERT INTO relay_session_event_outbox (
43457
+ event_id, session_id, source_seq, byte_length, envelope_json
43458
+ ) VALUES (?, ?, ?, ?, ?)`
43459
+ ).run(envelope2.delivery.eventId, sessionId, sourceSeq, byteLength, encoded);
43460
+ this.#database.prepare(
43461
+ `UPDATE relay_session_event_outbox_state
43462
+ SET next_source_seq = next_source_seq + 1,
43463
+ total_bytes = total_bytes + ?
43464
+ WHERE singleton = 1`
43465
+ ).run(byteLength);
43466
+ this.#database.exec("COMMIT");
43467
+ return envelope2;
43468
+ } catch (error62) {
43469
+ this.#database.exec("ROLLBACK");
43470
+ throw error62;
43471
+ }
43472
+ };
43473
+ pending = (limit, excludedEventIds) => {
43474
+ if (!Number.isSafeInteger(limit) || limit < 1) {
43475
+ throw new Error("Session event replay limit must be a positive integer");
43476
+ }
43477
+ const exclusion = excludedEventIds.length === 0 ? "" : `WHERE event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`;
43478
+ return this.#database.prepare(
43479
+ `SELECT envelope_json
43480
+ FROM relay_session_event_outbox
43481
+ ${exclusion}
43482
+ ORDER BY source_seq
43483
+ LIMIT ?`
43484
+ ).all(...excludedEventIds, limit).map((row) => {
43485
+ const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
43486
+ if (!("delivery" in envelope2)) {
43487
+ throw new Error("SQLite returned an invalid reliable Session event envelope");
43488
+ }
43489
+ return envelope2;
43490
+ });
43491
+ };
43492
+ quarantine = (rejection) => {
43493
+ this.#database.exec("BEGIN IMMEDIATE");
43494
+ try {
43495
+ const stored = this.#database.prepare(
43496
+ `SELECT byte_length, envelope_json
43497
+ FROM relay_session_event_outbox
43498
+ WHERE event_id = ? AND session_id = ?`
43499
+ ).get(rejection.eventId, rejection.sessionId);
43500
+ if (stored === void 0) {
43501
+ this.#database.exec("COMMIT");
43502
+ return false;
43503
+ }
43504
+ this.#database.prepare(
43505
+ `INSERT INTO relay_session_event_dead_letters (
43506
+ event_id, session_id, rejection_code, envelope_json
43507
+ ) VALUES (?, ?, ?, ?)`
43508
+ ).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
43509
+ this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
43510
+ this.#database.prepare(
43511
+ `DELETE FROM relay_session_event_dead_letters
43512
+ WHERE ordinal NOT IN (
43513
+ SELECT ordinal FROM relay_session_event_dead_letters
43514
+ ORDER BY ordinal DESC LIMIT ?
43515
+ )`
43516
+ ).run(MAX_DEAD_LETTERS);
43517
+ this.#database.exec("COMMIT");
43518
+ return true;
43519
+ } catch (error62) {
43520
+ this.#database.exec("ROLLBACK");
43521
+ throw error62;
43522
+ }
43523
+ };
43524
+ #deleteStored = (eventId, sessionId, byteLength) => {
43525
+ const deletion = this.#database.prepare(
43526
+ `DELETE FROM relay_session_event_outbox
43527
+ WHERE event_id = ? AND session_id = ?`
43528
+ ).run(eventId, sessionId);
43529
+ if (deletion.changes !== 1) {
43530
+ throw new Error("The Session event could not be removed from the outbox");
43531
+ }
43532
+ const state = this.#database.prepare(
43533
+ `UPDATE relay_session_event_outbox_state
43534
+ SET total_bytes = total_bytes - ?
43535
+ WHERE singleton = 1 AND total_bytes >= ?`
43536
+ ).run(byteLength, byteLength);
43537
+ if (state.changes !== 1) {
43538
+ throw new Error("The Session event outbox capacity state is inconsistent");
43539
+ }
43540
+ };
43541
+ #remove = (eventId, sessionId) => {
43542
+ this.#database.exec("BEGIN IMMEDIATE");
43543
+ try {
43544
+ const stored = this.#database.prepare(
43545
+ `SELECT byte_length
43546
+ FROM relay_session_event_outbox
43547
+ WHERE event_id = ? AND session_id = ?`
43548
+ ).get(eventId, sessionId);
43549
+ if (stored === void 0) {
43550
+ this.#database.exec("COMMIT");
43551
+ return false;
43552
+ }
43553
+ this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
43554
+ this.#database.exec("COMMIT");
43555
+ return true;
43556
+ } catch (error62) {
43557
+ this.#database.exec("ROLLBACK");
43558
+ throw error62;
43559
+ }
43560
+ };
43561
+ };
43562
+
43252
43563
  // src/relay-connection/session-event-publisher.ts
43253
43564
  import { WebSocket as WebSocket9 } from "undici";
43254
- var MAX_LIVE_EVENT_BUFFERED_BYTES = 16 * 1024 * 1024;
43565
+ var SESSION_EVENT_REPLAY_WINDOW = 16;
43566
+ var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
43567
+ function reservesSessionEventCapacity(event) {
43568
+ if (event.kind === SESSION_COMPLETED_KIND || event.kind === SESSION_FAILED_KIND) {
43569
+ return true;
43570
+ }
43571
+ return event.kind === AGENT_ACTIVITY_KIND && (event.activityType === "progress" || event.activityType === "reasoning") && (event.status === "completed" || event.status === "failed");
43572
+ }
43255
43573
  var DaemonSessionEventPublisher = class {
43256
- #socket = null;
43257
- #reportedDrop = false;
43258
- connect = (socket) => {
43259
- this.#socket = socket;
43260
- this.#reportedDrop = false;
43574
+ #inFlight = /* @__PURE__ */ new Map();
43575
+ #outbox;
43576
+ #reportedCapacityKinds = /* @__PURE__ */ new Set();
43577
+ #timer;
43578
+ #connection = { kind: "disconnected" };
43579
+ #deferredReplay = null;
43580
+ #inFlightBytes = 0;
43581
+ constructor(outbox, timer) {
43582
+ this.#outbox = outbox;
43583
+ this.#timer = timer;
43584
+ }
43585
+ acknowledge = (acknowledgement) => {
43586
+ if (!this.#outbox.acknowledge(acknowledgement)) {
43587
+ return;
43588
+ }
43589
+ this.#removeInFlight(acknowledgement.eventId);
43590
+ this.#publishNextBatch();
43591
+ };
43592
+ close = () => {
43593
+ this.#deferredReplay?.cancel();
43594
+ this.#deferredReplay = null;
43595
+ this.#connection = { kind: "disconnected" };
43596
+ this.#clearInFlight();
43597
+ this.#outbox.close();
43598
+ };
43599
+ connect = (socket, delivery) => {
43600
+ this.#connection = { delivery, kind: "connected", socket };
43601
+ this.#clearInFlight();
43602
+ this.#publishNextBatch();
43603
+ };
43604
+ defer = (deferred) => {
43605
+ if (!this.#inFlight.has(deferred.eventId)) {
43606
+ return;
43607
+ }
43608
+ this.#clearInFlight();
43609
+ if (this.#deferredReplay !== null) {
43610
+ return;
43611
+ }
43612
+ this.#deferredReplay = this.#timer.schedule(() => {
43613
+ this.#deferredReplay = null;
43614
+ this.#publishNextBatch();
43615
+ }, deferred.retryAfterMilliseconds);
43261
43616
  };
43262
43617
  disconnect = (socket) => {
43263
- if (this.#socket === socket) {
43264
- this.#socket = null;
43618
+ if (this.#connection.kind !== "connected" || this.#connection.socket !== socket) {
43619
+ return;
43265
43620
  }
43621
+ this.#connection = { kind: "disconnected" };
43622
+ this.#clearInFlight();
43266
43623
  };
43267
43624
  publish = (sessionId, event) => {
43268
- const socket = this.#socket;
43269
- if (socket === null || socket.readyState !== WebSocket9.OPEN) {
43270
- this.#reportDrop();
43625
+ try {
43626
+ this.#outbox.enqueue(sessionId, encodeRecordedSessionEvent(event), reservesSessionEventCapacity(event));
43627
+ } catch (error62) {
43628
+ if (!(error62 instanceof SessionEventOutboxCapacityError)) {
43629
+ throw error62;
43630
+ }
43631
+ if (!this.#reportedCapacityKinds.has(event.kind)) {
43632
+ this.#reportedCapacityKinds.add(event.kind);
43633
+ console.warn(
43634
+ `Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
43635
+ );
43636
+ }
43271
43637
  return;
43272
43638
  }
43273
- if (socket.bufferedAmount >= MAX_LIVE_EVENT_BUFFERED_BYTES) {
43274
- this.#socket = null;
43275
- socket.close(1013, "Live event backpressure");
43276
- this.#reportDrop();
43639
+ this.#publishNextBatch();
43640
+ };
43641
+ quarantine = (rejection) => {
43642
+ if (!this.#outbox.quarantine(rejection)) {
43277
43643
  return;
43278
43644
  }
43279
- try {
43280
- socket.send(
43281
- encodeDaemonToServiceMessage({
43282
- kind: SESSION_EVENT_KIND,
43283
- payload: encodeRecordedSessionEvent(event),
43284
- sessionId
43285
- })
43286
- );
43287
- } catch {
43288
- this.#reportDrop();
43289
- }
43645
+ this.#removeInFlight(rejection.eventId);
43646
+ console.error(`Mobius Relay rejected Session event ${rejection.eventId} with code ${rejection.code}`);
43647
+ this.#publishNextBatch();
43290
43648
  };
43291
- close = () => {
43292
- this.#socket = null;
43649
+ #clearInFlight = () => {
43650
+ this.#inFlight.clear();
43651
+ this.#inFlightBytes = 0;
43652
+ };
43653
+ #publishNextBatch = () => {
43654
+ if (this.#deferredReplay !== null || this.#connection.kind !== "connected") {
43655
+ return;
43656
+ }
43657
+ while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
43658
+ const batch = this.#outbox.pending(SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size, [
43659
+ ...this.#inFlight.keys()
43660
+ ]);
43661
+ let sent = 0;
43662
+ for (const envelope2 of batch) {
43663
+ const encoded = encodeDaemonToServiceMessage(
43664
+ this.#connection.delivery === "reliable" ? envelope2 : {
43665
+ kind: envelope2.kind,
43666
+ payload: envelope2.payload,
43667
+ sessionId: envelope2.sessionId
43668
+ }
43669
+ );
43670
+ const byteLength = Buffer.byteLength(encoded, "utf8");
43671
+ if (this.#inFlightBytes + byteLength > SESSION_EVENT_REPLAY_BYTE_LIMIT) {
43672
+ return;
43673
+ }
43674
+ if (!this.#send(encoded)) {
43675
+ this.#clearInFlight();
43676
+ return;
43677
+ }
43678
+ if (this.#connection.delivery === "ephemeral") {
43679
+ this.#outbox.acknowledge({
43680
+ eventId: envelope2.delivery.eventId,
43681
+ kind: "session.event.ack",
43682
+ outcome: { kind: "relayed" },
43683
+ sessionId: envelope2.sessionId
43684
+ });
43685
+ sent += 1;
43686
+ continue;
43687
+ }
43688
+ this.#inFlight.set(envelope2.delivery.eventId, { byteLength });
43689
+ this.#inFlightBytes += byteLength;
43690
+ sent += 1;
43691
+ }
43692
+ if (sent === 0) {
43693
+ return;
43694
+ }
43695
+ }
43293
43696
  };
43294
- #reportDrop = () => {
43295
- if (this.#reportedDrop) {
43697
+ #removeInFlight = (eventId) => {
43698
+ const inFlight = this.#inFlight.get(eventId);
43699
+ if (inFlight === void 0) {
43296
43700
  return;
43297
43701
  }
43298
- this.#reportedDrop = true;
43299
- console.warn("Mobius daemon dropped live Session events while the Relay was unavailable");
43702
+ this.#inFlight.delete(eventId);
43703
+ this.#inFlightBytes -= inFlight.byteLength;
43704
+ };
43705
+ #send = (encoded) => {
43706
+ if (this.#connection.kind !== "connected" || this.#connection.socket.readyState !== WebSocket9.OPEN) {
43707
+ return false;
43708
+ }
43709
+ try {
43710
+ this.#connection.socket.send(encoded);
43711
+ return true;
43712
+ } catch {
43713
+ return false;
43714
+ }
43300
43715
  };
43301
43716
  };
43302
43717
 
@@ -43357,7 +43772,7 @@ async function installDetectedAgentIntegrations(config2, installations, signal)
43357
43772
  {
43358
43773
  agentId: agent.id,
43359
43774
  kind: AGENT_INSTALL_REQUEST_KIND,
43360
- requestId: `automatic-integration-${randomUUID7()}`
43775
+ requestId: `automatic-integration-${randomUUID8()}`
43361
43776
  },
43362
43777
  signal
43363
43778
  );
@@ -43379,7 +43794,11 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
43379
43794
  join18(stateDirectory, "sessions", `${createHash6("sha256").update(config2.channelId).digest("hex")}.sqlite`)
43380
43795
  );
43381
43796
  const operations = new DaemonOperationState();
43382
- const publisher = new DaemonSessionEventPublisher();
43797
+ const channelHash = createHash6("sha256").update(config2.channelId).digest("hex");
43798
+ const publisher = new DaemonSessionEventPublisher(
43799
+ new SessionEventOutbox(join18(stateDirectory, "events", `${channelHash}.sqlite`)),
43800
+ timer
43801
+ );
43383
43802
  const activeTurns = new DaemonTurnRegistry(operations);
43384
43803
  const recordSessionEvent = (sessionId, event) => {
43385
43804
  publisher.publish(sessionId, activeTurns.recordEvent(event));