@moxt-ai/mobius 0.0.5 → 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 (73) 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 +211 -12
  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/discover-local-agents.d.ts +1 -1
  18. package/dist/agent-catalog/discover-local-agents.d.ts.map +1 -1
  19. package/dist/agent-catalog/discover-local-agents.js +19 -30
  20. package/dist/agent-catalog/discover-local-agents.js.map +1 -1
  21. package/dist/agent-installation/automatic-adapter-provisioner.d.ts +1 -1
  22. package/dist/agent-installation/automatic-adapter-provisioner.d.ts.map +1 -1
  23. package/dist/agent-installation/automatic-adapter-provisioner.js +1 -1
  24. package/dist/agent-installation/automatic-adapter-provisioner.js.map +1 -1
  25. package/dist/agent-installation/fixed-installation-executor.d.ts +1 -1
  26. package/dist/agent-installation/fixed-installation-executor.d.ts.map +1 -1
  27. package/dist/agent-installation/fixed-installation-executor.js +9 -18
  28. package/dist/agent-installation/fixed-installation-executor.js.map +1 -1
  29. package/dist/agent-installation/managed-adapter-installer.d.ts +0 -1
  30. package/dist/agent-installation/managed-adapter-installer.d.ts.map +1 -1
  31. package/dist/agent-installation/managed-adapter-installer.js +1 -3
  32. package/dist/agent-installation/managed-adapter-installer.js.map +1 -1
  33. package/dist/authorization/local-authorization-error.d.ts +5 -0
  34. package/dist/authorization/local-authorization-error.d.ts.map +1 -0
  35. package/dist/authorization/local-authorization-error.js +8 -0
  36. package/dist/authorization/local-authorization-error.js.map +1 -0
  37. package/dist/authorization/workspace-directory.d.ts +16 -5
  38. package/dist/authorization/workspace-directory.d.ts.map +1 -1
  39. package/dist/authorization/workspace-directory.js +109 -119
  40. package/dist/authorization/workspace-directory.js.map +1 -1
  41. package/dist/cli.js +1851 -543
  42. package/dist/cli.js.map +4 -4
  43. package/dist/config.d.ts +3 -3
  44. package/dist/config.d.ts.map +1 -1
  45. package/dist/config.js +7 -5
  46. package/dist/config.js.map +1 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +1866 -593
  50. package/dist/index.js.map +4 -4
  51. package/dist/native-sessions/codex-history.d.ts +11 -0
  52. package/dist/native-sessions/codex-history.d.ts.map +1 -0
  53. package/dist/native-sessions/codex-history.js +223 -0
  54. package/dist/native-sessions/codex-history.js.map +1 -0
  55. package/dist/native-sessions/service.d.ts +14 -0
  56. package/dist/native-sessions/service.d.ts.map +1 -0
  57. package/dist/native-sessions/service.js +249 -0
  58. package/dist/native-sessions/service.js.map +1 -0
  59. package/dist/pairing/pair-local-machine.d.ts.map +1 -1
  60. package/dist/pairing/pair-local-machine.js +29 -26
  61. package/dist/pairing/pair-local-machine.js.map +1 -1
  62. package/dist/process/executable.d.ts +1 -1
  63. package/dist/process/executable.d.ts.map +1 -1
  64. package/dist/process/executable.js +3 -11
  65. package/dist/process/executable.js.map +1 -1
  66. package/dist/run-daemon.d.ts.map +1 -1
  67. package/dist/run-daemon.js +274 -193
  68. package/dist/run-daemon.js.map +1 -1
  69. package/package.json +3 -2
  70. package/dist/authorization/authorize-binding.d.ts +0 -6
  71. package/dist/authorization/authorize-binding.d.ts.map +0 -1
  72. package/dist/authorization/authorize-binding.js +0 -30
  73. package/dist/authorization/authorize-binding.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { basename } from "node:path";
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, decodeRuntimeHeartbeatAckEvent, decodeRuntimeLivenessReadyEvent, decodeSessionCancelCommand, decodeSessionConfigureCommand, decodeSessionElicitationRespondCommand, decodeSessionPermissionRespondCommand, decodeSessionPrepareCommand, decodeSessionPromptCommand, decodeWorkspaceListCommand, encodeAgentActivityEvent, encodeAgentContentEvent, encodeAgentInstallAwaitingApprovalEvent, encodeAgentInstallCancelledEvent, encodeAgentInstallCompletedEvent, encodeAgentInstallFailedEvent, encodeAgentInstallProgressEvent, encodeAgentInstallStartedEvent, 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";
@@ -12,8 +12,9 @@ import { AutomaticAdapterProvisioner } from "./agent-installation/automatic-adap
12
12
  import { FixedInstallationExecutor } from "./agent-installation/fixed-installation-executor.js";
13
13
  import { ConsoleInstallationAuthorizer } from "./agent-installation/local-installation-authorizer.js";
14
14
  import { ManagedAdapterInstaller } from "./agent-installation/managed-adapter-installer.js";
15
- import { LocalAuthorizationError } from "./authorization/authorize-binding.js";
15
+ import { LocalAuthorizationError } from "./authorization/local-authorization-error.js";
16
16
  import { WorkspaceDirectoryService } from "./authorization/workspace-directory.js";
17
+ import { NativeSessionService } from "./native-sessions/service.js";
17
18
  import { connectRelay } from "./relay-connection/connect.js";
18
19
  import { RelayConnectionLiveness } from "./relay-connection/liveness.js";
19
20
  import { systemDaemonTimer, } from "./relay-connection/timer.js";
@@ -298,13 +299,13 @@ function reportOperationFailure(socket, requestId, sessionId, error) {
298
299
  sessionId,
299
300
  }));
300
301
  }
301
- async function resolveSessionDirectories(directories, bindingId, directoryId, additionalDirectoryIds) {
302
- 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);
303
304
  const additionalDirectories = [];
304
305
  const additionalDirectoryEntries = [];
305
306
  const unique = new Set([cwd]);
306
307
  for (const additionalDirectoryId of additionalDirectoryIds) {
307
- const directory = await directories.resolve(bindingId, additionalDirectoryId);
308
+ const directory = await directories.resolve(bindingId, additionalDirectoryId, signal);
308
309
  if (unique.has(directory)) {
309
310
  throw new LocalAuthorizationError("Workspace directories must resolve to distinct locations");
310
311
  }
@@ -317,9 +318,9 @@ async function resolveSessionDirectories(directories, bindingId, directoryId, ad
317
318
  }
318
319
  return new ResolvedSessionDirectories(cwd, additionalDirectories, additionalDirectoryEntries);
319
320
  }
320
- async function executeWorkspaceList(socket, command, directories, audit) {
321
+ async function executeWorkspaceList(socket, command, directories, audit, signal) {
321
322
  try {
322
- const listing = await directories.list(command.bindingId, command.directoryId);
323
+ const listing = await directories.list(command.bindingId, command.directoryId, signal);
323
324
  audit.record({
324
325
  commandId: command.requestId,
325
326
  event: "workspace_list_authorized",
@@ -342,7 +343,7 @@ async function executeWorkspaceList(socket, command, directories, audit) {
342
343
  }
343
344
  async function executeSessionPrepare(socket, command, config, directories, audit, sessions, signal) {
344
345
  try {
345
- const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
346
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
346
347
  const agent = config.catalog.find(command.agentId);
347
348
  const prepared = await sessions.prepareSession({
348
349
  additionalDirectories: resolved.additionalDirectories,
@@ -351,7 +352,7 @@ async function executeSessionPrepare(socket, command, config, directories, audit
351
352
  sessionId: command.sessionId,
352
353
  signal,
353
354
  });
354
- const listing = await directories.list(command.bindingId, command.directoryId);
355
+ const listing = await directories.list(command.bindingId, command.directoryId, signal);
355
356
  audit.record({
356
357
  commandId: command.requestId,
357
358
  event: "session_prepared",
@@ -380,7 +381,7 @@ async function executeSessionPrepare(socket, command, config, directories, audit
380
381
  }
381
382
  async function executeSessionConfigure(socket, command, config, directories, audit, sessions, signal) {
382
383
  try {
383
- const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
384
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
384
385
  const configOptions = await sessions.configureSession({
385
386
  additionalDirectories: resolved.additionalDirectories,
386
387
  agent: config.catalog.find(command.agentId),
@@ -412,7 +413,7 @@ async function executeSessionConfigure(socket, command, config, directories, aud
412
413
  }
413
414
  async function executePrompt(socket, command, config, directories, audit, sessions, signal) {
414
415
  try {
415
- const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds);
416
+ const resolved = await resolveSessionDirectories(directories, command.bindingId, command.directoryId, command.additionalDirectoryIds, signal);
416
417
  const agent = config.catalog.find(command.agentId);
417
418
  const processAttemptId = randomUUID();
418
419
  audit.record({
@@ -510,215 +511,295 @@ function waitForRelayRetry(signal, timer) {
510
511
  signal.addEventListener("abort", finish, { once: true });
511
512
  });
512
513
  }
513
- async function runRelayConnection(config, directories, audit, sessions, installations, installationObserver, signal, timer) {
514
+ async function runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, signal, timer) {
514
515
  const relayUrl = new URL(config.relayUrl);
515
516
  relayUrl.searchParams.set("liveness", "1");
516
517
  const socket = await connectRelay(relayUrl, signal);
517
518
  const activeTurns = new Map();
518
519
  const connectionController = new AbortController();
520
+ const nativeRequests = new Map();
519
521
  sessions.setObserver(new DaemonAcpObserver(socket));
520
522
  installationObserver.connect(socket);
521
- await new Promise((resolve, reject) => {
522
- let finished = false;
523
- const cleanup = () => {
524
- connectionRefresh.close();
525
- connectionLiveness.close();
526
- signal.removeEventListener("abort", stop);
527
- socket.removeEventListener("close", handleClose);
528
- socket.removeEventListener("error", handleError);
529
- socket.removeEventListener("message", handleMessage);
530
- installationObserver.disconnect(socket);
531
- connectionController.abort();
532
- for (const turn of activeTurns.values()) {
533
- turn.abortConnection();
534
- }
535
- activeTurns.clear();
536
- };
537
- const resolveConnection = () => {
538
- if (finished) {
539
- return;
540
- }
541
- finished = true;
542
- cleanup();
543
- resolve();
544
- };
545
- const rejectConnection = () => {
546
- if (finished) {
547
- return;
548
- }
549
- finished = true;
550
- cleanup();
551
- reject(new Error("Relay WebSocket failed"));
552
- };
553
- const stop = () => {
554
- socket.close(1000, "Daemon stopped");
555
- resolveConnection();
556
- };
557
- const refreshConnection = () => {
558
- socket.close(1000, "Refreshing idle relay connection");
559
- resolveConnection();
560
- };
561
- const connectionLiveness = new RelayConnectionLiveness(timer, {
562
- send: (message) => send(socket, message),
563
- unresponsive: () => {
564
- socket.close(4001, "Relay liveness check timed out");
565
- resolveConnection();
566
- },
567
- });
568
- const connectionRefresh = new RelayConnectionRefresh(timer, () => activeTurns.size === 0, refreshConnection);
569
- signal.addEventListener("abort", stop, { once: true });
570
- const handleClose = () => {
571
- resolveConnection();
572
- };
573
- const handleError = () => {
574
- if (signal.aborted) {
575
- resolveConnection();
576
- return;
577
- }
578
- rejectConnection();
579
- };
580
- const handleMessage = (event) => {
581
- const encoded = event.data;
582
- if (typeof encoded !== "string") {
583
- socket.close(4003, "Text message required");
584
- rejectConnection();
585
- return;
586
- }
587
- try {
588
- const kind = decodeMessageKind(encoded);
589
- if (kind === RUNTIME_LIVENESS_READY_KIND) {
590
- decodeRuntimeLivenessReadyEvent(encoded);
591
- connectionLiveness.start();
592
- return;
593
- }
594
- if (kind === RUNTIME_HEARTBEAT_ACK_KIND) {
595
- connectionLiveness.acknowledge(decodeRuntimeHeartbeatAckEvent(encoded));
596
- return;
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();
597
537
  }
598
- if (kind === SESSION_CANCEL_KIND) {
599
- const cancellation = decodeSessionCancelCommand(encoded);
600
- const turn = activeTurns.get(cancellation.commandId);
601
- if (turn === undefined || !turn.matches(cancellation)) {
602
- console.warn("Mobius daemon ignored cancellation for an inactive turn");
603
- return;
604
- }
605
- if (!turn.requestCancellation()) {
606
- return;
607
- }
608
- audit.record({
609
- commandId: cancellation.commandId,
610
- event: "session_cancellation_requested",
611
- sessionId: cancellation.sessionId,
612
- });
613
- void sessions.cancelPrompt(cancellation.sessionId).catch((error) => {
614
- console.error("Mobius daemon could not cancel the agent turn", error);
615
- });
538
+ activeTurns.clear();
539
+ };
540
+ const resolveConnection = () => {
541
+ if (finished) {
616
542
  return;
617
543
  }
618
- if (kind === WORKSPACE_LIST_KIND) {
619
- const command = decodeWorkspaceListCommand(encoded);
620
- void executeWorkspaceList(socket, command, directories, audit);
544
+ finished = true;
545
+ cleanup();
546
+ resolve();
547
+ };
548
+ const rejectConnection = () => {
549
+ if (finished) {
621
550
  return;
622
551
  }
623
- if (kind === SESSION_PREPARE_KIND) {
624
- const command = decodeSessionPrepareCommand(encoded);
625
- 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();
626
579
  return;
627
580
  }
628
- if (kind === SESSION_CONFIGURE_KIND) {
629
- const command = decodeSessionConfigureCommand(encoded);
630
- 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();
631
588
  return;
632
589
  }
633
- if (kind === SESSION_PERMISSION_RESPOND_KIND) {
634
- const command = decodeSessionPermissionRespondCommand(encoded);
635
- void sessions
636
- .respondPermission(command)
637
- .then(() => {
638
- audit.record({
639
- commandId: command.interactionId,
640
- 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,
641
657
  sessionId: command.sessionId,
658
+ controller,
659
+ completion,
642
660
  });
643
- })
644
- .catch((error) => {
645
- reportOperationFailure(socket, command.interactionId, command.sessionId, error);
646
- });
647
- return;
648
- }
649
- if (kind === SESSION_ELICITATION_RESPOND_KIND) {
650
- const command = decodeSessionElicitationRespondCommand(encoded);
651
- void sessions
652
- .respondElicitation(command)
653
- .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
+ }
654
682
  audit.record({
655
- commandId: command.interactionId,
656
- event: "session_elicitation_resolved",
657
- sessionId: command.sessionId,
683
+ commandId: cancellation.commandId,
684
+ event: "session_cancellation_requested",
685
+ sessionId: cancellation.sessionId,
658
686
  });
659
- })
660
- .catch((error) => {
661
- reportOperationFailure(socket, command.interactionId, command.sessionId, error);
662
- });
663
- return;
664
- }
665
- if (kind === AGENT_INSTALL_REQUEST_KIND) {
666
- const command = decodeAgentInstallRequestCommand(encoded);
667
- void installations
668
- .request(command, signal)
669
- .catch((error) => {
670
- console.error("Mobius Agent installation failed", error);
671
- });
672
- return;
673
- }
674
- if (kind === AGENT_INSTALL_CANCEL_KIND) {
675
- const command = decodeAgentInstallCancelCommand(encoded);
676
- void installations
677
- .cancel(command.requestId)
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)
678
770
  .catch((error) => {
679
- reportOperationFailure(socket, command.requestId, "", error);
771
+ console.error("Mobius daemon turn failed", error);
772
+ })
773
+ .finally(() => {
774
+ activeTurns.delete(command.commandId);
775
+ connectionRefresh.afterTurnFinished();
680
776
  });
681
- return;
682
777
  }
683
- if (kind !== SESSION_PROMPT_KIND) {
684
- 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();
685
782
  }
686
- const command = decodeSessionPromptCommand(encoded);
687
- if (activeTurns.has(command.commandId)) {
688
- reportFailure(socket, command, "duplicate_command", "The command is already running");
689
- return;
690
- }
691
- const turn = new ActiveDaemonTurn(command, new AbortController());
692
- activeTurns.set(command.commandId, turn);
693
- void executePrompt(socket, command, config, directories, audit, sessions, turn.signal)
694
- .catch((error) => {
695
- console.error("Mobius daemon turn failed", error);
696
- })
697
- .finally(() => {
698
- activeTurns.delete(command.commandId);
699
- connectionRefresh.afterTurnFinished();
700
- });
701
- }
702
- catch (error) {
703
- console.error("Mobius daemon rejected a message", error);
704
- socket.close(4008, "Invalid protocol message");
705
- rejectConnection();
783
+ };
784
+ socket.addEventListener("close", handleClose);
785
+ socket.addEventListener("error", handleError);
786
+ socket.addEventListener("message", handleMessage);
787
+ if (signal.aborted) {
788
+ stop();
706
789
  }
707
- };
708
- socket.addEventListener("close", handleClose);
709
- socket.addEventListener("error", handleError);
710
- socket.addEventListener("message", handleMessage);
711
- if (signal.aborted) {
712
- stop();
713
- }
714
- });
790
+ });
791
+ }
792
+ finally {
793
+ connectionController.abort();
794
+ await Promise.all([...nativeRequests.values()].map((request) => request.completion));
795
+ }
715
796
  }
716
797
  export async function runDaemon(config, audit, signal, timer = systemDaemonTimer) {
717
798
  const installationAudit = new AuditInstallationEvents(audit);
718
799
  const stateDirectory = readStateDirectory();
719
800
  const specifications = supportedLocalAgentSpecifications();
720
801
  const installationExecutor = new FixedInstallationExecutor({
721
- approvedRoot: config.approvedRoot,
802
+ defaultWorkspace: config.defaultWorkspace,
722
803
  audit: installationAudit,
723
804
  catalog: config.catalog,
724
805
  environment: process.env,
@@ -726,26 +807,26 @@ export async function runDaemon(config, audit, signal, timer = systemDaemonTimer
726
807
  stateDirectory,
727
808
  });
728
809
  await new AutomaticAdapterProvisioner({
729
- approvedRoot: config.approvedRoot,
810
+ defaultWorkspace: config.defaultWorkspace,
730
811
  audit: installationAudit,
731
812
  catalog: config.catalog,
732
813
  environment: process.env,
733
814
  }, new ManagedAdapterInstaller({
734
- approvedRoot: config.approvedRoot,
735
815
  environment: process.env,
736
816
  stateDirectory,
737
817
  }), specifications).prepare(signal);
738
818
  const sessionStore = new SqliteAcpSessionStore(config.auditDatabasePath);
739
819
  const sessions = new AcpSessionManager(new NullAcpSessionObserver(), sessionStore);
740
- const directories = new WorkspaceDirectoryService(config.bindingId, config.approvedRoot);
820
+ const directories = new WorkspaceDirectoryService(config.bindingId, config.defaultWorkspace);
741
821
  const installationObserver = new DaemonInstallationObserver();
822
+ const nativeSessions = new NativeSessionService(config, directories, sessions, audit);
742
823
  const installations = new AgentInstallationManager(specifications, new ConsoleInstallationAuthorizer(stateDirectory), installationExecutor, installationObserver, installationAudit);
743
824
  let retryAnnounced = false;
744
825
  try {
745
826
  while (!signal.aborted) {
746
827
  try {
747
828
  await publishAgentCatalog(config, signal);
748
- await runRelayConnection(config, directories, audit, sessions, installations, installationObserver, signal, timer);
829
+ await runRelayConnection(config, directories, audit, sessions, installations, installationObserver, nativeSessions, signal, timer);
749
830
  retryAnnounced = false;
750
831
  }
751
832
  catch {