@moxt-ai/mobius 0.0.24 → 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/agent-catalog/publish-agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog/publish-agent-catalog.js +12 -7
- package/dist/agent-catalog/publish-agent-catalog.js.map +1 -1
- package/dist/cli.js +685 -290
- package/dist/cli.js.map +4 -4
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +746 -254
- 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/pairing/pair-local-machine.d.ts +8 -0
- package/dist/pairing/pair-local-machine.d.ts.map +1 -1
- package/dist/pairing/pair-local-machine.js +64 -37
- package/dist/pairing/pair-local-machine.js.map +1 -1
- package/dist/prompt-artifact-resolver.d.ts.map +1 -1
- package/dist/prompt-artifact-resolver.js +11 -3
- 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 +25 -9
- 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 +193 -149
- package/dist/run-daemon.js.map +1 -1
- package/dist/runtime.d.ts +23 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +31339 -0
- package/dist/runtime.js.map +7 -0
- package/package.json +6 -2
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) {
|
|
@@ -266,10 +217,18 @@ class ActiveTurnReconciliationError extends Error {
|
|
|
266
217
|
function activeTurnReconciliationEndpoint(config, turn) {
|
|
267
218
|
const endpoint = new URL(config.relayUrl);
|
|
268
219
|
endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
|
|
269
|
-
|
|
270
|
-
|
|
220
|
+
let channelPath = "";
|
|
221
|
+
if (endpoint.pathname.endsWith("/daemon")) {
|
|
222
|
+
channelPath = endpoint.pathname.slice(0, -"/daemon".length);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const runtimePath = "/v1/runtimes/connect";
|
|
226
|
+
if (!endpoint.pathname.endsWith(runtimePath)) {
|
|
227
|
+
throw new ActiveTurnReconciliationError("The Relay URL is invalid");
|
|
228
|
+
}
|
|
229
|
+
channelPath = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config.channelId)}`;
|
|
271
230
|
}
|
|
272
|
-
endpoint.pathname = `${
|
|
231
|
+
endpoint.pathname = `${channelPath}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
|
|
273
232
|
endpoint.searchParams.delete("liveness");
|
|
274
233
|
return endpoint;
|
|
275
234
|
}
|
|
@@ -327,7 +286,8 @@ class DaemonSessionEventConnectionState {
|
|
|
327
286
|
}
|
|
328
287
|
class DisconnectedDaemonSessionEventState extends DaemonSessionEventConnectionState {
|
|
329
288
|
disconnect = () => this;
|
|
330
|
-
|
|
289
|
+
matches = () => false;
|
|
290
|
+
send = () => false;
|
|
331
291
|
}
|
|
332
292
|
class ConnectedDaemonSessionEventState extends DaemonSessionEventConnectionState {
|
|
333
293
|
#socket;
|
|
@@ -336,40 +296,151 @@ class ConnectedDaemonSessionEventState extends DaemonSessionEventConnectionState
|
|
|
336
296
|
this.#socket = socket;
|
|
337
297
|
}
|
|
338
298
|
disconnect = (socket) => socket === this.#socket ? new DisconnectedDaemonSessionEventState() : this;
|
|
299
|
+
matches = (socket) => socket === this.#socket;
|
|
339
300
|
send = (message) => {
|
|
340
|
-
if (this.#socket.readyState
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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;
|
|
347
311
|
}
|
|
348
312
|
};
|
|
349
313
|
}
|
|
314
|
+
const SESSION_EVENT_REPLAY_WINDOW = 4;
|
|
350
315
|
class DaemonSessionEventPublisher {
|
|
351
316
|
#outbox;
|
|
317
|
+
#inFlightEventIds = new Set();
|
|
352
318
|
#connection = new DisconnectedDaemonSessionEventState();
|
|
353
319
|
constructor(outbox) {
|
|
354
320
|
this.#outbox = outbox;
|
|
355
321
|
}
|
|
356
322
|
connect = (socket) => {
|
|
357
|
-
|
|
358
|
-
this.#
|
|
359
|
-
|
|
360
|
-
connection.send(pending);
|
|
361
|
-
}
|
|
323
|
+
this.#connection = new ConnectedDaemonSessionEventState(socket);
|
|
324
|
+
this.#inFlightEventIds.clear();
|
|
325
|
+
this.#publishNextBatch();
|
|
362
326
|
};
|
|
363
327
|
disconnect = (socket) => {
|
|
328
|
+
if (!this.#connection.matches(socket)) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
364
331
|
this.#connection = this.#connection.disconnect(socket);
|
|
332
|
+
this.#inFlightEventIds.clear();
|
|
365
333
|
};
|
|
366
334
|
recordAttempt = (sessionId, payload, attemptId) => {
|
|
367
|
-
this.#
|
|
335
|
+
this.#outbox.enqueue(sessionId, payload, attemptId);
|
|
336
|
+
this.#publishNextBatch();
|
|
368
337
|
};
|
|
369
338
|
recordRuntime = (sessionId, payload) => {
|
|
370
|
-
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
|
+
}
|
|
371
423
|
};
|
|
372
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
|
+
}
|
|
373
444
|
function reportFailure(publisher, command, code, message, attemptId) {
|
|
374
445
|
publisher.recordAttempt(command.sessionId, encodeSessionFailedEvent({
|
|
375
446
|
code,
|
|
@@ -529,7 +600,10 @@ async function executeSessionConfigure(publisher, socket, command, config, direc
|
|
|
529
600
|
reportOperationFailure(socket, command.requestId, command.sessionId, error);
|
|
530
601
|
}
|
|
531
602
|
}
|
|
532
|
-
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
|
+
});
|
|
533
607
|
try {
|
|
534
608
|
const resolved = await resolveSessionDirectories(directories, config.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
|
|
535
609
|
const agent = config.catalog.find(command.agentId);
|
|
@@ -574,26 +648,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
574
648
|
cwd: resolved.cwd,
|
|
575
649
|
disposition: command.disposition,
|
|
576
650
|
images: attachments.images,
|
|
577
|
-
onActivity:
|
|
578
|
-
publisher.recordAttempt(command.sessionId, encodeAgentActivityEvent({
|
|
579
|
-
activityId: activity.activityId,
|
|
580
|
-
activityType: activity.activityType,
|
|
581
|
-
commandId: command.commandId,
|
|
582
|
-
content: activity.content,
|
|
583
|
-
detail: activity.detail,
|
|
584
|
-
input: activity.input,
|
|
585
|
-
kind: AGENT_ACTIVITY_KIND,
|
|
586
|
-
locations: activity.locations,
|
|
587
|
-
name: activity.name,
|
|
588
|
-
output: activity.output,
|
|
589
|
-
sequence: activity.sequence,
|
|
590
|
-
sessionId: command.sessionId,
|
|
591
|
-
status: activity.status,
|
|
592
|
-
title: activity.title,
|
|
593
|
-
toolKind: activity.toolKind,
|
|
594
|
-
}), attemptId);
|
|
595
|
-
return Promise.resolve();
|
|
596
|
-
},
|
|
651
|
+
onActivity: activities.record,
|
|
597
652
|
onText: (text, messageId) => {
|
|
598
653
|
publisher.recordAttempt(command.sessionId, encodeAgentMessageDeltaEvent({
|
|
599
654
|
commandId: command.commandId,
|
|
@@ -612,6 +667,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
612
667
|
});
|
|
613
668
|
stopReason = result.stopReason;
|
|
614
669
|
}
|
|
670
|
+
activities.flush();
|
|
615
671
|
publisher.recordAttempt(command.sessionId, encodeSessionCompletedEvent({
|
|
616
672
|
commandId: command.commandId,
|
|
617
673
|
kind: SESSION_COMPLETED_KIND,
|
|
@@ -626,6 +682,7 @@ async function executePrompt(outbox, publisher, command, config, directories, au
|
|
|
626
682
|
});
|
|
627
683
|
}
|
|
628
684
|
catch (error) {
|
|
685
|
+
activities.flush();
|
|
629
686
|
audit.record({
|
|
630
687
|
commandId: command.commandId,
|
|
631
688
|
event: "session_failed",
|
|
@@ -648,7 +705,10 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
|
|
|
648
705
|
MOBIUS_RELAY_URL: "",
|
|
649
706
|
}, agent.adapterVersion, agent.agentVersion, agent.adapterIdentity, agent.agentIdentity, agent.promptTimeoutMilliseconds, agent.capabilities);
|
|
650
707
|
}
|
|
651
|
-
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
|
+
});
|
|
652
712
|
try {
|
|
653
713
|
const deadline = Date.parse(command.executionDeadlineAt);
|
|
654
714
|
const remaining = deadline - Date.now();
|
|
@@ -705,26 +765,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
705
765
|
commandId: command.commandId,
|
|
706
766
|
cwd: resolved.cwd,
|
|
707
767
|
images: attachments.images,
|
|
708
|
-
onActivity:
|
|
709
|
-
publisher.recordAttempt(command.sessionId, encodeAgentActivityEvent({
|
|
710
|
-
activityId: activity.activityId,
|
|
711
|
-
activityType: activity.activityType,
|
|
712
|
-
commandId: command.commandId,
|
|
713
|
-
content: activity.content,
|
|
714
|
-
detail: activity.detail,
|
|
715
|
-
input: activity.input,
|
|
716
|
-
kind: AGENT_ACTIVITY_KIND,
|
|
717
|
-
locations: activity.locations,
|
|
718
|
-
name: activity.name,
|
|
719
|
-
output: activity.output,
|
|
720
|
-
sequence: activity.sequence,
|
|
721
|
-
sessionId: command.sessionId,
|
|
722
|
-
status: activity.status,
|
|
723
|
-
title: activity.title,
|
|
724
|
-
toolKind: activity.toolKind,
|
|
725
|
-
}), attemptId);
|
|
726
|
-
return Promise.resolve();
|
|
727
|
-
},
|
|
768
|
+
onActivity: activities.record,
|
|
728
769
|
onText: (text, messageId) => {
|
|
729
770
|
publisher.recordAttempt(command.sessionId, encodeAgentMessageDeltaEvent({
|
|
730
771
|
commandId: command.commandId,
|
|
@@ -740,6 +781,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
740
781
|
sessionId: command.sessionId,
|
|
741
782
|
signal: executionSignal,
|
|
742
783
|
});
|
|
784
|
+
activities.flush();
|
|
743
785
|
publisher.recordAttempt(command.sessionId, encodeSessionCompletedEvent({
|
|
744
786
|
commandId: command.commandId,
|
|
745
787
|
kind: SESSION_COMPLETED_KIND,
|
|
@@ -759,6 +801,7 @@ async function executeBotPrompt(outbox, publisher, command, config, directories,
|
|
|
759
801
|
}
|
|
760
802
|
}
|
|
761
803
|
catch (error) {
|
|
804
|
+
activities.flush();
|
|
762
805
|
const failure = Date.parse(command.executionDeadlineAt) <= Date.now()
|
|
763
806
|
? new LocalBotAuthorizationError("turn_expired", "The Bot execution deadline has expired", {
|
|
764
807
|
cause: error,
|
|
@@ -796,16 +839,16 @@ function waitForRelayRetry(signal, timer) {
|
|
|
796
839
|
signal.addEventListener("abort", finish, { once: true });
|
|
797
840
|
});
|
|
798
841
|
}
|
|
799
|
-
async function publishAgentCatalogUntilAvailable(config, signal, timer) {
|
|
842
|
+
async function publishAgentCatalogUntilAvailable(config, diagnostics, signal, timer) {
|
|
800
843
|
try {
|
|
801
844
|
await publishAgentCatalog(config, signal);
|
|
802
845
|
return;
|
|
803
846
|
}
|
|
804
|
-
catch {
|
|
847
|
+
catch (error) {
|
|
805
848
|
if (signal.aborted) {
|
|
806
849
|
return;
|
|
807
850
|
}
|
|
808
|
-
|
|
851
|
+
diagnostics.catalogFailed(error);
|
|
809
852
|
}
|
|
810
853
|
while (!signal.aborted) {
|
|
811
854
|
await waitForRelayRetry(signal, timer);
|
|
@@ -820,11 +863,12 @@ async function publishAgentCatalogUntilAvailable(config, signal, timer) {
|
|
|
820
863
|
}
|
|
821
864
|
}
|
|
822
865
|
}
|
|
823
|
-
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) {
|
|
824
867
|
const relayUrl = new URL(config.relayUrl);
|
|
825
868
|
relayUrl.searchParams.set("liveness", "1");
|
|
826
869
|
const socket = await connectRelay(relayUrl, signal);
|
|
827
870
|
const reconnectingTurns = [...activeTurns.values()];
|
|
871
|
+
diagnostics.connectionOpened(connectionEpoch);
|
|
828
872
|
onConnected();
|
|
829
873
|
const connectionController = new AbortController();
|
|
830
874
|
const nativeRequests = new Map();
|
|
@@ -833,7 +877,6 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
833
877
|
await new Promise((resolve, reject) => {
|
|
834
878
|
let finished = false;
|
|
835
879
|
const cleanup = () => {
|
|
836
|
-
connectionRefresh.close();
|
|
837
880
|
connectionLiveness.close();
|
|
838
881
|
signal.removeEventListener("abort", stop);
|
|
839
882
|
socket.removeEventListener("close", handleClose);
|
|
@@ -851,57 +894,65 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
851
894
|
cleanup();
|
|
852
895
|
resolve();
|
|
853
896
|
};
|
|
854
|
-
const rejectConnection = () => {
|
|
897
|
+
const rejectConnection = (failure) => {
|
|
855
898
|
if (finished) {
|
|
856
899
|
return;
|
|
857
900
|
}
|
|
858
901
|
finished = true;
|
|
859
902
|
cleanup();
|
|
860
|
-
reject(
|
|
903
|
+
reject(failure);
|
|
861
904
|
};
|
|
862
905
|
const stop = () => {
|
|
906
|
+
diagnostics.connectionClosed(connectionEpoch, 1000, "Daemon stopped", "daemon_stop");
|
|
863
907
|
closeRelaySocket(socket, 1000, "Daemon stopped");
|
|
864
908
|
resolveConnection();
|
|
865
909
|
};
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
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);
|
|
869
915
|
};
|
|
870
916
|
const connectionLiveness = new RelayConnectionLiveness(timer, {
|
|
871
917
|
send: (message) => send(socket, message),
|
|
872
|
-
unresponsive: () => {
|
|
918
|
+
unresponsive: (heartbeatId, timeoutMilliseconds) => {
|
|
919
|
+
diagnostics.heartbeatTimedOut(connectionEpoch, heartbeatId, timeoutMilliseconds);
|
|
920
|
+
diagnostics.connectionClosed(connectionEpoch, 4001, "Relay liveness check timed out", "heartbeat_timeout");
|
|
873
921
|
closeRelaySocket(socket, 4001, "Relay liveness check timed out");
|
|
874
922
|
resolveConnection();
|
|
875
923
|
},
|
|
876
924
|
});
|
|
877
|
-
const connectionRefresh = new RelayConnectionRefresh(timer, () => activeTurns.size === 0, refreshConnection);
|
|
878
925
|
signal.addEventListener("abort", stop, { once: true });
|
|
879
|
-
const handleClose = () => {
|
|
926
|
+
const handleClose = (event) => {
|
|
927
|
+
diagnostics.connectionClosed(connectionEpoch, event.code, event.reason, "relay");
|
|
880
928
|
resolveConnection();
|
|
881
929
|
};
|
|
882
|
-
const handleError = () => {
|
|
930
|
+
const handleError = (event) => {
|
|
883
931
|
if (signal.aborted) {
|
|
884
932
|
resolveConnection();
|
|
885
933
|
return;
|
|
886
934
|
}
|
|
887
|
-
|
|
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);
|
|
888
940
|
};
|
|
889
941
|
const handleMessage = (event) => {
|
|
890
942
|
const encoded = event.data;
|
|
891
943
|
if (typeof encoded !== "string") {
|
|
892
|
-
|
|
893
|
-
rejectConnection();
|
|
944
|
+
failAndClose(new Error("Relay sent a non-text message"), 4003, "Text message required", "protocol");
|
|
894
945
|
return;
|
|
895
946
|
}
|
|
896
947
|
try {
|
|
897
948
|
const kind = decodeMessageKind(encoded);
|
|
898
949
|
if (kind === SESSION_EVENT_ACK_KIND) {
|
|
899
|
-
|
|
950
|
+
publisher.acknowledge(decodeSessionEventAcknowledgement(encoded));
|
|
900
951
|
return;
|
|
901
952
|
}
|
|
902
953
|
if (kind === SESSION_EVENT_REJECTED_KIND) {
|
|
903
954
|
const rejection = decodeSessionEventRejection(encoded);
|
|
904
|
-
if (
|
|
955
|
+
if (publisher.quarantine(rejection)) {
|
|
905
956
|
audit.record({
|
|
906
957
|
commandId: rejection.eventId,
|
|
907
958
|
details: { code: rejection.code },
|
|
@@ -1197,7 +1248,7 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1197
1248
|
const turn = new ActiveDaemonTurn(command, receipt.attemptId);
|
|
1198
1249
|
activeTurns.set(command.commandId, turn);
|
|
1199
1250
|
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
1200
|
-
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)
|
|
1201
1252
|
.catch((error) => {
|
|
1202
1253
|
console.error("Mobius daemon Bot turn failed", error);
|
|
1203
1254
|
})
|
|
@@ -1207,7 +1258,6 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1207
1258
|
activeAttemptIds.delete(command.sessionId);
|
|
1208
1259
|
}
|
|
1209
1260
|
turn.finish();
|
|
1210
|
-
connectionRefresh.afterTurnFinished();
|
|
1211
1261
|
});
|
|
1212
1262
|
return;
|
|
1213
1263
|
}
|
|
@@ -1227,7 +1277,7 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1227
1277
|
if (command.disposition !== SessionPromptDisposition.Steer) {
|
|
1228
1278
|
activeAttemptIds.set(command.sessionId, receipt.attemptId);
|
|
1229
1279
|
}
|
|
1230
|
-
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)
|
|
1231
1281
|
.catch((error) => {
|
|
1232
1282
|
console.error("Mobius daemon turn failed", error);
|
|
1233
1283
|
})
|
|
@@ -1237,13 +1287,11 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1237
1287
|
activeAttemptIds.delete(command.sessionId);
|
|
1238
1288
|
}
|
|
1239
1289
|
turn.finish();
|
|
1240
|
-
connectionRefresh.afterTurnFinished();
|
|
1241
1290
|
});
|
|
1242
1291
|
}
|
|
1243
1292
|
catch (error) {
|
|
1244
1293
|
console.error("Mobius daemon rejected a message", error);
|
|
1245
|
-
|
|
1246
|
-
rejectConnection();
|
|
1294
|
+
failAndClose(new Error("Relay sent an invalid protocol message", { cause: error }), 4008, "Invalid protocol message", "protocol");
|
|
1247
1295
|
}
|
|
1248
1296
|
};
|
|
1249
1297
|
socket.addEventListener("close", handleClose);
|
|
@@ -1254,14 +1302,12 @@ async function runRelayConnection(config, directories, audit, sessions, installa
|
|
|
1254
1302
|
return;
|
|
1255
1303
|
}
|
|
1256
1304
|
publisher.connect(socket);
|
|
1257
|
-
void Promise.all(reconnectingTurns.map((turn) => turn.completion)).then(connectionRefresh.afterTurnFinished);
|
|
1258
1305
|
}, (error) => {
|
|
1259
1306
|
if (finished) {
|
|
1260
1307
|
return;
|
|
1261
1308
|
}
|
|
1262
1309
|
console.error("Mobius daemon could not reconcile active Agent turns", error);
|
|
1263
|
-
|
|
1264
|
-
rejectConnection();
|
|
1310
|
+
failAndClose(new Error("Active Agent turn reconciliation failed", { cause: error }), ACTIVE_TURN_RECONCILIATION_CLOSE_CODE, "Active Agent turn reconciliation failed", "reconciliation");
|
|
1265
1311
|
});
|
|
1266
1312
|
if (signal.aborted) {
|
|
1267
1313
|
stop();
|
|
@@ -1305,9 +1351,10 @@ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer
|
|
|
1305
1351
|
const sessions = new AcpSessionManager(new DaemonAcpObserver(recordSessionEvent), sessionStore);
|
|
1306
1352
|
const directories = new WorkspaceDirectoryService(config.bindingId, config.defaultWorkspace);
|
|
1307
1353
|
const installationObserver = new DaemonInstallationObserver();
|
|
1354
|
+
const diagnostics = new RelayConnectionDiagnostics(audit, config.channelId, config.relayUrl);
|
|
1308
1355
|
const nativeSessions = new NativeSessionService(config, directories, sessions, audit);
|
|
1309
1356
|
const installations = new AgentInstallationManager(specifications, installationExecutor, installationObserver, installationAudit);
|
|
1310
|
-
let
|
|
1357
|
+
let connectionEpoch = 0;
|
|
1311
1358
|
let connectionAnnounced = false;
|
|
1312
1359
|
const announceConnection = () => {
|
|
1313
1360
|
if (connectionAnnounced) {
|
|
@@ -1318,22 +1365,19 @@ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer
|
|
|
1318
1365
|
process.stdout.write("Mobius daemon connected to the relay.\n\n");
|
|
1319
1366
|
process.stdout.write(`${styleText(["bold", "yellow"], `Reconnect this channel later:\n ${reconnectCommand}`)}\n\n`);
|
|
1320
1367
|
};
|
|
1321
|
-
const catalogPublication = publishAgentCatalogUntilAvailable(config, signal, timer);
|
|
1368
|
+
const catalogPublication = publishAgentCatalogUntilAvailable(config, diagnostics, signal, timer);
|
|
1322
1369
|
try {
|
|
1323
1370
|
await ipc.listen(signal);
|
|
1324
1371
|
while (!signal.aborted) {
|
|
1372
|
+
connectionEpoch += 1;
|
|
1325
1373
|
try {
|
|
1326
|
-
await runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, announceConnection, signal, timer);
|
|
1327
|
-
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);
|
|
1328
1375
|
}
|
|
1329
|
-
catch {
|
|
1376
|
+
catch (error) {
|
|
1330
1377
|
if (signal.aborted) {
|
|
1331
1378
|
return;
|
|
1332
1379
|
}
|
|
1333
|
-
|
|
1334
|
-
console.warn("Mobius daemon is waiting for the relay");
|
|
1335
|
-
retryAnnounced = true;
|
|
1336
|
-
}
|
|
1380
|
+
diagnostics.connectionFailed(connectionEpoch, error, "connection");
|
|
1337
1381
|
}
|
|
1338
1382
|
await waitForRelayRetry(signal, timer);
|
|
1339
1383
|
}
|