@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/runtime.js CHANGED
@@ -20740,9 +20740,17 @@ function decodeSessionQueuedEvent(encoded) {
20740
20740
  }
20741
20741
  function decodeRuntimeLivenessReadyEvent(encoded) {
20742
20742
  const value = parseJson2(encoded);
20743
- assertExactKeys(value, ["kind"]);
20744
20743
  assertKind(value, RUNTIME_LIVENESS_READY_KIND);
20745
- return { kind: RUNTIME_LIVENESS_READY_KIND };
20744
+ if (value["sessionEventDelivery"] === void 0) {
20745
+ assertExactKeys(value, ["kind"]);
20746
+ return { kind: RUNTIME_LIVENESS_READY_KIND };
20747
+ }
20748
+ assertExactKeys(value, ["kind", "sessionEventDelivery"]);
20749
+ const sessionEventDelivery = readString(value, "sessionEventDelivery", 32);
20750
+ if (sessionEventDelivery !== "reliable-v1") {
20751
+ throw new ProtocolMessageError("Unsupported Session event delivery mode");
20752
+ }
20753
+ return { kind: RUNTIME_LIVENESS_READY_KIND, sessionEventDelivery };
20746
20754
  }
20747
20755
  function decodeRuntimeHeartbeatEvent(encoded) {
20748
20756
  const value = parseJson2(encoded);
@@ -21034,18 +21042,21 @@ var activeTurnMessageSchema = external_exports.discriminatedUnion("role", [
21034
21042
  activeTurnAssistantMessageSchema
21035
21043
  ]);
21036
21044
  var activeTurnSnapshotSchema = external_exports.strictObject({
21037
- activities: external_exports.array(activeTurnActivitySchema).readonly(),
21038
21045
  commandId: identifierStringSchema,
21039
- content: external_exports.array(displayContentSchema).readonly(),
21040
- prompt: external_exports.string().max(1e5),
21041
- text: external_exports.string(),
21042
- truncated: external_exports.boolean(),
21043
21046
  epoch: identifierStringSchema,
21047
+ hasOlder: external_exports.boolean(),
21044
21048
  interaction: external_exports.discriminatedUnion("kind", [sessionPermissionRequestedEventSchema, sessionElicitationRequestedEventSchema]).nullable(),
21045
21049
  messages: external_exports.array(activeTurnMessageSchema).max(SESSION_HISTORY_MESSAGE_LIMIT).readonly(),
21046
21050
  sequence: external_exports.number().int().nonnegative(),
21047
- state: external_exports.literal("running")
21048
- }).readonly();
21051
+ state: external_exports.literal("running"),
21052
+ truncated: external_exports.boolean()
21053
+ }).readonly().refine(
21054
+ (snapshot) => snapshot.messages.reduce(
21055
+ (total, message) => total + message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0),
21056
+ 0
21057
+ ) <= SESSION_HISTORY_CHARACTER_LIMIT,
21058
+ "Active Turn snapshot content is too large"
21059
+ );
21049
21060
  var identity = {
21050
21061
  ...envelope,
21051
21062
  kind: external_exports.literal(NATIVE_SESSION_REQUEST_KIND),
@@ -21189,27 +21200,61 @@ var encodeNativeSessionCancel = (value) => JSON.stringify(nativeSessionCancelSch
21189
21200
 
21190
21201
  // ../protocol/src/session-events.ts
21191
21202
  var SESSION_EVENT_KIND = "session.event";
21192
- var SESSION_EVENT_COMMITTED_KIND = "session.event.committed";
21203
+ var SESSION_EVENT_ACK_KIND = "session.event.ack";
21204
+ var SESSION_EVENT_DEFERRED_KIND = "session.event.deferred";
21205
+ var SESSION_EVENT_REJECTED_KIND = "session.event.rejected";
21206
+ var SessionEventRejectionCode = {
21207
+ IdempotencyConflict: "idempotency_conflict",
21208
+ InvalidEvent: "invalid_event",
21209
+ RecordingRejected: "recording_rejected"
21210
+ };
21193
21211
  var MAX_RECORDED_EVENT_LENGTH = 8 * 1024 * 1024;
21194
- var eventEnvelopeSchema = external_exports.strictObject({
21212
+ var ephemeralEventEnvelopeSchema = external_exports.strictObject({
21195
21213
  kind: external_exports.literal(SESSION_EVENT_KIND),
21196
21214
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
21197
21215
  sessionId: identifierStringSchema
21198
21216
  }).readonly();
21199
- var committedEventSchema = external_exports.strictObject({
21200
- kind: external_exports.literal(SESSION_EVENT_COMMITTED_KIND),
21217
+ var reliableDeliverySchema = external_exports.strictObject({
21218
+ eventId: identifierStringSchema,
21219
+ kind: external_exports.literal("reliable"),
21220
+ sourceSeq: external_exports.string().regex(/^(?:0|[1-9][0-9]*)$/u).max(40),
21221
+ streamId: identifierStringSchema
21222
+ }).readonly();
21223
+ var reliableEventEnvelopeSchema = external_exports.strictObject({
21224
+ delivery: reliableDeliverySchema,
21225
+ kind: external_exports.literal(SESSION_EVENT_KIND),
21201
21226
  payload: external_exports.string().min(2).max(MAX_RECORDED_EVENT_LENGTH),
21202
21227
  sessionId: identifierStringSchema
21203
21228
  }).readonly();
21229
+ var eventEnvelopeSchema = external_exports.union([ephemeralEventEnvelopeSchema, reliableEventEnvelopeSchema]).readonly();
21230
+ var acknowledgementOutcomeSchema = external_exports.discriminatedUnion("kind", [
21231
+ external_exports.strictObject({ kind: external_exports.literal("relayed") }).readonly(),
21232
+ external_exports.strictObject({
21233
+ kind: external_exports.literal("recorded"),
21234
+ providerId: identifierStringSchema,
21235
+ recordingEpoch: identifierStringSchema
21236
+ }).readonly()
21237
+ ]);
21238
+ var eventAcknowledgementSchema = external_exports.strictObject({
21239
+ eventId: identifierStringSchema,
21240
+ kind: external_exports.literal(SESSION_EVENT_ACK_KIND),
21241
+ outcome: acknowledgementOutcomeSchema,
21242
+ sessionId: identifierStringSchema
21243
+ }).readonly();
21244
+ var eventDeferredSchema = external_exports.strictObject({
21245
+ eventId: identifierStringSchema,
21246
+ kind: external_exports.literal(SESSION_EVENT_DEFERRED_KIND),
21247
+ retryAfterMilliseconds: external_exports.number().int().min(100).max(6e4),
21248
+ sessionId: identifierStringSchema
21249
+ }).readonly();
21250
+ var eventRejectedSchema = external_exports.strictObject({
21251
+ code: external_exports.enum(SessionEventRejectionCode),
21252
+ eventId: identifierStringSchema,
21253
+ kind: external_exports.literal(SESSION_EVENT_REJECTED_KIND),
21254
+ sessionId: identifierStringSchema
21255
+ }).readonly();
21204
21256
  var SESSION_QUEUE_CHANGED_KIND = "session.queue.changed";
21205
21257
  var sessionQueueChangedSchema = external_exports.strictObject({ kind: external_exports.literal(SESSION_QUEUE_CHANGED_KIND), sessionId: identifierStringSchema }).readonly();
21206
- var SESSION_TURN_UPDATED_KIND = "session.turn.updated";
21207
- var sessionTurnUpdatedSchema = external_exports.strictObject({
21208
- kind: external_exports.literal(SESSION_TURN_UPDATED_KIND),
21209
- sessionId: identifierStringSchema,
21210
- turn: activeTurnSnapshotSchema
21211
- }).readonly();
21212
- var decodeSessionTurnUpdatedEvent = (encoded) => decode5(encoded, sessionTurnUpdatedSchema);
21213
21258
  function decode5(encoded, schema) {
21214
21259
  let value;
21215
21260
  try {
@@ -21236,11 +21281,15 @@ var encode4 = (message, schema) => {
21236
21281
  };
21237
21282
  var decodeSessionEventEnvelope = (encoded) => decode5(encoded, eventEnvelopeSchema);
21238
21283
  var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
21284
+ var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
21285
+ var encodeSessionEventAcknowledgement = (message) => encode4(message, eventAcknowledgementSchema);
21286
+ var decodeSessionEventDeferred = (encoded) => decode5(encoded, eventDeferredSchema);
21287
+ var encodeSessionEventDeferred = (message) => encode4(message, eventDeferredSchema);
21288
+ var decodeSessionEventRejected = (encoded) => decode5(encoded, eventRejectedSchema);
21289
+ var encodeSessionEventRejected = (message) => encode4(message, eventRejectedSchema);
21239
21290
  function decodeRecordedSessionEvent(encoded) {
21240
21291
  const kind = decodeMessageKind(encoded);
21241
21292
  switch (kind) {
21242
- case SESSION_TURN_UPDATED_KIND:
21243
- return decodeSessionTurnUpdatedEvent(encoded);
21244
21293
  case SESSION_QUEUE_CHANGED_KIND:
21245
21294
  return decode5(encoded, sessionQueueChangedSchema);
21246
21295
  case AGENT_ACTIVITY_KIND:
@@ -21409,6 +21458,21 @@ var serviceToDaemonSignatures = {
21409
21458
  encodeSessionElicitationRespondCommand,
21410
21459
  decodeSessionElicitationRespondCommand
21411
21460
  ),
21461
+ [SESSION_EVENT_ACK_KIND]: defineMessageSignature(
21462
+ SESSION_EVENT_ACK_KIND,
21463
+ encodeSessionEventAcknowledgement,
21464
+ decodeSessionEventAcknowledgement
21465
+ ),
21466
+ [SESSION_EVENT_DEFERRED_KIND]: defineMessageSignature(
21467
+ SESSION_EVENT_DEFERRED_KIND,
21468
+ encodeSessionEventDeferred,
21469
+ decodeSessionEventDeferred
21470
+ ),
21471
+ [SESSION_EVENT_REJECTED_KIND]: defineMessageSignature(
21472
+ SESSION_EVENT_REJECTED_KIND,
21473
+ encodeSessionEventRejected,
21474
+ decodeSessionEventRejected
21475
+ ),
21412
21476
  [SESSION_LIVE_HISTORY_LOAD_KIND]: defineMessageSignature(
21413
21477
  SESSION_LIVE_HISTORY_LOAD_KIND,
21414
21478
  encodeSessionLiveHistoryLoad,
@@ -26703,7 +26767,7 @@ function createDaemonConfig(connection, agents = [], installableAgents = [], loc
26703
26767
  }
26704
26768
 
26705
26769
  // src/run-daemon.ts
26706
- import { createHash as createHash5, randomUUID as randomUUID6 } from "node:crypto";
26770
+ import { createHash as createHash5, randomUUID as randomUUID7 } from "node:crypto";
26707
26771
  import { join as join17 } from "node:path";
26708
26772
  import { styleText } from "node:util";
26709
26773
 
@@ -37853,6 +37917,28 @@ var DaemonInstallationObserver = class {
37853
37917
 
37854
37918
  // src/daemon-active-turn.ts
37855
37919
  import { randomUUID } from "node:crypto";
37920
+ function activeMessageCharacters(message) {
37921
+ return message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0);
37922
+ }
37923
+ var ACTIVE_TURN_TEXT_CHUNK_LIMIT = Math.floor(SESSION_HISTORY_CHARACTER_LIMIT / 2);
37924
+ function boundedRecoveryMessages(messages) {
37925
+ const selected = [];
37926
+ let remainingCharacters = SESSION_HISTORY_CHARACTER_LIMIT;
37927
+ for (let index = messages.length - 1; index >= 0 && selected.length < SESSION_HISTORY_MESSAGE_LIMIT; index -= 1) {
37928
+ const message = messages[index];
37929
+ if (message === void 0) {
37930
+ break;
37931
+ }
37932
+ const characters = activeMessageCharacters(message);
37933
+ if (characters <= remainingCharacters) {
37934
+ selected.unshift(message);
37935
+ remainingCharacters -= characters;
37936
+ continue;
37937
+ }
37938
+ break;
37939
+ }
37940
+ return { hasOlder: selected.length !== messages.length, messages: selected };
37941
+ }
37856
37942
  var ActiveDaemonTurn = class {
37857
37943
  attemptId;
37858
37944
  command;
@@ -37860,9 +37946,7 @@ var ActiveDaemonTurn = class {
37860
37946
  #finished = Promise.withResolvers();
37861
37947
  #cancellationRequested = false;
37862
37948
  #dispatch;
37863
- #text = "";
37864
37949
  #activities = /* @__PURE__ */ new Map();
37865
- #content = /* @__PURE__ */ new Map();
37866
37950
  #timeline;
37867
37951
  #sequence = 0;
37868
37952
  #interaction = null;
@@ -37909,9 +37993,17 @@ var ActiveDaemonTurn = class {
37909
37993
  effectiveDisposition = () => this.#dispatch.kind === "promoted" ? SessionPromptDisposition.Steer : this.command.disposition;
37910
37994
  recordEvent = (event) => {
37911
37995
  if (event.kind === AGENT_MESSAGE_DELTA_KIND) {
37912
- this.#text += event.text;
37996
+ const sequence = this.advance();
37913
37997
  this.appendMessageText(event.messageId, event.text);
37914
- return this.updated();
37998
+ return {
37999
+ commandId: this.command.commandId,
38000
+ epoch: this.#epoch,
38001
+ kind: AGENT_MESSAGE_DELTA_KIND,
38002
+ messageId: event.messageId,
38003
+ sequence,
38004
+ sessionId: this.command.sessionId,
38005
+ text: event.text
38006
+ };
37915
38007
  }
37916
38008
  if (event.sessionId !== this.command.sessionId || "commandId" in event && event.commandId !== this.command.commandId) {
37917
38009
  return event;
@@ -37958,7 +38050,6 @@ var ActiveDaemonTurn = class {
37958
38050
  toolKind: previous?.toolKind ?? ""
37959
38051
  });
37960
38052
  } else if (event.kind === AGENT_CONTENT_KIND) {
37961
- this.#content.set(event.contentId, event.content);
37962
38053
  const target = this.#timeline.findLastIndex(
37963
38054
  (entry) => entry.kind === "message" && entry.sourceMessageId === event.messageId
37964
38055
  );
@@ -37984,7 +38075,7 @@ var ActiveDaemonTurn = class {
37984
38075
  this.#interaction = null;
37985
38076
  }
37986
38077
  if (event.kind === AGENT_ACTIVITY_KIND || event.kind === AGENT_ACTIVITY_NARRATION_KIND || event.kind === AGENT_CONTENT_KIND) {
37987
- return this.updated();
38078
+ this.advance();
37988
38079
  }
37989
38080
  return event;
37990
38081
  };
@@ -37993,7 +38084,7 @@ var ActiveDaemonTurn = class {
37993
38084
  while (remaining.length > 0) {
37994
38085
  const latest = this.#timeline.at(-1);
37995
38086
  if (latest?.kind === "message" && latest.sourceMessageId === messageId) {
37996
- const available = SESSION_HISTORY_CHARACTER_LIMIT - latest.text.length;
38087
+ const available = ACTIVE_TURN_TEXT_CHUNK_LIMIT - latest.text.length;
37997
38088
  if (available > 0) {
37998
38089
  this.#timeline = [
37999
38090
  ...this.#timeline.slice(0, -1),
@@ -38010,21 +38101,22 @@ var ActiveDaemonTurn = class {
38010
38101
  id: decodeIdentifier(randomUUID()),
38011
38102
  kind: "message",
38012
38103
  sourceMessageId: messageId,
38013
- text: remaining.slice(0, SESSION_HISTORY_CHARACTER_LIMIT)
38104
+ text: remaining.slice(0, ACTIVE_TURN_TEXT_CHUNK_LIMIT)
38014
38105
  }
38015
38106
  ];
38016
- remaining = remaining.slice(SESSION_HISTORY_CHARACTER_LIMIT);
38107
+ remaining = remaining.slice(ACTIVE_TURN_TEXT_CHUNK_LIMIT);
38017
38108
  }
38018
38109
  }
38019
38110
  recordSteeringPrompt = (command) => {
38020
38111
  if (this.#dispatch.kind !== "running" || command.sessionId !== this.command.sessionId || command.targetCommandId !== this.command.commandId) {
38021
- return null;
38112
+ return false;
38022
38113
  }
38023
38114
  this.#timeline = [
38024
38115
  ...this.#timeline,
38025
38116
  { id: decodeIdentifier(command.commandId), images: command.images, kind: "user", text: command.prompt }
38026
38117
  ];
38027
- return this.updated();
38118
+ this.advance();
38119
+ return true;
38028
38120
  };
38029
38121
  timelineMessages() {
38030
38122
  return this.#timeline.flatMap((entry) => {
@@ -38046,32 +38138,39 @@ var ActiveDaemonTurn = class {
38046
38138
  return activity2 === void 0 ? [] : [{ activities: [activity2], content: [], id: entry.id, role: "assistant", text: "" }];
38047
38139
  });
38048
38140
  }
38049
- updated = () => {
38050
- this.#sequence += 1;
38051
- const turn = this.snapshot();
38052
- if (turn === null) {
38053
- throw new Error("Only a running turn can publish a snapshot");
38141
+ advance = () => {
38142
+ if (this.#dispatch.kind !== "running") {
38143
+ throw new Error("Only a running turn can advance its snapshot");
38054
38144
  }
38055
- return { kind: "session.turn.updated", sessionId: this.command.sessionId, turn };
38145
+ this.#sequence += 1;
38146
+ return this.#sequence;
38056
38147
  };
38057
38148
  snapshot = () => {
38058
38149
  if (this.#dispatch.kind !== "running") {
38059
38150
  return null;
38060
38151
  }
38061
- const completeMessages = this.timelineMessages();
38062
- const messages = completeMessages.slice(-SESSION_HISTORY_MESSAGE_LIMIT);
38152
+ const bounded2 = boundedRecoveryMessages(this.timelineMessages());
38063
38153
  return {
38064
- activities: [...this.#activities.values()].sort((left, right) => left.sequence - right.sequence),
38065
38154
  commandId: decodeIdentifier(this.command.commandId),
38066
- content: [...this.#content.values()],
38067
- prompt: this.command.prompt,
38068
- text: this.#text,
38069
- truncated: messages.length !== completeMessages.length,
38070
38155
  epoch: this.#epoch,
38156
+ hasOlder: bounded2.hasOlder,
38071
38157
  interaction: this.#interaction,
38072
- messages,
38158
+ messages: bounded2.messages,
38073
38159
  sequence: this.#sequence,
38074
- state: "running"
38160
+ state: "running",
38161
+ truncated: false
38162
+ };
38163
+ };
38164
+ recovery = () => {
38165
+ if (this.#dispatch.kind !== "running") {
38166
+ return null;
38167
+ }
38168
+ return {
38169
+ commandId: decodeIdentifier(this.command.commandId),
38170
+ epoch: this.#epoch,
38171
+ messages: this.timelineMessages(),
38172
+ state: "running",
38173
+ truncated: false
38075
38174
  };
38076
38175
  };
38077
38176
  matches = (cancellation) => cancellation.commandId === this.command.commandId && cancellation.sessionId === this.command.sessionId;
@@ -38097,6 +38196,7 @@ var ActiveDaemonTurn = class {
38097
38196
  };
38098
38197
 
38099
38198
  // src/daemon-turn-registry.ts
38199
+ var COMPLETED_TURN_RECOVERY_LIMIT = 32;
38100
38200
  var waitForTurnCompletion = (turn, waitSignal) => {
38101
38201
  waitSignal.throwIfAborted();
38102
38202
  return new Promise((resolve9, reject) => {
@@ -38123,6 +38223,7 @@ var waitForTurnCompletion = (turn, waitSignal) => {
38123
38223
  };
38124
38224
  var DaemonTurnRegistry = class {
38125
38225
  #turns = /* @__PURE__ */ new Map();
38226
+ #completed = /* @__PURE__ */ new Map();
38126
38227
  #operations;
38127
38228
  constructor(operations) {
38128
38229
  this.#operations = operations;
@@ -38134,7 +38235,22 @@ var DaemonTurnRegistry = class {
38134
38235
  };
38135
38236
  get = (commandId) => this.#turns.get(commandId);
38136
38237
  finish = (turn) => {
38238
+ const recovery = turn.recovery();
38137
38239
  this.#turns.delete(turn.command.commandId);
38240
+ if (recovery !== null) {
38241
+ this.#completed.delete(turn.command.sessionId);
38242
+ this.#completed.set(turn.command.sessionId, {
38243
+ command: turn.command,
38244
+ recovery: { ...recovery, state: "completed" }
38245
+ });
38246
+ while (this.#completed.size > COMPLETED_TURN_RECOVERY_LIMIT) {
38247
+ const oldestSessionId = this.#completed.keys().next().value;
38248
+ if (oldestSessionId === void 0) {
38249
+ break;
38250
+ }
38251
+ this.#completed.delete(oldestSessionId);
38252
+ }
38253
+ }
38138
38254
  turn.finish();
38139
38255
  };
38140
38256
  recordEvent = (event) => {
@@ -38155,7 +38271,7 @@ var DaemonTurnRegistry = class {
38155
38271
  };
38156
38272
  recordSteeringPrompt = (command) => {
38157
38273
  const target = this.#turns.get(command.targetCommandId);
38158
- return target?.recordSteeringPrompt(command) ?? null;
38274
+ return target?.recordSteeringPrompt(command) ?? false;
38159
38275
  };
38160
38276
  executionSnapshot = async (matches) => {
38161
38277
  const matching = [];
@@ -38186,12 +38302,30 @@ var DaemonTurnRegistry = class {
38186
38302
  }
38187
38303
  return null;
38188
38304
  };
38305
+ recovery = (matches) => {
38306
+ for (const turn of this.#turns.values()) {
38307
+ if (!matches(turn.command)) {
38308
+ continue;
38309
+ }
38310
+ const recovery = turn.recovery();
38311
+ if (recovery !== null) {
38312
+ return recovery;
38313
+ }
38314
+ }
38315
+ for (const completed of this.#completed.values()) {
38316
+ if (matches(completed.command)) {
38317
+ return completed.recovery;
38318
+ }
38319
+ }
38320
+ return null;
38321
+ };
38189
38322
  close = async () => {
38190
38323
  const turns = [...this.#turns.values()];
38191
38324
  for (const turn of turns) {
38192
38325
  turn.abortDaemon();
38193
38326
  }
38194
38327
  await Promise.all(turns.map((turn) => turn.completion));
38328
+ this.#completed.clear();
38195
38329
  };
38196
38330
  admitAndWait = async (turn, waitSignal, onQueued) => {
38197
38331
  const command = turn.command;
@@ -40499,10 +40633,10 @@ async function resolveSessionDirectories(directories, bindingId, directoryId2, a
40499
40633
  }
40500
40634
 
40501
40635
  // src/native-sessions/live-history-page.ts
40502
- function liveHistoryCursor(sessionId, endIndex) {
40503
- return Buffer.from(JSON.stringify({ endIndex, sessionId }), "utf8").toString("base64url");
40636
+ function encodeLiveHistoryCursor(cursor) {
40637
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
40504
40638
  }
40505
- function liveHistoryEndIndex(cursor, sessionId) {
40639
+ function decodeLiveHistoryCursor(cursor, sessionId) {
40506
40640
  try {
40507
40641
  if (!/^[A-Za-z0-9_-]{1,512}$/u.test(cursor)) {
40508
40642
  throw new Error("Invalid cursor alphabet");
@@ -40512,14 +40646,32 @@ function liveHistoryEndIndex(cursor, sessionId) {
40512
40646
  throw new Error("Invalid cursor shape");
40513
40647
  }
40514
40648
  const record2 = Object.fromEntries(Object.entries(value));
40515
- if (Object.keys(record2).sort().join(",") !== "endIndex,sessionId" || record2["sessionId"] !== sessionId || typeof record2["endIndex"] !== "number" || !Number.isSafeInteger(record2["endIndex"]) || record2["endIndex"] < 0) {
40649
+ const keys = Object.keys(record2).sort().join(",");
40650
+ const legacyNative = keys === "endIndex,sessionId";
40651
+ const kind = legacyNative ? "native" : record2["kind"];
40652
+ 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) {
40516
40653
  throw new Error("Invalid cursor target");
40517
40654
  }
40518
- return record2["endIndex"];
40655
+ if (kind === "active") {
40656
+ if (keys !== "endIndex,epoch,kind,sessionId" || typeof record2["epoch"] !== "string") {
40657
+ throw new Error("Invalid active cursor target");
40658
+ }
40659
+ return { endIndex: record2["endIndex"], epoch: record2["epoch"], kind, sessionId };
40660
+ }
40661
+ if (keys !== "endIndex,kind,sessionId" && !legacyNative) {
40662
+ throw new Error("Invalid native cursor target");
40663
+ }
40664
+ return { endIndex: record2["endIndex"], kind, sessionId };
40519
40665
  } catch (cause) {
40520
40666
  throw new AcpExecutionError("The native Session history cursor is invalid", { cause }, "invalid_cursor");
40521
40667
  }
40522
40668
  }
40669
+ function activeHistoryCursor(sessionId, epoch, endIndex) {
40670
+ return encodeLiveHistoryCursor({ endIndex, epoch, kind: "active", sessionId });
40671
+ }
40672
+ function nativeHistoryCursor(sessionId, endIndex) {
40673
+ return encodeLiveHistoryCursor({ endIndex, kind: "native", sessionId });
40674
+ }
40523
40675
  function liveHistoryCoveredTurnRanges(history) {
40524
40676
  const ranges = [];
40525
40677
  let historyIndex = history.messages.length;
@@ -40540,139 +40692,180 @@ function liveHistoryCoveredTurnRanges(history) {
40540
40692
  function historyMessageCharacters(message) {
40541
40693
  return message.text.length + (message.role === "assistant" ? JSON.stringify(message.activities).length + JSON.stringify(message.content).length : 0);
40542
40694
  }
40543
- function boundedActiveMessages(messages, messageLimit, characterLimit) {
40695
+ function isNativeHistoryImageMimeType(mimeType) {
40696
+ return mimeType === "image/png" || mimeType === "image/jpeg" || mimeType === "image/gif" || mimeType === "image/webp";
40697
+ }
40698
+ function selectMessagesBefore(messages, endIndex, messageLimit, characterLimit) {
40544
40699
  const selected = [];
40545
40700
  let remainingCharacters = characterLimit;
40546
- for (let index = messages.length - 1; index >= 0 && selected.length < messageLimit; index -= 1) {
40701
+ let startIndex = endIndex;
40702
+ for (let index = endIndex - 1; index >= 0 && selected.length < messageLimit; index -= 1) {
40547
40703
  const message = messages[index];
40548
40704
  if (message === void 0) {
40549
40705
  break;
40550
40706
  }
40551
40707
  const characters = historyMessageCharacters(message);
40552
- if (characters <= remainingCharacters) {
40553
- selected.unshift(message);
40554
- remainingCharacters -= characters;
40555
- continue;
40556
- }
40557
- const structuredCharacters = characters - message.text.length;
40558
- if (selected.length === 0 && structuredCharacters <= remainingCharacters) {
40559
- const textCharacters = remainingCharacters - structuredCharacters;
40560
- selected.unshift({ ...message, text: textCharacters === 0 ? "" : message.text.slice(-textCharacters) });
40561
- remainingCharacters = 0;
40708
+ if (characters > remainingCharacters) {
40709
+ break;
40562
40710
  }
40563
- break;
40711
+ selected.unshift(message);
40712
+ remainingCharacters -= characters;
40713
+ startIndex = index;
40564
40714
  }
40565
- return {
40566
- characters: characterLimit - remainingCharacters,
40567
- messages: selected,
40568
- truncated: selected.length !== messages.length
40569
- };
40715
+ if (selected.length === 0 && endIndex > 0) {
40716
+ throw new AcpExecutionError(
40717
+ "A native Session history item exceeds the page budget",
40718
+ {},
40719
+ "history_item_too_large"
40720
+ );
40721
+ }
40722
+ return { characters: characterLimit - remainingCharacters, messages: selected, startIndex };
40570
40723
  }
40571
40724
  function nativeActiveMessage(message) {
40572
- return message.role === "user" ? { role: "user", text: message.text } : {
40573
- activities: message.activities,
40574
- content: message.content,
40575
- role: "assistant",
40576
- text: message.text
40577
- };
40725
+ if (message.role === "assistant") {
40726
+ return {
40727
+ activities: message.activities,
40728
+ content: message.content,
40729
+ role: "assistant",
40730
+ text: message.text
40731
+ };
40732
+ }
40733
+ const images = (message.images ?? []).flatMap((image) => {
40734
+ if (image.data.length < 4 || image.data.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/u.test(image.data) || !isNativeHistoryImageMimeType(image.mimeType)) {
40735
+ return [];
40736
+ }
40737
+ return [{ data: image.data, mimeType: image.mimeType, name: image.name }];
40738
+ });
40739
+ return { ...images.length === 0 ? {} : { images }, role: "user", text: message.text };
40740
+ }
40741
+ function nativeHistoryCoversCompletedRecovery(history, recovery) {
40742
+ const root = recovery.messages.find((message) => message.role === "user" && message.id === recovery.commandId);
40743
+ if (root === void 0) {
40744
+ return false;
40745
+ }
40746
+ const rootIndex = history.messages.findLastIndex(
40747
+ (message) => message.role === "user" && message.text === root.text
40748
+ );
40749
+ if (rootIndex < 0) {
40750
+ return false;
40751
+ }
40752
+ const nativeAssistant = history.messages.slice(rootIndex + 1).filter((message) => message.role === "assistant");
40753
+ if (nativeAssistant.length === 0) {
40754
+ return false;
40755
+ }
40756
+ const recoveryAssistant = recovery.messages.filter((message) => message.role === "assistant");
40757
+ const recoveryText = recoveryAssistant.map((message) => message.text).join("");
40758
+ const nativeText = nativeAssistant.map((message) => message.text).join("");
40759
+ if (recoveryText.length > 0 && !nativeText.includes(recoveryText)) {
40760
+ return false;
40761
+ }
40762
+ const nativeContent = new Set(
40763
+ nativeAssistant.flatMap((message) => message.content).map((content) => JSON.stringify(content))
40764
+ );
40765
+ return recoveryAssistant.flatMap((message) => message.content).every((content) => nativeContent.has(JSON.stringify(content)));
40578
40766
  }
40579
- function paginateAcpSessionHistory(history, sessionId, cursor, limit) {
40767
+ function historyBeforeActiveTurn(history, activeTurn) {
40580
40768
  if (history.status === "unavailable") {
40581
- if (cursor !== null) {
40582
- throw new AcpExecutionError("Unavailable native Session history has no cursor", {}, "invalid_cursor");
40583
- }
40584
- return { cause: history.cause, nextCursor: null, status: "unavailable" };
40769
+ return history;
40585
40770
  }
40586
- const endIndex = cursor === null ? history.messages.length : liveHistoryEndIndex(cursor, sessionId);
40771
+ const activeRoot = activeTurn.messages.find(
40772
+ (message) => message.role === "user" && message.id === activeTurn.commandId
40773
+ );
40774
+ 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;
40775
+ const activeUserIndex = matchingActiveUserIndex >= 0 ? matchingActiveUserIndex : liveHistoryCoveredTurnRanges(history).find((range) => range.commandId === activeTurn.commandId)?.userIndex ?? -1;
40776
+ if (activeUserIndex < 0) {
40777
+ return history;
40778
+ }
40779
+ return {
40780
+ ...history,
40781
+ coveredCommandIds: history.coveredCommandIds.filter((id) => id !== activeTurn.commandId),
40782
+ coveredMessageIds: history.coveredMessageIds.filter(
40783
+ (id) => id !== `${activeTurn.commandId}:user` && id !== `${activeTurn.commandId}:agent`
40784
+ ),
40785
+ messages: history.messages.slice(0, activeUserIndex)
40786
+ };
40787
+ }
40788
+ function paginateAvailableNativeHistory(history, sessionId, endIndex, limit, characterLimit) {
40587
40789
  if (endIndex > history.messages.length) {
40588
40790
  throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
40589
40791
  }
40590
- const startIndex = Math.max(0, endIndex - limit);
40792
+ const selected = selectMessagesBefore(history.messages, endIndex, limit, characterLimit);
40591
40793
  const coveredMessageIds = new Set(history.coveredMessageIds);
40592
40794
  const ranges = liveHistoryCoveredTurnRanges(history);
40593
40795
  return {
40594
- coveredCommandIds: ranges.filter((range) => range.userIndex >= startIndex && range.assistantEndIndex <= endIndex).map((range) => range.commandId),
40796
+ coveredCommandIds: ranges.filter((range) => range.userIndex >= selected.startIndex && range.assistantEndIndex <= endIndex).map((range) => range.commandId),
40595
40797
  coveredMessageIds: ranges.flatMap((range) => [
40596
- ...range.userIndex >= startIndex && range.userIndex < endIndex && coveredMessageIds.has(`${range.commandId}:user`) ? [`${range.commandId}:user`] : [],
40597
- ...range.assistantStartIndex >= startIndex && range.assistantEndIndex <= endIndex && coveredMessageIds.has(`${range.commandId}:agent`) ? [`${range.commandId}:agent`] : []
40798
+ ...range.userIndex >= selected.startIndex && range.userIndex < endIndex && coveredMessageIds.has(`${range.commandId}:user`) ? [`${range.commandId}:user`] : [],
40799
+ ...range.assistantStartIndex >= selected.startIndex && range.assistantEndIndex <= endIndex && coveredMessageIds.has(`${range.commandId}:agent`) ? [`${range.commandId}:agent`] : []
40598
40800
  ]),
40599
- messages: history.messages.slice(startIndex, endIndex),
40600
- nextCursor: startIndex > 0 ? liveHistoryCursor(sessionId, startIndex) : null,
40801
+ messages: selected.messages,
40802
+ nextCursor: selected.startIndex > 0 ? nativeHistoryCursor(sessionId, selected.startIndex) : null,
40601
40803
  status: "available",
40602
40804
  truncated: history.truncated
40603
40805
  };
40604
40806
  }
40605
- function paginateLiveSessionHistory(history, activeTurn, sessionId, cursor, limit) {
40606
- if (activeTurn === null || cursor !== null) {
40607
- return paginateAcpSessionHistory(history, sessionId, cursor, limit);
40807
+ function paginateNativeHistory(history, sessionId, cursor, limit) {
40808
+ if (cursor?.kind === "active") {
40809
+ throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
40608
40810
  }
40609
40811
  if (history.status === "unavailable") {
40610
- const bounded3 = boundedActiveMessages(
40611
- activeTurn.messages.map(nativeActiveMessage),
40612
- limit,
40613
- SESSION_HISTORY_CHARACTER_LIMIT
40614
- );
40615
- return {
40616
- coveredCommandIds: [],
40617
- coveredMessageIds: [],
40618
- messages: bounded3.messages,
40619
- nextCursor: null,
40620
- status: "available",
40621
- truncated: activeTurn.truncated || bounded3.truncated
40622
- };
40812
+ if (cursor !== null) {
40813
+ throw new AcpExecutionError("Unavailable native Session history has no cursor", {}, "invalid_cursor");
40814
+ }
40815
+ return { cause: history.cause, nextCursor: null, status: "unavailable" };
40623
40816
  }
40624
- const activeRoot = activeTurn.messages.find(
40625
- (message) => message.role === "user" && message.id === activeTurn.commandId
40817
+ return paginateAvailableNativeHistory(
40818
+ history,
40819
+ sessionId,
40820
+ cursor?.endIndex ?? history.messages.length,
40821
+ limit,
40822
+ SESSION_HISTORY_CHARACTER_LIMIT
40626
40823
  );
40627
- const coveredActiveRoot = history.coveredCommandIds.includes(activeTurn.commandId);
40628
- const matchingActiveUserIndex = coveredActiveRoot ? history.messages.findLastIndex(
40629
- (message) => message.role === "user" && message.text === (activeRoot?.text ?? activeTurn.prompt)
40630
- ) : -1;
40631
- const activeUserIndex = matchingActiveUserIndex >= 0 ? matchingActiveUserIndex : liveHistoryCoveredTurnRanges(history).find((range) => range.commandId === activeTurn.commandId)?.userIndex ?? -1;
40632
- if (activeUserIndex >= 0) {
40633
- history = {
40634
- ...history,
40635
- coveredCommandIds: history.coveredCommandIds.filter((id) => id !== activeTurn.commandId),
40636
- coveredMessageIds: history.coveredMessageIds.filter(
40637
- (id) => id !== `${activeTurn.commandId}:user` && id !== `${activeTurn.commandId}:agent`
40638
- ),
40639
- messages: history.messages.slice(0, activeUserIndex + 1)
40640
- };
40641
- }
40642
- const latestNativeMessage = history.messages.at(-1);
40643
- const firstActiveMessage = activeTurn.messages[0];
40644
- const preserveNativeRoot = activeUserIndex >= 0 || firstActiveMessage?.role === "user" && latestNativeMessage?.role === "user" && latestNativeMessage.text === firstActiveMessage.text;
40645
- const activeMessages = activeTurn.messages.slice(preserveNativeRoot ? 1 : 0).map(nativeActiveMessage);
40646
- const bounded2 = boundedActiveMessages(activeMessages, limit, SESSION_HISTORY_CHARACTER_LIMIT);
40647
- let historicalMessageCount = 0;
40648
- let remainingCharacters = SESSION_HISTORY_CHARACTER_LIMIT - bounded2.characters;
40649
- const historicalMessageLimit = Math.max(0, limit - bounded2.messages.length);
40650
- for (let index = history.messages.length - 1; index >= 0 && historicalMessageCount < historicalMessageLimit; index -= 1) {
40651
- const message = history.messages[index];
40652
- if (message === void 0) {
40653
- break;
40654
- }
40655
- const characters = historyMessageCharacters(message);
40656
- if (characters > remainingCharacters) {
40657
- break;
40658
- }
40659
- historicalMessageCount += 1;
40660
- remainingCharacters -= characters;
40824
+ }
40825
+ function paginateActiveTurn(history, activeTurn, sessionId, endIndex, limit) {
40826
+ if (endIndex > activeTurn.messages.length) {
40827
+ throw new AcpExecutionError("The native Session history cursor is invalid", {}, "invalid_cursor");
40661
40828
  }
40662
- const page = historicalMessageCount === 0 ? {
40663
- coveredCommandIds: [],
40664
- coveredMessageIds: [],
40665
- messages: [],
40666
- nextCursor: history.messages.length === 0 ? null : liveHistoryCursor(sessionId, history.messages.length),
40667
- status: "available",
40668
- truncated: history.truncated
40669
- } : paginateAcpSessionHistory(history, sessionId, null, historicalMessageCount);
40829
+ const activeMessages = activeTurn.messages.map(nativeActiveMessage);
40830
+ const active = selectMessagesBefore(activeMessages, endIndex, limit, SESSION_HISTORY_CHARACTER_LIMIT);
40831
+ const historicalLimit = limit - active.messages.length;
40832
+ const historicalCharacterLimit = SESSION_HISTORY_CHARACTER_LIMIT - active.characters;
40833
+ const pendingHistoricalCursor = history.status === "available" && history.messages.length > 0 ? nativeHistoryCursor(sessionId, history.messages.length) : null;
40834
+ const latestHistoricalMessage = history.status === "available" ? history.messages.at(-1) : void 0;
40835
+ const historicalPageFits = latestHistoricalMessage === void 0 || historyMessageCharacters(latestHistoricalMessage) <= historicalCharacterLimit;
40836
+ const historical = active.startIndex === 0 && history.status === "available" && historicalLimit > 0 && historicalPageFits ? paginateAvailableNativeHistory(
40837
+ history,
40838
+ sessionId,
40839
+ history.messages.length,
40840
+ historicalLimit,
40841
+ historicalCharacterLimit
40842
+ ) : null;
40670
40843
  return {
40671
- ...page,
40672
- messages: [...page.messages, ...bounded2.messages],
40673
- truncated: page.truncated || activeTurn.truncated || bounded2.truncated
40844
+ coveredCommandIds: historical?.coveredCommandIds ?? [],
40845
+ coveredMessageIds: [
40846
+ ...historical?.coveredMessageIds ?? [],
40847
+ ...active.startIndex === 0 ? [`${activeTurn.commandId}:user`, `${activeTurn.commandId}:agent`] : []
40848
+ ],
40849
+ messages: [...historical?.messages ?? [], ...active.messages],
40850
+ nextCursor: active.startIndex > 0 ? activeHistoryCursor(sessionId, activeTurn.epoch, active.startIndex) : historical === null ? pendingHistoricalCursor : historical.nextCursor,
40851
+ status: "available",
40852
+ truncated: activeTurn.truncated || (historical?.truncated ?? false)
40674
40853
  };
40675
40854
  }
40855
+ function paginateLiveSessionHistory(history, activeTurn, sessionId, encodedCursor, limit) {
40856
+ const cursor = encodedCursor === null ? null : decodeLiveHistoryCursor(encodedCursor, sessionId);
40857
+ if (activeTurn === null || activeTurn.state === "completed" && history.status === "available" && nativeHistoryCoversCompletedRecovery(history, activeTurn)) {
40858
+ return paginateNativeHistory(history, sessionId, cursor, limit);
40859
+ }
40860
+ const historical = historyBeforeActiveTurn(history, activeTurn);
40861
+ if (cursor?.kind === "native") {
40862
+ return paginateNativeHistory(historical, sessionId, cursor, limit);
40863
+ }
40864
+ if (cursor !== null && cursor.epoch !== activeTurn.epoch) {
40865
+ throw new AcpExecutionError("The active Turn history cursor is stale", {}, "stale_cursor");
40866
+ }
40867
+ return paginateActiveTurn(historical, activeTurn, sessionId, cursor?.endIndex ?? activeTurn.messages.length, limit);
40868
+ }
40676
40869
  function mergeLiveSessionHistory(history, activeTurn, sessionId) {
40677
40870
  const page = paginateLiveSessionHistory(history, activeTurn, sessionId, null, 1e3);
40678
40871
  if (page.status === "unavailable") {
@@ -40683,7 +40876,7 @@ function mergeLiveSessionHistory(history, activeTurn, sessionId) {
40683
40876
  coveredMessageIds: page.coveredMessageIds,
40684
40877
  messages: page.messages,
40685
40878
  status: "available",
40686
- truncated: page.truncated
40879
+ truncated: page.truncated || page.nextCursor !== null
40687
40880
  };
40688
40881
  }
40689
40882
 
@@ -41202,15 +41395,13 @@ async function executePrompt({
41202
41395
  });
41203
41396
  operations.transitionExecution(command.commandId, "running");
41204
41397
  if (availability.disposition === SessionPromptDisposition.Steer) {
41205
- const updated = recordSteeringPrompt();
41206
- if (updated === null) {
41398
+ if (!recordSteeringPrompt()) {
41207
41399
  throw new AcpExecutionError(
41208
41400
  "The steering target is unavailable",
41209
41401
  {},
41210
41402
  "prompt_queue_target_unavailable"
41211
41403
  );
41212
41404
  }
41213
- publisher.publish(command.sessionId, updated);
41214
41405
  }
41215
41406
  const stream = activateAttempt();
41216
41407
  publisher.publish(command.sessionId, {
@@ -41598,7 +41789,7 @@ function createDaemonCommandHandler({
41598
41789
  resolveEnvironment: resolveAgentCatalogEnvironment
41599
41790
  });
41600
41791
  const queueChanged = (sessionId) => publisher.publish(sessionId, { kind: "session.queue.changed", sessionId });
41601
- const activeTurnSnapshot = (sessionId) => activeTurns.snapshot((command) => command.sessionId === sessionId);
41792
+ const activeTurnRecovery = (sessionId) => activeTurns.recovery((command) => command.sessionId === sessionId);
41602
41793
  const nativeRequestHandler = createNativeSessionRequestHandler({
41603
41794
  audit,
41604
41795
  diagnostics,
@@ -41619,7 +41810,7 @@ function createDaemonCommandHandler({
41619
41810
  directories,
41620
41811
  audit,
41621
41812
  sessions,
41622
- activeTurnSnapshot,
41813
+ activeTurnRecovery,
41623
41814
  signal
41624
41815
  );
41625
41816
  return;
@@ -41633,7 +41824,7 @@ function createDaemonCommandHandler({
41633
41824
  directories,
41634
41825
  audit,
41635
41826
  sessions,
41636
- activeTurnSnapshot,
41827
+ activeTurnRecovery,
41637
41828
  signal
41638
41829
  );
41639
41830
  return;
@@ -42125,6 +42316,7 @@ async function runRelayConnection({
42125
42316
  }) {
42126
42317
  const relayUrl = new URL(config2.relayUrl);
42127
42318
  relayUrl.searchParams.set("liveness", "1");
42319
+ relayUrl.searchParams.set("sessionEventDelivery", "reliable-v1");
42128
42320
  const socket = await connectRelay(relayUrl, signal);
42129
42321
  diagnostics.connectionOpened(connectionEpoch);
42130
42322
  const connectionController = new AbortController();
@@ -42244,6 +42436,10 @@ async function runRelayConnection({
42244
42436
  const message = decodeServiceToDaemonMessage(encoded);
42245
42437
  const kind = message.kind;
42246
42438
  if (kind === RUNTIME_LIVENESS_READY_KIND) {
42439
+ publisher.connect(
42440
+ socket,
42441
+ message.sessionEventDelivery === "reliable-v1" ? "reliable" : "ephemeral"
42442
+ );
42247
42443
  connectionLiveness.start();
42248
42444
  return;
42249
42445
  }
@@ -42251,6 +42447,18 @@ async function runRelayConnection({
42251
42447
  connectionLiveness.acknowledge(message);
42252
42448
  return;
42253
42449
  }
42450
+ if (kind === SESSION_EVENT_ACK_KIND) {
42451
+ publisher.acknowledge(message);
42452
+ return;
42453
+ }
42454
+ if (kind === SESSION_EVENT_DEFERRED_KIND) {
42455
+ publisher.defer(message);
42456
+ return;
42457
+ }
42458
+ if (kind === SESSION_EVENT_REJECTED_KIND) {
42459
+ publisher.quarantine(message);
42460
+ return;
42461
+ }
42254
42462
  commands.handle(message);
42255
42463
  } catch (error62) {
42256
42464
  console.error("Mobius daemon rejected a message", error62);
@@ -42265,7 +42473,6 @@ async function runRelayConnection({
42265
42473
  socket.addEventListener("close", handleClose);
42266
42474
  socket.addEventListener("error", handleError);
42267
42475
  socket.addEventListener("message", handleMessage);
42268
- publisher.connect(socket);
42269
42476
  if (signal.aborted) {
42270
42477
  stop();
42271
42478
  }
@@ -42276,54 +42483,377 @@ async function runRelayConnection({
42276
42483
  }
42277
42484
  }
42278
42485
 
42486
+ // src/relay-connection/session-event-outbox.ts
42487
+ import { randomUUID as randomUUID6 } from "node:crypto";
42488
+ import { mkdirSync as mkdirSync3 } from "node:fs";
42489
+ import { dirname as dirname5 } from "node:path";
42490
+ import { DatabaseSync } from "node:sqlite";
42491
+ var DEFAULT_STANDARD_EVENT_BYTES = 96 * 1024 * 1024;
42492
+ var DEFAULT_CRITICAL_EVENT_BYTES = 128 * 1024 * 1024;
42493
+ var MAX_DEAD_LETTERS = 1024;
42494
+ var DEFAULT_LIMITS = {
42495
+ criticalEventBytes: DEFAULT_CRITICAL_EVENT_BYTES,
42496
+ standardEventBytes: DEFAULT_STANDARD_EVENT_BYTES
42497
+ };
42498
+ var SessionEventOutboxCapacityError = class extends Error {
42499
+ code = "session_event_outbox_full";
42500
+ constructor() {
42501
+ super("The persistent Session event outbox is full");
42502
+ this.name = "SessionEventOutboxCapacityError";
42503
+ }
42504
+ };
42505
+ function rowNumber(row, field) {
42506
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
42507
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
42508
+ throw new Error(`SQLite returned an invalid ${field} value`);
42509
+ }
42510
+ return value;
42511
+ }
42512
+ function rowString(row, field) {
42513
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
42514
+ if (typeof value !== "string" || value.length === 0) {
42515
+ throw new Error(`SQLite returned an invalid ${field} value`);
42516
+ }
42517
+ return value;
42518
+ }
42519
+ function validateLimits(limits) {
42520
+ if (!Number.isSafeInteger(limits.standardEventBytes) || limits.standardEventBytes < 1 || !Number.isSafeInteger(limits.criticalEventBytes) || limits.criticalEventBytes < limits.standardEventBytes) {
42521
+ throw new Error("Session event outbox limits are invalid");
42522
+ }
42523
+ }
42524
+ var SessionEventOutbox = class {
42525
+ #database;
42526
+ #limits;
42527
+ constructor(path2, limits = DEFAULT_LIMITS) {
42528
+ validateLimits(limits);
42529
+ mkdirSync3(dirname5(path2), { mode: 448, recursive: true });
42530
+ this.#limits = limits;
42531
+ this.#database = new DatabaseSync(path2);
42532
+ this.#database.exec("PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL; PRAGMA busy_timeout = 5000;");
42533
+ this.#database.exec(`
42534
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox (
42535
+ ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
42536
+ event_id TEXT NOT NULL UNIQUE,
42537
+ session_id TEXT NOT NULL,
42538
+ source_seq INTEGER NOT NULL UNIQUE,
42539
+ byte_length INTEGER NOT NULL,
42540
+ envelope_json TEXT NOT NULL
42541
+ ) STRICT;
42542
+ CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
42543
+ ON relay_session_event_outbox(source_seq);
42544
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
42545
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
42546
+ stream_id TEXT NOT NULL,
42547
+ next_source_seq INTEGER NOT NULL,
42548
+ total_bytes INTEGER NOT NULL
42549
+ ) STRICT;
42550
+ CREATE TABLE IF NOT EXISTS relay_session_event_dead_letters (
42551
+ ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
42552
+ event_id TEXT NOT NULL UNIQUE,
42553
+ session_id TEXT NOT NULL,
42554
+ rejection_code TEXT NOT NULL,
42555
+ envelope_json TEXT NOT NULL
42556
+ ) STRICT;
42557
+ `);
42558
+ this.#database.prepare(
42559
+ `INSERT OR IGNORE INTO relay_session_event_outbox_state (
42560
+ singleton, stream_id, next_source_seq, total_bytes
42561
+ ) VALUES (1, ?, 1, 0)`
42562
+ ).run(randomUUID6());
42563
+ }
42564
+ acknowledge = (acknowledgement) => this.#remove(acknowledgement.eventId, acknowledgement.sessionId);
42565
+ close = () => {
42566
+ this.#database.close();
42567
+ };
42568
+ enqueue = (sessionId, payload, critical) => {
42569
+ this.#database.exec("BEGIN IMMEDIATE");
42570
+ try {
42571
+ const state = this.#database.prepare(
42572
+ `SELECT stream_id, next_source_seq, total_bytes
42573
+ FROM relay_session_event_outbox_state
42574
+ WHERE singleton = 1`
42575
+ ).get();
42576
+ const sourceSeq = rowNumber(state, "next_source_seq");
42577
+ if (sourceSeq < 1 || !Number.isSafeInteger(sourceSeq + 1)) {
42578
+ throw new Error("The Session event source sequence is exhausted");
42579
+ }
42580
+ const envelope2 = {
42581
+ delivery: {
42582
+ eventId: randomUUID6(),
42583
+ kind: "reliable",
42584
+ sourceSeq: sourceSeq.toString(),
42585
+ streamId: rowString(state, "stream_id")
42586
+ },
42587
+ kind: SESSION_EVENT_KIND,
42588
+ payload,
42589
+ sessionId
42590
+ };
42591
+ const encoded = encodeSessionEventEnvelope(envelope2);
42592
+ const byteLength = Buffer.byteLength(encoded, "utf8");
42593
+ const maximumBytes = critical ? this.#limits.criticalEventBytes : this.#limits.standardEventBytes;
42594
+ if (rowNumber(state, "total_bytes") + byteLength > maximumBytes) {
42595
+ throw new SessionEventOutboxCapacityError();
42596
+ }
42597
+ this.#database.prepare(
42598
+ `INSERT INTO relay_session_event_outbox (
42599
+ event_id, session_id, source_seq, byte_length, envelope_json
42600
+ ) VALUES (?, ?, ?, ?, ?)`
42601
+ ).run(envelope2.delivery.eventId, sessionId, sourceSeq, byteLength, encoded);
42602
+ this.#database.prepare(
42603
+ `UPDATE relay_session_event_outbox_state
42604
+ SET next_source_seq = next_source_seq + 1,
42605
+ total_bytes = total_bytes + ?
42606
+ WHERE singleton = 1`
42607
+ ).run(byteLength);
42608
+ this.#database.exec("COMMIT");
42609
+ return envelope2;
42610
+ } catch (error62) {
42611
+ this.#database.exec("ROLLBACK");
42612
+ throw error62;
42613
+ }
42614
+ };
42615
+ pending = (limit, excludedEventIds) => {
42616
+ if (!Number.isSafeInteger(limit) || limit < 1) {
42617
+ throw new Error("Session event replay limit must be a positive integer");
42618
+ }
42619
+ const exclusion = excludedEventIds.length === 0 ? "" : `WHERE event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`;
42620
+ return this.#database.prepare(
42621
+ `SELECT envelope_json
42622
+ FROM relay_session_event_outbox
42623
+ ${exclusion}
42624
+ ORDER BY source_seq
42625
+ LIMIT ?`
42626
+ ).all(...excludedEventIds, limit).map((row) => {
42627
+ const envelope2 = decodeSessionEventEnvelope(rowString(row, "envelope_json"));
42628
+ if (!("delivery" in envelope2)) {
42629
+ throw new Error("SQLite returned an invalid reliable Session event envelope");
42630
+ }
42631
+ return envelope2;
42632
+ });
42633
+ };
42634
+ quarantine = (rejection) => {
42635
+ this.#database.exec("BEGIN IMMEDIATE");
42636
+ try {
42637
+ const stored = this.#database.prepare(
42638
+ `SELECT byte_length, envelope_json
42639
+ FROM relay_session_event_outbox
42640
+ WHERE event_id = ? AND session_id = ?`
42641
+ ).get(rejection.eventId, rejection.sessionId);
42642
+ if (stored === void 0) {
42643
+ this.#database.exec("COMMIT");
42644
+ return false;
42645
+ }
42646
+ this.#database.prepare(
42647
+ `INSERT INTO relay_session_event_dead_letters (
42648
+ event_id, session_id, rejection_code, envelope_json
42649
+ ) VALUES (?, ?, ?, ?)`
42650
+ ).run(rejection.eventId, rejection.sessionId, rejection.code, rowString(stored, "envelope_json"));
42651
+ this.#deleteStored(rejection.eventId, rejection.sessionId, rowNumber(stored, "byte_length"));
42652
+ this.#database.prepare(
42653
+ `DELETE FROM relay_session_event_dead_letters
42654
+ WHERE ordinal NOT IN (
42655
+ SELECT ordinal FROM relay_session_event_dead_letters
42656
+ ORDER BY ordinal DESC LIMIT ?
42657
+ )`
42658
+ ).run(MAX_DEAD_LETTERS);
42659
+ this.#database.exec("COMMIT");
42660
+ return true;
42661
+ } catch (error62) {
42662
+ this.#database.exec("ROLLBACK");
42663
+ throw error62;
42664
+ }
42665
+ };
42666
+ #deleteStored = (eventId, sessionId, byteLength) => {
42667
+ const deletion = this.#database.prepare(
42668
+ `DELETE FROM relay_session_event_outbox
42669
+ WHERE event_id = ? AND session_id = ?`
42670
+ ).run(eventId, sessionId);
42671
+ if (deletion.changes !== 1) {
42672
+ throw new Error("The Session event could not be removed from the outbox");
42673
+ }
42674
+ const state = this.#database.prepare(
42675
+ `UPDATE relay_session_event_outbox_state
42676
+ SET total_bytes = total_bytes - ?
42677
+ WHERE singleton = 1 AND total_bytes >= ?`
42678
+ ).run(byteLength, byteLength);
42679
+ if (state.changes !== 1) {
42680
+ throw new Error("The Session event outbox capacity state is inconsistent");
42681
+ }
42682
+ };
42683
+ #remove = (eventId, sessionId) => {
42684
+ this.#database.exec("BEGIN IMMEDIATE");
42685
+ try {
42686
+ const stored = this.#database.prepare(
42687
+ `SELECT byte_length
42688
+ FROM relay_session_event_outbox
42689
+ WHERE event_id = ? AND session_id = ?`
42690
+ ).get(eventId, sessionId);
42691
+ if (stored === void 0) {
42692
+ this.#database.exec("COMMIT");
42693
+ return false;
42694
+ }
42695
+ this.#deleteStored(eventId, sessionId, rowNumber(stored, "byte_length"));
42696
+ this.#database.exec("COMMIT");
42697
+ return true;
42698
+ } catch (error62) {
42699
+ this.#database.exec("ROLLBACK");
42700
+ throw error62;
42701
+ }
42702
+ };
42703
+ };
42704
+
42279
42705
  // src/relay-connection/session-event-publisher.ts
42280
42706
  import { WebSocket as WebSocket9 } from "undici";
42281
- var MAX_LIVE_EVENT_BUFFERED_BYTES = 16 * 1024 * 1024;
42707
+ var SESSION_EVENT_REPLAY_WINDOW = 16;
42708
+ var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
42709
+ function reservesSessionEventCapacity(event) {
42710
+ if (event.kind === SESSION_COMPLETED_KIND || event.kind === SESSION_FAILED_KIND) {
42711
+ return true;
42712
+ }
42713
+ return event.kind === AGENT_ACTIVITY_KIND && (event.activityType === "progress" || event.activityType === "reasoning") && (event.status === "completed" || event.status === "failed");
42714
+ }
42282
42715
  var DaemonSessionEventPublisher = class {
42283
- #socket = null;
42284
- #reportedDrop = false;
42285
- connect = (socket) => {
42286
- this.#socket = socket;
42287
- this.#reportedDrop = false;
42716
+ #inFlight = /* @__PURE__ */ new Map();
42717
+ #outbox;
42718
+ #reportedCapacityKinds = /* @__PURE__ */ new Set();
42719
+ #timer;
42720
+ #connection = { kind: "disconnected" };
42721
+ #deferredReplay = null;
42722
+ #inFlightBytes = 0;
42723
+ constructor(outbox, timer) {
42724
+ this.#outbox = outbox;
42725
+ this.#timer = timer;
42726
+ }
42727
+ acknowledge = (acknowledgement) => {
42728
+ if (!this.#outbox.acknowledge(acknowledgement)) {
42729
+ return;
42730
+ }
42731
+ this.#removeInFlight(acknowledgement.eventId);
42732
+ this.#publishNextBatch();
42733
+ };
42734
+ close = () => {
42735
+ this.#deferredReplay?.cancel();
42736
+ this.#deferredReplay = null;
42737
+ this.#connection = { kind: "disconnected" };
42738
+ this.#clearInFlight();
42739
+ this.#outbox.close();
42740
+ };
42741
+ connect = (socket, delivery) => {
42742
+ this.#connection = { delivery, kind: "connected", socket };
42743
+ this.#clearInFlight();
42744
+ this.#publishNextBatch();
42745
+ };
42746
+ defer = (deferred) => {
42747
+ if (!this.#inFlight.has(deferred.eventId)) {
42748
+ return;
42749
+ }
42750
+ this.#clearInFlight();
42751
+ if (this.#deferredReplay !== null) {
42752
+ return;
42753
+ }
42754
+ this.#deferredReplay = this.#timer.schedule(() => {
42755
+ this.#deferredReplay = null;
42756
+ this.#publishNextBatch();
42757
+ }, deferred.retryAfterMilliseconds);
42288
42758
  };
42289
42759
  disconnect = (socket) => {
42290
- if (this.#socket === socket) {
42291
- this.#socket = null;
42760
+ if (this.#connection.kind !== "connected" || this.#connection.socket !== socket) {
42761
+ return;
42292
42762
  }
42763
+ this.#connection = { kind: "disconnected" };
42764
+ this.#clearInFlight();
42293
42765
  };
42294
42766
  publish = (sessionId, event) => {
42295
- const socket = this.#socket;
42296
- if (socket === null || socket.readyState !== WebSocket9.OPEN) {
42297
- this.#reportDrop();
42767
+ try {
42768
+ this.#outbox.enqueue(sessionId, encodeRecordedSessionEvent(event), reservesSessionEventCapacity(event));
42769
+ } catch (error62) {
42770
+ if (!(error62 instanceof SessionEventOutboxCapacityError)) {
42771
+ throw error62;
42772
+ }
42773
+ if (!this.#reportedCapacityKinds.has(event.kind)) {
42774
+ this.#reportedCapacityKinds.add(event.kind);
42775
+ console.warn(
42776
+ `Mobius daemon could not persist "${event.kind}" because the Session event outbox is full`
42777
+ );
42778
+ }
42298
42779
  return;
42299
42780
  }
42300
- if (socket.bufferedAmount >= MAX_LIVE_EVENT_BUFFERED_BYTES) {
42301
- this.#socket = null;
42302
- socket.close(1013, "Live event backpressure");
42303
- this.#reportDrop();
42781
+ this.#publishNextBatch();
42782
+ };
42783
+ quarantine = (rejection) => {
42784
+ if (!this.#outbox.quarantine(rejection)) {
42304
42785
  return;
42305
42786
  }
42306
- try {
42307
- socket.send(
42308
- encodeDaemonToServiceMessage({
42309
- kind: SESSION_EVENT_KIND,
42310
- payload: encodeRecordedSessionEvent(event),
42311
- sessionId
42312
- })
42313
- );
42314
- } catch {
42315
- this.#reportDrop();
42316
- }
42787
+ this.#removeInFlight(rejection.eventId);
42788
+ console.error(`Mobius Relay rejected Session event ${rejection.eventId} with code ${rejection.code}`);
42789
+ this.#publishNextBatch();
42317
42790
  };
42318
- close = () => {
42319
- this.#socket = null;
42791
+ #clearInFlight = () => {
42792
+ this.#inFlight.clear();
42793
+ this.#inFlightBytes = 0;
42320
42794
  };
42321
- #reportDrop = () => {
42322
- if (this.#reportedDrop) {
42795
+ #publishNextBatch = () => {
42796
+ if (this.#deferredReplay !== null || this.#connection.kind !== "connected") {
42323
42797
  return;
42324
42798
  }
42325
- this.#reportedDrop = true;
42326
- console.warn("Mobius daemon dropped live Session events while the Relay was unavailable");
42799
+ while (this.#inFlight.size < SESSION_EVENT_REPLAY_WINDOW) {
42800
+ const batch = this.#outbox.pending(SESSION_EVENT_REPLAY_WINDOW - this.#inFlight.size, [
42801
+ ...this.#inFlight.keys()
42802
+ ]);
42803
+ let sent = 0;
42804
+ for (const envelope2 of batch) {
42805
+ const encoded = encodeDaemonToServiceMessage(
42806
+ this.#connection.delivery === "reliable" ? envelope2 : {
42807
+ kind: envelope2.kind,
42808
+ payload: envelope2.payload,
42809
+ sessionId: envelope2.sessionId
42810
+ }
42811
+ );
42812
+ const byteLength = Buffer.byteLength(encoded, "utf8");
42813
+ if (this.#inFlightBytes + byteLength > SESSION_EVENT_REPLAY_BYTE_LIMIT) {
42814
+ return;
42815
+ }
42816
+ if (!this.#send(encoded)) {
42817
+ this.#clearInFlight();
42818
+ return;
42819
+ }
42820
+ if (this.#connection.delivery === "ephemeral") {
42821
+ this.#outbox.acknowledge({
42822
+ eventId: envelope2.delivery.eventId,
42823
+ kind: "session.event.ack",
42824
+ outcome: { kind: "relayed" },
42825
+ sessionId: envelope2.sessionId
42826
+ });
42827
+ sent += 1;
42828
+ continue;
42829
+ }
42830
+ this.#inFlight.set(envelope2.delivery.eventId, { byteLength });
42831
+ this.#inFlightBytes += byteLength;
42832
+ sent += 1;
42833
+ }
42834
+ if (sent === 0) {
42835
+ return;
42836
+ }
42837
+ }
42838
+ };
42839
+ #removeInFlight = (eventId) => {
42840
+ const inFlight = this.#inFlight.get(eventId);
42841
+ if (inFlight === void 0) {
42842
+ return;
42843
+ }
42844
+ this.#inFlight.delete(eventId);
42845
+ this.#inFlightBytes -= inFlight.byteLength;
42846
+ };
42847
+ #send = (encoded) => {
42848
+ if (this.#connection.kind !== "connected" || this.#connection.socket.readyState !== WebSocket9.OPEN) {
42849
+ return false;
42850
+ }
42851
+ try {
42852
+ this.#connection.socket.send(encoded);
42853
+ return true;
42854
+ } catch {
42855
+ return false;
42856
+ }
42327
42857
  };
42328
42858
  };
42329
42859
 
@@ -42384,7 +42914,7 @@ async function installDetectedAgentIntegrations(config2, installations, signal)
42384
42914
  {
42385
42915
  agentId: agent.id,
42386
42916
  kind: AGENT_INSTALL_REQUEST_KIND,
42387
- requestId: `automatic-integration-${randomUUID6()}`
42917
+ requestId: `automatic-integration-${randomUUID7()}`
42388
42918
  },
42389
42919
  signal
42390
42920
  );
@@ -42406,7 +42936,11 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer, opti
42406
42936
  join17(stateDirectory, "sessions", `${createHash5("sha256").update(config2.channelId).digest("hex")}.sqlite`)
42407
42937
  );
42408
42938
  const operations = new DaemonOperationState();
42409
- const publisher = new DaemonSessionEventPublisher();
42939
+ const channelHash = createHash5("sha256").update(config2.channelId).digest("hex");
42940
+ const publisher = new DaemonSessionEventPublisher(
42941
+ new SessionEventOutbox(join17(stateDirectory, "events", `${channelHash}.sqlite`)),
42942
+ timer
42943
+ );
42410
42944
  const activeTurns = new DaemonTurnRegistry(operations);
42411
42945
  const recordSessionEvent = (sessionId, event) => {
42412
42946
  publisher.publish(sessionId, activeTurns.recordEvent(event));
@@ -42598,9 +43132,9 @@ import { basename as basename6, resolve as resolve7 } from "node:path";
42598
43132
  import { fetch as fetch4 } from "undici";
42599
43133
 
42600
43134
  // src/storage/runtime-installation-store.ts
42601
- import { createHash as createHash6, randomUUID as randomUUID7 } from "node:crypto";
43135
+ import { createHash as createHash6, randomUUID as randomUUID8 } from "node:crypto";
42602
43136
  import { mkdir as mkdir4, open as open5, readdir as readdir3, rename as rename3, unlink, writeFile as writeFile3 } from "node:fs/promises";
42603
- import { dirname as dirname5, join as join18, resolve as resolve6 } from "node:path";
43137
+ import { dirname as dirname6, join as join18, resolve as resolve6 } from "node:path";
42604
43138
  var APPLICATION_ID_PATTERN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+){1,31}$/;
42605
43139
  var CONNECTION_ID_PATTERN = /^mcn_[a-f0-9]{32}$/;
42606
43140
  var INSTALLATION_ID_PATTERN = /^min_[a-f0-9]{32}$/;
@@ -42728,10 +43262,10 @@ function parseRelayOrigin(value) {
42728
43262
  return origin;
42729
43263
  }
42730
43264
  function connectionId() {
42731
- return `mcn_${randomUUID7().replaceAll("-", "")}`;
43265
+ return `mcn_${randomUUID8().replaceAll("-", "")}`;
42732
43266
  }
42733
43267
  function installationId() {
42734
- return `min_${randomUUID7().replaceAll("-", "")}`;
43268
+ return `min_${randomUUID8().replaceAll("-", "")}`;
42735
43269
  }
42736
43270
  function channelIndexFileName(channelId) {
42737
43271
  return `mci_${createHash6("sha256").update(channelId).digest("hex")}.json`;
@@ -42842,8 +43376,8 @@ async function readJsonFile(path2, signal, missingCode) {
42842
43376
  }
42843
43377
  async function writeJsonAtomically(path2, value, signal) {
42844
43378
  signal.throwIfAborted();
42845
- const directory = dirname5(path2);
42846
- const temporaryPath = join18(directory, `.${randomUUID7()}.tmp`);
43379
+ const directory = dirname6(path2);
43380
+ const temporaryPath = join18(directory, `.${randomUUID8()}.tmp`);
42847
43381
  try {
42848
43382
  await mkdir4(directory, { mode: 448, recursive: true });
42849
43383
  signal.throwIfAborted();