@moxt-ai/mobius 0.0.26 → 0.0.27

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.
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { basename, join } from "node:path";
3
3
  import { styleText } from "node:util";
4
- import { AGENT_ACTIVITY_KIND, AGENT_INSTALL_CANCEL_KIND, AGENT_INSTALL_CANCELLED_KIND, AGENT_INSTALL_COMPLETED_KIND, AGENT_INSTALL_FAILED_KIND, AGENT_INSTALL_PROGRESS_KIND, AGENT_INSTALL_REQUEST_KIND, AGENT_INSTALL_STARTED_KIND, AGENT_MESSAGE_DELTA_KIND, BOT_BINDING_APPROVAL_REQUEST_KIND, BOT_BINDING_APPROVED_KIND, BOT_BINDING_UNAVAILABLE_KIND, BOT_SESSION_PROMPT_KIND, BotExecutionBindingUnavailableEvent, decodeAgentInstallCancelCommand, decodeAgentInstallRequestCommand, decodeBotExecutionBindingApprovalRequest, decodeBotExecutionBindingApprovedEvent, decodeBotExecutionBindingUnavailableEvent, decodeBotSessionPromptCommand, decodeMessageKind, decodeNativeSessionCancel, decodeNativeSessionCommand, decodeNativeSessionTurnForkCommand, decodeRuntimeHeartbeatAckEvent, decodeRuntimeLivenessReadyEvent, decodeSessionCancelCommand, decodeSessionConfigureCommand, decodeSessionElicitationRespondCommand, decodeSessionEventAcknowledgement, decodeSessionEventEnvelope, decodeSessionEventRejection, decodeSessionPermissionRespondCommand, decodeSessionPrepareCommand, decodeSessionPromptCommand, decodeWorkspaceListCommand, encodeAgentActivityEvent, encodeAgentContentEvent, encodeAgentInstallCancelledEvent, encodeAgentInstallCompletedEvent, encodeAgentInstallFailedEvent, encodeAgentInstallProgressEvent, encodeAgentInstallStartedEvent, encodeAgentMessageDeltaEvent, encodeBotExecutionBindingApprovedEvent, encodeBotExecutionBindingUnavailableEvent, encodeNativeSessionResult, encodeSessionCommandsUpdatedEvent, encodeSessionCompletedEvent, encodeSessionConfigUpdatedEvent, encodeSessionElicitationRequestedEvent, encodeSessionElicitationResolvedEvent, encodeSessionFailedEvent, encodeSessionInfoUpdatedEvent, encodeSessionOperationFailedEvent, encodeSessionPermissionRequestedEvent, encodeSessionPermissionResolvedEvent, encodeSessionReadyEvent, encodeSessionStartedEvent, encodeSessionUsageUpdatedEvent, encodeWorkspaceEntriesEvent, NATIVE_SESSION_CANCEL_KIND, NATIVE_SESSION_REQUEST_KIND, NATIVE_SESSION_TURN_FORK_KIND, OwnerBotReplyRoute, RUNTIME_HEARTBEAT_ACK_KIND, RUNTIME_LIVENESS_READY_KIND, SESSION_CANCEL_KIND, SESSION_COMPLETED_KIND, SESSION_CONFIGURE_KIND, SESSION_ELICITATION_RESPOND_KIND, SESSION_EVENT_ACK_KIND, SESSION_EVENT_REJECTED_KIND, SESSION_FAILED_KIND, SESSION_PERMISSION_RESPOND_KIND, SESSION_PREPARE_KIND, SESSION_PROMPT_KIND, SESSION_STARTED_KIND, SessionPromptDisposition, WORKSPACE_LIST_KIND, } from "@moxt-ai/mobius-protocol";
4
+ import { AGENT_ACTIVITY_KIND, AGENT_INSTALL_CANCEL_KIND, AGENT_INSTALL_CANCELLED_KIND, AGENT_INSTALL_COMPLETED_KIND, AGENT_INSTALL_FAILED_KIND, AGENT_INSTALL_PROGRESS_KIND, AGENT_INSTALL_REQUEST_KIND, AGENT_INSTALL_STARTED_KIND, AGENT_MESSAGE_DELTA_KIND, BOT_BINDING_APPROVAL_REQUEST_KIND, BOT_BINDING_APPROVED_KIND, BOT_BINDING_UNAVAILABLE_KIND, BOT_SESSION_PROMPT_KIND, BotExecutionBindingUnavailableEvent, decodeAgentInstallCancelCommand, decodeAgentInstallRequestCommand, decodeBotExecutionBindingApprovalRequest, decodeBotExecutionBindingApprovedEvent, decodeBotExecutionBindingUnavailableEvent, decodeBotSessionPromptCommand, decodeMessageKind, decodeNativeSessionCancel, decodeNativeSessionCommand, decodeNativeSessionTurnForkCommand, decodeRuntimeDelivery, decodeRuntimeDeliveryCommitted, decodeRuntimeHeartbeatAckEvent, decodeRuntimeLivenessReadyEvent, decodeSessionCancelCommand, decodeSessionConfigureCommand, decodeSessionElicitationRespondCommand, decodeSessionEventAcknowledgement, decodeSessionEventEnvelope, decodeSessionEventRejection, decodeSessionPermissionRespondCommand, decodeSessionPrepareCommand, decodeSessionPromptCommand, decodeWorkspaceListCommand, encodeAgentActivityEvent, encodeAgentContentEvent, encodeAgentInstallCancelledEvent, encodeAgentInstallCompletedEvent, encodeAgentInstallFailedEvent, encodeAgentInstallProgressEvent, encodeAgentInstallStartedEvent, encodeAgentMessageDeltaEvent, encodeBotExecutionBindingApprovedEvent, encodeBotExecutionBindingUnavailableEvent, encodeNativeSessionResult, encodeRuntimeDeliveryReceived, encodeSessionCommandsUpdatedEvent, encodeSessionCompletedEvent, encodeSessionConfigUpdatedEvent, encodeSessionElicitationRequestedEvent, encodeSessionElicitationResolvedEvent, encodeSessionFailedEvent, encodeSessionInfoUpdatedEvent, encodeSessionOperationFailedEvent, encodeSessionPermissionRequestedEvent, encodeSessionPermissionResolvedEvent, encodeSessionReadyEvent, encodeSessionStartedEvent, encodeSessionUsageUpdatedEvent, encodeWorkspaceEntriesEvent, NATIVE_SESSION_CANCEL_KIND, NATIVE_SESSION_REQUEST_KIND, NATIVE_SESSION_TURN_FORK_KIND, OwnerBotReplyRoute, RUNTIME_DELIVERY_COMMITTED_KIND, RUNTIME_DELIVERY_KIND, RUNTIME_DELIVERY_RECEIVED_KIND, RUNTIME_HEARTBEAT_ACK_KIND, RUNTIME_LIVENESS_READY_KIND, SESSION_CANCEL_KIND, SESSION_COMPLETED_KIND, SESSION_CONFIGURE_KIND, SESSION_ELICITATION_RESPOND_KIND, SESSION_EVENT_ACK_KIND, SESSION_EVENT_REJECTED_KIND, SESSION_FAILED_KIND, SESSION_PERMISSION_RESPOND_KIND, SESSION_PREPARE_KIND, SESSION_PROMPT_KIND, SESSION_STARTED_KIND, SessionPromptDisposition, WORKSPACE_LIST_KIND, } from "@moxt-ai/mobius-protocol";
5
5
  import { fetch, WebSocket } from "undici";
6
6
  import { AcpExecutionError, AcpSessionManager } from "./acp/session-manager.js";
7
7
  import { SqliteAcpSessionStore } from "./acp/session-store.js";
@@ -22,7 +22,8 @@ import { PromptArtifactError, resolvePromptArtifacts } from "./prompt-artifact-r
22
22
  import { connectRelay } from "./relay-connection/connect.js";
23
23
  import { RelayConnectionDiagnostics } from "./relay-connection/diagnostics.js";
24
24
  import { RelayConnectionLiveness } from "./relay-connection/liveness.js";
25
- import { SessionEventOutbox } from "./relay-connection/session-event-outbox.js";
25
+ import { relayHttpEndpoint, runtimeAuthorization } from "./relay-connection/runtime-authentication.js";
26
+ import { RuntimeDeliveryInboxCapacityError, SessionEventOutbox } from "./relay-connection/session-event-outbox.js";
26
27
  import { systemDaemonTimer } from "./relay-connection/timer.js";
27
28
  import { readStateDirectory } from "./storage/state-directory.js";
28
29
  const RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
@@ -215,8 +216,7 @@ class ActiveTurnReconciliationError extends Error {
215
216
  }
216
217
  }
217
218
  function activeTurnReconciliationEndpoint(config, turn) {
218
- const endpoint = new URL(config.relayUrl);
219
- endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
219
+ const endpoint = relayHttpEndpoint(config.relayUrl);
220
220
  let channelPath = "";
221
221
  if (endpoint.pathname.endsWith("/daemon")) {
222
222
  channelPath = endpoint.pathname.slice(0, -"/daemon".length);
@@ -229,7 +229,6 @@ function activeTurnReconciliationEndpoint(config, turn) {
229
229
  channelPath = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config.channelId)}`;
230
230
  }
231
231
  endpoint.pathname = `${channelPath}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
232
- endpoint.searchParams.delete("liveness");
233
232
  return endpoint;
234
233
  }
235
234
  async function reconcileActiveTurn(config, turn, signal) {
@@ -241,7 +240,7 @@ async function reconcileActiveTurn(config, turn, signal) {
241
240
  response = await fetch(activeTurnReconciliationEndpoint(config, turn), {
242
241
  body: JSON.stringify({ outcome: { kind: "running" } }),
243
242
  dispatcher: environmentProxyDispatcher,
244
- headers: { "content-type": "application/json" },
243
+ headers: { ...runtimeAuthorization(config.relayUrl), "content-type": "application/json" },
245
244
  method: "POST",
246
245
  signal: requestSignal,
247
246
  });
@@ -311,17 +310,33 @@ class ConnectedDaemonSessionEventState extends DaemonSessionEventConnectionState
311
310
  }
312
311
  };
313
312
  }
314
- const SESSION_EVENT_REPLAY_WINDOW = 4;
313
+ const SESSION_EVENT_REPLAY_WINDOW = 16;
314
+ const SESSION_EVENT_SOURCE_REPLAY_WINDOW = 4;
315
+ const SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
316
+ class InFlightSessionEvent {
317
+ byteLength;
318
+ sourceKey;
319
+ constructor(sourceKey, byteLength) {
320
+ this.byteLength = byteLength;
321
+ this.sourceKey = sourceKey;
322
+ }
323
+ }
324
+ function sessionEventSourceKey(envelope) {
325
+ return envelope.source.kind === "attempt"
326
+ ? `attempt:${envelope.sessionId}:${envelope.source.attemptId}`
327
+ : `runtime:${envelope.sessionId}`;
328
+ }
315
329
  class DaemonSessionEventPublisher {
316
330
  #outbox;
317
- #inFlightEventIds = new Set();
331
+ #inFlightEvents = new Map();
332
+ #inFlightBytes = 0;
318
333
  #connection = new DisconnectedDaemonSessionEventState();
319
334
  constructor(outbox) {
320
335
  this.#outbox = outbox;
321
336
  }
322
337
  connect = (socket) => {
323
338
  this.#connection = new ConnectedDaemonSessionEventState(socket);
324
- this.#inFlightEventIds.clear();
339
+ this.#clearInFlight();
325
340
  this.#publishNextBatch();
326
341
  };
327
342
  disconnect = (socket) => {
@@ -329,10 +344,16 @@ class DaemonSessionEventPublisher {
329
344
  return;
330
345
  }
331
346
  this.#connection = this.#connection.disconnect(socket);
332
- this.#inFlightEventIds.clear();
347
+ this.#clearInFlight();
333
348
  };
334
349
  recordAttempt = (sessionId, payload, attemptId) => {
335
- this.#outbox.enqueue(sessionId, payload, attemptId);
350
+ const kind = decodeMessageKind(payload);
351
+ if (kind === SESSION_COMPLETED_KIND || kind === SESSION_FAILED_KIND) {
352
+ this.#outbox.enqueueCritical(sessionId, payload, attemptId);
353
+ }
354
+ else {
355
+ this.#outbox.enqueue(sessionId, payload, attemptId);
356
+ }
336
357
  this.#publishNextBatch();
337
358
  };
338
359
  recordRuntime = (sessionId, payload) => {
@@ -343,7 +364,7 @@ class DaemonSessionEventPublisher {
343
364
  if (!this.#outbox.acknowledge(acknowledgement)) {
344
365
  return;
345
366
  }
346
- this.#inFlightEventIds.delete(acknowledgement.eventId);
367
+ this.#removeInFlight(acknowledgement.eventId);
347
368
  this.#publishNextBatch();
348
369
  };
349
370
  quarantine = (rejection) => {
@@ -351,24 +372,58 @@ class DaemonSessionEventPublisher {
351
372
  if (!quarantined) {
352
373
  return false;
353
374
  }
354
- this.#inFlightEventIds.delete(rejection.eventId);
375
+ this.#removeInFlight(rejection.eventId);
355
376
  this.#publishNextBatch();
356
377
  return true;
357
378
  };
358
379
  #publishNextBatch = () => {
359
- if (this.#inFlightEventIds.size > 0) {
360
- return;
361
- }
362
- const batch = this.#outbox.pendingBatch(SESSION_EVENT_REPLAY_WINDOW);
363
- for (const encoded of batch) {
364
- const envelope = decodeSessionEventEnvelope(encoded);
365
- if (!this.#connection.send(encoded)) {
366
- this.#inFlightEventIds.clear();
380
+ while (this.#inFlightEvents.size < SESSION_EVENT_REPLAY_WINDOW) {
381
+ const sourceCounts = new Map();
382
+ for (const inFlight of this.#inFlightEvents.values()) {
383
+ sourceCounts.set(inFlight.sourceKey, (sourceCounts.get(inFlight.sourceKey) ?? 0) + 1);
384
+ }
385
+ const saturatedSources = [...sourceCounts.entries()]
386
+ .filter(([, count]) => count >= SESSION_EVENT_SOURCE_REPLAY_WINDOW)
387
+ .map(([sourceKey]) => sourceKey);
388
+ const batch = this.#outbox.pendingSourceHeads(SESSION_EVENT_REPLAY_WINDOW - this.#inFlightEvents.size, [...this.#inFlightEvents.keys()], saturatedSources);
389
+ let sent = 0;
390
+ for (const encoded of batch) {
391
+ const envelope = decodeSessionEventEnvelope(encoded);
392
+ const sourceKey = sessionEventSourceKey(envelope);
393
+ const sourceCount = sourceCounts.get(sourceKey) ?? 0;
394
+ if (sourceCount >= SESSION_EVENT_SOURCE_REPLAY_WINDOW) {
395
+ continue;
396
+ }
397
+ const byteLength = new TextEncoder().encode(encoded).byteLength;
398
+ if (this.#inFlightBytes + byteLength > SESSION_EVENT_REPLAY_BYTE_LIMIT) {
399
+ continue;
400
+ }
401
+ if (!this.#connection.send(encoded)) {
402
+ this.#clearInFlight();
403
+ return;
404
+ }
405
+ this.#inFlightEvents.set(envelope.eventId, new InFlightSessionEvent(sourceKey, byteLength));
406
+ this.#inFlightBytes += byteLength;
407
+ sourceCounts.set(sourceKey, sourceCount + 1);
408
+ sent += 1;
409
+ }
410
+ if (sent === 0) {
367
411
  return;
368
412
  }
369
- this.#inFlightEventIds.add(envelope.eventId);
370
413
  }
371
414
  };
415
+ #removeInFlight = (eventId) => {
416
+ const inFlight = this.#inFlightEvents.get(eventId);
417
+ if (inFlight === undefined) {
418
+ return;
419
+ }
420
+ this.#inFlightEvents.delete(eventId);
421
+ this.#inFlightBytes -= inFlight.byteLength;
422
+ };
423
+ #clearInFlight = () => {
424
+ this.#inFlightEvents.clear();
425
+ this.#inFlightBytes = 0;
426
+ };
372
427
  }
373
428
  class CoalescingDaemonActivityPublisher {
374
429
  #pending = new Map();
@@ -597,6 +652,9 @@ async function executeSessionConfigure(publisher, socket, command, config, direc
597
652
  }));
598
653
  }
599
654
  catch (error) {
655
+ if (signal.aborted) {
656
+ signal.throwIfAborted();
657
+ }
600
658
  reportOperationFailure(socket, command.requestId, command.sessionId, error);
601
659
  }
602
660
  }
@@ -865,6 +923,7 @@ async function publishAgentCatalogUntilAvailable(config, diagnostics, signal, ti
865
923
  }
866
924
  async function runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, connectionEpoch, diagnostics, onConnected, signal, timer) {
867
925
  const relayUrl = new URL(config.relayUrl);
926
+ relayUrl.searchParams.set("deliveryReceipts", "1");
868
927
  relayUrl.searchParams.set("liveness", "1");
869
928
  const socket = await connectRelay(relayUrl, signal);
870
929
  const reconnectingTurns = [...activeTurns.values()];
@@ -913,6 +972,181 @@ async function runRelayConnection(config, directories, audit, sessions, installa
913
972
  closeRelaySocket(socket, code, reason);
914
973
  rejectConnection(failure);
915
974
  };
975
+ const processingDeliveryIds = new Set();
976
+ const processDeliveredPrompt = async (delivery) => {
977
+ const command = decodeSessionPromptCommand(delivery.payload);
978
+ if (command.sessionId !== delivery.sessionId) {
979
+ throw new Error("Runtime delivery Session identity does not match its payload");
980
+ }
981
+ const active = activeTurns.get(command.commandId);
982
+ if (active !== undefined) {
983
+ await active.completion;
984
+ return;
985
+ }
986
+ const receipt = outbox.claimExecution(command.commandId, command.sessionId, delivery.payload);
987
+ if (receipt.kind === "duplicate") {
988
+ if (receipt.state === "running") {
989
+ reportFailure(publisher, command, "agent_execution_interrupted", "The local Agent execution was interrupted before it completed", receipt.attemptId);
990
+ outbox.transitionExecution(command.commandId, "failed");
991
+ return;
992
+ }
993
+ if (receipt.state !== "dispatching") {
994
+ return;
995
+ }
996
+ }
997
+ const turn = new ActiveDaemonTurn(command, receipt.attemptId);
998
+ activeTurns.set(command.commandId, turn);
999
+ if (command.disposition !== SessionPromptDisposition.Steer) {
1000
+ activeAttemptIds.set(command.sessionId, receipt.attemptId);
1001
+ }
1002
+ try {
1003
+ await executePrompt(outbox, publisher, command, config, directories, audit, sessions, receipt.attemptId, timer, turn.signal);
1004
+ }
1005
+ finally {
1006
+ activeTurns.delete(command.commandId);
1007
+ if (activeAttemptIds.get(command.sessionId) === receipt.attemptId) {
1008
+ activeAttemptIds.delete(command.sessionId);
1009
+ }
1010
+ turn.finish();
1011
+ }
1012
+ };
1013
+ const processDeliveredPayload = async (delivery) => {
1014
+ const kind = decodeMessageKind(delivery.payload);
1015
+ if (kind === SESSION_CANCEL_KIND) {
1016
+ const cancellation = decodeSessionCancelCommand(delivery.payload);
1017
+ if (cancellation.sessionId !== delivery.sessionId) {
1018
+ throw new Error("Runtime delivery Session identity does not match its payload");
1019
+ }
1020
+ const turn = activeTurns.get(cancellation.commandId);
1021
+ if (turn === undefined || !turn.matches(cancellation) || !turn.requestCancellation()) {
1022
+ return;
1023
+ }
1024
+ audit.record({
1025
+ commandId: cancellation.commandId,
1026
+ event: "session_cancellation_requested",
1027
+ sessionId: cancellation.sessionId,
1028
+ });
1029
+ await sessions.cancelPrompt(cancellation.sessionId);
1030
+ return;
1031
+ }
1032
+ if (kind === SESSION_CONFIGURE_KIND) {
1033
+ const command = decodeSessionConfigureCommand(delivery.payload);
1034
+ if (command.sessionId !== delivery.sessionId) {
1035
+ throw new Error("Runtime delivery Session identity does not match its payload");
1036
+ }
1037
+ await executeSessionConfigure(publisher, socket, command, config, directories, audit, sessions, connectionController.signal);
1038
+ return;
1039
+ }
1040
+ if (kind === SESSION_PERMISSION_RESPOND_KIND) {
1041
+ const command = decodeSessionPermissionRespondCommand(delivery.payload);
1042
+ if (command.sessionId !== delivery.sessionId) {
1043
+ throw new Error("Runtime delivery Session identity does not match its payload");
1044
+ }
1045
+ try {
1046
+ await sessions.respondPermission(command);
1047
+ audit.record({
1048
+ commandId: command.interactionId,
1049
+ event: "session_permission_resolved",
1050
+ sessionId: command.sessionId,
1051
+ });
1052
+ }
1053
+ catch (error) {
1054
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error);
1055
+ }
1056
+ return;
1057
+ }
1058
+ if (kind === SESSION_ELICITATION_RESPOND_KIND) {
1059
+ const command = decodeSessionElicitationRespondCommand(delivery.payload);
1060
+ if (command.sessionId !== delivery.sessionId) {
1061
+ throw new Error("Runtime delivery Session identity does not match its payload");
1062
+ }
1063
+ try {
1064
+ await sessions.respondElicitation(command);
1065
+ audit.record({
1066
+ commandId: command.interactionId,
1067
+ event: "session_elicitation_resolved",
1068
+ sessionId: command.sessionId,
1069
+ });
1070
+ }
1071
+ catch (error) {
1072
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error);
1073
+ }
1074
+ return;
1075
+ }
1076
+ if (kind === SESSION_PROMPT_KIND) {
1077
+ await processDeliveredPrompt(delivery);
1078
+ return;
1079
+ }
1080
+ throw new Error("Runtime delivery contains an unsupported protocol message");
1081
+ };
1082
+ const processRuntimeDelivery = (delivery, state) => {
1083
+ if (state === "processed" || processingDeliveryIds.has(delivery.deliveryId)) {
1084
+ return;
1085
+ }
1086
+ processingDeliveryIds.add(delivery.deliveryId);
1087
+ void processDeliveredPayload(delivery)
1088
+ .then(() => {
1089
+ outbox.markRuntimeDeliveryProcessed(delivery.deliveryId, delivery.sessionId);
1090
+ })
1091
+ .catch((error) => {
1092
+ if (connectionController.signal.aborted) {
1093
+ return;
1094
+ }
1095
+ console.error("Mobius daemon could not process a durable Runtime delivery", error);
1096
+ failAndClose(new Error("Runtime delivery processing failed", { cause: error }), 4008, "Invalid Runtime delivery", "delivery");
1097
+ })
1098
+ .finally(() => processingDeliveryIds.delete(delivery.deliveryId));
1099
+ };
1100
+ const receiveRuntimeDelivery = (delivery) => {
1101
+ const payloadKind = decodeMessageKind(delivery.payload);
1102
+ let payloadSessionId = "";
1103
+ if (payloadKind === SESSION_CANCEL_KIND) {
1104
+ payloadSessionId = decodeSessionCancelCommand(delivery.payload).sessionId;
1105
+ }
1106
+ else if (payloadKind === SESSION_CONFIGURE_KIND) {
1107
+ payloadSessionId = decodeSessionConfigureCommand(delivery.payload).sessionId;
1108
+ }
1109
+ else if (payloadKind === SESSION_PERMISSION_RESPOND_KIND) {
1110
+ payloadSessionId = decodeSessionPermissionRespondCommand(delivery.payload).sessionId;
1111
+ }
1112
+ else if (payloadKind === SESSION_ELICITATION_RESPOND_KIND) {
1113
+ payloadSessionId = decodeSessionElicitationRespondCommand(delivery.payload).sessionId;
1114
+ }
1115
+ else if (payloadKind === SESSION_PROMPT_KIND) {
1116
+ payloadSessionId = decodeSessionPromptCommand(delivery.payload).sessionId;
1117
+ }
1118
+ else {
1119
+ throw new Error("Runtime delivery contains an unsupported protocol message");
1120
+ }
1121
+ if (payloadSessionId !== delivery.sessionId) {
1122
+ throw new Error("Runtime delivery Session identity does not match its payload");
1123
+ }
1124
+ const stored = outbox.claimRuntimeDelivery(delivery);
1125
+ send(socket, encodeRuntimeDeliveryReceived({
1126
+ deliveryId: delivery.deliveryId,
1127
+ kind: RUNTIME_DELIVERY_RECEIVED_KIND,
1128
+ sessionId: delivery.sessionId,
1129
+ }));
1130
+ processRuntimeDelivery(delivery, stored.state);
1131
+ };
1132
+ const resumeRuntimeDeliveries = () => {
1133
+ for (const stored of outbox.runtimeDeliveries()) {
1134
+ const delivery = {
1135
+ deliveryId: stored.deliveryId,
1136
+ kind: RUNTIME_DELIVERY_KIND,
1137
+ payload: stored.payload,
1138
+ sessionId: stored.sessionId,
1139
+ };
1140
+ if (stored.state !== "relay_committed") {
1141
+ send(socket, encodeRuntimeDeliveryReceived({
1142
+ deliveryId: stored.deliveryId,
1143
+ kind: RUNTIME_DELIVERY_RECEIVED_KIND,
1144
+ sessionId: stored.sessionId,
1145
+ }));
1146
+ }
1147
+ processRuntimeDelivery(delivery, stored.state);
1148
+ }
1149
+ };
916
1150
  const connectionLiveness = new RelayConnectionLiveness(timer, {
917
1151
  send: (message) => send(socket, message),
918
1152
  unresponsive: (heartbeatId, timeoutMilliseconds) => {
@@ -946,6 +1180,17 @@ async function runRelayConnection(config, directories, audit, sessions, installa
946
1180
  }
947
1181
  try {
948
1182
  const kind = decodeMessageKind(encoded);
1183
+ if (kind === RUNTIME_DELIVERY_COMMITTED_KIND) {
1184
+ const committed = decodeRuntimeDeliveryCommitted(encoded);
1185
+ if (!outbox.markRuntimeDeliveryCommitted(committed.deliveryId, committed.sessionId)) {
1186
+ console.warn("Mobius daemon ignored confirmation for an unknown Runtime delivery");
1187
+ }
1188
+ return;
1189
+ }
1190
+ if (kind === RUNTIME_DELIVERY_KIND) {
1191
+ receiveRuntimeDelivery(decodeRuntimeDelivery(encoded));
1192
+ return;
1193
+ }
949
1194
  if (kind === SESSION_EVENT_ACK_KIND) {
950
1195
  publisher.acknowledge(decodeSessionEventAcknowledgement(encoded));
951
1196
  return;
@@ -1291,6 +1536,10 @@ async function runRelayConnection(config, directories, audit, sessions, installa
1291
1536
  }
1292
1537
  catch (error) {
1293
1538
  console.error("Mobius daemon rejected a message", error);
1539
+ if (error instanceof RuntimeDeliveryInboxCapacityError) {
1540
+ failAndClose(error, 4013, "Runtime delivery queue full", "delivery_backpressure");
1541
+ return;
1542
+ }
1294
1543
  failAndClose(new Error("Relay sent an invalid protocol message", { cause: error }), 4008, "Invalid protocol message", "protocol");
1295
1544
  }
1296
1545
  };
@@ -1302,6 +1551,7 @@ async function runRelayConnection(config, directories, audit, sessions, installa
1302
1551
  return;
1303
1552
  }
1304
1553
  publisher.connect(socket);
1554
+ resumeRuntimeDeliveries();
1305
1555
  }, (error) => {
1306
1556
  if (finished) {
1307
1557
  return;