@moxt-ai/mobius 0.0.147 → 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.
Files changed (41) hide show
  1. package/dist/cli.js +731 -197
  2. package/dist/cli.js.map +4 -4
  3. package/dist/daemon-active-turn.d.ts +15 -2
  4. package/dist/daemon-active-turn.d.ts.map +1 -1
  5. package/dist/daemon-active-turn.js +63 -25
  6. package/dist/daemon-active-turn.js.map +1 -1
  7. package/dist/daemon-command-handler.js +3 -3
  8. package/dist/daemon-history-commands.d.ts +4 -7
  9. package/dist/daemon-history-commands.d.ts.map +1 -1
  10. package/dist/daemon-history-commands.js.map +1 -1
  11. package/dist/daemon-prompt-executor.d.ts +1 -1
  12. package/dist/daemon-prompt-executor.d.ts.map +1 -1
  13. package/dist/daemon-prompt-executor.js +1 -3
  14. package/dist/daemon-prompt-executor.js.map +1 -1
  15. package/dist/daemon-turn-registry.d.ts +18 -56
  16. package/dist/daemon-turn-registry.d.ts.map +1 -1
  17. package/dist/daemon-turn-registry.js +40 -1
  18. package/dist/daemon-turn-registry.js.map +1 -1
  19. package/dist/index.js +730 -196
  20. package/dist/index.js.map +4 -4
  21. package/dist/native-sessions/live-history-page.d.ts +4 -7
  22. package/dist/native-sessions/live-history-page.d.ts.map +1 -1
  23. package/dist/native-sessions/live-history-page.js +163 -107
  24. package/dist/native-sessions/live-history-page.js.map +1 -1
  25. package/dist/relay-connection/run-relay-connection.d.ts.map +1 -1
  26. package/dist/relay-connection/run-relay-connection.js +15 -2
  27. package/dist/relay-connection/run-relay-connection.js.map +1 -1
  28. package/dist/relay-connection/session-event-outbox.d.ts +19 -0
  29. package/dist/relay-connection/session-event-outbox.d.ts.map +1 -0
  30. package/dist/relay-connection/session-event-outbox.js +228 -0
  31. package/dist/relay-connection/session-event-outbox.js.map +1 -0
  32. package/dist/relay-connection/session-event-publisher.d.ts +10 -4
  33. package/dist/relay-connection/session-event-publisher.d.ts.map +1 -1
  34. package/dist/relay-connection/session-event-publisher.js +140 -32
  35. package/dist/relay-connection/session-event-publisher.js.map +1 -1
  36. package/dist/run-daemon.d.ts.map +1 -1
  37. package/dist/run-daemon.js +3 -1
  38. package/dist/run-daemon.js.map +1 -1
  39. package/dist/runtime.js +736 -202
  40. package/dist/runtime.js.map +4 -4
  41. package/package.json +1 -1
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);
@@ -21060,18 +21068,21 @@ var activeTurnMessageSchema = external_exports.discriminatedUnion("role", [
21060
21068
  activeTurnAssistantMessageSchema
21061
21069
  ]);
21062
21070
  var activeTurnSnapshotSchema = external_exports.strictObject({
21063
- activities: external_exports.array(activeTurnActivitySchema).readonly(),
21064
21071
  commandId: identifierStringSchema,
21065
- content: external_exports.array(displayContentSchema).readonly(),
21066
- prompt: external_exports.string().max(1e5),
21067
- text: external_exports.string(),
21068
- truncated: external_exports.boolean(),
21069
21072
  epoch: identifierStringSchema,
21073
+ hasOlder: external_exports.boolean(),
21070
21074
  interaction: external_exports.discriminatedUnion("kind", [sessionPermissionRequestedEventSchema, sessionElicitationRequestedEventSchema]).nullable(),
21071
21075
  messages: external_exports.array(activeTurnMessageSchema).max(SESSION_HISTORY_MESSAGE_LIMIT).readonly(),
21072
21076
  sequence: external_exports.number().int().nonnegative(),
21073
- state: external_exports.literal("running")
21074
- }).readonly();
21077
+ state: external_exports.literal("running"),
21078
+ truncated: external_exports.boolean()
21079
+ }).readonly().refine(
21080
+ (snapshot) => snapshot.messages.reduce(
21081
+ (total, message) => total + message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0),
21082
+ 0
21083
+ ) <= SESSION_HISTORY_CHARACTER_LIMIT,
21084
+ "Active Turn snapshot content is too large"
21085
+ );
21075
21086
  var identity = {
21076
21087
  ...envelope,
21077
21088
  kind: external_exports.literal(NATIVE_SESSION_REQUEST_KIND),
@@ -21215,27 +21226,61 @@ var encodeNativeSessionCancel = (value) => JSON.stringify(nativeSessionCancelSch
21215
21226
 
21216
21227
  // ../protocol/src/session-events.ts
21217
21228
  var SESSION_EVENT_KIND = "session.event";
21218
- var SESSION_EVENT_COMMITTED_KIND = "session.event.committed";
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
+ };
21219
21237
  var MAX_RECORDED_EVENT_LENGTH = 8 * 1024 * 1024;
21220
- var eventEnvelopeSchema = external_exports.strictObject({
21238
+ var ephemeralEventEnvelopeSchema = external_exports.strictObject({
21221
21239
  kind: external_exports.literal(SESSION_EVENT_KIND),
21222
21240
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
21223
21241
  sessionId: identifierStringSchema
21224
21242
  }).readonly();
21225
- var committedEventSchema = external_exports.strictObject({
21226
- kind: external_exports.literal(SESSION_EVENT_COMMITTED_KIND),
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),
21227
21252
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
21228
21253
  sessionId: identifierStringSchema
21229
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();
21230
21282
  var SESSION_QUEUE_CHANGED_KIND = "session.queue.changed";
21231
21283
  var sessionQueueChangedSchema = external_exports.strictObject({ kind: external_exports.literal(SESSION_QUEUE_CHANGED_KIND), sessionId: identifierStringSchema }).readonly();
21232
- var SESSION_TURN_UPDATED_KIND = "session.turn.updated";
21233
- var sessionTurnUpdatedSchema = external_exports.strictObject({
21234
- kind: external_exports.literal(SESSION_TURN_UPDATED_KIND),
21235
- sessionId: identifierStringSchema,
21236
- turn: activeTurnSnapshotSchema
21237
- }).readonly();
21238
- var decodeSessionTurnUpdatedEvent = (encoded) => decode5(encoded, sessionTurnUpdatedSchema);
21239
21284
  function decode5(encoded, schema) {
21240
21285
  let value;
21241
21286
  try {
@@ -21262,11 +21307,15 @@ var encode4 = (message, schema) => {
21262
21307
  };
21263
21308
  var decodeSessionEventEnvelope = (encoded) => decode5(encoded, eventEnvelopeSchema);
21264
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);
21265
21316
  function decodeRecordedSessionEvent(encoded) {
21266
21317
  const kind = decodeMessageKind(encoded);
21267
21318
  switch (kind) {
21268
- case SESSION_TURN_UPDATED_KIND:
21269
- return decodeSessionTurnUpdatedEvent(encoded);
21270
21319
  case SESSION_QUEUE_CHANGED_KIND:
21271
21320
  return decode5(encoded, sessionQueueChangedSchema);
21272
21321
  case AGENT_ACTIVITY_KIND:
@@ -21435,6 +21484,21 @@ var serviceToDaemonSignatures = {
21435
21484
  encodeSessionElicitationRespondCommand,
21436
21485
  decodeSessionElicitationRespondCommand
21437
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
+ ),
21438
21502
  [SESSION_LIVE_HISTORY_LOAD_KIND]: defineMessageSignature(
21439
21503
  SESSION_LIVE_HISTORY_LOAD_KIND,
21440
21504
  encodeSessionLiveHistoryLoad,
@@ -37527,7 +37591,7 @@ function isRuntimeRelayRevocation(closeCode, closeReason) {
37527
37591
  }
37528
37592
 
37529
37593
  // src/run-daemon.ts
37530
- import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
37594
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
37531
37595
  import { join as join18 } from "node:path";
37532
37596
  import { styleText } from "node:util";
37533
37597
 
@@ -38731,6 +38795,28 @@ var DaemonInstallationObserver = class {
38731
38795
 
38732
38796
  // src/daemon-active-turn.ts
38733
38797
  import { randomUUID as randomUUID2 } from "node:crypto";
38798
+ function activeMessageCharacters(message) {
38799
+ return message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0);
38800
+ }
38801
+ var ACTIVE_TURN_TEXT_CHUNK_LIMIT = Math.floor(SESSION_HISTORY_CHARACTER_LIMIT / 2);
38802
+ function boundedRecoveryMessages(messages) {
38803
+ const selected = [];
38804
+ let remainingCharacters = SESSION_HISTORY_CHARACTER_LIMIT;
38805
+ for (let index = messages.length - 1; index >= 0 && selected.length < SESSION_HISTORY_MESSAGE_LIMIT; index -= 1) {
38806
+ const message = messages[index];
38807
+ if (message === void 0) {
38808
+ break;
38809
+ }
38810
+ const characters = activeMessageCharacters(message);
38811
+ if (characters <= remainingCharacters) {
38812
+ selected.unshift(message);
38813
+ remainingCharacters -= characters;
38814
+ continue;
38815
+ }
38816
+ break;
38817
+ }
38818
+ return { hasOlder: selected.length !== messages.length, messages: selected };
38819
+ }
38734
38820
  var ActiveDaemonTurn = class {
38735
38821
  attemptId;
38736
38822
  command;
@@ -38738,9 +38824,7 @@ var ActiveDaemonTurn = class {
38738
38824
  #finished = Promise.withResolvers();
38739
38825
  #cancellationRequested = false;
38740
38826
  #dispatch;
38741
- #text = "";
38742
38827
  #activities = /* @__PURE__ */ new Map();
38743
- #content = /* @__PURE__ */ new Map();
38744
38828
  #timeline;
38745
38829
  #sequence = 0;
38746
38830
  #interaction = null;
@@ -38787,9 +38871,17 @@ var ActiveDaemonTurn = class {
38787
38871
  effectiveDisposition = () => this.#dispatch.kind === "promoted" ? SessionPromptDisposition.Steer : this.command.disposition;
38788
38872
  recordEvent = (event) => {
38789
38873
  if (event.kind === AGENT_MESSAGE_DELTA_KIND) {
38790
- this.#text += event.text;
38874
+ const sequence = this.advance();
38791
38875
  this.appendMessageText(event.messageId, event.text);
38792
- return this.updated();
38876
+ return {
38877
+ commandId: this.command.commandId,
38878
+ epoch: this.#epoch,
38879
+ kind: AGENT_MESSAGE_DELTA_KIND,
38880
+ messageId: event.messageId,
38881
+ sequence,
38882
+ sessionId: this.command.sessionId,
38883
+ text: event.text
38884
+ };
38793
38885
  }
38794
38886
  if (event.sessionId !== this.command.sessionId || "commandId" in event && event.commandId !== this.command.commandId) {
38795
38887
  return event;
@@ -38836,7 +38928,6 @@ var ActiveDaemonTurn = class {
38836
38928
  toolKind: previous?.toolKind ?? ""
38837
38929
  });
38838
38930
  } else if (event.kind === AGENT_CONTENT_KIND) {
38839
- this.#content.set(event.contentId, event.content);
38840
38931
  const target = this.#timeline.findLastIndex(
38841
38932
  (entry) => entry.kind === "message" && entry.sourceMessageId === event.messageId
38842
38933
  );
@@ -38862,7 +38953,7 @@ var ActiveDaemonTurn = class {
38862
38953
  this.#interaction = null;
38863
38954
  }
38864
38955
  if (event.kind === AGENT_ACTIVITY_KIND || event.kind === AGENT_ACTIVITY_NARRATION_KIND || event.kind === AGENT_CONTENT_KIND) {
38865
- return this.updated();
38956
+ this.advance();
38866
38957
  }
38867
38958
  return event;
38868
38959
  };
@@ -38871,7 +38962,7 @@ var ActiveDaemonTurn = class {
38871
38962
  while (remaining.length > 0) {
38872
38963
  const latest = this.#timeline.at(-1);
38873
38964
  if (latest?.kind === "message" && latest.sourceMessageId === messageId) {
38874
- const available = SESSION_HISTORY_CHARACTER_LIMIT - latest.text.length;
38965
+ const available = ACTIVE_TURN_TEXT_CHUNK_LIMIT - latest.text.length;
38875
38966
  if (available > 0) {
38876
38967
  this.#timeline = [
38877
38968
  ...this.#timeline.slice(0, -1),
@@ -38888,21 +38979,22 @@ var ActiveDaemonTurn = class {
38888
38979
  id: decodeIdentifier(randomUUID2()),
38889
38980
  kind: "message",
38890
38981
  sourceMessageId: messageId,
38891
- text: remaining.slice(0, SESSION_HISTORY_CHARACTER_LIMIT)
38982
+ text: remaining.slice(0, ACTIVE_TURN_TEXT_CHUNK_LIMIT)
38892
38983
  }
38893
38984
  ];
38894
- remaining = remaining.slice(SESSION_HISTORY_CHARACTER_LIMIT);
38985
+ remaining = remaining.slice(ACTIVE_TURN_TEXT_CHUNK_LIMIT);
38895
38986
  }
38896
38987
  }
38897
38988
  recordSteeringPrompt = (command) => {
38898
38989
  if (this.#dispatch.kind !== "running" || command.sessionId !== this.command.sessionId || command.targetCommandId !== this.command.commandId) {
38899
- return null;
38990
+ return false;
38900
38991
  }
38901
38992
  this.#timeline = [
38902
38993
  ...this.#timeline,
38903
38994
  { id: decodeIdentifier(command.commandId), images: command.images, kind: "user", text: command.prompt }
38904
38995
  ];
38905
- return this.updated();
38996
+ this.advance();
38997
+ return true;
38906
38998
  };
38907
38999
  timelineMessages() {
38908
39000
  return this.#timeline.flatMap((entry) => {
@@ -38924,32 +39016,39 @@ var ActiveDaemonTurn = class {
38924
39016
  return activity2 === void 0 ? [] : [{ activities: [activity2], content: [], id: entry.id, role: "assistant", text: "" }];
38925
39017
  });
38926
39018
  }
38927
- updated = () => {
38928
- this.#sequence += 1;
38929
- const turn = this.snapshot();
38930
- if (turn === null) {
38931
- throw new Error("Only a running turn can publish a snapshot");
39019
+ advance = () => {
39020
+ if (this.#dispatch.kind !== "running") {
39021
+ throw new Error("Only a running turn can advance its snapshot");
38932
39022
  }
38933
- return { kind: "session.turn.updated", sessionId: this.command.sessionId, turn };
39023
+ this.#sequence += 1;
39024
+ return this.#sequence;
38934
39025
  };
38935
39026
  snapshot = () => {
38936
39027
  if (this.#dispatch.kind !== "running") {
38937
39028
  return null;
38938
39029
  }
38939
- const completeMessages = this.timelineMessages();
38940
- const messages = completeMessages.slice(-SESSION_HISTORY_MESSAGE_LIMIT);
39030
+ const bounded2 = boundedRecoveryMessages(this.timelineMessages());
38941
39031
  return {
38942
- activities: [...this.#activities.values()].sort((left, right) => left.sequence - right.sequence),
38943
39032
  commandId: decodeIdentifier(this.command.commandId),
38944
- content: [...this.#content.values()],
38945
- prompt: this.command.prompt,
38946
- text: this.#text,
38947
- truncated: messages.length !== completeMessages.length,
38948
39033
  epoch: this.#epoch,
39034
+ hasOlder: bounded2.hasOlder,
38949
39035
  interaction: this.#interaction,
38950
- messages,
39036
+ messages: bounded2.messages,
38951
39037
  sequence: this.#sequence,
38952
- state: "running"
39038
+ state: "running",
39039
+ truncated: false
39040
+ };
39041
+ };
39042
+ recovery = () => {
39043
+ if (this.#dispatch.kind !== "running") {
39044
+ return null;
39045
+ }
39046
+ return {
39047
+ commandId: decodeIdentifier(this.command.commandId),
39048
+ epoch: this.#epoch,
39049
+ messages: this.timelineMessages(),
39050
+ state: "running",
39051
+ truncated: false
38953
39052
  };
38954
39053
  };
38955
39054
  matches = (cancellation) => cancellation.commandId === this.command.commandId && cancellation.sessionId === this.command.sessionId;
@@ -38975,6 +39074,7 @@ var ActiveDaemonTurn = class {
38975
39074
  };
38976
39075
 
38977
39076
  // src/daemon-turn-registry.ts
39077
+ var COMPLETED_TURN_RECOVERY_LIMIT = 32;
38978
39078
  var waitForTurnCompletion = (turn, waitSignal) => {
38979
39079
  waitSignal.throwIfAborted();
38980
39080
  return new Promise((resolve9, reject) => {
@@ -39001,6 +39101,7 @@ var waitForTurnCompletion = (turn, waitSignal) => {
39001
39101
  };
39002
39102
  var DaemonTurnRegistry = class {
39003
39103
  #turns = /* @__PURE__ */ new Map();
39104
+ #completed = /* @__PURE__ */ new Map();
39004
39105
  #operations;
39005
39106
  constructor(operations) {
39006
39107
  this.#operations = operations;
@@ -39012,7 +39113,22 @@ var DaemonTurnRegistry = class {
39012
39113
  };
39013
39114
  get = (commandId) => this.#turns.get(commandId);
39014
39115
  finish = (turn) => {
39116
+ const recovery = turn.recovery();
39015
39117
  this.#turns.delete(turn.command.commandId);
39118
+ if (recovery !== null) {
39119
+ this.#completed.delete(turn.command.sessionId);
39120
+ this.#completed.set(turn.command.sessionId, {
39121
+ command: turn.command,
39122
+ recovery: { ...recovery, state: "completed" }
39123
+ });
39124
+ while (this.#completed.size > COMPLETED_TURN_RECOVERY_LIMIT) {
39125
+ const oldestSessionId = this.#completed.keys().next().value;
39126
+ if (oldestSessionId === void 0) {
39127
+ break;
39128
+ }
39129
+ this.#completed.delete(oldestSessionId);
39130
+ }
39131
+ }
39016
39132
  turn.finish();
39017
39133
  };
39018
39134
  recordEvent = (event) => {
@@ -39033,7 +39149,7 @@ var DaemonTurnRegistry = class {
39033
39149
  };
39034
39150
  recordSteeringPrompt = (command) => {
39035
39151
  const target = this.#turns.get(command.targetCommandId);
39036
- return target?.recordSteeringPrompt(command) ?? null;
39152
+ return target?.recordSteeringPrompt(command) ?? false;
39037
39153
  };
39038
39154
  executionSnapshot = async (matches) => {
39039
39155
  const matching = [];
@@ -39064,12 +39180,30 @@ var DaemonTurnRegistry = class {
39064
39180
  }
39065
39181
  return null;
39066
39182
  };
39183
+ recovery = (matches) => {
39184
+ for (const turn of this.#turns.values()) {
39185
+ if (!matches(turn.command)) {
39186
+ continue;
39187
+ }
39188
+ const recovery = turn.recovery();
39189
+ if (recovery !== null) {
39190
+ return recovery;
39191
+ }
39192
+ }
39193
+ for (const completed of this.#completed.values()) {
39194
+ if (matches(completed.command)) {
39195
+ return completed.recovery;
39196
+ }
39197
+ }
39198
+ return null;
39199
+ };
39067
39200
  close = async () => {
39068
39201
  const turns = [...this.#turns.values()];
39069
39202
  for (const turn of turns) {
39070
39203
  turn.abortDaemon();
39071
39204
  }
39072
39205
  await Promise.all(turns.map((turn) => turn.completion));
39206
+ this.#completed.clear();
39073
39207
  };
39074
39208
  admitAndWait = async (turn, waitSignal, onQueued) => {
39075
39209
  const command = turn.command;
@@ -41377,10 +41511,10 @@ async function resolveSessionDirectories(directories, bindingId, directoryId2, a
41377
41511
  }
41378
41512
 
41379
41513
  // src/native-sessions/live-history-page.ts
41380
- function liveHistoryCursor(sessionId, endIndex) {
41381
- return Buffer.from(JSON.stringify({ endIndex, sessionId }), "utf8").toString("base64url");
41514
+ function encodeLiveHistoryCursor(cursor) {
41515
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
41382
41516
  }
41383
- function liveHistoryEndIndex(cursor, sessionId) {
41517
+ function decodeLiveHistoryCursor(cursor, sessionId) {
41384
41518
  try {
41385
41519
  if (!/^[A-Za-z0-9_-]{1,512}$/u.test(cursor)) {
41386
41520
  throw new Error("Invalid cursor alphabet");
@@ -41390,14 +41524,32 @@ function liveHistoryEndIndex(cursor, sessionId) {
41390
41524
  throw new Error("Invalid cursor shape");
41391
41525
  }
41392
41526
  const record2 = Object.fromEntries(Object.entries(value));
41393
- if (Object.keys(record2).sort().join(",") !== "endIndex,sessionId" || record2["sessionId"] !== sessionId || typeof record2["endIndex"] !== "number" || !Number.isSafeInteger(record2["endIndex"]) || record2["endIndex"] < 0) {
41527
+ const keys = Object.keys(record2).sort().join(",");
41528
+ const legacyNative = keys === "endIndex,sessionId";
41529
+ const kind = legacyNative ? "native" : record2["kind"];
41530
+ if (keys !== "endIndex,kind,sessionId" && keys !== "endIndex,epoch,kind,sessionId" && !legacyNative || record2["sessionId"] !== sessionId || kind !== "active" && kind !== "native" || typeof record2["endIndex"] !== "number" || !Number.isSafeInteger(record2["endIndex"]) || record2["endIndex"] < 0) {
41394
41531
  throw new Error("Invalid cursor target");
41395
41532
  }
41396
- return record2["endIndex"];
41533
+ if (kind === "active") {
41534
+ if (keys !== "endIndex,epoch,kind,sessionId" || typeof record2["epoch"] !== "string") {
41535
+ throw new Error("Invalid active cursor target");
41536
+ }
41537
+ return { endIndex: record2["endIndex"], epoch: record2["epoch"], kind, sessionId };
41538
+ }
41539
+ if (keys !== "endIndex,kind,sessionId" && !legacyNative) {
41540
+ throw new Error("Invalid native cursor target");
41541
+ }
41542
+ return { endIndex: record2["endIndex"], kind, sessionId };
41397
41543
  } catch (cause) {
41398
41544
  throw new AcpExecutionError("The native Session history cursor is invalid", { cause }, "invalid_cursor");
41399
41545
  }
41400
41546
  }
41547
+ function activeHistoryCursor(sessionId, epoch, endIndex) {
41548
+ return encodeLiveHistoryCursor({ endIndex, epoch, kind: "active", sessionId });
41549
+ }
41550
+ function nativeHistoryCursor(sessionId, endIndex) {
41551
+ return encodeLiveHistoryCursor({ endIndex, kind: "native", sessionId });
41552
+ }
41401
41553
  function liveHistoryCoveredTurnRanges(history) {
41402
41554
  const ranges = [];
41403
41555
  let historyIndex = history.messages.length;
@@ -41418,139 +41570,180 @@ function liveHistoryCoveredTurnRanges(history) {
41418
41570
  function historyMessageCharacters(message) {
41419
41571
  return message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0);
41420
41572
  }
41421
- function boundedActiveMessages(messages, messageLimit, characterLimit) {
41573
+ function isNativeHistoryImageMimeType(mimeType) {
41574
+ return mimeType === "image/png" || mimeType === "image/jpeg" || mimeType === "image/gif" || mimeType === "image/webp";
41575
+ }
41576
+ function selectMessagesBefore(messages, endIndex, messageLimit, characterLimit) {
41422
41577
  const selected = [];
41423
41578
  let remainingCharacters = characterLimit;
41424
- for (let index = messages.length - 1; index >= 0 && selected.length < messageLimit; index -= 1) {
41579
+ let startIndex = endIndex;
41580
+ for (let index = endIndex - 1; index >= 0 && selected.length < messageLimit; index -= 1) {
41425
41581
  const message = messages[index];
41426
41582
  if (message === void 0) {
41427
41583
  break;
41428
41584
  }
41429
41585
  const characters = historyMessageCharacters(message);
41430
- if (characters <= remainingCharacters) {
41431
- selected.unshift(message);
41432
- remainingCharacters -= characters;
41433
- continue;
41434
- }
41435
- const structuredCharacters = characters - message.text.length;
41436
- if (selected.length === 0 && structuredCharacters <= remainingCharacters) {
41437
- const textCharacters = remainingCharacters - structuredCharacters;
41438
- selected.unshift({ ...message, text: textCharacters === 0 ? "" : message.text.slice(-textCharacters) });
41439
- remainingCharacters = 0;
41586
+ if (characters > remainingCharacters) {
41587
+ break;
41440
41588
  }
41441
- break;
41589
+ selected.unshift(message);
41590
+ remainingCharacters -= characters;
41591
+ startIndex = index;
41442
41592
  }
41443
- return {
41444
- characters: characterLimit - remainingCharacters,
41445
- messages: selected,
41446
- truncated: selected.length !== messages.length
41447
- };
41593
+ if (selected.length === 0 && endIndex > 0) {
41594
+ throw new AcpExecutionError(
41595
+ "A native Session history item exceeds the page budget",
41596
+ {},
41597
+ "history_item_too_large"
41598
+ );
41599
+ }
41600
+ return { characters: characterLimit - remainingCharacters, messages: selected, startIndex };
41448
41601
  }
41449
41602
  function nativeActiveMessage(message) {
41450
- return message.role === "user" ? { role: "user", text: message.text } : {
41451
- activities: message.activities,
41452
- content: message.content,
41453
- role: "assistant",
41454
- text: message.text
41455
- };
41603
+ if (message.role === "assistant") {
41604
+ return {
41605
+ activities: message.activities,
41606
+ content: message.content,
41607
+ role: "assistant",
41608
+ text: message.text
41609
+ };
41610
+ }
41611
+ const images = (message.images ?? []).flatMap((image) => {
41612
+ if (image.data.length < 4 || image.data.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/u.test(image.data) || !isNativeHistoryImageMimeType(image.mimeType)) {
41613
+ return [];
41614
+ }
41615
+ return [{ data: image.data, mimeType: image.mimeType, name: image.name }];
41616
+ });
41617
+ return { ...images.length === 0 ? {} : { images }, role: "user", text: message.text };
41618
+ }
41619
+ function nativeHistoryCoversCompletedRecovery(history, recovery) {
41620
+ const root = recovery.messages.find((message) => message.role === "user" && message.id === recovery.commandId);
41621
+ if (root === void 0) {
41622
+ return false;
41623
+ }
41624
+ const rootIndex = history.messages.findLastIndex(
41625
+ (message) => message.role === "user" && message.text === root.text
41626
+ );
41627
+ if (rootIndex < 0) {
41628
+ return false;
41629
+ }
41630
+ const nativeAssistant = history.messages.slice(rootIndex + 1).filter((message) => message.role === "assistant");
41631
+ if (nativeAssistant.length === 0) {
41632
+ return false;
41633
+ }
41634
+ const recoveryAssistant = recovery.messages.filter((message) => message.role === "assistant");
41635
+ const recoveryText = recoveryAssistant.map((message) => message.text).join("");
41636
+ const nativeText = nativeAssistant.map((message) => message.text).join("");
41637
+ if (recoveryText.length > 0 && !nativeText.includes(recoveryText)) {
41638
+ return false;
41639
+ }
41640
+ const nativeContent = new Set(
41641
+ nativeAssistant.flatMap((message) => message.content).map((content) => JSON.stringify(content))
41642
+ );
41643
+ return recoveryAssistant.flatMap((message) => message.content).every((content) => nativeContent.has(JSON.stringify(content)));
41456
41644
  }
41457
- function paginateAcpSessionHistory(history, sessionId, cursor, limit) {
41645
+ function historyBeforeActiveTurn(history, activeTurn) {
41458
41646
  if (history.status === "unavailable") {
41459
- if (cursor !== null) {
41460
- throw new AcpExecutionError("Unavailable native Session history has no cursor", {}, "invalid_cursor");
41461
- }
41462
- return { cause: history.cause, nextCursor: null, status: "unavailable" };
41647
+ return history;
41463
41648
  }
41464
- const endIndex = cursor === null ? history.messages.length : liveHistoryEndIndex(cursor, sessionId);
41649
+ const activeRoot = activeTurn.messages.find(
41650
+ (message) => message.role === "user" && message.id === activeTurn.commandId
41651
+ );
41652
+ const matchingActiveUserIndex = activeRoot === void 0 ? -1 : activeTurn.state === "completed" || history.messages.at(-1)?.role === "user" && history.messages.at(-1)?.text === activeRoot.text || history.coveredCommandIds.includes(activeTurn.commandId) ? history.messages.findLastIndex((message) => message.role === "user" && message.text === activeRoot.text) : -1;
41653
+ const activeUserIndex = matchingActiveUserIndex >= 0 ? matchingActiveUserIndex : liveHistoryCoveredTurnRanges(history).find((range) => range.commandId === activeTurn.commandId)?.userIndex ?? -1;
41654
+ if (activeUserIndex < 0) {
41655
+ return history;
41656
+ }
41657
+ return {
41658
+ ...history,
41659
+ coveredCommandIds: history.coveredCommandIds.filter((id) => id !== activeTurn.commandId),
41660
+ coveredMessageIds: history.coveredMessageIds.filter(
41661
+ (id) => id !== `${activeTurn.commandId}:user` && id !== `${activeTurn.commandId}:agent`
41662
+ ),
41663
+ messages: history.messages.slice(0, activeUserIndex)
41664
+ };
41665
+ }
41666
+ function paginateAvailableNativeHistory(history, sessionId, endIndex, limit, characterLimit) {
41465
41667
  if (endIndex > history.messages.length) {
41466
41668
  throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
41467
41669
  }
41468
- const startIndex = Math.max(0, endIndex - limit);
41670
+ const selected = selectMessagesBefore(history.messages, endIndex, limit, characterLimit);
41469
41671
  const coveredMessageIds = new Set(history.coveredMessageIds);
41470
41672
  const ranges = liveHistoryCoveredTurnRanges(history);
41471
41673
  return {
41472
- coveredCommandIds: ranges.filter((range) => range.userIndex >= startIndex && range.assistantEndIndex <= endIndex).map((range) => range.commandId),
41674
+ coveredCommandIds: ranges.filter((range) => range.userIndex >= selected.startIndex && range.assistantEndIndex <= endIndex).map((range) => range.commandId),
41473
41675
  coveredMessageIds: ranges.flatMap((range) => [
41474
- ...range.userIndex >= startIndex && range.userIndex < endIndex && coveredMessageIds.has(`${range.commandId}:user`) ? [`${range.commandId}:user`] : [],
41475
- ...range.assistantStartIndex >= startIndex && range.assistantEndIndex <= endIndex && coveredMessageIds.has(`${range.commandId}:agent`) ? [`${range.commandId}:agent`] : []
41676
+ ...range.userIndex >= selected.startIndex && range.userIndex < endIndex && coveredMessageIds.has(`${range.commandId}:user`) ? [`${range.commandId}:user`] : [],
41677
+ ...range.assistantStartIndex >= selected.startIndex && range.assistantEndIndex <= endIndex && coveredMessageIds.has(`${range.commandId}:agent`) ? [`${range.commandId}:agent`] : []
41476
41678
  ]),
41477
- messages: history.messages.slice(startIndex, endIndex),
41478
- nextCursor: startIndex > 0 ? liveHistoryCursor(sessionId, startIndex) : null,
41679
+ messages: selected.messages,
41680
+ nextCursor: selected.startIndex > 0 ? nativeHistoryCursor(sessionId, selected.startIndex) : null,
41479
41681
  status: "available",
41480
41682
  truncated: history.truncated
41481
41683
  };
41482
41684
  }
41483
- function paginateLiveSessionHistory(history, activeTurn, sessionId, cursor, limit) {
41484
- if (activeTurn === null || cursor !== null) {
41485
- return paginateAcpSessionHistory(history, sessionId, cursor, limit);
41685
+ function paginateNativeHistory(history, sessionId, cursor, limit) {
41686
+ if (cursor?.kind === "active") {
41687
+ throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
41486
41688
  }
41487
41689
  if (history.status === "unavailable") {
41488
- const bounded3 = boundedActiveMessages(
41489
- activeTurn.messages.map(nativeActiveMessage),
41490
- limit,
41491
- SESSION_HISTORY_CHARACTER_LIMIT
41492
- );
41493
- return {
41494
- coveredCommandIds: [],
41495
- coveredMessageIds: [],
41496
- messages: bounded3.messages,
41497
- nextCursor: null,
41498
- status: "available",
41499
- truncated: activeTurn.truncated || bounded3.truncated
41500
- };
41690
+ if (cursor !== null) {
41691
+ throw new AcpExecutionError("Unavailable native Session history has no cursor", {}, "invalid_cursor");
41692
+ }
41693
+ return { cause: history.cause, nextCursor: null, status: "unavailable" };
41501
41694
  }
41502
- const activeRoot = activeTurn.messages.find(
41503
- (message) => message.role === "user" && message.id === activeTurn.commandId
41695
+ return paginateAvailableNativeHistory(
41696
+ history,
41697
+ sessionId,
41698
+ cursor?.endIndex ?? history.messages.length,
41699
+ limit,
41700
+ SESSION_HISTORY_CHARACTER_LIMIT
41504
41701
  );
41505
- const coveredActiveRoot = history.coveredCommandIds.includes(activeTurn.commandId);
41506
- const matchingActiveUserIndex = coveredActiveRoot ? history.messages.findLastIndex(
41507
- (message) => message.role === "user" && message.text === (activeRoot?.text ?? activeTurn.prompt)
41508
- ) : -1;
41509
- const activeUserIndex = matchingActiveUserIndex >= 0 ? matchingActiveUserIndex : liveHistoryCoveredTurnRanges(history).find((range) => range.commandId === activeTurn.commandId)?.userIndex ?? -1;
41510
- if (activeUserIndex >= 0) {
41511
- history = {
41512
- ...history,
41513
- coveredCommandIds: history.coveredCommandIds.filter((id) => id !== activeTurn.commandId),
41514
- coveredMessageIds: history.coveredMessageIds.filter(
41515
- (id) => id !== `${activeTurn.commandId}:user` && id !== `${activeTurn.commandId}:agent`
41516
- ),
41517
- messages: history.messages.slice(0, activeUserIndex + 1)
41518
- };
41519
- }
41520
- const latestNativeMessage = history.messages.at(-1);
41521
- const firstActiveMessage = activeTurn.messages[0];
41522
- const preserveNativeRoot = activeUserIndex >= 0 || firstActiveMessage?.role === "user" && latestNativeMessage?.role === "user" && latestNativeMessage.text === firstActiveMessage.text;
41523
- const activeMessages = activeTurn.messages.slice(preserveNativeRoot ? 1 : 0).map(nativeActiveMessage);
41524
- const bounded2 = boundedActiveMessages(activeMessages, limit, SESSION_HISTORY_CHARACTER_LIMIT);
41525
- let historicalMessageCount = 0;
41526
- let remainingCharacters = SESSION_HISTORY_CHARACTER_LIMIT - bounded2.characters;
41527
- const historicalMessageLimit = Math.max(0, limit - bounded2.messages.length);
41528
- for (let index = history.messages.length - 1; index >= 0 && historicalMessageCount < historicalMessageLimit; index -= 1) {
41529
- const message = history.messages[index];
41530
- if (message === void 0) {
41531
- break;
41532
- }
41533
- const characters = historyMessageCharacters(message);
41534
- if (characters > remainingCharacters) {
41535
- break;
41536
- }
41537
- historicalMessageCount += 1;
41538
- remainingCharacters -= characters;
41702
+ }
41703
+ function paginateActiveTurn(history, activeTurn, sessionId, endIndex, limit) {
41704
+ if (endIndex > activeTurn.messages.length) {
41705
+ throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
41539
41706
  }
41540
- const page = historicalMessageCount === 0 ? {
41541
- coveredCommandIds: [],
41542
- coveredMessageIds: [],
41543
- messages: [],
41544
- nextCursor: history.messages.length === 0 ? null : liveHistoryCursor(sessionId, history.messages.length),
41545
- status: "available",
41546
- truncated: history.truncated
41547
- } : paginateAcpSessionHistory(history, sessionId, null, historicalMessageCount);
41707
+ const activeMessages = activeTurn.messages.map(nativeActiveMessage);
41708
+ const active = selectMessagesBefore(activeMessages, endIndex, limit, SESSION_HISTORY_CHARACTER_LIMIT);
41709
+ const historicalLimit = limit - active.messages.length;
41710
+ const historicalCharacterLimit = SESSION_HISTORY_CHARACTER_LIMIT - active.characters;
41711
+ const pendingHistoricalCursor = history.status === "available" && history.messages.length > 0 ? nativeHistoryCursor(sessionId, history.messages.length) : null;
41712
+ const latestHistoricalMessage = history.status === "available" ? history.messages.at(-1) : void 0;
41713
+ const historicalPageFits = latestHistoricalMessage === void 0 || historyMessageCharacters(latestHistoricalMessage) <= historicalCharacterLimit;
41714
+ const historical = active.startIndex === 0 && history.status === "available" && historicalLimit > 0 && historicalPageFits ? paginateAvailableNativeHistory(
41715
+ history,
41716
+ sessionId,
41717
+ history.messages.length,
41718
+ historicalLimit,
41719
+ historicalCharacterLimit
41720
+ ) : null;
41548
41721
  return {
41549
- ...page,
41550
- messages: [...page.messages, ...bounded2.messages],
41551
- truncated: page.truncated || activeTurn.truncated || bounded2.truncated
41722
+ coveredCommandIds: historical?.coveredCommandIds ?? [],
41723
+ coveredMessageIds: [
41724
+ ...historical?.coveredMessageIds ?? [],
41725
+ ...active.startIndex === 0 ? [`${activeTurn.commandId}:user`, `${activeTurn.commandId}:agent`] : []
41726
+ ],
41727
+ messages: [...historical?.messages ?? [], ...active.messages],
41728
+ nextCursor: active.startIndex > 0 ? activeHistoryCursor(sessionId, activeTurn.epoch, active.startIndex) : historical === null ? pendingHistoricalCursor : historical.nextCursor,
41729
+ status: "available",
41730
+ truncated: activeTurn.truncated || (historical?.truncated ?? false)
41552
41731
  };
41553
41732
  }
41733
+ function paginateLiveSessionHistory(history, activeTurn, sessionId, encodedCursor, limit) {
41734
+ const cursor = encodedCursor === null ? null : decodeLiveHistoryCursor(encodedCursor, sessionId);
41735
+ if (activeTurn === null || activeTurn.state === "completed" && history.status === "available" && nativeHistoryCoversCompletedRecovery(history, activeTurn)) {
41736
+ return paginateNativeHistory(history, sessionId, cursor, limit);
41737
+ }
41738
+ const historical = historyBeforeActiveTurn(history, activeTurn);
41739
+ if (cursor?.kind === "native") {
41740
+ return paginateNativeHistory(historical, sessionId, cursor, limit);
41741
+ }
41742
+ if (cursor !== null && cursor.epoch !== activeTurn.epoch) {
41743
+ throw new AcpExecutionError("The active Turn history cursor is stale", {}, "stale_cursor");
41744
+ }
41745
+ return paginateActiveTurn(historical, activeTurn, sessionId, cursor?.endIndex ?? activeTurn.messages.length, limit);
41746
+ }
41554
41747
  function mergeLiveSessionHistory(history, activeTurn, sessionId) {
41555
41748
  const page = paginateLiveSessionHistory(history, activeTurn, sessionId, null, 1e3);
41556
41749
  if (page.status === "unavailable") {
@@ -41561,7 +41754,7 @@ function mergeLiveSessionHistory(history, activeTurn, sessionId) {
41561
41754
  coveredMessageIds: page.coveredMessageIds,
41562
41755
  messages: page.messages,
41563
41756
  status: "available",
41564
- truncated: page.truncated
41757
+ truncated: page.truncated || page.nextCursor !== null
41565
41758
  };
41566
41759
  }
41567
41760
 
@@ -42080,15 +42273,13 @@ async function executePrompt({
42080
42273
  });
42081
42274
  operations.transitionExecution(command.commandId, "running");
42082
42275
  if (availability.disposition === SessionPromptDisposition.Steer) {
42083
- const updated = recordSteeringPrompt();
42084
- if (updated === null) {
42276
+ if (!recordSteeringPrompt()) {
42085
42277
  throw new AcpExecutionError(
42086
42278
  "The steering target is unavailable",
42087
42279
  {},
42088
42280
  "prompt_queue_target_unavailable"
42089
42281
  );
42090
42282
  }
42091
- publisher.publish(command.sessionId, updated);
42092
42283
  }
42093
42284
  const stream = activateAttempt();
42094
42285
  publisher.publish(command.sessionId, {
@@ -42476,7 +42667,7 @@ function createDaemonCommandHandler({
42476
42667
  resolveEnvironment: resolveAgentCatalogEnvironment
42477
42668
  });
42478
42669
  const queueChanged = (sessionId) => publisher.publish(sessionId, { kind: "session.queue.changed", sessionId });
42479
- const activeTurnSnapshot = (sessionId) => activeTurns.snapshot((command) => command.sessionId === sessionId);
42670
+ const activeTurnRecovery = (sessionId) => activeTurns.recovery((command) => command.sessionId === sessionId);
42480
42671
  const nativeRequestHandler = createNativeSessionRequestHandler({
42481
42672
  audit,
42482
42673
  diagnostics,
@@ -42497,7 +42688,7 @@ function createDaemonCommandHandler({
42497
42688
  directories,
42498
42689
  audit,
42499
42690
  sessions,
42500
- activeTurnSnapshot,
42691
+ activeTurnRecovery,
42501
42692
  signal
42502
42693
  );
42503
42694
  return;
@@ -42511,7 +42702,7 @@ function createDaemonCommandHandler({
42511
42702
  directories,
42512
42703
  audit,
42513
42704
  sessions,
42514
- activeTurnSnapshot,
42705
+ activeTurnRecovery,
42515
42706
  signal
42516
42707
  );
42517
42708
  return;
@@ -42983,6 +43174,7 @@ async function runRelayConnection({
42983
43174
  }) {
42984
43175
  const relayUrl = new URL(config2.relayUrl);
42985
43176
  relayUrl.searchParams.set("liveness", "1");
43177
+ relayUrl.searchParams.set("sessionEventDelivery", "reliable-v1");
42986
43178
  const socket = await connectRelay(relayUrl, signal);
42987
43179
  diagnostics.connectionOpened(connectionEpoch);
42988
43180
  const connectionController = new AbortController();
@@ -43102,6 +43294,10 @@ async function runRelayConnection({
43102
43294
  const message = decodeServiceToDaemonMessage(encoded);
43103
43295
  const kind = message.kind;
43104
43296
  if (kind === RUNTIME_LIVENESS_READY_KIND) {
43297
+ publisher.connect(
43298
+ socket,
43299
+ message.sessionEventDelivery === "reliable-v1" ? "reliable" : "ephemeral"
43300
+ );
43105
43301
  connectionLiveness.start();
43106
43302
  return;
43107
43303
  }
@@ -43109,6 +43305,18 @@ async function runRelayConnection({
43109
43305
  connectionLiveness.acknowledge(message);
43110
43306
  return;
43111
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
+ }
43112
43320
  commands.handle(message);
43113
43321
  } catch (error62) {
43114
43322
  console.error("Mobius daemon rejected a message", error62);
@@ -43123,7 +43331,6 @@ async function runRelayConnection({
43123
43331
  socket.addEventListener("close", handleClose);
43124
43332
  socket.addEventListener("error", handleError);
43125
43333
  socket.addEventListener("message", handleMessage);
43126
- publisher.connect(socket);
43127
43334
  if (signal.aborted) {
43128
43335
  stop();
43129
43336
  }
@@ -43134,54 +43341,377 @@ async function runRelayConnection({
43134
43341
  }
43135
43342
  }
43136
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
+
43137
43563
  // src/relay-connection/session-event-publisher.ts
43138
43564
  import { WebSocket as WebSocket9 } from "undici";
43139
- 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
+ }
43140
43573
  var DaemonSessionEventPublisher = class {
43141
- #socket = null;
43142
- #reportedDrop = false;
43143
- connect = (socket) => {
43144
- this.#socket = socket;
43145
- 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);
43146
43616
  };
43147
43617
  disconnect = (socket) => {
43148
- if (this.#socket === socket) {
43149
- this.#socket = null;
43618
+ if (this.#connection.kind !== "connected" || this.#connection.socket !== socket) {
43619
+ return;
43150
43620
  }
43621
+ this.#connection = { kind: "disconnected" };
43622
+ this.#clearInFlight();
43151
43623
  };
43152
43624
  publish = (sessionId, event) => {
43153
- const socket = this.#socket;
43154
- if (socket === null || socket.readyState !== WebSocket9.OPEN) {
43155
- 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
+ }
43156
43637
  return;
43157
43638
  }
43158
- if (socket.bufferedAmount >= MAX_LIVE_EVENT_BUFFERED_BYTES) {
43159
- this.#socket = null;
43160
- socket.close(1013, "Live event backpressure");
43161
- this.#reportDrop();
43639
+ this.#publishNextBatch();
43640
+ };
43641
+ quarantine = (rejection) => {
43642
+ if (!this.#outbox.quarantine(rejection)) {
43162
43643
  return;
43163
43644
  }
43164
- try {
43165
- socket.send(
43166
- encodeDaemonToServiceMessage({
43167
- kind: SESSION_EVENT_KIND,
43168
- payload: encodeRecordedSessionEvent(event),
43169
- sessionId
43170
- })
43171
- );
43172
- } catch {
43173
- this.#reportDrop();
43174
- }
43645
+ this.#removeInFlight(rejection.eventId);
43646
+ console.error(`Mobius Relay rejected Session event ${rejection.eventId} with code ${rejection.code}`);
43647
+ this.#publishNextBatch();
43175
43648
  };
43176
- close = () => {
43177
- this.#socket = null;
43649
+ #clearInFlight = () => {
43650
+ this.#inFlight.clear();
43651
+ this.#inFlightBytes = 0;
43178
43652
  };
43179
- #reportDrop = () => {
43180
- if (this.#reportedDrop) {
43653
+ #publishNextBatch = () => {
43654
+ if (this.#deferredReplay !== null || this.#connection.kind !== "connected") {
43181
43655
  return;
43182
43656
  }
43183
- this.#reportedDrop = true;
43184
- console.warn("Mobius daemon dropped live Session events while the Relay was unavailable");
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
+ }
43696
+ };
43697
+ #removeInFlight = (eventId) => {
43698
+ const inFlight = this.#inFlight.get(eventId);
43699
+ if (inFlight === void 0) {
43700
+ return;
43701
+ }
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
+ }
43185
43715
  };
43186
43716
  };
43187
43717
 
@@ -43242,7 +43772,7 @@ async function installDetectedAgentIntegrations(config2, installations, signal)
43242
43772
  {
43243
43773
  agentId: agent.id,
43244
43774
  kind: AGENT_INSTALL_REQUEST_KIND,
43245
- requestId: `automatic-integration-${randomUUID7()}`
43775
+ requestId: `automatic-integration-${randomUUID8()}`
43246
43776
  },
43247
43777
  signal
43248
43778
  );
@@ -43264,7 +43794,11 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
43264
43794
  join18(stateDirectory, "sessions", `${createHash6("sha256").update(config2.channelId).digest("hex")}.sqlite`)
43265
43795
  );
43266
43796
  const operations = new DaemonOperationState();
43267
- 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
+ );
43268
43802
  const activeTurns = new DaemonTurnRegistry(operations);
43269
43803
  const recordSessionEvent = (sessionId, event) => {
43270
43804
  publisher.publish(sessionId, activeTurns.recordEvent(event));