@moxt-ai/mobius 0.0.4 → 0.0.6

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 (115) hide show
  1. package/dist/acp/native-session.d.ts +30 -0
  2. package/dist/acp/native-session.d.ts.map +1 -0
  3. package/dist/acp/native-session.js +25 -0
  4. package/dist/acp/native-session.js.map +1 -0
  5. package/dist/acp/session-history.d.ts +9 -0
  6. package/dist/acp/session-history.d.ts.map +1 -0
  7. package/dist/acp/session-history.js +69 -0
  8. package/dist/acp/session-history.js.map +1 -0
  9. package/dist/acp/session-manager.d.ts +16 -5
  10. package/dist/acp/session-manager.d.ts.map +1 -1
  11. package/dist/acp/session-manager.js +226 -41
  12. package/dist/acp/session-manager.js.map +1 -1
  13. package/dist/acp/session-store.d.ts +9 -0
  14. package/dist/acp/session-store.d.ts.map +1 -1
  15. package/dist/acp/session-store.js +43 -0
  16. package/dist/acp/session-store.js.map +1 -1
  17. package/dist/agent-catalog/agent-diagnostics.d.ts +3 -0
  18. package/dist/agent-catalog/agent-diagnostics.d.ts.map +1 -0
  19. package/dist/agent-catalog/agent-diagnostics.js +62 -0
  20. package/dist/agent-catalog/agent-diagnostics.js.map +1 -0
  21. package/dist/agent-catalog/discover-local-agents.d.ts +77 -0
  22. package/dist/agent-catalog/discover-local-agents.d.ts.map +1 -0
  23. package/dist/agent-catalog/discover-local-agents.js +467 -0
  24. package/dist/agent-catalog/discover-local-agents.js.map +1 -0
  25. package/dist/agent-catalog/local-agent.d.ts +38 -0
  26. package/dist/agent-catalog/local-agent.d.ts.map +1 -0
  27. package/dist/agent-catalog/local-agent.js +93 -0
  28. package/dist/agent-catalog/local-agent.js.map +1 -0
  29. package/dist/agent-catalog/publish-agent-catalog.d.ts +3 -0
  30. package/dist/agent-catalog/publish-agent-catalog.d.ts.map +1 -0
  31. package/dist/agent-catalog/publish-agent-catalog.js +58 -0
  32. package/dist/agent-catalog/publish-agent-catalog.js.map +1 -0
  33. package/dist/agent-installation/agent-installation-manager.d.ts +60 -0
  34. package/dist/agent-installation/agent-installation-manager.d.ts.map +1 -0
  35. package/dist/agent-installation/agent-installation-manager.js +242 -0
  36. package/dist/agent-installation/agent-installation-manager.js.map +1 -0
  37. package/dist/agent-installation/automatic-adapter-provisioner.d.ts +18 -0
  38. package/dist/agent-installation/automatic-adapter-provisioner.d.ts.map +1 -0
  39. package/dist/agent-installation/automatic-adapter-provisioner.js +116 -0
  40. package/dist/agent-installation/automatic-adapter-provisioner.js.map +1 -0
  41. package/dist/agent-installation/fixed-agent-install-recipes.d.ts +20 -0
  42. package/dist/agent-installation/fixed-agent-install-recipes.d.ts.map +1 -0
  43. package/dist/agent-installation/fixed-agent-install-recipes.js +163 -0
  44. package/dist/agent-installation/fixed-agent-install-recipes.js.map +1 -0
  45. package/dist/agent-installation/fixed-installation-executor.d.ts +20 -0
  46. package/dist/agent-installation/fixed-installation-executor.d.ts.map +1 -0
  47. package/dist/agent-installation/fixed-installation-executor.js +204 -0
  48. package/dist/agent-installation/fixed-installation-executor.js.map +1 -0
  49. package/dist/agent-installation/installation-grants.d.ts +2 -0
  50. package/dist/agent-installation/installation-grants.d.ts.map +1 -0
  51. package/dist/agent-installation/installation-grants.js +52 -0
  52. package/dist/agent-installation/installation-grants.js.map +1 -0
  53. package/dist/agent-installation/local-installation-authorizer.d.ts +7 -0
  54. package/dist/agent-installation/local-installation-authorizer.d.ts.map +1 -0
  55. package/dist/agent-installation/local-installation-authorizer.js +32 -0
  56. package/dist/agent-installation/local-installation-authorizer.js.map +1 -0
  57. package/dist/agent-installation/managed-adapter-installer.d.ts +20 -0
  58. package/dist/agent-installation/managed-adapter-installer.d.ts.map +1 -0
  59. package/dist/agent-installation/managed-adapter-installer.js +159 -0
  60. package/dist/agent-installation/managed-adapter-installer.js.map +1 -0
  61. package/dist/agent-installation/verified-download.d.ts +24 -0
  62. package/dist/agent-installation/verified-download.d.ts.map +1 -0
  63. package/dist/agent-installation/verified-download.js +158 -0
  64. package/dist/agent-installation/verified-download.js.map +1 -0
  65. package/dist/audit/audit-store.d.ts +1 -0
  66. package/dist/audit/audit-store.d.ts.map +1 -1
  67. package/dist/audit/audit-store.js +12 -4
  68. package/dist/audit/audit-store.js.map +1 -1
  69. package/dist/authorization/local-authorization-error.d.ts +5 -0
  70. package/dist/authorization/local-authorization-error.d.ts.map +1 -0
  71. package/dist/authorization/local-authorization-error.js +8 -0
  72. package/dist/authorization/local-authorization-error.js.map +1 -0
  73. package/dist/authorization/workspace-directory.d.ts +16 -5
  74. package/dist/authorization/workspace-directory.d.ts.map +1 -1
  75. package/dist/authorization/workspace-directory.js +109 -119
  76. package/dist/authorization/workspace-directory.js.map +1 -1
  77. package/dist/cli.js +6289 -2124
  78. package/dist/cli.js.map +4 -4
  79. package/dist/config.d.ts +8 -12
  80. package/dist/config.d.ts.map +1 -1
  81. package/dist/config.js +14 -55
  82. package/dist/config.js.map +1 -1
  83. package/dist/index.d.ts +1 -1
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +4894 -852
  86. package/dist/index.js.map +4 -4
  87. package/dist/native-sessions/codex-history.d.ts +11 -0
  88. package/dist/native-sessions/codex-history.d.ts.map +1 -0
  89. package/dist/native-sessions/codex-history.js +223 -0
  90. package/dist/native-sessions/codex-history.js.map +1 -0
  91. package/dist/native-sessions/service.d.ts +14 -0
  92. package/dist/native-sessions/service.d.ts.map +1 -0
  93. package/dist/native-sessions/service.js +249 -0
  94. package/dist/native-sessions/service.js.map +1 -0
  95. package/dist/pairing/pair-local-machine.d.ts +2 -2
  96. package/dist/pairing/pair-local-machine.d.ts.map +1 -1
  97. package/dist/pairing/pair-local-machine.js +65 -41
  98. package/dist/pairing/pair-local-machine.js.map +1 -1
  99. package/dist/process/executable.d.ts +40 -0
  100. package/dist/process/executable.d.ts.map +1 -0
  101. package/dist/process/executable.js +305 -0
  102. package/dist/process/executable.js.map +1 -0
  103. package/dist/run-daemon.d.ts +1 -1
  104. package/dist/run-daemon.d.ts.map +1 -1
  105. package/dist/run-daemon.js +431 -180
  106. package/dist/run-daemon.js.map +1 -1
  107. package/dist/storage/state-directory.d.ts +2 -0
  108. package/dist/storage/state-directory.d.ts.map +1 -0
  109. package/dist/storage/state-directory.js +10 -0
  110. package/dist/storage/state-directory.js.map +1 -0
  111. package/package.json +5 -6
  112. package/dist/authorization/authorize-binding.d.ts +0 -6
  113. package/dist/authorization/authorize-binding.d.ts.map +0 -1
  114. package/dist/authorization/authorize-binding.js +0 -30
  115. package/dist/authorization/authorize-binding.js.map +0 -1
@@ -1,15 +1,24 @@
1
1
  import { randomUUID } from "node:crypto";
2
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
+ import { AGENT_ACTIVITY_KIND, AGENT_INSTALL_AWAITING_APPROVAL_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, decodeAgentInstallCancelCommand, decodeAgentInstallRequestCommand, decodeMessageKind, decodeNativeSessionCancel, decodeNativeSessionCommand, decodeRuntimeHeartbeatAckEvent, decodeRuntimeLivenessReadyEvent, decodeSessionCancelCommand, decodeSessionConfigureCommand, decodeSessionElicitationRespondCommand, decodeSessionPermissionRespondCommand, decodeSessionPrepareCommand, decodeSessionPromptCommand, decodeWorkspaceListCommand, encodeAgentActivityEvent, encodeAgentContentEvent, encodeAgentInstallAwaitingApprovalEvent, encodeAgentInstallCancelledEvent, encodeAgentInstallCompletedEvent, encodeAgentInstallFailedEvent, encodeAgentInstallProgressEvent, encodeAgentInstallStartedEvent, encodeAgentMessageDeltaEvent, encodeNativeSessionResult, encodeSessionCommandsUpdatedEvent, encodeSessionCompletedEvent, encodeSessionConfigUpdatedEvent, encodeSessionElicitationRequestedEvent, encodeSessionElicitationResolvedEvent, encodeSessionFailedEvent, encodeSessionInfoUpdatedEvent, encodeSessionOperationFailedEvent, encodeSessionPermissionRequestedEvent, encodeSessionPermissionResolvedEvent, encodeSessionReadyEvent, encodeSessionStartedEvent, encodeSessionUsageUpdatedEvent, encodeWorkspaceEntriesEvent, MOBIUS_PROTOCOL_VERSION, NATIVE_SESSION_CANCEL_KIND, NATIVE_SESSION_REQUEST_KIND, 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";
4
4
  import { WebSocket } from "undici";
5
5
  import { AcpExecutionError, AcpSessionManager, NullAcpSessionObserver, } from "./acp/session-manager.js";
6
6
  import { SqliteAcpSessionStore } from "./acp/session-store.js";
7
- import { LocalAuthorizationError } from "./authorization/authorize-binding.js";
7
+ import { supportedLocalAgentSpecifications } from "./agent-catalog/discover-local-agents.js";
8
+ import { LocalAgentCatalogError } from "./agent-catalog/local-agent.js";
9
+ import { publishAgentCatalog } from "./agent-catalog/publish-agent-catalog.js";
10
+ import { AgentInstallationManager, AwaitingLocalApproval, CancelledInstallation, FailedInstallation, RequestedInstallation, RunningInstallation, SucceededInstallation, } from "./agent-installation/agent-installation-manager.js";
11
+ import { AutomaticAdapterProvisioner } from "./agent-installation/automatic-adapter-provisioner.js";
12
+ import { FixedInstallationExecutor } from "./agent-installation/fixed-installation-executor.js";
13
+ import { ConsoleInstallationAuthorizer } from "./agent-installation/local-installation-authorizer.js";
14
+ import { ManagedAdapterInstaller } from "./agent-installation/managed-adapter-installer.js";
15
+ import { LocalAuthorizationError } from "./authorization/local-authorization-error.js";
8
16
  import { WorkspaceDirectoryService } from "./authorization/workspace-directory.js";
9
- import { findLocalAgent } from "./config.js";
17
+ import { NativeSessionService } from "./native-sessions/service.js";
10
18
  import { connectRelay } from "./relay-connection/connect.js";
11
19
  import { RelayConnectionLiveness } from "./relay-connection/liveness.js";
12
20
  import { systemDaemonTimer, } from "./relay-connection/timer.js";
21
+ import { readStateDirectory } from "./storage/state-directory.js";
13
22
  const RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
14
23
  const RELAY_CONNECTION_REFRESH_MILLISECONDS = 20 * 60 * 1_000;
15
24
  class RelayConnectionRefreshState {
@@ -104,6 +113,110 @@ class DaemonAcpObserver {
104
113
  return Promise.resolve();
105
114
  };
106
115
  }
116
+ class AuditInstallationEvents {
117
+ #audit;
118
+ constructor(audit) {
119
+ this.#audit = audit;
120
+ }
121
+ record = (event, requestId, agentId, details = {}) => {
122
+ this.#audit.record({
123
+ commandId: requestId,
124
+ details,
125
+ event,
126
+ sessionId: `agent-installation:${agentId}`,
127
+ });
128
+ };
129
+ }
130
+ class DaemonInstallationObserver {
131
+ #started = new Set();
132
+ #socket = null;
133
+ connect = (socket) => {
134
+ this.#socket = socket;
135
+ };
136
+ disconnect = (socket) => {
137
+ if (this.#socket === socket) {
138
+ this.#socket = null;
139
+ }
140
+ };
141
+ #send = (socket, message) => {
142
+ try {
143
+ send(socket, message);
144
+ }
145
+ catch {
146
+ if (this.#socket === socket) {
147
+ this.#socket = null;
148
+ }
149
+ }
150
+ };
151
+ onState = (state) => {
152
+ const socket = this.#socket;
153
+ if (socket === null || socket.readyState !== WebSocket.OPEN) {
154
+ return Promise.resolve();
155
+ }
156
+ if (state instanceof RequestedInstallation) {
157
+ return Promise.resolve();
158
+ }
159
+ if (state instanceof AwaitingLocalApproval) {
160
+ this.#send(socket, encodeAgentInstallAwaitingApprovalEvent({
161
+ agentId: state.agentId,
162
+ kind: AGENT_INSTALL_AWAITING_APPROVAL_KIND,
163
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
164
+ requestId: state.requestId,
165
+ }));
166
+ return Promise.resolve();
167
+ }
168
+ if (state instanceof RunningInstallation) {
169
+ if (!this.#started.has(state.requestId)) {
170
+ this.#started.add(state.requestId);
171
+ this.#send(socket, encodeAgentInstallStartedEvent({
172
+ agentId: state.agentId,
173
+ kind: AGENT_INSTALL_STARTED_KIND,
174
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
175
+ requestId: state.requestId,
176
+ }));
177
+ }
178
+ this.#send(socket, encodeAgentInstallProgressEvent({
179
+ agentId: state.agentId,
180
+ kind: AGENT_INSTALL_PROGRESS_KIND,
181
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
182
+ requestId: state.requestId,
183
+ stage: state.stage,
184
+ }));
185
+ return Promise.resolve();
186
+ }
187
+ if (state instanceof SucceededInstallation) {
188
+ this.#send(socket, encodeAgentInstallCompletedEvent({
189
+ agentId: state.agentId,
190
+ authenticationMayBeRequired: state.authenticationMayBeRequired,
191
+ kind: AGENT_INSTALL_COMPLETED_KIND,
192
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
193
+ requestId: state.requestId,
194
+ }));
195
+ return Promise.resolve();
196
+ }
197
+ if (state instanceof FailedInstallation) {
198
+ this.#send(socket, encodeAgentInstallFailedEvent({
199
+ agentId: state.agentId,
200
+ code: state.code,
201
+ kind: AGENT_INSTALL_FAILED_KIND,
202
+ message: state.message,
203
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
204
+ requestId: state.requestId,
205
+ }));
206
+ return Promise.resolve();
207
+ }
208
+ if (state instanceof CancelledInstallation) {
209
+ this.#send(socket, encodeAgentInstallCancelledEvent({
210
+ agentId: state.agentId,
211
+ kind: AGENT_INSTALL_CANCELLED_KIND,
212
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
213
+ requestId: state.requestId,
214
+ }));
215
+ return Promise.resolve();
216
+ }
217
+ throw new Error("Unsupported Agent installation state");
218
+ };
219
+ }
107
220
  class ResolvedSessionDirectories {
108
221
  additionalDirectories;
109
222
  additionalDirectoryEntries;
@@ -159,14 +272,16 @@ function reportFailure(socket, command, code, message) {
159
272
  }
160
273
  function operationErrorCode(error) {
161
274
  if (error instanceof AcpExecutionError ||
162
- error instanceof LocalAuthorizationError) {
275
+ error instanceof LocalAuthorizationError ||
276
+ error instanceof LocalAgentCatalogError) {
163
277
  return error.code;
164
278
  }
165
279
  return "session_operation_failed";
166
280
  }
167
281
  function operationErrorMessage(error) {
168
282
  if (error instanceof AcpExecutionError ||
169
- error instanceof LocalAuthorizationError) {
283
+ error instanceof LocalAuthorizationError ||
284
+ error instanceof LocalAgentCatalogError) {
170
285
  return error.message;
171
286
  }
172
287
  return "The local agent operation could not be completed";
@@ -184,13 +299,13 @@ function reportOperationFailure(socket, requestId, sessionId, error) {
184
299
  sessionId,
185
300
  }));
186
301
  }
187
- async function resolveSessionDirectories(directories, bindingId, directoryId, additionalDirectoryIds) {
188
- const cwd = await directories.resolve(bindingId, directoryId);
302
+ async function resolveSessionDirectories(directories, bindingId, directoryId, additionalDirectoryIds, signal) {
303
+ const cwd = await directories.resolve(bindingId, directoryId, signal);
189
304
  const additionalDirectories = [];
190
305
  const additionalDirectoryEntries = [];
191
306
  const unique = new Set([cwd]);
192
307
  for (const additionalDirectoryId of additionalDirectoryIds) {
193
- const directory = await directories.resolve(bindingId, additionalDirectoryId);
308
+ const directory = await directories.resolve(bindingId, additionalDirectoryId, signal);
194
309
  if (unique.has(directory)) {
195
310
  throw new LocalAuthorizationError("Workspace directories must resolve to distinct locations");
196
311
  }
@@ -203,9 +318,9 @@ async function resolveSessionDirectories(directories, bindingId, directoryId, ad
203
318
  }
204
319
  return new ResolvedSessionDirectories(cwd, additionalDirectories, additionalDirectoryEntries);
205
320
  }
206
- async function executeWorkspaceList(socket, command, directories, audit) {
321
+ async function executeWorkspaceList(socket, command, directories, audit, signal) {
207
322
  try {
208
- const listing = await directories.list(command.bindingId, command.directoryId);
323
+ const listing = await directories.list(command.bindingId, command.directoryId, signal);
209
324
  audit.record({
210
325
  commandId: command.requestId,
211
326
  event: "workspace_list_authorized",
@@ -228,8 +343,8 @@ async function executeWorkspaceList(socket, command, directories, audit) {
228
343
  }
229
344
  async function executeSessionPrepare(socket, command, config, directories, audit, sessions, signal) {
230
345
  try {
231
- const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
232
- const agent = findLocalAgent(config.agents, command.agentId);
346
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
347
+ const agent = config.catalog.find(command.agentId);
233
348
  const prepared = await sessions.prepareSession({
234
349
  additionalDirectories: resolved.additionalDirectories,
235
350
  agent,
@@ -237,7 +352,7 @@ async function executeSessionPrepare(socket, command, config, directories, audit
237
352
  sessionId: command.sessionId,
238
353
  signal,
239
354
  });
240
- const listing = await directories.list(command.bindingId, command.directoryId);
355
+ const listing = await directories.list(command.bindingId, command.directoryId, signal);
241
356
  audit.record({
242
357
  commandId: command.requestId,
243
358
  event: "session_prepared",
@@ -266,10 +381,10 @@ async function executeSessionPrepare(socket, command, config, directories, audit
266
381
  }
267
382
  async function executeSessionConfigure(socket, command, config, directories, audit, sessions, signal) {
268
383
  try {
269
- const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
384
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
270
385
  const configOptions = await sessions.configureSession({
271
386
  additionalDirectories: resolved.additionalDirectories,
272
- agent: findLocalAgent(config.agents, command.agentId),
387
+ agent: config.catalog.find(command.agentId),
273
388
  booleanValue: command.booleanValue,
274
389
  configId: command.configId,
275
390
  cwd: resolved.cwd,
@@ -298,8 +413,8 @@ async function executeSessionConfigure(socket, command, config, directories, aud
298
413
  }
299
414
  async function executePrompt(socket, command, config, directories, audit, sessions, signal) {
300
415
  try {
301
- const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
302
- const agent = findLocalAgent(config.agents, command.agentId);
416
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
417
+ const agent = config.catalog.find(command.agentId);
303
418
  const processAttemptId = randomUUID();
304
419
  audit.record({
305
420
  commandId: command.commandId,
@@ -375,7 +490,10 @@ async function executePrompt(socket, command, config, directories, audit, sessio
375
490
  event: "session_failed",
376
491
  sessionId: command.sessionId,
377
492
  });
378
- reportFailure(socket, command, "agent_execution_failed", "The local agent could not complete the request");
493
+ const code = operationErrorCode(error);
494
+ reportFailure(socket, command, code === "session_operation_failed" ? "agent_execution_failed" : code, code === "session_operation_failed"
495
+ ? "The local agent could not complete the request"
496
+ : operationErrorMessage(error));
379
497
  console.error("Mobius daemon command failed", error);
380
498
  }
381
499
  }
@@ -393,198 +511,322 @@ function waitForRelayRetry(signal, timer) {
393
511
  signal.addEventListener("abort", finish, { once: true });
394
512
  });
395
513
  }
396
- async function runRelayConnection(config, directories, audit, sessions, signal, timer) {
514
+ async function runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, signal, timer) {
397
515
  const relayUrl = new URL(config.relayUrl);
398
516
  relayUrl.searchParams.set("liveness", "1");
399
517
  const socket = await connectRelay(relayUrl, signal);
400
518
  const activeTurns = new Map();
401
519
  const connectionController = new AbortController();
520
+ const nativeRequests = new Map();
402
521
  sessions.setObserver(new DaemonAcpObserver(socket));
403
- await new Promise((resolve, reject) => {
404
- let finished = false;
405
- const cleanup = () => {
406
- connectionRefresh.close();
407
- connectionLiveness.close();
408
- signal.removeEventListener("abort", stop);
409
- socket.removeEventListener("close", handleClose);
410
- socket.removeEventListener("error", handleError);
411
- socket.removeEventListener("message", handleMessage);
412
- connectionController.abort();
413
- for (const turn of activeTurns.values()) {
414
- turn.abortConnection();
415
- }
416
- activeTurns.clear();
417
- };
418
- const resolveConnection = () => {
419
- if (finished) {
420
- return;
421
- }
422
- finished = true;
423
- cleanup();
424
- resolve();
425
- };
426
- const rejectConnection = () => {
427
- if (finished) {
428
- return;
429
- }
430
- finished = true;
431
- cleanup();
432
- reject(new Error("Relay WebSocket failed"));
433
- };
434
- const stop = () => {
435
- socket.close(1000, "Daemon stopped");
436
- resolveConnection();
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);
450
- signal.addEventListener("abort", stop, { once: true });
451
- const handleClose = () => {
452
- resolveConnection();
453
- };
454
- const handleError = () => {
455
- if (signal.aborted) {
456
- resolveConnection();
457
- return;
458
- }
459
- rejectConnection();
460
- };
461
- const handleMessage = (event) => {
462
- const encoded = event.data;
463
- if (typeof encoded !== "string") {
464
- socket.close(4003, "Text message required");
465
- rejectConnection();
466
- return;
467
- }
468
- try {
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;
522
+ installationObserver.connect(socket);
523
+ try {
524
+ await new Promise((resolve, reject) => {
525
+ let finished = false;
526
+ const cleanup = () => {
527
+ connectionRefresh.close();
528
+ connectionLiveness.close();
529
+ signal.removeEventListener("abort", stop);
530
+ socket.removeEventListener("close", handleClose);
531
+ socket.removeEventListener("error", handleError);
532
+ socket.removeEventListener("message", handleMessage);
533
+ installationObserver.disconnect(socket);
534
+ connectionController.abort();
535
+ for (const turn of activeTurns.values()) {
536
+ turn.abortConnection();
478
537
  }
479
- if (kind === SESSION_CANCEL_KIND) {
480
- const cancellation = decodeSessionCancelCommand(encoded);
481
- const turn = activeTurns.get(cancellation.commandId);
482
- if (turn === undefined || !turn.matches(cancellation)) {
483
- console.warn("Mobius daemon ignored cancellation for an inactive turn");
484
- return;
485
- }
486
- if (!turn.requestCancellation()) {
487
- return;
488
- }
489
- audit.record({
490
- commandId: cancellation.commandId,
491
- event: "session_cancellation_requested",
492
- sessionId: cancellation.sessionId,
493
- });
494
- void sessions.cancelPrompt(cancellation.sessionId).catch((error) => {
495
- console.error("Mobius daemon could not cancel the agent turn", error);
496
- });
538
+ activeTurns.clear();
539
+ };
540
+ const resolveConnection = () => {
541
+ if (finished) {
497
542
  return;
498
543
  }
499
- if (kind === WORKSPACE_LIST_KIND) {
500
- const command = decodeWorkspaceListCommand(encoded);
501
- void executeWorkspaceList(socket, command, directories, audit);
544
+ finished = true;
545
+ cleanup();
546
+ resolve();
547
+ };
548
+ const rejectConnection = () => {
549
+ if (finished) {
502
550
  return;
503
551
  }
504
- if (kind === SESSION_PREPARE_KIND) {
505
- const command = decodeSessionPrepareCommand(encoded);
506
- void executeSessionPrepare(socket, command, config, directories, audit, sessions, connectionController.signal);
552
+ finished = true;
553
+ cleanup();
554
+ reject(new Error("Relay WebSocket failed"));
555
+ };
556
+ const stop = () => {
557
+ socket.close(1000, "Daemon stopped");
558
+ resolveConnection();
559
+ };
560
+ const refreshConnection = () => {
561
+ socket.close(1000, "Refreshing idle relay connection");
562
+ resolveConnection();
563
+ };
564
+ const connectionLiveness = new RelayConnectionLiveness(timer, {
565
+ send: (message) => send(socket, message),
566
+ unresponsive: () => {
567
+ socket.close(4001, "Relay liveness check timed out");
568
+ resolveConnection();
569
+ },
570
+ });
571
+ const connectionRefresh = new RelayConnectionRefresh(timer, () => activeTurns.size === 0, refreshConnection);
572
+ signal.addEventListener("abort", stop, { once: true });
573
+ const handleClose = () => {
574
+ resolveConnection();
575
+ };
576
+ const handleError = () => {
577
+ if (signal.aborted) {
578
+ resolveConnection();
507
579
  return;
508
580
  }
509
- if (kind === SESSION_CONFIGURE_KIND) {
510
- const command = decodeSessionConfigureCommand(encoded);
511
- void executeSessionConfigure(socket, command, config, directories, audit, sessions, connectionController.signal);
581
+ rejectConnection();
582
+ };
583
+ const handleMessage = (event) => {
584
+ const encoded = event.data;
585
+ if (typeof encoded !== "string") {
586
+ socket.close(4003, "Text message required");
587
+ rejectConnection();
512
588
  return;
513
589
  }
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",
590
+ try {
591
+ const kind = decodeMessageKind(encoded);
592
+ if (kind === NATIVE_SESSION_CANCEL_KIND) {
593
+ const command = decodeNativeSessionCancel(encoded);
594
+ const pending = nativeRequests.get(command.requestId);
595
+ if (pending?.sessionId === command.sessionId)
596
+ pending.controller.abort(new AcpExecutionError("Native session operation cancelled", {}, "native_session_cancelled"));
597
+ return;
598
+ }
599
+ if (kind === NATIVE_SESSION_REQUEST_KIND) {
600
+ const command = decodeNativeSessionCommand(encoded);
601
+ const previous = [...nativeRequests.values()].filter((request) => request.agentId === command.agentId);
602
+ if (nativeRequests.has(command.requestId) ||
603
+ nativeRequests.size >= 4 ||
604
+ previous.some((request) => !request.controller.signal.aborted)) {
605
+ reportOperationFailure(socket, command.requestId, command.sessionId, new AcpExecutionError("Another native session operation is running", {}, "native_session_busy"));
606
+ return;
607
+ }
608
+ const controller = new AbortController();
609
+ const operationSignal = AbortSignal.any([
610
+ connectionController.signal,
611
+ controller.signal,
612
+ ]);
613
+ const timeout = timer.schedule(() => controller.abort(new AcpExecutionError("Native session operation timed out", {}, "native_session_timeout")), 90_000);
614
+ const completion = (async () => {
615
+ try {
616
+ operationSignal.throwIfAborted();
617
+ if (previous.length > 0) {
618
+ await new Promise((resolve, reject) => {
619
+ const cancel = () => reject(operationSignal.reason);
620
+ operationSignal.addEventListener("abort", cancel, {
621
+ once: true,
622
+ });
623
+ void Promise.all(previous.map((request) => request.completion)).then(() => {
624
+ operationSignal.removeEventListener("abort", cancel);
625
+ resolve();
626
+ }, (error) => {
627
+ operationSignal.removeEventListener("abort", cancel);
628
+ reject(error);
629
+ });
630
+ });
631
+ }
632
+ operationSignal.throwIfAborted();
633
+ const result = await nativeSessions.execute(command, operationSignal);
634
+ if (socket.readyState === WebSocket.OPEN)
635
+ send(socket, encodeNativeSessionResult(result));
636
+ }
637
+ catch (error) {
638
+ const failure = operationSignal.aborted
639
+ ? (controller.signal.reason ??
640
+ new AcpExecutionError("Native session operation cancelled", {}, "native_session_cancelled"))
641
+ : error;
642
+ audit.record({
643
+ commandId: command.requestId,
644
+ sessionId: command.sessionId,
645
+ event: "native_session_operation_failed",
646
+ details: { code: operationErrorCode(failure) },
647
+ });
648
+ reportOperationFailure(socket, command.requestId, command.sessionId, failure);
649
+ }
650
+ finally {
651
+ timeout.cancel();
652
+ nativeRequests.delete(command.requestId);
653
+ }
654
+ })();
655
+ nativeRequests.set(command.requestId, {
656
+ agentId: command.agentId,
522
657
  sessionId: command.sessionId,
658
+ controller,
659
+ completion,
523
660
  });
524
- })
525
- .catch((error) => {
526
- reportOperationFailure(socket, command.interactionId, command.sessionId, error);
527
- });
528
- return;
529
- }
530
- if (kind === SESSION_ELICITATION_RESPOND_KIND) {
531
- const command = decodeSessionElicitationRespondCommand(encoded);
532
- void sessions
533
- .respondElicitation(command)
534
- .then(() => {
661
+ return;
662
+ }
663
+ if (kind === RUNTIME_LIVENESS_READY_KIND) {
664
+ decodeRuntimeLivenessReadyEvent(encoded);
665
+ connectionLiveness.start();
666
+ return;
667
+ }
668
+ if (kind === RUNTIME_HEARTBEAT_ACK_KIND) {
669
+ connectionLiveness.acknowledge(decodeRuntimeHeartbeatAckEvent(encoded));
670
+ return;
671
+ }
672
+ if (kind === SESSION_CANCEL_KIND) {
673
+ const cancellation = decodeSessionCancelCommand(encoded);
674
+ const turn = activeTurns.get(cancellation.commandId);
675
+ if (turn === undefined || !turn.matches(cancellation)) {
676
+ console.warn("Mobius daemon ignored cancellation for an inactive turn");
677
+ return;
678
+ }
679
+ if (!turn.requestCancellation()) {
680
+ return;
681
+ }
535
682
  audit.record({
536
- commandId: command.interactionId,
537
- event: "session_elicitation_resolved",
538
- sessionId: command.sessionId,
683
+ commandId: cancellation.commandId,
684
+ event: "session_cancellation_requested",
685
+ sessionId: cancellation.sessionId,
539
686
  });
540
- })
687
+ void sessions
688
+ .cancelPrompt(cancellation.sessionId)
689
+ .catch((error) => {
690
+ console.error("Mobius daemon could not cancel the agent turn", error);
691
+ });
692
+ return;
693
+ }
694
+ if (kind === WORKSPACE_LIST_KIND) {
695
+ const command = decodeWorkspaceListCommand(encoded);
696
+ void executeWorkspaceList(socket, command, directories, audit, connectionController.signal);
697
+ return;
698
+ }
699
+ if (kind === SESSION_PREPARE_KIND) {
700
+ const command = decodeSessionPrepareCommand(encoded);
701
+ void executeSessionPrepare(socket, command, config, directories, audit, sessions, connectionController.signal);
702
+ return;
703
+ }
704
+ if (kind === SESSION_CONFIGURE_KIND) {
705
+ const command = decodeSessionConfigureCommand(encoded);
706
+ void executeSessionConfigure(socket, command, config, directories, audit, sessions, connectionController.signal);
707
+ return;
708
+ }
709
+ if (kind === SESSION_PERMISSION_RESPOND_KIND) {
710
+ const command = decodeSessionPermissionRespondCommand(encoded);
711
+ void sessions
712
+ .respondPermission(command)
713
+ .then(() => {
714
+ audit.record({
715
+ commandId: command.interactionId,
716
+ event: "session_permission_resolved",
717
+ sessionId: command.sessionId,
718
+ });
719
+ })
720
+ .catch((error) => {
721
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error);
722
+ });
723
+ return;
724
+ }
725
+ if (kind === SESSION_ELICITATION_RESPOND_KIND) {
726
+ const command = decodeSessionElicitationRespondCommand(encoded);
727
+ void sessions
728
+ .respondElicitation(command)
729
+ .then(() => {
730
+ audit.record({
731
+ commandId: command.interactionId,
732
+ event: "session_elicitation_resolved",
733
+ sessionId: command.sessionId,
734
+ });
735
+ })
736
+ .catch((error) => {
737
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error);
738
+ });
739
+ return;
740
+ }
741
+ if (kind === AGENT_INSTALL_REQUEST_KIND) {
742
+ const command = decodeAgentInstallRequestCommand(encoded);
743
+ void installations
744
+ .request(command, signal)
745
+ .catch((error) => {
746
+ console.error("Mobius Agent installation failed", error);
747
+ });
748
+ return;
749
+ }
750
+ if (kind === AGENT_INSTALL_CANCEL_KIND) {
751
+ const command = decodeAgentInstallCancelCommand(encoded);
752
+ void installations
753
+ .cancel(command.requestId)
754
+ .catch((error) => {
755
+ reportOperationFailure(socket, command.requestId, "", error);
756
+ });
757
+ return;
758
+ }
759
+ if (kind !== SESSION_PROMPT_KIND) {
760
+ throw new Error("Unsupported protocol message");
761
+ }
762
+ const command = decodeSessionPromptCommand(encoded);
763
+ if (activeTurns.has(command.commandId)) {
764
+ reportFailure(socket, command, "duplicate_command", "The command is already running");
765
+ return;
766
+ }
767
+ const turn = new ActiveDaemonTurn(command, new AbortController());
768
+ activeTurns.set(command.commandId, turn);
769
+ void executePrompt(socket, command, config, directories, audit, sessions, turn.signal)
541
770
  .catch((error) => {
542
- reportOperationFailure(socket, command.interactionId, command.sessionId, error);
771
+ console.error("Mobius daemon turn failed", error);
772
+ })
773
+ .finally(() => {
774
+ activeTurns.delete(command.commandId);
775
+ connectionRefresh.afterTurnFinished();
543
776
  });
544
- return;
545
777
  }
546
- if (kind !== SESSION_PROMPT_KIND) {
547
- throw new Error("Unsupported protocol message");
778
+ catch (error) {
779
+ console.error("Mobius daemon rejected a message", error);
780
+ socket.close(4008, "Invalid protocol message");
781
+ rejectConnection();
548
782
  }
549
- const command = decodeSessionPromptCommand(encoded);
550
- if (activeTurns.has(command.commandId)) {
551
- reportFailure(socket, command, "duplicate_command", "The command is already running");
552
- return;
553
- }
554
- const turn = new ActiveDaemonTurn(command, new AbortController());
555
- activeTurns.set(command.commandId, turn);
556
- void executePrompt(socket, command, config, directories, audit, sessions, turn.signal)
557
- .catch((error) => {
558
- console.error("Mobius daemon turn failed", error);
559
- })
560
- .finally(() => {
561
- activeTurns.delete(command.commandId);
562
- connectionRefresh.afterTurnFinished();
563
- });
564
- }
565
- catch (error) {
566
- console.error("Mobius daemon rejected a message", error);
567
- socket.close(4008, "Invalid protocol message");
568
- rejectConnection();
783
+ };
784
+ socket.addEventListener("close", handleClose);
785
+ socket.addEventListener("error", handleError);
786
+ socket.addEventListener("message", handleMessage);
787
+ if (signal.aborted) {
788
+ stop();
569
789
  }
570
- };
571
- socket.addEventListener("close", handleClose);
572
- socket.addEventListener("error", handleError);
573
- socket.addEventListener("message", handleMessage);
574
- if (signal.aborted) {
575
- stop();
576
- }
577
- });
790
+ });
791
+ }
792
+ finally {
793
+ connectionController.abort();
794
+ await Promise.all([...nativeRequests.values()].map((request) => request.completion));
795
+ }
578
796
  }
579
797
  export async function runDaemon(config, audit, signal, timer = systemDaemonTimer) {
798
+ const installationAudit = new AuditInstallationEvents(audit);
799
+ const stateDirectory = readStateDirectory();
800
+ const specifications = supportedLocalAgentSpecifications();
801
+ const installationExecutor = new FixedInstallationExecutor({
802
+ defaultWorkspace: config.defaultWorkspace,
803
+ audit: installationAudit,
804
+ catalog: config.catalog,
805
+ environment: process.env,
806
+ publishCatalog: async (installationSignal) => await publishAgentCatalog(config, installationSignal),
807
+ stateDirectory,
808
+ });
809
+ await new AutomaticAdapterProvisioner({
810
+ defaultWorkspace: config.defaultWorkspace,
811
+ audit: installationAudit,
812
+ catalog: config.catalog,
813
+ environment: process.env,
814
+ }, new ManagedAdapterInstaller({
815
+ environment: process.env,
816
+ stateDirectory,
817
+ }), specifications).prepare(signal);
580
818
  const sessionStore = new SqliteAcpSessionStore(config.auditDatabasePath);
581
819
  const sessions = new AcpSessionManager(new NullAcpSessionObserver(), sessionStore);
582
- const directories = new WorkspaceDirectoryService(config.bindingId, config.approvedRoot);
820
+ const directories = new WorkspaceDirectoryService(config.bindingId, config.defaultWorkspace);
821
+ const installationObserver = new DaemonInstallationObserver();
822
+ const nativeSessions = new NativeSessionService(config, directories, sessions, audit);
823
+ const installations = new AgentInstallationManager(specifications, new ConsoleInstallationAuthorizer(stateDirectory), installationExecutor, installationObserver, installationAudit);
583
824
  let retryAnnounced = false;
584
825
  try {
585
826
  while (!signal.aborted) {
586
827
  try {
587
- await runRelayConnection(config, directories, audit, sessions, signal, timer);
828
+ await publishAgentCatalog(config, signal);
829
+ await runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, signal, timer);
588
830
  retryAnnounced = false;
589
831
  }
590
832
  catch {
@@ -600,8 +842,17 @@ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer
600
842
  }
601
843
  }
602
844
  finally {
603
- await sessions.close();
604
- sessionStore.close();
845
+ try {
846
+ await installations.close();
847
+ }
848
+ finally {
849
+ try {
850
+ await sessions.close();
851
+ }
852
+ finally {
853
+ sessionStore.close();
854
+ }
855
+ }
605
856
  }
606
857
  }
607
858
  //# sourceMappingURL=run-daemon.js.map