@moxt-ai/mobius 0.0.25 → 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 +1194 -205
- package/dist/cli.js.map +4 -4
- package/dist/index.js +1186 -204
- package/dist/index.js.map +4 -4
- package/dist/network/environment-proxy.d.ts +1 -0
- package/dist/network/environment-proxy.d.ts.map +1 -1
- package/dist/network/environment-proxy.js +47 -0
- package/dist/network/environment-proxy.js.map +1 -1
- 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 +19 -14
- package/dist/relay-connection/connect.js.map +1 -1
- package/dist/relay-connection/diagnostics.d.ts +11 -0
- package/dist/relay-connection/diagnostics.d.ts.map +1 -0
- package/dist/relay-connection/diagnostics.js +122 -0
- package/dist/relay-connection/diagnostics.js.map +1 -0
- package/dist/relay-connection/liveness.d.ts +1 -1
- package/dist/relay-connection/liveness.d.ts.map +1 -1
- package/dist/relay-connection/liveness.js +2 -1
- package/dist/relay-connection/liveness.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 +34 -3
- package/dist/relay-connection/session-event-outbox.d.ts.map +1 -1
- package/dist/relay-connection/session-event-outbox.js +404 -13
- 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 +437 -151
- package/dist/run-daemon.js.map +1 -1
- package/dist/runtime.js +1186 -204
- 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, 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";
|
|
@@ -20,12 +20,14 @@ import { NativeSessionService } from "./native-sessions/service.js";
|
|
|
20
20
|
import { environmentProxyDispatcher } from "./network/environment-proxy.js";
|
|
21
21
|
import { PromptArtifactError, resolvePromptArtifacts } from "./prompt-artifact-resolver.js";
|
|
22
22
|
import { connectRelay } from "./relay-connection/connect.js";
|
|
23
|
+
import { RelayConnectionDiagnostics } from "./relay-connection/diagnostics.js";
|
|
23
24
|
import { RelayConnectionLiveness } from "./relay-connection/liveness.js";
|
|
24
|
-
import {
|
|
25
|
+
import { relayHttpEndpoint, runtimeAuthorization } from "./relay-connection/runtime-authentication.js";
|
|
26
|
+
import { RuntimeDeliveryInboxCapacityError, SessionEventOutbox } from "./relay-connection/session-event-outbox.js";
|
|
25
27
|
import { systemDaemonTimer } from "./relay-connection/timer.js";
|
|
26
28
|
import { readStateDirectory } from "./storage/state-directory.js";
|
|
27
29
|
const RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
|
|
28
|
-
const
|
|
30
|
+
const AGENT_ACTIVITY_COALESCING_MILLISECONDS = 100;
|
|
29
31
|
const ACTIVE_TURN_RECONCILIATION_TIMEOUT_MILLISECONDS = 20_000;
|
|
30
32
|
const ACTIVE_TURN_RECONCILIATION_CLOSE_CODE = 4009;
|
|
31
33
|
function botIpcSocketPath() {
|
|
@@ -34,56 +36,6 @@ function botIpcSocketPath() {
|
|
|
34
36
|
}
|
|
35
37
|
return join("/tmp", `mobius-bot-${randomUUID()}.sock`);
|
|
36
38
|
}
|
|
37
|
-
class RelayConnectionRefreshState {
|
|
38
|
-
}
|
|
39
|
-
class ScheduledRelayConnectionRefreshState extends RelayConnectionRefreshState {
|
|
40
|
-
}
|
|
41
|
-
class DueRelayConnectionRefreshState extends RelayConnectionRefreshState {
|
|
42
|
-
}
|
|
43
|
-
class FinishedRelayConnectionRefreshState extends RelayConnectionRefreshState {
|
|
44
|
-
}
|
|
45
|
-
class RelayConnectionRefresh {
|
|
46
|
-
#isSafe;
|
|
47
|
-
#refresh;
|
|
48
|
-
#scheduledTask;
|
|
49
|
-
#state = new ScheduledRelayConnectionRefreshState();
|
|
50
|
-
constructor(timer, isSafe, refresh) {
|
|
51
|
-
this.#isSafe = isSafe;
|
|
52
|
-
this.#refresh = refresh;
|
|
53
|
-
this.#scheduledTask = timer.schedule(this.#request, RELAY_CONNECTION_REFRESH_MILLISECONDS);
|
|
54
|
-
}
|
|
55
|
-
afterTurnFinished = () => {
|
|
56
|
-
if (this.#state instanceof DueRelayConnectionRefreshState) {
|
|
57
|
-
this.#request();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (this.#state instanceof ScheduledRelayConnectionRefreshState ||
|
|
61
|
-
this.#state instanceof FinishedRelayConnectionRefreshState) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
throw new Error("Unknown relay connection refresh state");
|
|
65
|
-
};
|
|
66
|
-
close = () => {
|
|
67
|
-
this.#scheduledTask.cancel();
|
|
68
|
-
this.#state = new FinishedRelayConnectionRefreshState();
|
|
69
|
-
};
|
|
70
|
-
#request = () => {
|
|
71
|
-
if (this.#state instanceof FinishedRelayConnectionRefreshState) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (!(this.#state instanceof ScheduledRelayConnectionRefreshState) &&
|
|
75
|
-
!(this.#state instanceof DueRelayConnectionRefreshState)) {
|
|
76
|
-
throw new Error("Unknown relay connection refresh state");
|
|
77
|
-
}
|
|
78
|
-
if (!this.#isSafe()) {
|
|
79
|
-
this.#state = new DueRelayConnectionRefreshState();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
this.#state = new FinishedRelayConnectionRefreshState();
|
|
83
|
-
this.#scheduledTask.cancel();
|
|
84
|
-
this.#refresh();
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
39
|
class DaemonAcpObserver {
|
|
88
40
|
#record;
|
|
89
41
|
constructor(record) {
|
|
@@ -264,8 +216,7 @@ class ActiveTurnReconciliationError extends Error {
|
|
|
264
216
|
}
|
|
265
217
|
}
|
|
266
218
|
function activeTurnReconciliationEndpoint(config, turn) {
|
|
267
|
-
const endpoint =
|
|
268
|
-
endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
|
|
219
|
+
const endpoint = relayHttpEndpoint(config.relayUrl);
|
|
269
220
|
let channelPath = "";
|
|
270
221
|
if (endpoint.pathname.endsWith("/daemon")) {
|
|
271
222
|
channelPath = endpoint.pathname.slice(0, -"/daemon".length);
|
|
@@ -278,7 +229,6 @@ function activeTurnReconciliationEndpoint(config, turn) {
|
|
|
278
229
|
channelPath = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config.channelId)}`;
|
|
279
230
|
}
|
|
280
231
|
endpoint.pathname = `${channelPath}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
|
|
281
|
-
endpoint.searchParams.delete("liveness");
|
|
282
232
|
return endpoint;
|
|
283
233
|
}
|
|
284
234
|
async function reconcileActiveTurn(config, turn, signal) {
|
|
@@ -290,7 +240,7 @@ async function reconcileActiveTurn(config, turn, signal) {
|
|
|
290
240
|
response = await fetch(activeTurnReconciliationEndpoint(config, turn), {
|
|
291
241
|
body: JSON.stringify({ outcome: { kind: "running" } }),
|
|
292
242
|
dispatcher: environmentProxyDispatcher,
|
|
293
|
-
headers: { "content-type": "application/json" },
|
|
243
|
+
headers: { ...runtimeAuthorization(config.relayUrl), "content-type": "application/json" },
|
|
294
244
|
method: "POST",
|
|
295
245
|
signal: requestSignal,
|
|
296
246
|
});
|
|
@@ -335,7 +285,8 @@ class DaemonSessionEventConnectionState {
|
|
|
335
285
|
}
|
|
336
286
|
class DisconnectedDaemonSessionEventState extends DaemonSessionEventConnectionState {
|
|
337
287
|
disconnect = () => this;
|
|
338
|
-
|
|
288
|
+
matches = () => false;
|
|
289
|
+
send = () => false;
|
|
339
290
|
}
|
|
340
291
|
class ConnectedDaemonSessionEventState extends DaemonSessionEventConnectionState {
|
|
341
292
|
#socket;
|
|
@@ -344,40 +295,207 @@ class ConnectedDaemonSessionEventState extends DaemonSessionEventConnectionState
|
|
|
344
295
|
this.#socket = socket;
|
|
345
296
|
}
|
|
346
297
|
disconnect = (socket) => socket === this.#socket ? new DisconnectedDaemonSessionEventState() : this;
|
|
298
|
+
matches = (socket) => socket === this.#socket;
|
|
347
299
|
send = (message) => {
|
|
348
|
-
if (this.#socket.readyState
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
300
|
+
if (this.#socket.readyState !== WebSocket.OPEN) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
this.#socket.send(message);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// The durable outbox retains the event for the replacement connection.
|
|
309
|
+
return false;
|
|
355
310
|
}
|
|
356
311
|
};
|
|
357
312
|
}
|
|
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
|
+
}
|
|
358
329
|
class DaemonSessionEventPublisher {
|
|
359
330
|
#outbox;
|
|
331
|
+
#inFlightEvents = new Map();
|
|
332
|
+
#inFlightBytes = 0;
|
|
360
333
|
#connection = new DisconnectedDaemonSessionEventState();
|
|
361
334
|
constructor(outbox) {
|
|
362
335
|
this.#outbox = outbox;
|
|
363
336
|
}
|
|
364
337
|
connect = (socket) => {
|
|
365
|
-
|
|
366
|
-
this.#
|
|
367
|
-
|
|
368
|
-
connection.send(pending);
|
|
369
|
-
}
|
|
338
|
+
this.#connection = new ConnectedDaemonSessionEventState(socket);
|
|
339
|
+
this.#clearInFlight();
|
|
340
|
+
this.#publishNextBatch();
|
|
370
341
|
};
|
|
371
342
|
disconnect = (socket) => {
|
|
343
|
+
if (!this.#connection.matches(socket)) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
372
346
|
this.#connection = this.#connection.disconnect(socket);
|
|
347
|
+
this.#clearInFlight();
|
|
373
348
|
};
|
|
374
349
|
recordAttempt = (sessionId, payload, attemptId) => {
|
|
375
|
-
|
|
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
|
+
}
|
|
357
|
+
this.#publishNextBatch();
|
|
376
358
|
};
|
|
377
359
|
recordRuntime = (sessionId, payload) => {
|
|
378
|
-
this.#
|
|
360
|
+
this.#outbox.enqueue(sessionId, payload);
|
|
361
|
+
this.#publishNextBatch();
|
|
362
|
+
};
|
|
363
|
+
acknowledge = (acknowledgement) => {
|
|
364
|
+
if (!this.#outbox.acknowledge(acknowledgement)) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
this.#removeInFlight(acknowledgement.eventId);
|
|
368
|
+
this.#publishNextBatch();
|
|
369
|
+
};
|
|
370
|
+
quarantine = (rejection) => {
|
|
371
|
+
const quarantined = this.#outbox.quarantine(rejection);
|
|
372
|
+
if (!quarantined) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
this.#removeInFlight(rejection.eventId);
|
|
376
|
+
this.#publishNextBatch();
|
|
377
|
+
return true;
|
|
378
|
+
};
|
|
379
|
+
#publishNextBatch = () => {
|
|
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) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
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;
|
|
379
426
|
};
|
|
380
427
|
}
|
|
428
|
+
class CoalescingDaemonActivityPublisher {
|
|
429
|
+
#pending = new Map();
|
|
430
|
+
#publish;
|
|
431
|
+
#publishedActivityIds = new Set();
|
|
432
|
+
#scheduled = new Map();
|
|
433
|
+
#timer;
|
|
434
|
+
constructor(timer, publish) {
|
|
435
|
+
this.#publish = publish;
|
|
436
|
+
this.#timer = timer;
|
|
437
|
+
}
|
|
438
|
+
flush = () => {
|
|
439
|
+
const activities = [...this.#pending.values()].sort((left, right) => left.sequence - right.sequence);
|
|
440
|
+
for (const task of this.#scheduled.values()) {
|
|
441
|
+
task.cancel();
|
|
442
|
+
}
|
|
443
|
+
this.#pending.clear();
|
|
444
|
+
this.#scheduled.clear();
|
|
445
|
+
for (const activity of activities) {
|
|
446
|
+
this.#publish(activity);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
record = (activity) => {
|
|
450
|
+
if (activity.status !== "pending" && activity.status !== "running") {
|
|
451
|
+
this.#discardPending(activity.activityId);
|
|
452
|
+
this.#publishedActivityIds.add(activity.activityId);
|
|
453
|
+
this.#publish(activity);
|
|
454
|
+
return Promise.resolve();
|
|
455
|
+
}
|
|
456
|
+
if (!this.#publishedActivityIds.has(activity.activityId)) {
|
|
457
|
+
this.#publishedActivityIds.add(activity.activityId);
|
|
458
|
+
this.#publish(activity);
|
|
459
|
+
return Promise.resolve();
|
|
460
|
+
}
|
|
461
|
+
this.#pending.set(activity.activityId, activity);
|
|
462
|
+
if (!this.#scheduled.has(activity.activityId)) {
|
|
463
|
+
this.#scheduled.set(activity.activityId, this.#timer.schedule(() => this.#flushActivity(activity.activityId), AGENT_ACTIVITY_COALESCING_MILLISECONDS));
|
|
464
|
+
}
|
|
465
|
+
return Promise.resolve();
|
|
466
|
+
};
|
|
467
|
+
#discardPending = (activityId) => {
|
|
468
|
+
this.#scheduled.get(activityId)?.cancel();
|
|
469
|
+
this.#scheduled.delete(activityId);
|
|
470
|
+
this.#pending.delete(activityId);
|
|
471
|
+
};
|
|
472
|
+
#flushActivity = (activityId) => {
|
|
473
|
+
const activity = this.#pending.get(activityId);
|
|
474
|
+
this.#discardPending(activityId);
|
|
475
|
+
if (activity !== undefined) {
|
|
476
|
+
this.#publish(activity);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function encodeDaemonActivity(command, activity) {
|
|
481
|
+
return encodeAgentActivityEvent({
|
|
482
|
+
activityId: activity.activityId,
|
|
483
|
+
activityType: activity.activityType,
|
|
484
|
+
commandId: command.commandId,
|
|
485
|
+
content: activity.content,
|
|
486
|
+
detail: activity.detail,
|
|
487
|
+
input: activity.input,
|
|
488
|
+
kind: AGENT_ACTIVITY_KIND,
|
|
489
|
+
locations: activity.locations,
|
|
490
|
+
name: activity.name,
|
|
491
|
+
output: activity.output,
|
|
492
|
+
sequence: activity.sequence,
|
|
493
|
+
sessionId: command.sessionId,
|
|
494
|
+
status: activity.status,
|
|
495
|
+
title: activity.title,
|
|
496
|
+
toolKind: activity.toolKind,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
381
499
|
function reportFailure(publisher, command, code, message, attemptId) {
|
|
382
500
|
publisher.recordAttempt(command.sessionId, encodeSessionFailedEvent({
|
|
383
501
|
code,
|
|
@@ -534,10 +652,16 @@ async function executeSessionConfigure(publisher, socket, command, config, direc
|
|
|
534
652
|
}));
|
|
535
653
|
}
|
|
536
654
|
catch (error) {
|
|
655
|
+
if (signal.aborted) {
|
|
656
|
+
signal.throwIfAborted();
|
|
657
|
+
}
|
|
537
658
|
reportOperationFailure(socket, command.requestId, command.sessionId, error);
|
|
538
659
|
}
|
|
539
660
|
}
|
|
540
|
-
async function executePrompt(outbox, publisher, command, config, directories, audit, sessions, attemptId, signal) {
|
|
661
|
+
async function executePrompt(outbox, publisher, command, config, directories, audit, sessions, attemptId, timer, signal) {
|
|
662
|
+
const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
|
|
663
|
+
publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
|
|
664
|
+
});
|
|
541
665
|
try {
|
|
542
666
|
const resolved = await resolveSessionDirectories(directories, config.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
|
|
543
667
|
const agent = config.catalog.find(command.agentId);
|
|
@@ -582,26 +706,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
582
706
|
cwd: resolved.cwd,
|
|
583
707
|
disposition: command.disposition,
|
|
584
708
|
images: attachments.images,
|
|
585
|
-
onActivity:
|
|
586
|
-
publisher.recordAttempt(command.sessionId, encodeAgentActivityEvent({
|
|
587
|
-
activityId: activity.activityId,
|
|
588
|
-
activityType: activity.activityType,
|
|
589
|
-
commandId: command.commandId,
|
|
590
|
-
content: activity.content,
|
|
591
|
-
detail: activity.detail,
|
|
592
|
-
input: activity.input,
|
|
593
|
-
kind: AGENT_ACTIVITY_KIND,
|
|
594
|
-
locations: activity.locations,
|
|
595
|
-
name: activity.name,
|
|
596
|
-
output: activity.output,
|
|
597
|
-
sequence: activity.sequence,
|
|
598
|
-
sessionId: command.sessionId,
|
|
599
|
-
status: activity.status,
|
|
600
|
-
title: activity.title,
|
|
601
|
-
toolKind: activity.toolKind,
|
|
602
|
-
}), attemptId);
|
|
603
|
-
return Promise.resolve();
|
|
604
|
-
},
|
|
709
|
+
onActivity: activities.record,
|
|
605
710
|
onText: (text, messageId) => {
|
|
606
711
|
publisher.recordAttempt(command.sessionId, encodeAgentMessageDeltaEvent({
|
|
607
712
|
commandId: command.commandId,
|
|
@@ -620,6 +725,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
620
725
|
});
|
|
621
726
|
stopReason = result.stopReason;
|
|
622
727
|
}
|
|
728
|
+
activities.flush();
|
|
623
729
|
publisher.recordAttempt(command.sessionId, encodeSessionCompletedEvent({
|
|
624
730
|
commandId: command.commandId,
|
|
625
731
|
kind: SESSION_COMPLETED_KIND,
|
|
@@ -634,6 +740,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
634
740
|
});
|
|
635
741
|
}
|
|
636
742
|
catch (error) {
|
|
743
|
+
activities.flush();
|
|
637
744
|
audit.record({
|
|
638
745
|
commandId: command.commandId,
|
|
639
746
|
event: "session_failed",
|
|
@@ -656,7 +763,10 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
|
|
|
656
763
|
MOBIUS_RELAY_URL: "",
|
|
657
764
|
}, agent.adapterVersion, agent.agentVersion, agent.adapterIdentity, agent.agentIdentity, agent.promptTimeoutMilliseconds, agent.capabilities);
|
|
658
765
|
}
|
|
659
|
-
async function executeBotPrompt(outbox, publisher, command, config, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, signal) {
|
|
766
|
+
async function executeBotPrompt(outbox, publisher, command, config, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, timer, signal) {
|
|
767
|
+
const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
|
|
768
|
+
publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
|
|
769
|
+
});
|
|
660
770
|
try {
|
|
661
771
|
const deadline = Date.parse(command.executionDeadlineAt);
|
|
662
772
|
const remaining = deadline - Date.now();
|
|
@@ -713,26 +823,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
713
823
|
commandId: command.commandId,
|
|
714
824
|
cwd: resolved.cwd,
|
|
715
825
|
images: attachments.images,
|
|
716
|
-
onActivity:
|
|
717
|
-
publisher.recordAttempt(command.sessionId, encodeAgentActivityEvent({
|
|
718
|
-
activityId: activity.activityId,
|
|
719
|
-
activityType: activity.activityType,
|
|
720
|
-
commandId: command.commandId,
|
|
721
|
-
content: activity.content,
|
|
722
|
-
detail: activity.detail,
|
|
723
|
-
input: activity.input,
|
|
724
|
-
kind: AGENT_ACTIVITY_KIND,
|
|
725
|
-
locations: activity.locations,
|
|
726
|
-
name: activity.name,
|
|
727
|
-
output: activity.output,
|
|
728
|
-
sequence: activity.sequence,
|
|
729
|
-
sessionId: command.sessionId,
|
|
730
|
-
status: activity.status,
|
|
731
|
-
title: activity.title,
|
|
732
|
-
toolKind: activity.toolKind,
|
|
733
|
-
}), attemptId);
|
|
734
|
-
return Promise.resolve();
|
|
735
|
-
},
|
|
826
|
+
onActivity: activities.record,
|
|
736
827
|
onText: (text, messageId) => {
|
|
737
828
|
publisher.recordAttempt(command.sessionId, encodeAgentMessageDeltaEvent({
|
|
738
829
|
commandId: command.commandId,
|
|
@@ -748,6 +839,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
748
839
|
sessionId: command.sessionId,
|
|
749
840
|
signal: executionSignal,
|
|
750
841
|
});
|
|
842
|
+
activities.flush();
|
|
751
843
|
publisher.recordAttempt(command.sessionId, encodeSessionCompletedEvent({
|
|
752
844
|
commandId: command.commandId,
|
|
753
845
|
kind: SESSION_COMPLETED_KIND,
|
|
@@ -767,6 +859,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
767
859
|
}
|
|
768
860
|
}
|
|
769
861
|
catch (error) {
|
|
862
|
+
activities.flush();
|
|
770
863
|
const failure = Date.parse(command.executionDeadlineAt) <= Date.now()
|
|
771
864
|
? new LocalBotAuthorizationError("turn_expired", "The Bot execution deadline has expired", {
|
|
772
865
|
cause: error,
|
|
@@ -804,16 +897,16 @@ function waitForRelayRetry(signal, timer) {
|
|
|
804
897
|
signal.addEventListener("abort", finish, { once: true });
|
|
805
898
|
});
|
|
806
899
|
}
|
|
807
|
-
async function publishAgentCatalogUntilAvailable(config, signal, timer) {
|
|
900
|
+
async function publishAgentCatalogUntilAvailable(config, diagnostics, signal, timer) {
|
|
808
901
|
try {
|
|
809
902
|
await publishAgentCatalog(config, signal);
|
|
810
903
|
return;
|
|
811
904
|
}
|
|
812
|
-
catch {
|
|
905
|
+
catch (error) {
|
|
813
906
|
if (signal.aborted) {
|
|
814
907
|
return;
|
|
815
908
|
}
|
|
816
|
-
|
|
909
|
+
diagnostics.catalogFailed(error);
|
|
817
910
|
}
|
|
818
911
|
while (!signal.aborted) {
|
|
819
912
|
await waitForRelayRetry(signal, timer);
|
|
@@ -828,11 +921,13 @@ async function publishAgentCatalogUntilAvailable(config, signal, timer) {
|
|
|
828
921
|
}
|
|
829
922
|
}
|
|
830
923
|
}
|
|
831
|
-
async function runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, onConnected, signal, timer) {
|
|
924
|
+
async function runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, connectionEpoch, diagnostics, onConnected, signal, timer) {
|
|
832
925
|
const relayUrl = new URL(config.relayUrl);
|
|
926
|
+
relayUrl.searchParams.set("deliveryReceipts", "1");
|
|
833
927
|
relayUrl.searchParams.set("liveness", "1");
|
|
834
928
|
const socket = await connectRelay(relayUrl, signal);
|
|
835
929
|
const reconnectingTurns = [...activeTurns.values()];
|
|
930
|
+
diagnostics.connectionOpened(connectionEpoch);
|
|
836
931
|
onConnected();
|
|
837
932
|
const connectionController = new AbortController();
|
|
838
933
|
const nativeRequests = new Map();
|
|
@@ -841,7 +936,6 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
841
936
|
await new Promise((resolve, reject) => {
|
|
842
937
|
let finished = false;
|
|
843
938
|
const cleanup = () => {
|
|
844
|
-
connectionRefresh.close();
|
|
845
939
|
connectionLiveness.close();
|
|
846
940
|
signal.removeEventListener("abort", stop);
|
|
847
941
|
socket.removeEventListener("close", handleClose);
|
|
@@ -859,57 +953,251 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
859
953
|
cleanup();
|
|
860
954
|
resolve();
|
|
861
955
|
};
|
|
862
|
-
const rejectConnection = () => {
|
|
956
|
+
const rejectConnection = (failure) => {
|
|
863
957
|
if (finished) {
|
|
864
958
|
return;
|
|
865
959
|
}
|
|
866
960
|
finished = true;
|
|
867
961
|
cleanup();
|
|
868
|
-
reject(
|
|
962
|
+
reject(failure);
|
|
869
963
|
};
|
|
870
964
|
const stop = () => {
|
|
965
|
+
diagnostics.connectionClosed(connectionEpoch, 1000, "Daemon stopped", "daemon_stop");
|
|
871
966
|
closeRelaySocket(socket, 1000, "Daemon stopped");
|
|
872
967
|
resolveConnection();
|
|
873
968
|
};
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
969
|
+
const failAndClose = (failure, code, reason, source) => {
|
|
970
|
+
diagnostics.connectionFailed(connectionEpoch, failure, source);
|
|
971
|
+
diagnostics.connectionClosed(connectionEpoch, code, reason, source);
|
|
972
|
+
closeRelaySocket(socket, code, reason);
|
|
973
|
+
rejectConnection(failure);
|
|
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
|
+
}
|
|
877
1149
|
};
|
|
878
1150
|
const connectionLiveness = new RelayConnectionLiveness(timer, {
|
|
879
1151
|
send: (message) => send(socket, message),
|
|
880
|
-
unresponsive: () => {
|
|
1152
|
+
unresponsive: (heartbeatId, timeoutMilliseconds) => {
|
|
1153
|
+
diagnostics.heartbeatTimedOut(connectionEpoch, heartbeatId, timeoutMilliseconds);
|
|
1154
|
+
diagnostics.connectionClosed(connectionEpoch, 4001, "Relay liveness check timed out", "heartbeat_timeout");
|
|
881
1155
|
closeRelaySocket(socket, 4001, "Relay liveness check timed out");
|
|
882
1156
|
resolveConnection();
|
|
883
1157
|
},
|
|
884
1158
|
});
|
|
885
|
-
const connectionRefresh = new RelayConnectionRefresh(timer, () => activeTurns.size === 0, refreshConnection);
|
|
886
1159
|
signal.addEventListener("abort", stop, { once: true });
|
|
887
|
-
const handleClose = () => {
|
|
1160
|
+
const handleClose = (event) => {
|
|
1161
|
+
diagnostics.connectionClosed(connectionEpoch, event.code, event.reason, "relay");
|
|
888
1162
|
resolveConnection();
|
|
889
1163
|
};
|
|
890
|
-
const handleError = () => {
|
|
1164
|
+
const handleError = (event) => {
|
|
891
1165
|
if (signal.aborted) {
|
|
892
1166
|
resolveConnection();
|
|
893
1167
|
return;
|
|
894
1168
|
}
|
|
895
|
-
|
|
1169
|
+
const cause = Reflect.get(event, "error");
|
|
1170
|
+
const failure = new Error("Relay WebSocket emitted an error", { cause: cause ?? event });
|
|
1171
|
+
diagnostics.connectionFailed(connectionEpoch, failure, "socket_error");
|
|
1172
|
+
diagnostics.connectionClosed(connectionEpoch, 1006, "WebSocket error", "socket_error");
|
|
1173
|
+
rejectConnection(failure);
|
|
896
1174
|
};
|
|
897
1175
|
const handleMessage = (event) => {
|
|
898
1176
|
const encoded = event.data;
|
|
899
1177
|
if (typeof encoded !== "string") {
|
|
900
|
-
|
|
901
|
-
rejectConnection();
|
|
1178
|
+
failAndClose(new Error("Relay sent a non-text message"), 4003, "Text message required", "protocol");
|
|
902
1179
|
return;
|
|
903
1180
|
}
|
|
904
1181
|
try {
|
|
905
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
|
+
}
|
|
906
1194
|
if (kind === SESSION_EVENT_ACK_KIND) {
|
|
907
|
-
|
|
1195
|
+
publisher.acknowledge(decodeSessionEventAcknowledgement(encoded));
|
|
908
1196
|
return;
|
|
909
1197
|
}
|
|
910
1198
|
if (kind === SESSION_EVENT_REJECTED_KIND) {
|
|
911
1199
|
const rejection = decodeSessionEventRejection(encoded);
|
|
912
|
-
if (
|
|
1200
|
+
if (publisher.quarantine(rejection)) {
|
|
913
1201
|
audit.record({
|
|
914
1202
|
commandId: rejection.eventId,
|
|
915
1203
|
details: { code: rejection.code },
|
|
@@ -1205,7 +1493,7 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1205
1493
|
const turn = new ActiveDaemonTurn(command, receipt.attemptId);
|
|
1206
1494
|
activeTurns.set(command.commandId, turn);
|
|
1207
1495
|
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
1208
|
-
void executeBotPrompt(outbox, publisher, command, config, directories, audit, sessions, grants, contexts, applicator, ipc, receipt.attemptId, turn.signal)
|
|
1496
|
+
void executeBotPrompt(outbox, publisher, command, config, directories, audit, sessions, grants, contexts, applicator, ipc, receipt.attemptId, timer, turn.signal)
|
|
1209
1497
|
.catch((error) => {
|
|
1210
1498
|
console.error("Mobius daemon Bot turn failed", error);
|
|
1211
1499
|
})
|
|
@@ -1215,7 +1503,6 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1215
1503
|
activeAttemptIds.delete(command.sessionId);
|
|
1216
1504
|
}
|
|
1217
1505
|
turn.finish();
|
|
1218
|
-
connectionRefresh.afterTurnFinished();
|
|
1219
1506
|
});
|
|
1220
1507
|
return;
|
|
1221
1508
|
}
|
|
@@ -1235,7 +1522,7 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1235
1522
|
if (command.disposition !== SessionPromptDisposition.Steer) {
|
|
1236
1523
|
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
1237
1524
|
}
|
|
1238
|
-
void executePrompt(outbox, publisher, command, config, directories, audit, sessions, receipt.attemptId, turn.signal)
|
|
1525
|
+
void executePrompt(outbox, publisher, command, config, directories, audit, sessions, receipt.attemptId, timer, turn.signal)
|
|
1239
1526
|
.catch((error) => {
|
|
1240
1527
|
console.error("Mobius daemon turn failed", error);
|
|
1241
1528
|
})
|
|
@@ -1245,13 +1532,15 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1245
1532
|
activeAttemptIds.delete(command.sessionId);
|
|
1246
1533
|
}
|
|
1247
1534
|
turn.finish();
|
|
1248
|
-
connectionRefresh.afterTurnFinished();
|
|
1249
1535
|
});
|
|
1250
1536
|
}
|
|
1251
1537
|
catch (error) {
|
|
1252
1538
|
console.error("Mobius daemon rejected a message", error);
|
|
1253
|
-
|
|
1254
|
-
|
|
1539
|
+
if (error instanceof RuntimeDeliveryInboxCapacityError) {
|
|
1540
|
+
failAndClose(error, 4013, "Runtime delivery queue full", "delivery_backpressure");
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
failAndClose(new Error("Relay sent an invalid protocol message", { cause: error }), 4008, "Invalid protocol message", "protocol");
|
|
1255
1544
|
}
|
|
1256
1545
|
};
|
|
1257
1546
|
socket.addEventListener("close", handleClose);
|
|
@@ -1262,14 +1551,13 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1262
1551
|
return;
|
|
1263
1552
|
}
|
|
1264
1553
|
publisher.connect(socket);
|
|
1265
|
-
|
|
1554
|
+
resumeRuntimeDeliveries();
|
|
1266
1555
|
}, (error) => {
|
|
1267
1556
|
if (finished) {
|
|
1268
1557
|
return;
|
|
1269
1558
|
}
|
|
1270
1559
|
console.error("Mobius daemon could not reconcile active Agent turns", error);
|
|
1271
|
-
|
|
1272
|
-
rejectConnection();
|
|
1560
|
+
failAndClose(new Error("Active Agent turn reconciliation failed", { cause: error }), ACTIVE_TURN_RECONCILIATION_CLOSE_CODE, "Active Agent turn reconciliation failed", "reconciliation");
|
|
1273
1561
|
});
|
|
1274
1562
|
if (signal.aborted) {
|
|
1275
1563
|
stop();
|
|
@@ -1313,9 +1601,10 @@ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer
|
|
|
1313
1601
|
const sessions = new AcpSessionManager(new DaemonAcpObserver(recordSessionEvent), sessionStore);
|
|
1314
1602
|
const directories = new WorkspaceDirectoryService(config.bindingId, config.defaultWorkspace);
|
|
1315
1603
|
const installationObserver = new DaemonInstallationObserver();
|
|
1604
|
+
const diagnostics = new RelayConnectionDiagnostics(audit, config.channelId, config.relayUrl);
|
|
1316
1605
|
const nativeSessions = new NativeSessionService(config, directories, sessions, audit);
|
|
1317
1606
|
const installations = new AgentInstallationManager(specifications, installationExecutor, installationObserver, installationAudit);
|
|
1318
|
-
let
|
|
1607
|
+
let connectionEpoch = 0;
|
|
1319
1608
|
let connectionAnnounced = false;
|
|
1320
1609
|
const announceConnection = () => {
|
|
1321
1610
|
if (connectionAnnounced) {
|
|
@@ -1326,22 +1615,19 @@ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer
|
|
|
1326
1615
|
process.stdout.write("Mobius daemon connected to the relay.\n\n");
|
|
1327
1616
|
process.stdout.write(`${styleText(["bold", "yellow"], `Reconnect this channel later:\n ${reconnectCommand}`)}\n\n`);
|
|
1328
1617
|
};
|
|
1329
|
-
const catalogPublication = publishAgentCatalogUntilAvailable(config, signal, timer);
|
|
1618
|
+
const catalogPublication = publishAgentCatalogUntilAvailable(config, diagnostics, signal, timer);
|
|
1330
1619
|
try {
|
|
1331
1620
|
await ipc.listen(signal);
|
|
1332
1621
|
while (!signal.aborted) {
|
|
1622
|
+
connectionEpoch += 1;
|
|
1333
1623
|
try {
|
|
1334
|
-
await runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, announceConnection, signal, timer);
|
|
1335
|
-
retryAnnounced = false;
|
|
1624
|
+
await runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, connectionEpoch, diagnostics, announceConnection, signal, timer);
|
|
1336
1625
|
}
|
|
1337
|
-
catch {
|
|
1626
|
+
catch (error) {
|
|
1338
1627
|
if (signal.aborted) {
|
|
1339
1628
|
return;
|
|
1340
1629
|
}
|
|
1341
|
-
|
|
1342
|
-
console.warn("Mobius daemon is waiting for the relay");
|
|
1343
|
-
retryAnnounced = true;
|
|
1344
|
-
}
|
|
1630
|
+
diagnostics.connectionFailed(connectionEpoch, error, "connection");
|
|
1345
1631
|
}
|
|
1346
1632
|
await waitForRelayRetry(signal, timer);
|
|
1347
1633
|
}
|