@moxt-ai/mobius 0.0.6 → 0.0.8

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 (38) hide show
  1. package/dist/acp/activity.d.ts +7 -2
  2. package/dist/acp/activity.d.ts.map +1 -1
  3. package/dist/acp/activity.js +95 -7
  4. package/dist/acp/activity.js.map +1 -1
  5. package/dist/acp/native-session.d.ts +2 -2
  6. package/dist/acp/native-session.d.ts.map +1 -1
  7. package/dist/acp/native-session.js +2 -1
  8. package/dist/acp/native-session.js.map +1 -1
  9. package/dist/acp/session-manager.d.ts +2 -0
  10. package/dist/acp/session-manager.d.ts.map +1 -1
  11. package/dist/acp/session-manager.js +127 -44
  12. package/dist/acp/session-manager.js.map +1 -1
  13. package/dist/agent-installation/installation-grants.d.ts.map +1 -1
  14. package/dist/agent-installation/installation-grants.js +1 -2
  15. package/dist/agent-installation/installation-grants.js.map +1 -1
  16. package/dist/cli.js +1226 -240
  17. package/dist/cli.js.map +4 -4
  18. package/dist/config.js +1 -1
  19. package/dist/config.js.map +1 -1
  20. package/dist/index.js +1233 -247
  21. package/dist/index.js.map +4 -4
  22. package/dist/native-sessions/service.d.ts.map +1 -1
  23. package/dist/native-sessions/service.js +16 -11
  24. package/dist/native-sessions/service.js.map +1 -1
  25. package/dist/pairing/pair-local-machine.d.ts.map +1 -1
  26. package/dist/pairing/pair-local-machine.js +12 -5
  27. package/dist/pairing/pair-local-machine.js.map +1 -1
  28. package/dist/relay-connection/liveness.d.ts.map +1 -1
  29. package/dist/relay-connection/liveness.js +1 -2
  30. package/dist/relay-connection/liveness.js.map +1 -1
  31. package/dist/relay-connection/session-event-outbox.d.ts +19 -0
  32. package/dist/relay-connection/session-event-outbox.d.ts.map +1 -0
  33. package/dist/relay-connection/session-event-outbox.js +182 -0
  34. package/dist/relay-connection/session-event-outbox.js.map +1 -0
  35. package/dist/run-daemon.d.ts.map +1 -1
  36. package/dist/run-daemon.js +87 -63
  37. package/dist/run-daemon.js.map +1 -1
  38. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { methods, ndJsonStream, PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
3
- import { AGENT_CONTENT_KIND, decodeAgentPromptResponse, MOBIUS_PROTOCOL_VERSION, SESSION_COMMANDS_UPDATED_KIND, SESSION_CONFIG_UPDATED_KIND, SESSION_ELICITATION_REQUESTED_KIND, SESSION_ELICITATION_RESOLVED_KIND, SESSION_INFO_UPDATED_KIND, SESSION_PERMISSION_REQUESTED_KIND, SESSION_PERMISSION_RESOLVED_KIND, SESSION_USAGE_UPDATED_KIND, } from "@moxt-ai/mobius-protocol";
3
+ import { AGENT_CONTENT_KIND, decodeAgentPromptResponse, SESSION_COMMANDS_UPDATED_KIND, SESSION_CONFIG_UPDATED_KIND, SESSION_ELICITATION_REQUESTED_KIND, SESSION_ELICITATION_RESOLVED_KIND, SESSION_INFO_UPDATED_KIND, SESSION_PERMISSION_REQUESTED_KIND, SESSION_PERMISSION_RESOLVED_KIND, SESSION_USAGE_UPDATED_KIND, } from "@moxt-ai/mobius-protocol";
4
4
  import { revalidateExecutable, stopProcessTree, } from "../process/executable.js";
5
5
  import { AcpActivityTracker, isProgressMessage, normalizeContentBlock, } from "./activity.js";
6
6
  import { createAcpClient } from "./create-client.js";
@@ -44,6 +44,12 @@ export class AcpExecutionError extends Error {
44
44
  this.name = "AcpExecutionError";
45
45
  }
46
46
  }
47
+ class RetryableAcpInitializationError extends AcpExecutionError {
48
+ constructor(cause) {
49
+ super("ACP agent initialization failed", { cause });
50
+ this.name = "RetryableAcpInitializationError";
51
+ }
52
+ }
47
53
  function readableWebStream(input) {
48
54
  let streamOpen = true;
49
55
  return new ReadableStream({
@@ -631,32 +637,41 @@ class AcpProcessSession {
631
637
  const stream = ndJsonStream(createWritableAcpStream(stdin), readableWebStream(stdout));
632
638
  this.#connection = createAcpClient(this).connect(stream);
633
639
  }
634
- static start = async (options, getObserver, store) => {
635
- options.signal.throwIfAborted();
636
- const [adapterUnchanged, agentUnchanged] = await Promise.all([
637
- revalidateExecutable(options.agent.adapterIdentity),
638
- revalidateExecutable(options.agent.agentIdentity),
639
- ]);
640
- options.signal.throwIfAborted();
641
- if (!adapterUnchanged || !agentUnchanged) {
642
- throw new AcpExecutionError("The local Agent runtime changed; restart Mobius before starting a new conversation", {}, "agent_runtime_changed");
643
- }
644
- const runtime = new AcpProcessSession(options.agent, options.cwd, options.additionalDirectories, options.sessionId, getObserver, store);
645
- try {
646
- await runtime.initialize(options.signal, options.origin);
647
- return runtime;
648
- }
649
- catch (error) {
650
- await runtime.closeProcess();
640
+ static async #start(options, getObserver, store, initialize) {
641
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
651
642
  options.signal.throwIfAborted();
652
- if (error instanceof AcpExecutionError) {
653
- throw error;
643
+ const [adapterUnchanged, agentUnchanged] = await Promise.all([
644
+ revalidateExecutable(options.agent.adapterIdentity),
645
+ revalidateExecutable(options.agent.agentIdentity),
646
+ ]);
647
+ options.signal.throwIfAborted();
648
+ if (!adapterUnchanged || !agentUnchanged) {
649
+ throw new AcpExecutionError("The local Agent runtime changed; restart Mobius before starting a new conversation", {}, "agent_runtime_changed");
650
+ }
651
+ const runtime = new AcpProcessSession(options.agent, options.cwd, options.additionalDirectories, options.sessionId, getObserver, store);
652
+ try {
653
+ await initialize(runtime);
654
+ return runtime;
655
+ }
656
+ catch (error) {
657
+ await runtime.closeProcess();
658
+ options.signal.throwIfAborted();
659
+ if (error instanceof RetryableAcpInitializationError && attempt === 1) {
660
+ console.warn(`[${options.agent.id}-acp] ACP agent initialization failed; retrying once`);
661
+ continue;
662
+ }
663
+ if (error instanceof AcpExecutionError) {
664
+ throw error;
665
+ }
666
+ throw new AcpExecutionError("ACP agent initialization failed", {
667
+ cause: error,
668
+ });
654
669
  }
655
- throw new AcpExecutionError("ACP agent initialization failed", {
656
- cause: error,
657
- });
658
670
  }
659
- };
671
+ throw new AcpExecutionError("ACP agent initialization failed");
672
+ }
673
+ static start = async (options, getObserver, store) => await AcpProcessSession.#start(options, getObserver, store, async (runtime) => await runtime.initialize(options.signal, options.origin));
674
+ static startNativeSessionBrowser = async (options, getObserver, store) => await AcpProcessSession.#start(options, getObserver, store, async (runtime) => await runtime.#initializeAgent(options.signal));
660
675
  belongsTo = (agentId, cwd, additionalDirectories) => this.#agentId === agentId &&
661
676
  this.#cwd === cwd &&
662
677
  arraysEqual(this.#additionalDirectories, additionalDirectories);
@@ -682,7 +697,7 @@ class AcpProcessSession {
682
697
  this.#replay = { kind: "idle" };
683
698
  }
684
699
  };
685
- initialize = async (signal, origin) => {
700
+ async #initializeAgent(signal) {
686
701
  const initialized = await this.race(this.#connection.agent.request(methods.agent.initialize, {
687
702
  clientCapabilities: {
688
703
  elicitation: { form: {}, url: {} },
@@ -691,7 +706,10 @@ class AcpProcessSession {
691
706
  },
692
707
  clientInfo: { name: "mobius", version: "0.0.3" },
693
708
  protocolVersion: PROTOCOL_VERSION,
694
- }, { cancellationSignal: signal }), "ACP agent exited during initialization", signal);
709
+ }, { cancellationSignal: signal }), "ACP agent exited during initialization", signal).catch((cause) => {
710
+ signal.throwIfAborted();
711
+ throw new RetryableAcpInitializationError(cause);
712
+ });
695
713
  if (initialized.protocolVersion !== PROTOCOL_VERSION) {
696
714
  throw new AcpExecutionError("ACP agent selected an unsupported protocol version", {}, "unsupported_acp_protocol");
697
715
  }
@@ -701,6 +719,9 @@ class AcpProcessSession {
701
719
  initialized.agentInfo?.name ??
702
720
  this.#agentId, 255);
703
721
  this.#agentVersion = truncate(initialized.agentInfo?.version ?? "", 128);
722
+ }
723
+ initialize = async (signal, origin) => {
724
+ await this.#initializeAgent(signal);
704
725
  const stored = origin?.kind === "import"
705
726
  ? new FoundAcpStoredSession(new AcpStoredSession(this.#mobiusSessionId, this.#agentId, nativeSessionIdSchema.parse(origin.nativeSessionId), this.#cwd, this.#additionalDirectories, true))
706
727
  : this.#store.read(this.#mobiusSessionId);
@@ -899,7 +920,6 @@ class AcpProcessSession {
899
920
  _meta: result._meta ?? null,
900
921
  commandId: options.commandId,
901
922
  kind: SESSION_USAGE_UPDATED_KIND,
902
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
903
923
  sessionId: this.#mobiusSessionId,
904
924
  source: "prompt",
905
925
  usage: result.usage ?? null,
@@ -950,7 +970,6 @@ class AcpProcessSession {
950
970
  kind: SESSION_PERMISSION_RESOLVED_KIND,
951
971
  optionId: options.outcome === "selected" ? options.optionId : "",
952
972
  outcome: options.outcome,
953
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
954
973
  sessionId: this.#mobiusSessionId,
955
974
  });
956
975
  };
@@ -985,7 +1004,6 @@ class AcpProcessSession {
985
1004
  action: options.action,
986
1005
  interactionId: options.interactionId,
987
1006
  kind: SESSION_ELICITATION_RESOLVED_KIND,
988
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
989
1007
  sessionId: this.#mobiusSessionId,
990
1008
  });
991
1009
  };
@@ -1014,7 +1032,6 @@ class AcpProcessSession {
1014
1032
  interactionId,
1015
1033
  kind: SESSION_PERMISSION_REQUESTED_KIND,
1016
1034
  options,
1017
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1018
1035
  sessionId: this.#mobiusSessionId,
1019
1036
  title: truncate(request.toolCall.title ?? "Permission required", 500),
1020
1037
  toolCallId: truncate(request.toolCall.toolCallId, 255),
@@ -1059,7 +1076,6 @@ class AcpProcessSession {
1059
1076
  kind: SESSION_ELICITATION_REQUESTED_KIND,
1060
1077
  message: truncate(request.message, 2_000),
1061
1078
  mode: request.mode,
1062
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1063
1079
  sessionId: this.#mobiusSessionId,
1064
1080
  url: request.mode === "url" &&
1065
1081
  typeof Reflect.get(request, "url") === "string"
@@ -1083,7 +1099,6 @@ class AcpProcessSession {
1083
1099
  action: "complete",
1084
1100
  interactionId,
1085
1101
  kind: SESSION_ELICITATION_RESOLVED_KIND,
1086
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1087
1102
  sessionId: this.#mobiusSessionId,
1088
1103
  });
1089
1104
  };
@@ -1125,7 +1140,6 @@ class AcpProcessSession {
1125
1140
  contentId: `content-${crypto.randomUUID()}`,
1126
1141
  kind: AGENT_CONTENT_KIND,
1127
1142
  messageId: truncate(update.messageId ?? "", 255),
1128
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1129
1143
  sessionId: this.#mobiusSessionId,
1130
1144
  });
1131
1145
  }
@@ -1170,7 +1184,6 @@ class AcpProcessSession {
1170
1184
  await this.#getObserver().onCommands({
1171
1185
  commands,
1172
1186
  kind: SESSION_COMMANDS_UPDATED_KIND,
1173
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1174
1187
  sessionId: this.#mobiusSessionId,
1175
1188
  });
1176
1189
  return;
@@ -1196,7 +1209,6 @@ class AcpProcessSession {
1196
1209
  }
1197
1210
  await this.#getObserver().onInfo({
1198
1211
  kind: SESSION_INFO_UPDATED_KIND,
1199
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1200
1212
  sessionId: this.#mobiusSessionId,
1201
1213
  title: this.#sessionTitle,
1202
1214
  updatedAt: this.#sessionUpdatedAt,
@@ -1209,7 +1221,6 @@ class AcpProcessSession {
1209
1221
  costCurrency: truncate(update.cost?.currency ?? "", 16),
1210
1222
  hasCost: update.cost !== undefined && update.cost !== null,
1211
1223
  kind: SESSION_USAGE_UPDATED_KIND,
1212
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1213
1224
  sessionId: this.#mobiusSessionId,
1214
1225
  size: Math.max(0, Math.trunc(update.size)),
1215
1226
  source: "context",
@@ -1269,7 +1280,6 @@ class AcpProcessSession {
1269
1280
  await this.#getObserver().onConfig({
1270
1281
  configOptions: this.#configOptions,
1271
1282
  kind: SESSION_CONFIG_UPDATED_KIND,
1272
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1273
1283
  requestId: `agent-update-${crypto.randomUUID()}`,
1274
1284
  sessionId: this.#mobiusSessionId,
1275
1285
  });
@@ -1285,7 +1295,6 @@ class AcpProcessSession {
1285
1295
  kind: SESSION_PERMISSION_RESOLVED_KIND,
1286
1296
  optionId: "",
1287
1297
  outcome: "cancelled",
1288
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1289
1298
  sessionId: this.#mobiusSessionId,
1290
1299
  });
1291
1300
  };
@@ -1299,7 +1308,6 @@ class AcpProcessSession {
1299
1308
  action: "cancel",
1300
1309
  interactionId,
1301
1310
  kind: SESSION_ELICITATION_RESOLVED_KIND,
1302
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1303
1311
  sessionId: this.#mobiusSessionId,
1304
1312
  });
1305
1313
  };
@@ -1319,7 +1327,6 @@ class AcpProcessSession {
1319
1327
  action: "cancel",
1320
1328
  interactionId,
1321
1329
  kind: SESSION_ELICITATION_RESOLVED_KIND,
1322
- protocolVersion: MOBIUS_PROTOCOL_VERSION,
1323
1330
  sessionId: this.#mobiusSessionId,
1324
1331
  });
1325
1332
  }
@@ -1327,6 +1334,10 @@ class AcpProcessSession {
1327
1334
  }
1328
1335
  export class AcpSessionManager {
1329
1336
  #nativeOperations = new Set();
1337
+ #nativeBrowserObserver = new NullAcpSessionObserver();
1338
+ #nativeBrowserPreparing = new Map();
1339
+ #nativeBrowserSessions = new Map();
1340
+ #nativeBrowserStore = new MemoryAcpSessionStore();
1330
1341
  #activeSessionIds = new Set();
1331
1342
  #cancellationRequests = new Set();
1332
1343
  #preparing = new Map();
@@ -1340,6 +1351,37 @@ export class AcpSessionManager {
1340
1351
  setObserver = (observer) => {
1341
1352
  this.#observer = observer;
1342
1353
  };
1354
+ async #startNativeBrowserPreparation(options) {
1355
+ const operation = (async () => {
1356
+ try {
1357
+ const runtime = await AcpProcessSession.startNativeSessionBrowser(options, () => this.#nativeBrowserObserver, this.#nativeBrowserStore);
1358
+ this.#nativeBrowserSessions.set(options.sessionId, runtime);
1359
+ return runtime;
1360
+ }
1361
+ finally {
1362
+ this.#nativeBrowserPreparing.delete(options.sessionId);
1363
+ }
1364
+ })();
1365
+ this.#nativeBrowserPreparing.set(options.sessionId, operation);
1366
+ return await operation;
1367
+ }
1368
+ async #replaceNativeBrowserPreparation(existing, options) {
1369
+ const operation = (async () => {
1370
+ try {
1371
+ this.#nativeBrowserSessions.delete(options.sessionId);
1372
+ await existing.close();
1373
+ this.#nativeBrowserStore.remove(options.sessionId);
1374
+ const runtime = await AcpProcessSession.startNativeSessionBrowser(options, () => this.#nativeBrowserObserver, this.#nativeBrowserStore);
1375
+ this.#nativeBrowserSessions.set(options.sessionId, runtime);
1376
+ return runtime;
1377
+ }
1378
+ finally {
1379
+ this.#nativeBrowserPreparing.delete(options.sessionId);
1380
+ }
1381
+ })();
1382
+ this.#nativeBrowserPreparing.set(options.sessionId, operation);
1383
+ return await operation;
1384
+ }
1343
1385
  async #startPreparation(options) {
1344
1386
  const operation = (async () => {
1345
1387
  try {
@@ -1400,6 +1442,29 @@ export class AcpSessionManager {
1400
1442
  const runtime = await this.#startPreparation(options);
1401
1443
  return runtime.prepared();
1402
1444
  };
1445
+ prepareNativeSessionBrowser = async (options) => {
1446
+ const pending = this.#nativeBrowserPreparing.get(options.sessionId);
1447
+ if (pending !== undefined) {
1448
+ const runtime = await pending;
1449
+ if (runtime.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
1450
+ return runtime.prepared();
1451
+ }
1452
+ return await this.prepareNativeSessionBrowser(options);
1453
+ }
1454
+ const existing = this.#nativeBrowserSessions.get(options.sessionId);
1455
+ if (existing !== undefined) {
1456
+ if (existing.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
1457
+ return existing.prepared();
1458
+ }
1459
+ if (!existing.canRebind()) {
1460
+ throw new AcpExecutionError("The agent or workspace cannot change after a conversation has started", {}, "session_identity_mismatch");
1461
+ }
1462
+ const runtime = await this.#replaceNativeBrowserPreparation(existing, options);
1463
+ return runtime.prepared();
1464
+ }
1465
+ const runtime = await this.#startNativeBrowserPreparation(options);
1466
+ return runtime.prepared();
1467
+ };
1403
1468
  configureSession = async (options) => {
1404
1469
  const runtime = await this.ensureSession(options);
1405
1470
  return await runtime.configure(options);
@@ -1467,8 +1532,10 @@ export class AcpSessionManager {
1467
1532
  };
1468
1533
  close = async () => {
1469
1534
  const sessions = [...this.#sessions.values()];
1535
+ const nativeBrowserSessions = [...this.#nativeBrowserSessions.values()];
1470
1536
  this.#sessions.clear();
1471
- await Promise.all(sessions.map(async (session) => await session.close()));
1537
+ this.#nativeBrowserSessions.clear();
1538
+ await Promise.all([...sessions, ...nativeBrowserSessions].map(async (session) => await session.close()));
1472
1539
  };
1473
1540
  ensureSession = async (options) => {
1474
1541
  const existing = this.#sessions.get(options.sessionId);
@@ -1485,7 +1552,22 @@ export class AcpSessionManager {
1485
1552
  }
1486
1553
  return started;
1487
1554
  };
1488
- listNativeSessions = async (options, cursor) => await (await this.ensureSession(options)).listNativeSessions(cursor, options.signal);
1555
+ async #ensureNativeSessionBrowser(options) {
1556
+ const existing = this.#nativeBrowserSessions.get(options.sessionId);
1557
+ if (existing !== undefined) {
1558
+ if (!existing.belongsTo(options.agent.id, options.cwd, options.additionalDirectories)) {
1559
+ throw new AcpExecutionError("The agent or workspace cannot change after a conversation has started", {}, "session_identity_mismatch");
1560
+ }
1561
+ return existing;
1562
+ }
1563
+ await this.prepareNativeSessionBrowser(options);
1564
+ const started = this.#nativeBrowserSessions.get(options.sessionId);
1565
+ if (started === undefined) {
1566
+ throw new AcpExecutionError("ACP native session browser did not start");
1567
+ }
1568
+ return started;
1569
+ }
1570
+ listNativeSessions = async (options, cursor) => await (await this.#ensureNativeSessionBrowser(options)).listNativeSessions(cursor, options.signal);
1489
1571
  openNativeSession = async (options, origin) => {
1490
1572
  const key = `${options.agent.id}:${origin.nativeSessionId}`;
1491
1573
  if (this.#nativeOperations.has(key))
@@ -1554,7 +1636,7 @@ export class AcpSessionManager {
1554
1636
  }
1555
1637
  };
1556
1638
  deleteNativeSession = async (options, nativeSessionId) => {
1557
- const runtime = await this.ensureSession(options);
1639
+ const runtime = await this.#ensureNativeSessionBrowser(options);
1558
1640
  const key = `${options.agent.id}:${nativeSessionId}`;
1559
1641
  const linked = this.#store.findNative(options.agent.id, nativeSessionId);
1560
1642
  if (this.#nativeOperations.has(key) ||
@@ -1574,7 +1656,8 @@ export class AcpSessionManager {
1574
1656
  await runtime.deleteNativeSession(nativeSessionId, options.signal);
1575
1657
  const deleted = this.#store.markDeleted(options.agent.id, nativeSessionId);
1576
1658
  if (runtime.nativeSessionId() === nativeSessionId) {
1577
- this.#sessions.delete(options.sessionId);
1659
+ this.#nativeBrowserSessions.delete(options.sessionId);
1660
+ this.#nativeBrowserStore.remove(options.sessionId);
1578
1661
  await runtime.close();
1579
1662
  }
1580
1663
  return deleted;