@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.
- package/dist/agent-catalog/publish-agent-catalog.js +3 -3
- package/dist/agent-catalog/publish-agent-catalog.js.map +1 -1
- package/dist/cli.js +750 -54
- package/dist/cli.js.map +4 -4
- package/dist/index.js +742 -53
- package/dist/index.js.map +4 -4
- package/dist/prompt-artifact-resolver.d.ts.map +1 -1
- package/dist/prompt-artifact-resolver.js +7 -4
- package/dist/prompt-artifact-resolver.js.map +1 -1
- package/dist/relay-connection/connect.d.ts.map +1 -1
- package/dist/relay-connection/connect.js +3 -6
- package/dist/relay-connection/connect.js.map +1 -1
- package/dist/relay-connection/runtime-authentication.d.ts +3 -0
- package/dist/relay-connection/runtime-authentication.d.ts.map +1 -0
- package/dist/relay-connection/runtime-authentication.js +14 -0
- package/dist/relay-connection/runtime-authentication.js.map +1 -0
- package/dist/relay-connection/session-event-outbox.d.ts +32 -2
- package/dist/relay-connection/session-event-outbox.d.ts.map +1 -1
- package/dist/relay-connection/session-event-outbox.js +362 -11
- package/dist/relay-connection/session-event-outbox.js.map +1 -1
- package/dist/run-daemon.d.ts.map +1 -1
- package/dist/run-daemon.js +272 -22
- package/dist/run-daemon.js.map +1 -1
- package/dist/runtime.js +742 -53
- package/dist/runtime.js.map +4 -4
- package/package.json +1 -1
package/dist/run-daemon.js
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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 =
|
|
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
|
-
#
|
|
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.#
|
|
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.#
|
|
347
|
+
this.#clearInFlight();
|
|
333
348
|
};
|
|
334
349
|
recordAttempt = (sessionId, payload, attemptId) => {
|
|
335
|
-
|
|
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.#
|
|
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.#
|
|
375
|
+
this.#removeInFlight(rejection.eventId);
|
|
355
376
|
this.#publishNextBatch();
|
|
356
377
|
return true;
|
|
357
378
|
};
|
|
358
379
|
#publishNextBatch = () => {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
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;
|