@moxt-ai/mobius 0.0.25 → 0.0.26
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/cli.js +466 -173
- package/dist/cli.js.map +4 -4
- package/dist/index.js +465 -172
- 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/relay-connection/connect.d.ts.map +1 -1
- package/dist/relay-connection/connect.js +16 -8
- 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/session-event-outbox.d.ts +2 -1
- package/dist/relay-connection/session-event-outbox.d.ts.map +1 -1
- package/dist/relay-connection/session-event-outbox.js +48 -8
- 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 +182 -146
- package/dist/run-daemon.js.map +1 -1
- package/dist/runtime.js +465 -172
- 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, 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";
|
|
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,13 @@ 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
25
|
import { SessionEventOutbox } from "./relay-connection/session-event-outbox.js";
|
|
25
26
|
import { systemDaemonTimer } from "./relay-connection/timer.js";
|
|
26
27
|
import { readStateDirectory } from "./storage/state-directory.js";
|
|
27
28
|
const RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
|
|
28
|
-
const
|
|
29
|
+
const AGENT_ACTIVITY_COALESCING_MILLISECONDS = 100;
|
|
29
30
|
const ACTIVE_TURN_RECONCILIATION_TIMEOUT_MILLISECONDS = 20_000;
|
|
30
31
|
const ACTIVE_TURN_RECONCILIATION_CLOSE_CODE = 4009;
|
|
31
32
|
function botIpcSocketPath() {
|
|
@@ -34,56 +35,6 @@ function botIpcSocketPath() {
|
|
|
34
35
|
}
|
|
35
36
|
return join("/tmp", `mobius-bot-${randomUUID()}.sock`);
|
|
36
37
|
}
|
|
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
38
|
class DaemonAcpObserver {
|
|
88
39
|
#record;
|
|
89
40
|
constructor(record) {
|
|
@@ -335,7 +286,8 @@ class DaemonSessionEventConnectionState {
|
|
|
335
286
|
}
|
|
336
287
|
class DisconnectedDaemonSessionEventState extends DaemonSessionEventConnectionState {
|
|
337
288
|
disconnect = () => this;
|
|
338
|
-
|
|
289
|
+
matches = () => false;
|
|
290
|
+
send = () => false;
|
|
339
291
|
}
|
|
340
292
|
class ConnectedDaemonSessionEventState extends DaemonSessionEventConnectionState {
|
|
341
293
|
#socket;
|
|
@@ -344,40 +296,151 @@ class ConnectedDaemonSessionEventState extends DaemonSessionEventConnectionState
|
|
|
344
296
|
this.#socket = socket;
|
|
345
297
|
}
|
|
346
298
|
disconnect = (socket) => socket === this.#socket ? new DisconnectedDaemonSessionEventState() : this;
|
|
299
|
+
matches = (socket) => socket === this.#socket;
|
|
347
300
|
send = (message) => {
|
|
348
|
-
if (this.#socket.readyState
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
301
|
+
if (this.#socket.readyState !== WebSocket.OPEN) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
this.#socket.send(message);
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// The durable outbox retains the event for the replacement connection.
|
|
310
|
+
return false;
|
|
355
311
|
}
|
|
356
312
|
};
|
|
357
313
|
}
|
|
314
|
+
const SESSION_EVENT_REPLAY_WINDOW = 4;
|
|
358
315
|
class DaemonSessionEventPublisher {
|
|
359
316
|
#outbox;
|
|
317
|
+
#inFlightEventIds = new Set();
|
|
360
318
|
#connection = new DisconnectedDaemonSessionEventState();
|
|
361
319
|
constructor(outbox) {
|
|
362
320
|
this.#outbox = outbox;
|
|
363
321
|
}
|
|
364
322
|
connect = (socket) => {
|
|
365
|
-
|
|
366
|
-
this.#
|
|
367
|
-
|
|
368
|
-
connection.send(pending);
|
|
369
|
-
}
|
|
323
|
+
this.#connection = new ConnectedDaemonSessionEventState(socket);
|
|
324
|
+
this.#inFlightEventIds.clear();
|
|
325
|
+
this.#publishNextBatch();
|
|
370
326
|
};
|
|
371
327
|
disconnect = (socket) => {
|
|
328
|
+
if (!this.#connection.matches(socket)) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
372
331
|
this.#connection = this.#connection.disconnect(socket);
|
|
332
|
+
this.#inFlightEventIds.clear();
|
|
373
333
|
};
|
|
374
334
|
recordAttempt = (sessionId, payload, attemptId) => {
|
|
375
|
-
this.#
|
|
335
|
+
this.#outbox.enqueue(sessionId, payload, attemptId);
|
|
336
|
+
this.#publishNextBatch();
|
|
376
337
|
};
|
|
377
338
|
recordRuntime = (sessionId, payload) => {
|
|
378
|
-
this.#
|
|
339
|
+
this.#outbox.enqueue(sessionId, payload);
|
|
340
|
+
this.#publishNextBatch();
|
|
341
|
+
};
|
|
342
|
+
acknowledge = (acknowledgement) => {
|
|
343
|
+
if (!this.#outbox.acknowledge(acknowledgement)) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this.#inFlightEventIds.delete(acknowledgement.eventId);
|
|
347
|
+
this.#publishNextBatch();
|
|
348
|
+
};
|
|
349
|
+
quarantine = (rejection) => {
|
|
350
|
+
const quarantined = this.#outbox.quarantine(rejection);
|
|
351
|
+
if (!quarantined) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
this.#inFlightEventIds.delete(rejection.eventId);
|
|
355
|
+
this.#publishNextBatch();
|
|
356
|
+
return true;
|
|
357
|
+
};
|
|
358
|
+
#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();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
this.#inFlightEventIds.add(envelope.eventId);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
class CoalescingDaemonActivityPublisher {
|
|
374
|
+
#pending = new Map();
|
|
375
|
+
#publish;
|
|
376
|
+
#publishedActivityIds = new Set();
|
|
377
|
+
#scheduled = new Map();
|
|
378
|
+
#timer;
|
|
379
|
+
constructor(timer, publish) {
|
|
380
|
+
this.#publish = publish;
|
|
381
|
+
this.#timer = timer;
|
|
382
|
+
}
|
|
383
|
+
flush = () => {
|
|
384
|
+
const activities = [...this.#pending.values()].sort((left, right) => left.sequence - right.sequence);
|
|
385
|
+
for (const task of this.#scheduled.values()) {
|
|
386
|
+
task.cancel();
|
|
387
|
+
}
|
|
388
|
+
this.#pending.clear();
|
|
389
|
+
this.#scheduled.clear();
|
|
390
|
+
for (const activity of activities) {
|
|
391
|
+
this.#publish(activity);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
record = (activity) => {
|
|
395
|
+
if (activity.status !== "pending" && activity.status !== "running") {
|
|
396
|
+
this.#discardPending(activity.activityId);
|
|
397
|
+
this.#publishedActivityIds.add(activity.activityId);
|
|
398
|
+
this.#publish(activity);
|
|
399
|
+
return Promise.resolve();
|
|
400
|
+
}
|
|
401
|
+
if (!this.#publishedActivityIds.has(activity.activityId)) {
|
|
402
|
+
this.#publishedActivityIds.add(activity.activityId);
|
|
403
|
+
this.#publish(activity);
|
|
404
|
+
return Promise.resolve();
|
|
405
|
+
}
|
|
406
|
+
this.#pending.set(activity.activityId, activity);
|
|
407
|
+
if (!this.#scheduled.has(activity.activityId)) {
|
|
408
|
+
this.#scheduled.set(activity.activityId, this.#timer.schedule(() => this.#flushActivity(activity.activityId), AGENT_ACTIVITY_COALESCING_MILLISECONDS));
|
|
409
|
+
}
|
|
410
|
+
return Promise.resolve();
|
|
411
|
+
};
|
|
412
|
+
#discardPending = (activityId) => {
|
|
413
|
+
this.#scheduled.get(activityId)?.cancel();
|
|
414
|
+
this.#scheduled.delete(activityId);
|
|
415
|
+
this.#pending.delete(activityId);
|
|
416
|
+
};
|
|
417
|
+
#flushActivity = (activityId) => {
|
|
418
|
+
const activity = this.#pending.get(activityId);
|
|
419
|
+
this.#discardPending(activityId);
|
|
420
|
+
if (activity !== undefined) {
|
|
421
|
+
this.#publish(activity);
|
|
422
|
+
}
|
|
379
423
|
};
|
|
380
424
|
}
|
|
425
|
+
function encodeDaemonActivity(command, activity) {
|
|
426
|
+
return encodeAgentActivityEvent({
|
|
427
|
+
activityId: activity.activityId,
|
|
428
|
+
activityType: activity.activityType,
|
|
429
|
+
commandId: command.commandId,
|
|
430
|
+
content: activity.content,
|
|
431
|
+
detail: activity.detail,
|
|
432
|
+
input: activity.input,
|
|
433
|
+
kind: AGENT_ACTIVITY_KIND,
|
|
434
|
+
locations: activity.locations,
|
|
435
|
+
name: activity.name,
|
|
436
|
+
output: activity.output,
|
|
437
|
+
sequence: activity.sequence,
|
|
438
|
+
sessionId: command.sessionId,
|
|
439
|
+
status: activity.status,
|
|
440
|
+
title: activity.title,
|
|
441
|
+
toolKind: activity.toolKind,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
381
444
|
function reportFailure(publisher, command, code, message, attemptId) {
|
|
382
445
|
publisher.recordAttempt(command.sessionId, encodeSessionFailedEvent({
|
|
383
446
|
code,
|
|
@@ -537,7 +600,10 @@ async function executeSessionConfigure(publisher, socket, command, config, direc
|
|
|
537
600
|
reportOperationFailure(socket, command.requestId, command.sessionId, error);
|
|
538
601
|
}
|
|
539
602
|
}
|
|
540
|
-
async function executePrompt(outbox, publisher, command, config, directories, audit, sessions, attemptId, signal) {
|
|
603
|
+
async function executePrompt(outbox, publisher, command, config, directories, audit, sessions, attemptId, timer, signal) {
|
|
604
|
+
const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
|
|
605
|
+
publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
|
|
606
|
+
});
|
|
541
607
|
try {
|
|
542
608
|
const resolved = await resolveSessionDirectories(directories, config.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
|
|
543
609
|
const agent = config.catalog.find(command.agentId);
|
|
@@ -582,26 +648,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
582
648
|
cwd: resolved.cwd,
|
|
583
649
|
disposition: command.disposition,
|
|
584
650
|
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
|
-
},
|
|
651
|
+
onActivity: activities.record,
|
|
605
652
|
onText: (text, messageId) => {
|
|
606
653
|
publisher.recordAttempt(command.sessionId, encodeAgentMessageDeltaEvent({
|
|
607
654
|
commandId: command.commandId,
|
|
@@ -620,6 +667,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
620
667
|
});
|
|
621
668
|
stopReason = result.stopReason;
|
|
622
669
|
}
|
|
670
|
+
activities.flush();
|
|
623
671
|
publisher.recordAttempt(command.sessionId, encodeSessionCompletedEvent({
|
|
624
672
|
commandId: command.commandId,
|
|
625
673
|
kind: SESSION_COMPLETED_KIND,
|
|
@@ -634,6 +682,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
634
682
|
});
|
|
635
683
|
}
|
|
636
684
|
catch (error) {
|
|
685
|
+
activities.flush();
|
|
637
686
|
audit.record({
|
|
638
687
|
commandId: command.commandId,
|
|
639
688
|
event: "session_failed",
|
|
@@ -656,7 +705,10 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
|
|
|
656
705
|
MOBIUS_RELAY_URL: "",
|
|
657
706
|
}, agent.adapterVersion, agent.agentVersion, agent.adapterIdentity, agent.agentIdentity, agent.promptTimeoutMilliseconds, agent.capabilities);
|
|
658
707
|
}
|
|
659
|
-
async function executeBotPrompt(outbox, publisher, command, config, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, signal) {
|
|
708
|
+
async function executeBotPrompt(outbox, publisher, command, config, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, timer, signal) {
|
|
709
|
+
const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
|
|
710
|
+
publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
|
|
711
|
+
});
|
|
660
712
|
try {
|
|
661
713
|
const deadline = Date.parse(command.executionDeadlineAt);
|
|
662
714
|
const remaining = deadline - Date.now();
|
|
@@ -713,26 +765,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
713
765
|
commandId: command.commandId,
|
|
714
766
|
cwd: resolved.cwd,
|
|
715
767
|
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
|
-
},
|
|
768
|
+
onActivity: activities.record,
|
|
736
769
|
onText: (text, messageId) => {
|
|
737
770
|
publisher.recordAttempt(command.sessionId, encodeAgentMessageDeltaEvent({
|
|
738
771
|
commandId: command.commandId,
|
|
@@ -748,6 +781,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
748
781
|
sessionId: command.sessionId,
|
|
749
782
|
signal: executionSignal,
|
|
750
783
|
});
|
|
784
|
+
activities.flush();
|
|
751
785
|
publisher.recordAttempt(command.sessionId, encodeSessionCompletedEvent({
|
|
752
786
|
commandId: command.commandId,
|
|
753
787
|
kind: SESSION_COMPLETED_KIND,
|
|
@@ -767,6 +801,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
767
801
|
}
|
|
768
802
|
}
|
|
769
803
|
catch (error) {
|
|
804
|
+
activities.flush();
|
|
770
805
|
const failure = Date.parse(command.executionDeadlineAt) <= Date.now()
|
|
771
806
|
? new LocalBotAuthorizationError("turn_expired", "The Bot execution deadline has expired", {
|
|
772
807
|
cause: error,
|
|
@@ -804,16 +839,16 @@ function waitForRelayRetry(signal, timer) {
|
|
|
804
839
|
signal.addEventListener("abort", finish, { once: true });
|
|
805
840
|
});
|
|
806
841
|
}
|
|
807
|
-
async function publishAgentCatalogUntilAvailable(config, signal, timer) {
|
|
842
|
+
async function publishAgentCatalogUntilAvailable(config, diagnostics, signal, timer) {
|
|
808
843
|
try {
|
|
809
844
|
await publishAgentCatalog(config, signal);
|
|
810
845
|
return;
|
|
811
846
|
}
|
|
812
|
-
catch {
|
|
847
|
+
catch (error) {
|
|
813
848
|
if (signal.aborted) {
|
|
814
849
|
return;
|
|
815
850
|
}
|
|
816
|
-
|
|
851
|
+
diagnostics.catalogFailed(error);
|
|
817
852
|
}
|
|
818
853
|
while (!signal.aborted) {
|
|
819
854
|
await waitForRelayRetry(signal, timer);
|
|
@@ -828,11 +863,12 @@ async function publishAgentCatalogUntilAvailable(config, signal, timer) {
|
|
|
828
863
|
}
|
|
829
864
|
}
|
|
830
865
|
}
|
|
831
|
-
async function runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, onConnected, signal, timer) {
|
|
866
|
+
async function runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, connectionEpoch, diagnostics, onConnected, signal, timer) {
|
|
832
867
|
const relayUrl = new URL(config.relayUrl);
|
|
833
868
|
relayUrl.searchParams.set("liveness", "1");
|
|
834
869
|
const socket = await connectRelay(relayUrl, signal);
|
|
835
870
|
const reconnectingTurns = [...activeTurns.values()];
|
|
871
|
+
diagnostics.connectionOpened(connectionEpoch);
|
|
836
872
|
onConnected();
|
|
837
873
|
const connectionController = new AbortController();
|
|
838
874
|
const nativeRequests = new Map();
|
|
@@ -841,7 +877,6 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
841
877
|
await new Promise((resolve, reject) => {
|
|
842
878
|
let finished = false;
|
|
843
879
|
const cleanup = () => {
|
|
844
|
-
connectionRefresh.close();
|
|
845
880
|
connectionLiveness.close();
|
|
846
881
|
signal.removeEventListener("abort", stop);
|
|
847
882
|
socket.removeEventListener("close", handleClose);
|
|
@@ -859,57 +894,65 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
859
894
|
cleanup();
|
|
860
895
|
resolve();
|
|
861
896
|
};
|
|
862
|
-
const rejectConnection = () => {
|
|
897
|
+
const rejectConnection = (failure) => {
|
|
863
898
|
if (finished) {
|
|
864
899
|
return;
|
|
865
900
|
}
|
|
866
901
|
finished = true;
|
|
867
902
|
cleanup();
|
|
868
|
-
reject(
|
|
903
|
+
reject(failure);
|
|
869
904
|
};
|
|
870
905
|
const stop = () => {
|
|
906
|
+
diagnostics.connectionClosed(connectionEpoch, 1000, "Daemon stopped", "daemon_stop");
|
|
871
907
|
closeRelaySocket(socket, 1000, "Daemon stopped");
|
|
872
908
|
resolveConnection();
|
|
873
909
|
};
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
910
|
+
const failAndClose = (failure, code, reason, source) => {
|
|
911
|
+
diagnostics.connectionFailed(connectionEpoch, failure, source);
|
|
912
|
+
diagnostics.connectionClosed(connectionEpoch, code, reason, source);
|
|
913
|
+
closeRelaySocket(socket, code, reason);
|
|
914
|
+
rejectConnection(failure);
|
|
877
915
|
};
|
|
878
916
|
const connectionLiveness = new RelayConnectionLiveness(timer, {
|
|
879
917
|
send: (message) => send(socket, message),
|
|
880
|
-
unresponsive: () => {
|
|
918
|
+
unresponsive: (heartbeatId, timeoutMilliseconds) => {
|
|
919
|
+
diagnostics.heartbeatTimedOut(connectionEpoch, heartbeatId, timeoutMilliseconds);
|
|
920
|
+
diagnostics.connectionClosed(connectionEpoch, 4001, "Relay liveness check timed out", "heartbeat_timeout");
|
|
881
921
|
closeRelaySocket(socket, 4001, "Relay liveness check timed out");
|
|
882
922
|
resolveConnection();
|
|
883
923
|
},
|
|
884
924
|
});
|
|
885
|
-
const connectionRefresh = new RelayConnectionRefresh(timer, () => activeTurns.size === 0, refreshConnection);
|
|
886
925
|
signal.addEventListener("abort", stop, { once: true });
|
|
887
|
-
const handleClose = () => {
|
|
926
|
+
const handleClose = (event) => {
|
|
927
|
+
diagnostics.connectionClosed(connectionEpoch, event.code, event.reason, "relay");
|
|
888
928
|
resolveConnection();
|
|
889
929
|
};
|
|
890
|
-
const handleError = () => {
|
|
930
|
+
const handleError = (event) => {
|
|
891
931
|
if (signal.aborted) {
|
|
892
932
|
resolveConnection();
|
|
893
933
|
return;
|
|
894
934
|
}
|
|
895
|
-
|
|
935
|
+
const cause = Reflect.get(event, "error");
|
|
936
|
+
const failure = new Error("Relay WebSocket emitted an error", { cause: cause ?? event });
|
|
937
|
+
diagnostics.connectionFailed(connectionEpoch, failure, "socket_error");
|
|
938
|
+
diagnostics.connectionClosed(connectionEpoch, 1006, "WebSocket error", "socket_error");
|
|
939
|
+
rejectConnection(failure);
|
|
896
940
|
};
|
|
897
941
|
const handleMessage = (event) => {
|
|
898
942
|
const encoded = event.data;
|
|
899
943
|
if (typeof encoded !== "string") {
|
|
900
|
-
|
|
901
|
-
rejectConnection();
|
|
944
|
+
failAndClose(new Error("Relay sent a non-text message"), 4003, "Text message required", "protocol");
|
|
902
945
|
return;
|
|
903
946
|
}
|
|
904
947
|
try {
|
|
905
948
|
const kind = decodeMessageKind(encoded);
|
|
906
949
|
if (kind === SESSION_EVENT_ACK_KIND) {
|
|
907
|
-
|
|
950
|
+
publisher.acknowledge(decodeSessionEventAcknowledgement(encoded));
|
|
908
951
|
return;
|
|
909
952
|
}
|
|
910
953
|
if (kind === SESSION_EVENT_REJECTED_KIND) {
|
|
911
954
|
const rejection = decodeSessionEventRejection(encoded);
|
|
912
|
-
if (
|
|
955
|
+
if (publisher.quarantine(rejection)) {
|
|
913
956
|
audit.record({
|
|
914
957
|
commandId: rejection.eventId,
|
|
915
958
|
details: { code: rejection.code },
|
|
@@ -1205,7 +1248,7 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1205
1248
|
const turn = new ActiveDaemonTurn(command, receipt.attemptId);
|
|
1206
1249
|
activeTurns.set(command.commandId, turn);
|
|
1207
1250
|
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
1208
|
-
void executeBotPrompt(outbox, publisher, command, config, directories, audit, sessions, grants, contexts, applicator, ipc, receipt.attemptId, turn.signal)
|
|
1251
|
+
void executeBotPrompt(outbox, publisher, command, config, directories, audit, sessions, grants, contexts, applicator, ipc, receipt.attemptId, timer, turn.signal)
|
|
1209
1252
|
.catch((error) => {
|
|
1210
1253
|
console.error("Mobius daemon Bot turn failed", error);
|
|
1211
1254
|
})
|
|
@@ -1215,7 +1258,6 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1215
1258
|
activeAttemptIds.delete(command.sessionId);
|
|
1216
1259
|
}
|
|
1217
1260
|
turn.finish();
|
|
1218
|
-
connectionRefresh.afterTurnFinished();
|
|
1219
1261
|
});
|
|
1220
1262
|
return;
|
|
1221
1263
|
}
|
|
@@ -1235,7 +1277,7 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1235
1277
|
if (command.disposition !== SessionPromptDisposition.Steer) {
|
|
1236
1278
|
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
1237
1279
|
}
|
|
1238
|
-
void executePrompt(outbox, publisher, command, config, directories, audit, sessions, receipt.attemptId, turn.signal)
|
|
1280
|
+
void executePrompt(outbox, publisher, command, config, directories, audit, sessions, receipt.attemptId, timer, turn.signal)
|
|
1239
1281
|
.catch((error) => {
|
|
1240
1282
|
console.error("Mobius daemon turn failed", error);
|
|
1241
1283
|
})
|
|
@@ -1245,13 +1287,11 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1245
1287
|
activeAttemptIds.delete(command.sessionId);
|
|
1246
1288
|
}
|
|
1247
1289
|
turn.finish();
|
|
1248
|
-
connectionRefresh.afterTurnFinished();
|
|
1249
1290
|
});
|
|
1250
1291
|
}
|
|
1251
1292
|
catch (error) {
|
|
1252
1293
|
console.error("Mobius daemon rejected a message", error);
|
|
1253
|
-
|
|
1254
|
-
rejectConnection();
|
|
1294
|
+
failAndClose(new Error("Relay sent an invalid protocol message", { cause: error }), 4008, "Invalid protocol message", "protocol");
|
|
1255
1295
|
}
|
|
1256
1296
|
};
|
|
1257
1297
|
socket.addEventListener("close", handleClose);
|
|
@@ -1262,14 +1302,12 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1262
1302
|
return;
|
|
1263
1303
|
}
|
|
1264
1304
|
publisher.connect(socket);
|
|
1265
|
-
void Promise.all(reconnectingTurns.map((turn) => turn.completion)).then(connectionRefresh.afterTurnFinished);
|
|
1266
1305
|
}, (error) => {
|
|
1267
1306
|
if (finished) {
|
|
1268
1307
|
return;
|
|
1269
1308
|
}
|
|
1270
1309
|
console.error("Mobius daemon could not reconcile active Agent turns", error);
|
|
1271
|
-
|
|
1272
|
-
rejectConnection();
|
|
1310
|
+
failAndClose(new Error("Active Agent turn reconciliation failed", { cause: error }), ACTIVE_TURN_RECONCILIATION_CLOSE_CODE, "Active Agent turn reconciliation failed", "reconciliation");
|
|
1273
1311
|
});
|
|
1274
1312
|
if (signal.aborted) {
|
|
1275
1313
|
stop();
|
|
@@ -1313,9 +1351,10 @@ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer
|
|
|
1313
1351
|
const sessions = new AcpSessionManager(new DaemonAcpObserver(recordSessionEvent), sessionStore);
|
|
1314
1352
|
const directories = new WorkspaceDirectoryService(config.bindingId, config.defaultWorkspace);
|
|
1315
1353
|
const installationObserver = new DaemonInstallationObserver();
|
|
1354
|
+
const diagnostics = new RelayConnectionDiagnostics(audit, config.channelId, config.relayUrl);
|
|
1316
1355
|
const nativeSessions = new NativeSessionService(config, directories, sessions, audit);
|
|
1317
1356
|
const installations = new AgentInstallationManager(specifications, installationExecutor, installationObserver, installationAudit);
|
|
1318
|
-
let
|
|
1357
|
+
let connectionEpoch = 0;
|
|
1319
1358
|
let connectionAnnounced = false;
|
|
1320
1359
|
const announceConnection = () => {
|
|
1321
1360
|
if (connectionAnnounced) {
|
|
@@ -1326,22 +1365,19 @@ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer
|
|
|
1326
1365
|
process.stdout.write("Mobius daemon connected to the relay.\n\n");
|
|
1327
1366
|
process.stdout.write(`${styleText(["bold", "yellow"], `Reconnect this channel later:\n ${reconnectCommand}`)}\n\n`);
|
|
1328
1367
|
};
|
|
1329
|
-
const catalogPublication = publishAgentCatalogUntilAvailable(config, signal, timer);
|
|
1368
|
+
const catalogPublication = publishAgentCatalogUntilAvailable(config, diagnostics, signal, timer);
|
|
1330
1369
|
try {
|
|
1331
1370
|
await ipc.listen(signal);
|
|
1332
1371
|
while (!signal.aborted) {
|
|
1372
|
+
connectionEpoch += 1;
|
|
1333
1373
|
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;
|
|
1374
|
+
await runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, connectionEpoch, diagnostics, announceConnection, signal, timer);
|
|
1336
1375
|
}
|
|
1337
|
-
catch {
|
|
1376
|
+
catch (error) {
|
|
1338
1377
|
if (signal.aborted) {
|
|
1339
1378
|
return;
|
|
1340
1379
|
}
|
|
1341
|
-
|
|
1342
|
-
console.warn("Mobius daemon is waiting for the relay");
|
|
1343
|
-
retryAnnounced = true;
|
|
1344
|
-
}
|
|
1380
|
+
diagnostics.connectionFailed(connectionEpoch, error, "connection");
|
|
1345
1381
|
}
|
|
1346
1382
|
await waitForRelayRetry(signal, timer);
|
|
1347
1383
|
}
|