@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.
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.149",
3384
+ version: "0.0.150",
3385
3385
  description: "Mobius CLI for pairing and running local AI agent services.",
3386
3386
  type: "module",
3387
3387
  bin: {
@@ -29330,9 +29330,17 @@ function decodeSessionQueuedEvent(encoded) {
29330
29330
  }
29331
29331
  function decodeRuntimeLivenessReadyEvent(encoded) {
29332
29332
  const value = parseJson2(encoded);
29333
- assertExactKeys(value, ["kind"]);
29334
29333
  assertKind(value, RUNTIME_LIVENESS_READY_KIND);
29335
- return { kind: RUNTIME_LIVENESS_READY_KIND };
29334
+ if (value["sessionEventDelivery"] === void 0) {
29335
+ assertExactKeys(value, ["kind"]);
29336
+ return { kind: RUNTIME_LIVENESS_READY_KIND };
29337
+ }
29338
+ assertExactKeys(value, ["kind", "sessionEventDelivery"]);
29339
+ const sessionEventDelivery = readString2(value, "sessionEventDelivery", 32);
29340
+ if (sessionEventDelivery !== "reliable-v1") {
29341
+ throw new ProtocolMessageError("Unsupported Session event delivery mode");
29342
+ }
29343
+ return { kind: RUNTIME_LIVENESS_READY_KIND, sessionEventDelivery };
29336
29344
  }
29337
29345
  function decodeRuntimeHeartbeatEvent(encoded) {
29338
29346
  const value = parseJson2(encoded);
@@ -29782,12 +29790,59 @@ var encodeNativeSessionCancel = (value) => JSON.stringify(nativeSessionCancelSch
29782
29790
 
29783
29791
  // ../protocol/src/session-events.ts
29784
29792
  var SESSION_EVENT_KIND = "session.event";
29793
+ var SESSION_EVENT_ACK_KIND = "session.event.ack";
29794
+ var SESSION_EVENT_DEFERRED_KIND = "session.event.deferred";
29795
+ var SESSION_EVENT_REJECTED_KIND = "session.event.rejected";
29796
+ var SessionEventRejectionCode = {
29797
+ IdempotencyConflict: "idempotency_conflict",
29798
+ InvalidEvent: "invalid_event",
29799
+ RecordingRejected: "recording_rejected"
29800
+ };
29785
29801
  var MAX_RECORDED_EVENT_LENGTH = 8 * 1024 * 1024;
29786
- var eventEnvelopeSchema = external_exports.strictObject({
29802
+ var ephemeralEventEnvelopeSchema = external_exports.strictObject({
29787
29803
  kind: external_exports.literal(SESSION_EVENT_KIND),
29788
29804
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
29789
29805
  sessionId: identifierStringSchema
29790
29806
  }).readonly();
29807
+ var reliableDeliverySchema = external_exports.strictObject({
29808
+ eventId: identifierStringSchema,
29809
+ kind: external_exports.literal("reliable"),
29810
+ sourceSeq: external_exports.string().regex(/^(?:0|[1-9][0-9]*)$/u).max(40),
29811
+ streamId: identifierStringSchema
29812
+ }).readonly();
29813
+ var reliableEventEnvelopeSchema = external_exports.strictObject({
29814
+ delivery: reliableDeliverySchema,
29815
+ kind: external_exports.literal(SESSION_EVENT_KIND),
29816
+ payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
29817
+ sessionId: identifierStringSchema
29818
+ }).readonly();
29819
+ var eventEnvelopeSchema = external_exports.union([ephemeralEventEnvelopeSchema, reliableEventEnvelopeSchema]).readonly();
29820
+ var acknowledgementOutcomeSchema = external_exports.discriminatedUnion("kind", [
29821
+ external_exports.strictObject({ kind: external_exports.literal("relayed") }).readonly(),
29822
+ external_exports.strictObject({
29823
+ kind: external_exports.literal("recorded"),
29824
+ providerId: identifierStringSchema,
29825
+ recordingEpoch: identifierStringSchema
29826
+ }).readonly()
29827
+ ]);
29828
+ var eventAcknowledgementSchema = external_exports.strictObject({
29829
+ eventId: identifierStringSchema,
29830
+ kind: external_exports.literal(SESSION_EVENT_ACK_KIND),
29831
+ outcome: acknowledgementOutcomeSchema,
29832
+ sessionId: identifierStringSchema
29833
+ }).readonly();
29834
+ var eventDeferredSchema = external_exports.strictObject({
29835
+ eventId: identifierStringSchema,
29836
+ kind: external_exports.literal(SESSION_EVENT_DEFERRED_KIND),
29837
+ retryAfterMilliseconds: external_exports.number().int().min(100).max(6e4),
29838
+ sessionId: identifierStringSchema
29839
+ }).readonly();
29840
+ var eventRejectedSchema = external_exports.strictObject({
29841
+ code: external_exports.enum(SessionEventRejectionCode),
29842
+ eventId: identifierStringSchema,
29843
+ kind: external_exports.literal(SESSION_EVENT_REJECTED_KIND),
29844
+ sessionId: identifierStringSchema
29845
+ }).readonly();
29791
29846
  var SESSION_QUEUE_CHANGED_KIND = "session.queue.changed";
29792
29847
  var sessionQueueChangedSchema = external_exports.strictObject({ kind: external_exports.literal(SESSION_QUEUE_CHANGED_KIND), sessionId: identifierStringSchema }).readonly();
29793
29848
  function decode5(encoded, schema) {
@@ -29816,6 +29871,12 @@ var encode4 = (message, schema) => {
29816
29871
  };
29817
29872
  var decodeSessionEventEnvelope = (encoded) => decode5(encoded, eventEnvelopeSchema);
29818
29873
  var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
29874
+ var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
29875
+ var encodeSessionEventAcknowledgement = (message) => encode4(message, eventAcknowledgementSchema);
29876
+ var decodeSessionEventDeferred = (encoded) => decode5(encoded, eventDeferredSchema);
29877
+ var encodeSessionEventDeferred = (message) => encode4(message, eventDeferredSchema);
29878
+ var decodeSessionEventRejected = (encoded) => decode5(encoded, eventRejectedSchema);
29879
+ var encodeSessionEventRejected = (message) => encode4(message, eventRejectedSchema);
29819
29880
  function decodeRecordedSessionEvent(encoded) {
29820
29881
  const kind = decodeMessageKind(encoded);
29821
29882
  switch (kind) {
@@ -29987,6 +30048,21 @@ var serviceToDaemonSignatures = {
29987
30048
  encodeSessionElicitationRespondCommand,
29988
30049
  decodeSessionElicitationRespondCommand
29989
30050
  ),
30051
+ [SESSION_EVENT_ACK_KIND]: defineMessageSignature(
30052
+ SESSION_EVENT_ACK_KIND,
30053
+ encodeSessionEventAcknowledgement,
30054
+ decodeSessionEventAcknowledgement
30055
+ ),
30056
+ [SESSION_EVENT_DEFERRED_KIND]: defineMessageSignature(
30057
+ SESSION_EVENT_DEFERRED_KIND,
30058
+ encodeSessionEventDeferred,
30059
+ decodeSessionEventDeferred
30060
+ ),
30061
+ [SESSION_EVENT_REJECTED_KIND]: defineMessageSignature(
30062
+ SESSION_EVENT_REJECTED_KIND,
30063
+ encodeSessionEventRejected,
30064
+ decodeSessionEventRejected
30065
+ ),
29990
30066
  [SESSION_LIVE_HISTORY_LOAD_KIND]: defineMessageSignature(
29991
30067
  SESSION_LIVE_HISTORY_LOAD_KIND,
29992
30068
  encodeSessionLiveHistoryLoad,
@@ -31045,7 +31121,7 @@ var AuditStore = class {
31045
31121
  };
31046
31122
 
31047
31123
  // src/run-daemon.ts
31048
- import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
31124
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
31049
31125
  import { join as join18 } from "node:path";
31050
31126
  import { styleText } from "node:util";
31051
31127
 
@@ -46594,6 +46670,7 @@ async function runRelayConnection({
46594
46670
  }) {
46595
46671
  const relayUrl = new URL(config2.relayUrl);
46596
46672
  relayUrl.searchParams.set("liveness", "1");
46673
+ relayUrl.searchParams.set("sessionEventDelivery", "reliable-v1");
46597
46674
  const socket = await connectRelay(relayUrl, signal);
46598
46675
  diagnostics.connectionOpened(connectionEpoch);
46599
46676
  const connectionController = new AbortController();
@@ -46713,6 +46790,10 @@ async function runRelayConnection({
46713
46790
  const message = decodeServiceToDaemonMessage(encoded);
46714
46791
  const kind = message.kind;
46715
46792
  if (kind === RUNTIME_LIVENESS_READY_KIND) {
46793
+ publisher.connect(
46794
+ socket,
46795
+ message.sessionEventDelivery === "reliable-v1" ? "reliable" : "ephemeral"
46796
+ );
46716
46797
  connectionLiveness.start();
46717
46798
  return;
46718
46799
  }
@@ -46720,6 +46801,18 @@ async function runRelayConnection({
46720
46801
  connectionLiveness.acknowledge(message);
46721
46802
  return;
46722
46803
  }
46804
+ if (kind === SESSION_EVENT_ACK_KIND) {
46805
+ publisher.acknowledge(message);
46806
+ return;
46807
+ }
46808
+ if (kind === SESSION_EVENT_DEFERRED_KIND) {
46809
+ publisher.defer(message);
46810
+ return;
46811
+ }
46812
+ if (kind === SESSION_EVENT_REJECTED_KIND) {
46813
+ publisher.quarantine(message);
46814
+ return;
46815
+ }
46723
46816
  commands.handle(message);
46724
46817
  } catch (error62) {
46725
46818
  console.error("Mobius daemon rejected a message", error62);
@@ -46734,7 +46827,6 @@ async function runRelayConnection({
46734
46827
  socket.addEventListener("close", handleClose);
46735
46828
  socket.addEventListener("error", handleError);
46736
46829
  socket.addEventListener("message", handleMessage);
46737
- publisher.connect(socket);
46738
46830
  if (signal.aborted) {
46739
46831
  stop();
46740
46832
  }
@@ -46745,54 +46837,377 @@ async function runRelayConnection({
46745
46837
  }
46746
46838
  }
46747
46839
 
46840
+ // src/relay-connection/session-event-outbox.ts
46841
+ import { randomUUID as randomUUID7 } from "node:crypto";
46842
+ import { mkdirSync as mkdirSync3 } from "node:fs";
46843
+ import { dirname as dirname6 } from "node:path";
46844
+ import { DatabaseSync } from "node:sqlite";
46845
+ var DEFAULT_STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
46846
+ var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
46847
+ var MAX_DEAD_LETTERS = 1024;
46848
+ var DEFAULT_LIMITS = {
46849
+ criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
46850
+ standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
46851
+ };
46852
+ var SessionEventOutboxCapacityError = class extends Error {
46853
+ code = "session_event_outbox_full";
46854
+ constructor() {
46855
+ super("The persistent Session event outbox is full");
46856
+ this.name = "SessionEventOutboxCapacityError";
46857
+ }
46858
+ };
46859
+ function rowNumber(row, field) {
46860
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
46861
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
46862
+ throw new Error(`SQLite returned an invalid ${field} value`);
46863
+ }
46864
+ return value;
46865
+ }
46866
+ function rowString(row, field) {
46867
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
46868
+ if (typeof value !== "string" || value.length === 0) {
46869
+ throw new Error(`SQLite returned an invalid ${field} value`);
46870
+ }
46871
+ return value;
46872
+ }
46873
+ function validateLimits(limits) {
46874
+ if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
46875
+ throw new Error("Session event outbox limits are invalid");
46876
+ }
46877
+ }
46878
+ var SessionEventOutbox = class {
46879
+ #database;
46880
+ #limits;
46881
+ constructor(path3, limits = DEFAULT_LIMITS) {
46882
+ validateLimits(limits);
46883
+ mkdirSync3(dirname6(path3), { mode: 448, recursive: true });
46884
+ this.#limits = limits;
46885
+ this.#database = new DatabaseSync(path3);
46886
+ this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
46887
+ this.#database.exec(`
46888
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
46889
+ ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
46890
+ event_id TEXT NOT NULL UNIQUE,
46891
+ session_id TEXT NOT NULL,
46892
+ source_seq INTEGER NOT NULL UNIQUE,
46893
+ byte_length INTEGER NOT NULL,
46894
+ envelope_json TEXT NOT NULL
46895
+ ) STRICT;
46896
+ CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
46897
+ ON relay_session_event_outbox(source_seq);
46898
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
46899
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
46900
+ stream_id TEXT NOT NULL,
46901
+ next_source_seq INTEGER NOT NULL,
46902
+ total_bytes INTEGER NOT NULL
46903
+ ) STRICT;
46904
+ CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
46905
+ ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
46906
+ event_id TEXT NOT NULL UNIQUE,
46907
+ session_id TEXT NOT NULL,
46908
+ rejection_code TEXT NOT NULL,
46909
+ envelope_json TEXT NOT NULL
46910
+ ) STRICT;
46911
+ `);
46912
+ this.#database.prepare(
46913
+ `INSERT OR IGNORE INTO relay_session_event_outbox_state (
46914
+ singleton, stream_id, next_source_seq, total_bytes
46915
+ ) VALUES (1, ?, 1, 0)`
46916
+ ).run(randomUUID7());
46917
+ }
46918
+ acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
46919
+ close = () => {
46920
+ this.#database.close();
46921
+ };
46922
+ enqueue = (sessionId, payload, critical) => {
46923
+ this.#database.exec("BEGIN IMMEDIATE");
46924
+ try {
46925
+ const state = this.#database.prepare(
46926
+ `SELECT stream_id, next_source_seq, total_bytes
46927
+ FROM relay_session_event_outbox_state
46928
+ WHERE singleton = 1`
46929
+ ).get();
46930
+ const sourceSeq = rowNumber(state, "next_source_seq");
46931
+ if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + 1)) {
46932
+ throw new Error("The Session event source sequence is exhausted");
46933
+ }
46934
+ const envelope2 = {
46935
+ delivery: {
46936
+ eventId: randomUUID7(),
46937
+ kind: "reliable",
46938
+ sourceSeq: sourceSeq.toString(),
46939
+ streamId: rowString(state, "stream_id")
46940
+ },
46941
+ kind: SESSION_EVENT_KIND,
46942
+ payload,
46943
+ sessionId
46944
+ };
46945
+ const encoded = encodeSessionEventEnvelope(envelope2);
46946
+ const byteLength = Buffer.byteLength(encoded, "utf8");
46947
+ const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
46948
+ if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
46949
+ throw new SessionEventOutboxCapacityError();
46950
+ }
46951
+ this.#database.prepare(
46952
+ `INSERT INTO relay_session_event_outbox (
46953
+ event_id, session_id, source_seq, byte_length, envelope_json
46954
+ ) VALUES (?, ?, ?, ?, ?)`
46955
+ ).run(envelope2.delivery.eventId, sessionId, sourceSeq, byteLength, encoded);
46956
+ this.#database.prepare(
46957
+ `UPDATE relay_session_event_outbox_state
46958
+ SET next_source_seq = next_source_seq + 1,
46959
+ total_bytes = total_bytes + ?
46960
+ WHERE singleton = 1`
46961
+ ).run(byteLength);
46962
+ this.#database.exec("COMMIT");
46963
+ return envelope2;
46964
+ } catch (error62) {
46965
+ this.#database.exec("ROLLBACK");
46966
+ throw error62;
46967
+ }
46968
+ };
46969
+ pending = (limit, excludedEventIds) => {
46970
+ if (!Number.isSafeInteger(limit) || limit < 1) {
46971
+ throw new Error("Session event replay limit must be a positive integer");
46972
+ }
46973
+ const exclusion = excludedEventIds.length === 0 ? "" : `WHERE event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`;
46974
+ return this.#database.prepare(
46975
+ `SELECT envelope_json
46976
+ FROM relay_session_event_outbox
46977
+ ${exclusion}
46978
+ ORDER BY source_seq
46979
+ LIMIT ?`
46980
+ ).all(...excludedEventIds, limit).map((row) => {
46981
+ const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
46982
+ if (!("delivery" in envelope2)) {
46983
+ throw new Error("SQLite returned an invalid reliable Session event envelope");
46984
+ }
46985
+ return envelope2;
46986
+ });
46987
+ };
46988
+ quarantine = (rejection) => {
46989
+ this.#database.exec("BEGIN IMMEDIATE");
46990
+ try {
46991
+ const stored = this.#database.prepare(
46992
+ `SELECT byte_length, envelope_json
46993
+ FROM relay_session_event_outbox
46994
+ WHERE event_id = ? AND session_id = ?`
46995
+ ).get(rejection.eventId, rejection.sessionId);
46996
+ if (stored === void 0) {
46997
+ this.#database.exec("COMMIT");
46998
+ return false;
46999
+ }
47000
+ this.#database.prepare(
47001
+ `INSERT INTO relay_session_event_dead_letters (
47002
+ event_id, session_id, rejection_code, envelope_json
47003
+ ) VALUES (?, ?, ?, ?)`
47004
+ ).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
47005
+ this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
47006
+ this.#database.prepare(
47007
+ `DELETE FROM relay_session_event_dead_letters
47008
+ WHERE ordinal NOT IN (
47009
+ SELECT ordinal FROM relay_session_event_dead_letters
47010
+ ORDER BY ordinal DESC LIMIT ?
47011
+ )`
47012
+ ).run(MAX_DEAD_LETTERS);
47013
+ this.#database.exec("COMMIT");
47014
+ return true;
47015
+ } catch (error62) {
47016
+ this.#database.exec("ROLLBACK");
47017
+ throw error62;
47018
+ }
47019
+ };
47020
+ #deleteStored = (eventId, sessionId, byteLength) => {
47021
+ const deletion = this.#database.prepare(
47022
+ `DELETE FROM relay_session_event_outbox
47023
+ WHERE event_id = ? AND session_id = ?`
47024
+ ).run(eventId, sessionId);
47025
+ if (deletion.changes !== 1) {
47026
+ throw new Error("The Session event could not be removed from the outbox");
47027
+ }
47028
+ const state = this.#database.prepare(
47029
+ `UPDATE relay_session_event_outbox_state
47030
+ SET total_bytes = total_bytes - ?
47031
+ WHERE singleton = 1 AND total_bytes >= ?`
47032
+ ).run(byteLength, byteLength);
47033
+ if (state.changes !== 1) {
47034
+ throw new Error("The Session event outbox capacity state is inconsistent");
47035
+ }
47036
+ };
47037
+ #remove = (eventId, sessionId) => {
47038
+ this.#database.exec("BEGIN IMMEDIATE");
47039
+ try {
47040
+ const stored = this.#database.prepare(
47041
+ `SELECT byte_length
47042
+ FROM relay_session_event_outbox
47043
+ WHERE event_id = ? AND session_id = ?`
47044
+ ).get(eventId, sessionId);
47045
+ if (stored === void 0) {
47046
+ this.#database.exec("COMMIT");
47047
+ return false;
47048
+ }
47049
+ this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
47050
+ this.#database.exec("COMMIT");
47051
+ return true;
47052
+ } catch (error62) {
47053
+ this.#database.exec("ROLLBACK");
47054
+ throw error62;
47055
+ }
47056
+ };
47057
+ };
47058
+
46748
47059
  // src/relay-connection/session-event-publisher.ts
46749
47060
  import { WebSocket as WebSocket9 } from "undici";
46750
- var MAX_LIVE_EVENT_BUFFERED_BYTES = 16 * 1024 * 1024;
47061
+ var SESSION_EVENT_REPLAY_WINDOW = 16;
47062
+ var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
47063
+ function reservesSessionEventCapacity(event) {
47064
+ if (event.kind === SESSION_COMPLETED_KIND || event.kind === SESSION_FAILED_KIND) {
47065
+ return true;
47066
+ }
47067
+ return event.kind === AGENT_ACTIVITY_KIND && (event.activityType === "progress" || event.activityType === "reasoning") && (event.status === "completed" || event.status === "failed");
47068
+ }
46751
47069
  var DaemonSessionEventPublisher = class {
46752
- #socket = null;
46753
- #reportedDrop = false;
46754
- connect = (socket) => {
46755
- this.#socket = socket;
46756
- this.#reportedDrop = false;
47070
+ #inFlight = /* @__PURE__ */ new Map();
47071
+ #outbox;
47072
+ #reportedCapacityKinds = /* @__PURE__ */ new Set();
47073
+ #timer;
47074
+ #connection = { kind: "disconnected" };
47075
+ #deferredReplay = null;
47076
+ #inFlightBytes = 0;
47077
+ constructor(outbox, timer) {
47078
+ this.#outbox = outbox;
47079
+ this.#timer = timer;
47080
+ }
47081
+ acknowledge = (acknowledgement) => {
47082
+ if (!this.#outbox.acknowledge(acknowledgement)) {
47083
+ return;
47084
+ }
47085
+ this.#removeInFlight(acknowledgement.eventId);
47086
+ this.#publishNextBatch();
47087
+ };
47088
+ close = () => {
47089
+ this.#deferredReplay?.cancel();
47090
+ this.#deferredReplay = null;
47091
+ this.#connection = { kind: "disconnected" };
47092
+ this.#clearInFlight();
47093
+ this.#outbox.close();
47094
+ };
47095
+ connect = (socket, delivery) => {
47096
+ this.#connection = { delivery, kind: "connected", socket };
47097
+ this.#clearInFlight();
47098
+ this.#publishNextBatch();
47099
+ };
47100
+ defer = (deferred) => {
47101
+ if (!this.#inFlight.has(deferred.eventId)) {
47102
+ return;
47103
+ }
47104
+ this.#clearInFlight();
47105
+ if (this.#deferredReplay !== null) {
47106
+ return;
47107
+ }
47108
+ this.#deferredReplay = this.#timer.schedule(() => {
47109
+ this.#deferredReplay = null;
47110
+ this.#publishNextBatch();
47111
+ }, deferred.retryAfterMilliseconds);
46757
47112
  };
46758
47113
  disconnect = (socket) => {
46759
- if (this.#socket === socket) {
46760
- this.#socket = null;
47114
+ if (this.#connection.kind !== "connected" || this.#connection.socket !== socket) {
47115
+ return;
46761
47116
  }
47117
+ this.#connection = { kind: "disconnected" };
47118
+ this.#clearInFlight();
46762
47119
  };
46763
47120
  publish = (sessionId, event) => {
46764
- const socket = this.#socket;
46765
- if (socket === null || socket.readyState !== WebSocket9.OPEN) {
46766
- this.#reportDrop();
47121
+ try {
47122
+ this.#outbox.enqueue(sessionId, encodeRecordedSessionEvent(event), reservesSessionEventCapacity(event));
47123
+ } catch (error62) {
47124
+ if (!(error62 instanceof SessionEventOutboxCapacityError)) {
47125
+ throw error62;
47126
+ }
47127
+ if (!this.#reportedCapacityKinds.has(event.kind)) {
47128
+ this.#reportedCapacityKinds.add(event.kind);
47129
+ console.warn(
47130
+ `Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
47131
+ );
47132
+ }
46767
47133
  return;
46768
47134
  }
46769
- if (socket.bufferedAmount >= MAX_LIVE_EVENT_BUFFERED_BYTES) {
46770
- this.#socket = null;
46771
- socket.close(1013, "Live event backpressure");
46772
- this.#reportDrop();
47135
+ this.#publishNextBatch();
47136
+ };
47137
+ quarantine = (rejection) => {
47138
+ if (!this.#outbox.quarantine(rejection)) {
46773
47139
  return;
46774
47140
  }
46775
- try {
46776
- socket.send(
46777
- encodeDaemonToServiceMessage({
46778
- kind: SESSION_EVENT_KIND,
46779
- payload: encodeRecordedSessionEvent(event),
46780
- sessionId
46781
- })
46782
- );
46783
- } catch {
46784
- this.#reportDrop();
46785
- }
47141
+ this.#removeInFlight(rejection.eventId);
47142
+ console.error(`Mobius Relay rejected Session event ${rejection.eventId} with code ${rejection.code}`);
47143
+ this.#publishNextBatch();
46786
47144
  };
46787
- close = () => {
46788
- this.#socket = null;
47145
+ #clearInFlight = () => {
47146
+ this.#inFlight.clear();
47147
+ this.#inFlightBytes = 0;
47148
+ };
47149
+ #publishNextBatch = () => {
47150
+ if (this.#deferredReplay !== null || this.#connection.kind !== "connected") {
47151
+ return;
47152
+ }
47153
+ while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
47154
+ const batch = this.#outbox.pending(SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size, [
47155
+ ...this.#inFlight.keys()
47156
+ ]);
47157
+ let sent = 0;
47158
+ for (const envelope2 of batch) {
47159
+ const encoded = encodeDaemonToServiceMessage(
47160
+ this.#connection.delivery === "reliable" ? envelope2 : {
47161
+ kind: envelope2.kind,
47162
+ payload: envelope2.payload,
47163
+ sessionId: envelope2.sessionId
47164
+ }
47165
+ );
47166
+ const byteLength = Buffer.byteLength(encoded, "utf8");
47167
+ if (this.#inFlightBytes + byteLength > SESSION_EVENT_REPLAY_BYTE_LIMIT) {
47168
+ return;
47169
+ }
47170
+ if (!this.#send(encoded)) {
47171
+ this.#clearInFlight();
47172
+ return;
47173
+ }
47174
+ if (this.#connection.delivery === "ephemeral") {
47175
+ this.#outbox.acknowledge({
47176
+ eventId: envelope2.delivery.eventId,
47177
+ kind: "session.event.ack",
47178
+ outcome: { kind: "relayed" },
47179
+ sessionId: envelope2.sessionId
47180
+ });
47181
+ sent += 1;
47182
+ continue;
47183
+ }
47184
+ this.#inFlight.set(envelope2.delivery.eventId, { byteLength });
47185
+ this.#inFlightBytes += byteLength;
47186
+ sent += 1;
47187
+ }
47188
+ if (sent === 0) {
47189
+ return;
47190
+ }
47191
+ }
46789
47192
  };
46790
- #reportDrop = () => {
46791
- if (this.#reportedDrop) {
47193
+ #removeInFlight = (eventId) => {
47194
+ const inFlight = this.#inFlight.get(eventId);
47195
+ if (inFlight === void 0) {
46792
47196
  return;
46793
47197
  }
46794
- this.#reportedDrop = true;
46795
- console.warn("Mobius daemon dropped live Session events while the Relay was unavailable");
47198
+ this.#inFlight.delete(eventId);
47199
+ this.#inFlightBytes -= inFlight.byteLength;
47200
+ };
47201
+ #send = (encoded) => {
47202
+ if (this.#connection.kind !== "connected" || this.#connection.socket.readyState !== WebSocket9.OPEN) {
47203
+ return false;
47204
+ }
47205
+ try {
47206
+ this.#connection.socket.send(encoded);
47207
+ return true;
47208
+ } catch {
47209
+ return false;
47210
+ }
46796
47211
  };
46797
47212
  };
46798
47213
 
@@ -46853,7 +47268,7 @@ async function installDetectedAgentIntegrations(config2, installations, signal)
46853
47268
  {
46854
47269
  agentId: agent.id,
46855
47270
  kind: AGENT_INSTALL_REQUEST_KIND,
46856
- requestId: `automatic-integration-${randomUUID7()}`
47271
+ requestId: `automatic-integration-${randomUUID8()}`
46857
47272
  },
46858
47273
  signal
46859
47274
  );
@@ -46875,7 +47290,11 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
46875
47290
  join18(stateDirectory, "sessions", `${createHash6("sha256").update(config2.channelId).digest("hex")}.sqlite`)
46876
47291
  );
46877
47292
  const operations = new DaemonOperationState();
46878
- const publisher = new DaemonSessionEventPublisher();
47293
+ const channelHash = createHash6("sha256").update(config2.channelId).digest("hex");
47294
+ const publisher = new DaemonSessionEventPublisher(
47295
+ new SessionEventOutbox(join18(stateDirectory, "events", `${channelHash}.sqlite`)),
47296
+ timer
47297
+ );
46879
47298
  const activeTurns = new DaemonTurnRegistry(operations);
46880
47299
  const recordSessionEvent = (sessionId, event) => {
46881
47300
  publisher.publish(sessionId, activeTurns.recordEvent(event));