@moxt-ai/mobius 0.0.2 → 0.0.4

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.
Files changed (40) hide show
  1. package/dist/acp/activity.d.ts +5 -2
  2. package/dist/acp/activity.d.ts.map +1 -1
  3. package/dist/acp/activity.js +89 -5
  4. package/dist/acp/activity.js.map +1 -1
  5. package/dist/acp/create-client.d.ts +8 -2
  6. package/dist/acp/create-client.d.ts.map +1 -1
  7. package/dist/acp/create-client.js +10 -2
  8. package/dist/acp/create-client.js.map +1 -1
  9. package/dist/acp/run-prompt.d.ts.map +1 -1
  10. package/dist/acp/run-prompt.js +4 -0
  11. package/dist/acp/run-prompt.js.map +1 -1
  12. package/dist/acp/session-manager.d.ts +100 -4
  13. package/dist/acp/session-manager.d.ts.map +1 -1
  14. package/dist/acp/session-manager.js +1239 -138
  15. package/dist/acp/session-manager.js.map +1 -1
  16. package/dist/acp/session-store.d.ts +37 -0
  17. package/dist/acp/session-store.d.ts.map +1 -0
  18. package/dist/acp/session-store.js +158 -0
  19. package/dist/acp/session-store.js.map +1 -0
  20. package/dist/authorization/workspace-directory.d.ts +17 -0
  21. package/dist/authorization/workspace-directory.d.ts.map +1 -0
  22. package/dist/authorization/workspace-directory.js +164 -0
  23. package/dist/authorization/workspace-directory.js.map +1 -0
  24. package/dist/cli.js +22429 -697
  25. package/dist/cli.js.map +4 -4
  26. package/dist/index.js +22435 -677
  27. package/dist/index.js.map +4 -4
  28. package/dist/relay-connection/liveness.d.ts +14 -0
  29. package/dist/relay-connection/liveness.d.ts.map +1 -0
  30. package/dist/relay-connection/liveness.js +102 -0
  31. package/dist/relay-connection/liveness.js.map +1 -0
  32. package/dist/relay-connection/timer.d.ts +8 -0
  33. package/dist/relay-connection/timer.d.ts.map +1 -0
  34. package/dist/relay-connection/timer.js +8 -0
  35. package/dist/relay-connection/timer.js.map +1 -0
  36. package/dist/run-daemon.d.ts +2 -1
  37. package/dist/run-daemon.d.ts.map +1 -1
  38. package/dist/run-daemon.js +352 -20
  39. package/dist/run-daemon.js.map +1 -1
  40. package/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ import { type RuntimeHeartbeatAckEvent } from "@moxt-ai/mobius-protocol";
2
+ import type { DaemonTimer } from "./timer.ts";
3
+ export interface RelayLivenessTransport {
4
+ readonly send: (message: string) => void;
5
+ readonly unresponsive: () => void;
6
+ }
7
+ export declare class RelayConnectionLiveness {
8
+ #private;
9
+ constructor(timer: DaemonTimer, transport: RelayLivenessTransport);
10
+ readonly acknowledge: (event: RuntimeHeartbeatAckEvent) => void;
11
+ readonly close: () => void;
12
+ readonly start: () => void;
13
+ }
14
+ //# sourceMappingURL=liveness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveness.d.ts","sourceRoot":"","sources":["../../src/relay-connection/liveness.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAuB,WAAW,EAAE,MAAM,YAAY,CAAC;AA+BnE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,YAAY,EAAE,MAAM,IAAI,CAAC;CACnC;AAED,qBAAa,uBAAuB;;IAKlC,YAAY,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,sBAAsB,EAGhE;IAED,QAAQ,CAAC,WAAW,UAAW,wBAAwB,KAAG,IAAI,CAc5D;IAEF,QAAQ,CAAC,KAAK,QAAO,IAAI,CAYvB;IAEF,QAAQ,CAAC,KAAK,QAAO,IAAI,CAevB;CAuCH"}
@@ -0,0 +1,102 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { encodeRuntimeHeartbeatEvent, MOBIUS_PROTOCOL_VERSION, RUNTIME_HEARTBEAT_KIND, } from "@moxt-ai/mobius-protocol";
3
+ const HEARTBEAT_ACK_TIMEOUT_MILLISECONDS = 10_000;
4
+ const HEARTBEAT_INTERVAL_MILLISECONDS = 20_000;
5
+ class RelayLivenessState {
6
+ }
7
+ class UnsupportedRelayLivenessState extends RelayLivenessState {
8
+ }
9
+ class AwaitingHeartbeatAckState extends RelayLivenessState {
10
+ heartbeatId;
11
+ timeout;
12
+ constructor(heartbeatId, timeout) {
13
+ super();
14
+ this.heartbeatId = heartbeatId;
15
+ this.timeout = timeout;
16
+ }
17
+ }
18
+ class WaitingForHeartbeatState extends RelayLivenessState {
19
+ schedule;
20
+ constructor(schedule) {
21
+ super();
22
+ this.schedule = schedule;
23
+ }
24
+ }
25
+ class FinishedRelayLivenessState extends RelayLivenessState {
26
+ }
27
+ export class RelayConnectionLiveness {
28
+ #timer;
29
+ #transport;
30
+ #state = new UnsupportedRelayLivenessState();
31
+ constructor(timer, transport) {
32
+ this.#timer = timer;
33
+ this.#transport = transport;
34
+ }
35
+ acknowledge = (event) => {
36
+ if (!(this.#state instanceof AwaitingHeartbeatAckState)) {
37
+ throw new Error("Unexpected relay heartbeat acknowledgement");
38
+ }
39
+ if (event.heartbeatId !== this.#state.heartbeatId) {
40
+ throw new Error("Relay acknowledged the wrong heartbeat");
41
+ }
42
+ this.#state.timeout.cancel();
43
+ this.#state = new WaitingForHeartbeatState(this.#timer.schedule(this.#sendHeartbeat, HEARTBEAT_INTERVAL_MILLISECONDS));
44
+ };
45
+ close = () => {
46
+ if (this.#state instanceof AwaitingHeartbeatAckState) {
47
+ this.#state.timeout.cancel();
48
+ }
49
+ else if (this.#state instanceof WaitingForHeartbeatState) {
50
+ this.#state.schedule.cancel();
51
+ }
52
+ else if (!(this.#state instanceof UnsupportedRelayLivenessState) &&
53
+ !(this.#state instanceof FinishedRelayLivenessState)) {
54
+ throw new Error("Unknown relay liveness state");
55
+ }
56
+ this.#state = new FinishedRelayLivenessState();
57
+ };
58
+ start = () => {
59
+ if (this.#state instanceof UnsupportedRelayLivenessState) {
60
+ this.#sendHeartbeat();
61
+ return;
62
+ }
63
+ if (this.#state instanceof AwaitingHeartbeatAckState ||
64
+ this.#state instanceof WaitingForHeartbeatState) {
65
+ return;
66
+ }
67
+ if (this.#state instanceof FinishedRelayLivenessState) {
68
+ return;
69
+ }
70
+ throw new Error("Unknown relay liveness state");
71
+ };
72
+ #handleTimeout = () => {
73
+ if (this.#state instanceof FinishedRelayLivenessState) {
74
+ return;
75
+ }
76
+ if (!(this.#state instanceof AwaitingHeartbeatAckState)) {
77
+ throw new Error("Heartbeat timeout occurred in an invalid state");
78
+ }
79
+ this.#state.timeout.cancel();
80
+ this.#state = new FinishedRelayLivenessState();
81
+ this.#transport.unresponsive();
82
+ };
83
+ #sendHeartbeat = () => {
84
+ if (this.#state instanceof WaitingForHeartbeatState) {
85
+ this.#state.schedule.cancel();
86
+ }
87
+ else if (!(this.#state instanceof UnsupportedRelayLivenessState)) {
88
+ if (this.#state instanceof FinishedRelayLivenessState) {
89
+ return;
90
+ }
91
+ throw new Error("Heartbeat was scheduled in an invalid state");
92
+ }
93
+ const heartbeatId = randomUUID();
94
+ this.#state = new AwaitingHeartbeatAckState(heartbeatId, this.#timer.schedule(this.#handleTimeout, HEARTBEAT_ACK_TIMEOUT_MILLISECONDS));
95
+ this.#transport.send(encodeRuntimeHeartbeatEvent({
96
+ heartbeatId,
97
+ kind: RUNTIME_HEARTBEAT_KIND,
98
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
99
+ }));
100
+ };
101
+ }
102
+ //# sourceMappingURL=liveness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveness.js","sourceRoot":"","sources":["../../src/relay-connection/liveness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface DaemonScheduledTask {
2
+ readonly cancel: () => void;
3
+ }
4
+ export interface DaemonTimer {
5
+ readonly schedule: (callback: () => void, milliseconds: number) => DaemonScheduledTask;
6
+ }
7
+ export declare const systemDaemonTimer: DaemonTimer;
8
+ //# sourceMappingURL=timer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timer.d.ts","sourceRoot":"","sources":["../../src/relay-connection/timer.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,CACjB,QAAQ,EAAE,MAAM,IAAI,EACpB,YAAY,EAAE,MAAM,KACjB,mBAAmB,CAAC;CAC1B;AAYD,eAAO,MAAM,iBAAiB,EAAE,WAAqC,CAAC"}
@@ -0,0 +1,8 @@
1
+ class SystemDaemonTimer {
2
+ schedule = (callback, milliseconds) => {
3
+ const timeout = setTimeout(callback, milliseconds);
4
+ return { cancel: () => clearTimeout(timeout) };
5
+ };
6
+ }
7
+ export const systemDaemonTimer = new SystemDaemonTimer();
8
+ //# sourceMappingURL=timer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timer.js","sourceRoot":"","sources":["../../src/relay-connection/timer.ts"],"names":[],"mappings":"AAWA,MAAM,iBAAiB;IACZ,QAAQ,GAAG,CAClB,QAAoB,EACpB,YAAoB,EACC,EAAE;QACvB,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACnD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;IACjD,CAAC,CAAC;CACH;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAgB,IAAI,iBAAiB,EAAE,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { AuditStore } from "./audit/audit-store.ts";
2
2
  import { type DaemonConfig } from "./config.ts";
3
- export declare function runDaemon(config: DaemonConfig, audit: AuditStore, signal: AbortSignal): Promise<void>;
3
+ import { type DaemonTimer } from "./relay-connection/timer.ts";
4
+ export declare function runDaemon(config: DaemonConfig, audit: AuditStore, signal: AbortSignal, timer?: DaemonTimer): Promise<void>;
4
5
  //# sourceMappingURL=run-daemon.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run-daemon.d.ts","sourceRoot":"","sources":["../src/run-daemon.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,aAAa,CAAC;AA4UhE,wBAAsB,SAAS,CAC7B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAwBf"}
1
+ {"version":3,"file":"run-daemon.d.ts","sourceRoot":"","sources":["../src/run-daemon.ts"],"names":[],"mappings":"AAsEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD,OAAO,EAAE,KAAK,YAAY,EAAkB,MAAM,aAAa,CAAC;AAGhE,OAAO,EAEL,KAAK,WAAW,EAEjB,MAAM,6BAA6B,CAAC;AAqzBrC,wBAAsB,SAAS,CAC7B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,EACnB,KAAK,GAAE,WAA+B,GACrC,OAAO,CAAC,IAAI,CAAC,CAwCf"}
@@ -1,11 +1,119 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { AGENT_ACTIVITY_KIND, AGENT_MESSAGE_DELTA_KIND, decodeMessageKind, decodeSessionCancelCommand, decodeSessionPromptCommand, encodeAgentActivityEvent, encodeAgentMessageDeltaEvent, encodeSessionCompletedEvent, encodeSessionFailedEvent, encodeSessionStartedEvent, MOBIUS_PROTOCOL_VERSION, SESSION_CANCEL_KIND, SESSION_COMPLETED_KIND, SESSION_FAILED_KIND, SESSION_PROMPT_KIND, SESSION_STARTED_KIND, } from "@moxt-ai/mobius-protocol";
2
+ import { basename } from "node:path";
3
+ import { AGENT_ACTIVITY_KIND, AGENT_MESSAGE_DELTA_KIND, decodeMessageKind, decodeRuntimeHeartbeatAckEvent, decodeRuntimeLivenessReadyEvent, decodeSessionCancelCommand, decodeSessionConfigureCommand, decodeSessionElicitationRespondCommand, decodeSessionPermissionRespondCommand, decodeSessionPrepareCommand, decodeSessionPromptCommand, decodeWorkspaceListCommand, encodeAgentActivityEvent, encodeAgentContentEvent, encodeAgentMessageDeltaEvent, encodeSessionCommandsUpdatedEvent, encodeSessionCompletedEvent, encodeSessionConfigUpdatedEvent, encodeSessionElicitationRequestedEvent, encodeSessionElicitationResolvedEvent, encodeSessionFailedEvent, encodeSessionInfoUpdatedEvent, encodeSessionOperationFailedEvent, encodeSessionPermissionRequestedEvent, encodeSessionPermissionResolvedEvent, encodeSessionReadyEvent, encodeSessionStartedEvent, encodeSessionUsageUpdatedEvent, encodeWorkspaceEntriesEvent, MOBIUS_PROTOCOL_VERSION, RUNTIME_HEARTBEAT_ACK_KIND, RUNTIME_LIVENESS_READY_KIND, SESSION_CANCEL_KIND, SESSION_COMPLETED_KIND, SESSION_CONFIGURE_KIND, SESSION_ELICITATION_RESPOND_KIND, SESSION_FAILED_KIND, SESSION_PERMISSION_RESPOND_KIND, SESSION_PREPARE_KIND, SESSION_PROMPT_KIND, SESSION_STARTED_KIND, WORKSPACE_LIST_KIND, } from "@moxt-ai/mobius-protocol";
3
4
  import { WebSocket } from "undici";
4
- import { AcpSessionManager } from "./acp/session-manager.js";
5
- import { authorizeBinding } from "./authorization/authorize-binding.js";
5
+ import { AcpExecutionError, AcpSessionManager, NullAcpSessionObserver, } from "./acp/session-manager.js";
6
+ import { SqliteAcpSessionStore } from "./acp/session-store.js";
7
+ import { LocalAuthorizationError } from "./authorization/authorize-binding.js";
8
+ import { WorkspaceDirectoryService } from "./authorization/workspace-directory.js";
6
9
  import { findLocalAgent } from "./config.js";
7
10
  import { connectRelay } from "./relay-connection/connect.js";
11
+ import { RelayConnectionLiveness } from "./relay-connection/liveness.js";
12
+ import { systemDaemonTimer, } from "./relay-connection/timer.js";
8
13
  const RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
14
+ const RELAY_CONNECTION_REFRESH_MILLISECONDS = 20 * 60 * 1_000;
15
+ class RelayConnectionRefreshState {
16
+ }
17
+ class ScheduledRelayConnectionRefreshState extends RelayConnectionRefreshState {
18
+ }
19
+ class DueRelayConnectionRefreshState extends RelayConnectionRefreshState {
20
+ }
21
+ class FinishedRelayConnectionRefreshState extends RelayConnectionRefreshState {
22
+ }
23
+ class RelayConnectionRefresh {
24
+ #isSafe;
25
+ #refresh;
26
+ #scheduledTask;
27
+ #state = new ScheduledRelayConnectionRefreshState();
28
+ constructor(timer, isSafe, refresh) {
29
+ this.#isSafe = isSafe;
30
+ this.#refresh = refresh;
31
+ this.#scheduledTask = timer.schedule(this.#request, RELAY_CONNECTION_REFRESH_MILLISECONDS);
32
+ }
33
+ afterTurnFinished = () => {
34
+ if (this.#state instanceof DueRelayConnectionRefreshState) {
35
+ this.#request();
36
+ return;
37
+ }
38
+ if (this.#state instanceof ScheduledRelayConnectionRefreshState ||
39
+ this.#state instanceof FinishedRelayConnectionRefreshState) {
40
+ return;
41
+ }
42
+ throw new Error("Unknown relay connection refresh state");
43
+ };
44
+ close = () => {
45
+ this.#scheduledTask.cancel();
46
+ this.#state = new FinishedRelayConnectionRefreshState();
47
+ };
48
+ #request = () => {
49
+ if (this.#state instanceof FinishedRelayConnectionRefreshState) {
50
+ return;
51
+ }
52
+ if (!(this.#state instanceof ScheduledRelayConnectionRefreshState) &&
53
+ !(this.#state instanceof DueRelayConnectionRefreshState)) {
54
+ throw new Error("Unknown relay connection refresh state");
55
+ }
56
+ if (!this.#isSafe()) {
57
+ this.#state = new DueRelayConnectionRefreshState();
58
+ return;
59
+ }
60
+ this.#state = new FinishedRelayConnectionRefreshState();
61
+ this.#scheduledTask.cancel();
62
+ this.#refresh();
63
+ };
64
+ }
65
+ class DaemonAcpObserver {
66
+ #socket;
67
+ constructor(socket) {
68
+ this.#socket = socket;
69
+ }
70
+ onCommands = (event) => {
71
+ send(this.#socket, encodeSessionCommandsUpdatedEvent(event));
72
+ return Promise.resolve();
73
+ };
74
+ onConfig = (event) => {
75
+ send(this.#socket, encodeSessionConfigUpdatedEvent(event));
76
+ return Promise.resolve();
77
+ };
78
+ onContent = (event) => {
79
+ send(this.#socket, encodeAgentContentEvent(event));
80
+ return Promise.resolve();
81
+ };
82
+ onElicitation = (event) => {
83
+ send(this.#socket, encodeSessionElicitationRequestedEvent(event));
84
+ return Promise.resolve();
85
+ };
86
+ onElicitationResolved = (event) => {
87
+ send(this.#socket, encodeSessionElicitationResolvedEvent(event));
88
+ return Promise.resolve();
89
+ };
90
+ onInfo = (event) => {
91
+ send(this.#socket, encodeSessionInfoUpdatedEvent(event));
92
+ return Promise.resolve();
93
+ };
94
+ onPermission = (event) => {
95
+ send(this.#socket, encodeSessionPermissionRequestedEvent(event));
96
+ return Promise.resolve();
97
+ };
98
+ onPermissionResolved = (event) => {
99
+ send(this.#socket, encodeSessionPermissionResolvedEvent(event));
100
+ return Promise.resolve();
101
+ };
102
+ onUsage = (event) => {
103
+ send(this.#socket, encodeSessionUsageUpdatedEvent(event));
104
+ return Promise.resolve();
105
+ };
106
+ }
107
+ class ResolvedSessionDirectories {
108
+ additionalDirectories;
109
+ additionalDirectoryEntries;
110
+ cwd;
111
+ constructor(cwd, additionalDirectories, additionalDirectoryEntries) {
112
+ this.additionalDirectories = additionalDirectories;
113
+ this.additionalDirectoryEntries = additionalDirectoryEntries;
114
+ this.cwd = cwd;
115
+ }
116
+ }
9
117
  class ActiveDaemonTurn {
10
118
  command;
11
119
  #connectionController;
@@ -49,9 +157,148 @@ function reportFailure(socket, command, code, message) {
49
157
  sessionId: command.sessionId,
50
158
  }));
51
159
  }
52
- async function executePrompt(socket, command, config, audit, sessions, signal) {
160
+ function operationErrorCode(error) {
161
+ if (error instanceof AcpExecutionError ||
162
+ error instanceof LocalAuthorizationError) {
163
+ return error.code;
164
+ }
165
+ return "session_operation_failed";
166
+ }
167
+ function operationErrorMessage(error) {
168
+ if (error instanceof AcpExecutionError ||
169
+ error instanceof LocalAuthorizationError) {
170
+ return error.message;
171
+ }
172
+ return "The local agent operation could not be completed";
173
+ }
174
+ function reportOperationFailure(socket, requestId, sessionId, error) {
175
+ if (socket.readyState !== WebSocket.OPEN) {
176
+ return;
177
+ }
178
+ send(socket, encodeSessionOperationFailedEvent({
179
+ code: operationErrorCode(error),
180
+ kind: "session.operation.failed",
181
+ message: operationErrorMessage(error),
182
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
183
+ requestId,
184
+ sessionId,
185
+ }));
186
+ }
187
+ async function resolveSessionDirectories(directories, bindingId, directoryId, additionalDirectoryIds) {
188
+ const cwd = await directories.resolve(bindingId, directoryId);
189
+ const additionalDirectories = [];
190
+ const additionalDirectoryEntries = [];
191
+ const unique = new Set([cwd]);
192
+ for (const additionalDirectoryId of additionalDirectoryIds) {
193
+ const directory = await directories.resolve(bindingId, additionalDirectoryId);
194
+ if (unique.has(directory)) {
195
+ throw new LocalAuthorizationError("Workspace directories must resolve to distinct locations");
196
+ }
197
+ unique.add(directory);
198
+ additionalDirectories.push(directory);
199
+ additionalDirectoryEntries.push({
200
+ directoryId: additionalDirectoryId,
201
+ name: basename(directory),
202
+ });
203
+ }
204
+ return new ResolvedSessionDirectories(cwd, additionalDirectories, additionalDirectoryEntries);
205
+ }
206
+ async function executeWorkspaceList(socket, command, directories, audit) {
207
+ try {
208
+ const listing = await directories.list(command.bindingId, command.directoryId);
209
+ audit.record({
210
+ commandId: command.requestId,
211
+ event: "workspace_list_authorized",
212
+ sessionId: "workspace-browser",
213
+ });
214
+ send(socket, encodeWorkspaceEntriesEvent({
215
+ bindingId: command.bindingId,
216
+ directoryId: listing.directoryId,
217
+ directoryLabel: listing.directoryLabel,
218
+ entries: listing.entries,
219
+ kind: "workspace.entries",
220
+ parentDirectoryId: listing.parentDirectoryId,
221
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
222
+ requestId: command.requestId,
223
+ }));
224
+ }
225
+ catch (error) {
226
+ reportOperationFailure(socket, command.requestId, "", error);
227
+ }
228
+ }
229
+ async function executeSessionPrepare(socket, command, config, directories, audit, sessions, signal) {
230
+ try {
231
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
232
+ const agent = findLocalAgent(config.agents, command.agentId);
233
+ const prepared = await sessions.prepareSession({
234
+ additionalDirectories: resolved.additionalDirectories,
235
+ agent,
236
+ cwd: resolved.cwd,
237
+ sessionId: command.sessionId,
238
+ signal,
239
+ });
240
+ const listing = await directories.list(command.bindingId, command.directoryId);
241
+ audit.record({
242
+ commandId: command.requestId,
243
+ event: "session_prepared",
244
+ sessionId: command.sessionId,
245
+ });
246
+ send(socket, encodeSessionReadyEvent({
247
+ additionalDirectories: resolved.additionalDirectoryEntries,
248
+ agentId: command.agentId,
249
+ agentName: prepared.agentName,
250
+ agentVersion: prepared.agentVersion,
251
+ capabilities: prepared.capabilities,
252
+ commands: prepared.commands,
253
+ configOptions: prepared.configOptions,
254
+ directoryId: command.directoryId,
255
+ directoryLabel: listing.directoryLabel,
256
+ kind: "session.ready",
257
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
258
+ requestId: command.requestId,
259
+ restored: prepared.restored,
260
+ sessionId: command.sessionId,
261
+ }));
262
+ }
263
+ catch (error) {
264
+ reportOperationFailure(socket, command.requestId, command.sessionId, error);
265
+ }
266
+ }
267
+ async function executeSessionConfigure(socket, command, config, directories, audit, sessions, signal) {
268
+ try {
269
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
270
+ const configOptions = await sessions.configureSession({
271
+ additionalDirectories: resolved.additionalDirectories,
272
+ agent: findLocalAgent(config.agents, command.agentId),
273
+ booleanValue: command.booleanValue,
274
+ configId: command.configId,
275
+ cwd: resolved.cwd,
276
+ requestId: command.requestId,
277
+ sessionId: command.sessionId,
278
+ signal,
279
+ value: command.value,
280
+ valueType: command.valueType,
281
+ });
282
+ audit.record({
283
+ commandId: command.requestId,
284
+ event: "session_configured",
285
+ sessionId: command.sessionId,
286
+ });
287
+ send(socket, encodeSessionConfigUpdatedEvent({
288
+ configOptions,
289
+ kind: "session.config.updated",
290
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
291
+ requestId: command.requestId,
292
+ sessionId: command.sessionId,
293
+ }));
294
+ }
295
+ catch (error) {
296
+ reportOperationFailure(socket, command.requestId, command.sessionId, error);
297
+ }
298
+ }
299
+ async function executePrompt(socket, command, config, directories, audit, sessions, signal) {
53
300
  try {
54
- const approvedRoot = await authorizeBinding(command.bindingId, config.bindingId, config.approvedRoot);
301
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
55
302
  const agent = findLocalAgent(config.agents, command.agentId);
56
303
  const processAttemptId = randomUUID();
57
304
  audit.record({
@@ -67,13 +314,17 @@ async function executePrompt(socket, command, config, audit, sessions, signal) {
67
314
  sessionId: command.sessionId,
68
315
  }));
69
316
  const result = await sessions.runPrompt({
317
+ additionalDirectories: resolved.additionalDirectories,
70
318
  agent,
71
- cwd: approvedRoot,
319
+ commandId: command.commandId,
320
+ cwd: resolved.cwd,
321
+ images: command.images,
72
322
  onActivity: (activity) => {
73
323
  send(socket, encodeAgentActivityEvent({
74
324
  activityId: activity.activityId,
75
325
  activityType: activity.activityType,
76
326
  commandId: command.commandId,
327
+ content: activity.content,
77
328
  input: activity.input,
78
329
  kind: AGENT_ACTIVITY_KIND,
79
330
  locations: activity.locations,
@@ -99,6 +350,7 @@ async function executePrompt(socket, command, config, audit, sessions, signal) {
99
350
  return Promise.resolve();
100
351
  },
101
352
  prompt: command.prompt,
353
+ resources: command.resources,
102
354
  sessionId: command.sessionId,
103
355
  signal,
104
356
  });
@@ -127,30 +379,37 @@ async function executePrompt(socket, command, config, audit, sessions, signal) {
127
379
  console.error("Mobius daemon command failed", error);
128
380
  }
129
381
  }
130
- function waitForRelayRetry(signal) {
382
+ function waitForRelayRetry(signal, timer) {
131
383
  if (signal.aborted) {
132
384
  return Promise.resolve();
133
385
  }
134
386
  return new Promise((resolve) => {
135
- const timeout = setTimeout(finish, RELAY_RECONNECT_DELAY_MILLISECONDS);
387
+ const retry = timer.schedule(finish, RELAY_RECONNECT_DELAY_MILLISECONDS);
136
388
  function finish() {
137
- clearTimeout(timeout);
389
+ retry.cancel();
138
390
  signal.removeEventListener("abort", finish);
139
391
  resolve();
140
392
  }
141
393
  signal.addEventListener("abort", finish, { once: true });
142
394
  });
143
395
  }
144
- async function runRelayConnection(config, audit, sessions, signal) {
145
- const socket = await connectRelay(config.relayUrl, signal);
396
+ async function runRelayConnection(config, directories, audit, sessions, signal, timer) {
397
+ const relayUrl = new URL(config.relayUrl);
398
+ relayUrl.searchParams.set("liveness", "1");
399
+ const socket = await connectRelay(relayUrl, signal);
146
400
  const activeTurns = new Map();
401
+ const connectionController = new AbortController();
402
+ sessions.setObserver(new DaemonAcpObserver(socket));
147
403
  await new Promise((resolve, reject) => {
148
404
  let finished = false;
149
405
  const cleanup = () => {
406
+ connectionRefresh.close();
407
+ connectionLiveness.close();
150
408
  signal.removeEventListener("abort", stop);
151
409
  socket.removeEventListener("close", handleClose);
152
410
  socket.removeEventListener("error", handleError);
153
411
  socket.removeEventListener("message", handleMessage);
412
+ connectionController.abort();
154
413
  for (const turn of activeTurns.values()) {
155
414
  turn.abortConnection();
156
415
  }
@@ -176,6 +435,18 @@ async function runRelayConnection(config, audit, sessions, signal) {
176
435
  socket.close(1000, "Daemon stopped");
177
436
  resolveConnection();
178
437
  };
438
+ const refreshConnection = () => {
439
+ socket.close(1000, "Refreshing idle relay connection");
440
+ resolveConnection();
441
+ };
442
+ const connectionLiveness = new RelayConnectionLiveness(timer, {
443
+ send: (message) => send(socket, message),
444
+ unresponsive: () => {
445
+ socket.close(4001, "Relay liveness check timed out");
446
+ resolveConnection();
447
+ },
448
+ });
449
+ const connectionRefresh = new RelayConnectionRefresh(timer, () => activeTurns.size === 0, refreshConnection);
179
450
  signal.addEventListener("abort", stop, { once: true });
180
451
  const handleClose = () => {
181
452
  resolveConnection();
@@ -190,11 +461,21 @@ async function runRelayConnection(config, audit, sessions, signal) {
190
461
  const handleMessage = (event) => {
191
462
  const encoded = event.data;
192
463
  if (typeof encoded !== "string") {
193
- socket.close(1003, "Text message required");
464
+ socket.close(4003, "Text message required");
465
+ rejectConnection();
194
466
  return;
195
467
  }
196
468
  try {
197
469
  const kind = decodeMessageKind(encoded);
470
+ if (kind === RUNTIME_LIVENESS_READY_KIND) {
471
+ decodeRuntimeLivenessReadyEvent(encoded);
472
+ connectionLiveness.start();
473
+ return;
474
+ }
475
+ if (kind === RUNTIME_HEARTBEAT_ACK_KIND) {
476
+ connectionLiveness.acknowledge(decodeRuntimeHeartbeatAckEvent(encoded));
477
+ return;
478
+ }
198
479
  if (kind === SESSION_CANCEL_KIND) {
199
480
  const cancellation = decodeSessionCancelCommand(encoded);
200
481
  const turn = activeTurns.get(cancellation.commandId);
@@ -215,10 +496,56 @@ async function runRelayConnection(config, audit, sessions, signal) {
215
496
  });
216
497
  return;
217
498
  }
218
- if (kind !== SESSION_PROMPT_KIND) {
219
- socket.close(1008, "Unsupported protocol message");
499
+ if (kind === WORKSPACE_LIST_KIND) {
500
+ const command = decodeWorkspaceListCommand(encoded);
501
+ void executeWorkspaceList(socket, command, directories, audit);
502
+ return;
503
+ }
504
+ if (kind === SESSION_PREPARE_KIND) {
505
+ const command = decodeSessionPrepareCommand(encoded);
506
+ void executeSessionPrepare(socket, command, config, directories, audit, sessions, connectionController.signal);
507
+ return;
508
+ }
509
+ if (kind === SESSION_CONFIGURE_KIND) {
510
+ const command = decodeSessionConfigureCommand(encoded);
511
+ void executeSessionConfigure(socket, command, config, directories, audit, sessions, connectionController.signal);
512
+ return;
513
+ }
514
+ if (kind === SESSION_PERMISSION_RESPOND_KIND) {
515
+ const command = decodeSessionPermissionRespondCommand(encoded);
516
+ void sessions
517
+ .respondPermission(command)
518
+ .then(() => {
519
+ audit.record({
520
+ commandId: command.interactionId,
521
+ event: "session_permission_resolved",
522
+ sessionId: command.sessionId,
523
+ });
524
+ })
525
+ .catch((error) => {
526
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error);
527
+ });
220
528
  return;
221
529
  }
530
+ if (kind === SESSION_ELICITATION_RESPOND_KIND) {
531
+ const command = decodeSessionElicitationRespondCommand(encoded);
532
+ void sessions
533
+ .respondElicitation(command)
534
+ .then(() => {
535
+ audit.record({
536
+ commandId: command.interactionId,
537
+ event: "session_elicitation_resolved",
538
+ sessionId: command.sessionId,
539
+ });
540
+ })
541
+ .catch((error) => {
542
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error);
543
+ });
544
+ return;
545
+ }
546
+ if (kind !== SESSION_PROMPT_KIND) {
547
+ throw new Error("Unsupported protocol message");
548
+ }
222
549
  const command = decodeSessionPromptCommand(encoded);
223
550
  if (activeTurns.has(command.commandId)) {
224
551
  reportFailure(socket, command, "duplicate_command", "The command is already running");
@@ -226,17 +553,19 @@ async function runRelayConnection(config, audit, sessions, signal) {
226
553
  }
227
554
  const turn = new ActiveDaemonTurn(command, new AbortController());
228
555
  activeTurns.set(command.commandId, turn);
229
- void executePrompt(socket, command, config, audit, sessions, turn.signal)
556
+ void executePrompt(socket, command, config, directories, audit, sessions, turn.signal)
230
557
  .catch((error) => {
231
558
  console.error("Mobius daemon turn failed", error);
232
559
  })
233
560
  .finally(() => {
234
561
  activeTurns.delete(command.commandId);
562
+ connectionRefresh.afterTurnFinished();
235
563
  });
236
564
  }
237
565
  catch (error) {
238
566
  console.error("Mobius daemon rejected a message", error);
239
- socket.close(1008, "Invalid protocol message");
567
+ socket.close(4008, "Invalid protocol message");
568
+ rejectConnection();
240
569
  }
241
570
  };
242
571
  socket.addEventListener("close", handleClose);
@@ -247,13 +576,15 @@ async function runRelayConnection(config, audit, sessions, signal) {
247
576
  }
248
577
  });
249
578
  }
250
- export async function runDaemon(config, audit, signal) {
251
- const sessions = new AcpSessionManager();
579
+ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer) {
580
+ const sessionStore = new SqliteAcpSessionStore(config.auditDatabasePath);
581
+ const sessions = new AcpSessionManager(new NullAcpSessionObserver(), sessionStore);
582
+ const directories = new WorkspaceDirectoryService(config.bindingId, config.approvedRoot);
252
583
  let retryAnnounced = false;
253
584
  try {
254
585
  while (!signal.aborted) {
255
586
  try {
256
- await runRelayConnection(config, audit, sessions, signal);
587
+ await runRelayConnection(config, directories, audit, sessions, signal, timer);
257
588
  retryAnnounced = false;
258
589
  }
259
590
  catch {
@@ -265,11 +596,12 @@ export async function runDaemon(config, audit, signal) {
265
596
  retryAnnounced = true;
266
597
  }
267
598
  }
268
- await waitForRelayRetry(signal);
599
+ await waitForRelayRetry(signal, timer);
269
600
  }
270
601
  }
271
602
  finally {
272
603
  await sessions.close();
604
+ sessionStore.close();
273
605
  }
274
606
  }
275
607
  //# sourceMappingURL=run-daemon.js.map