@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
package/dist/cli.js CHANGED
@@ -5,10 +5,12 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
+ // src/cli.ts
9
+ import { mkdir as mkdir4 } from "node:fs/promises";
10
+
8
11
  // src/agent-catalog/discover-local-agents.ts
9
12
  import { spawn as spawn2 } from "node:child_process";
10
- import { realpath as realpath3 } from "node:fs/promises";
11
- import { isAbsolute as isAbsolute2, join as join5, relative as relative3, resolve as resolve3 } from "node:path";
13
+ import { isAbsolute, join as join5 } from "node:path";
12
14
  import {
13
15
  methods as methods2,
14
16
  ndJsonStream,
@@ -41,10 +43,10 @@ function isRecord(value) {
41
43
  return typeof value === "object" && value !== null && !Array.isArray(value);
42
44
  }
43
45
  async function readGrantedAgents(stateDirectory) {
44
- const path = join(stateDirectory, "installation-grants.json");
46
+ const path2 = join(stateDirectory, "installation-grants.json");
45
47
  let handle;
46
48
  try {
47
- handle = await open(path, "r");
49
+ handle = await open(path2, "r");
48
50
  } catch (error61) {
49
51
  if (error61 instanceof Error && "code" in error61 && error61.code === "ENOENT") {
50
52
  return /* @__PURE__ */ new Set();
@@ -84,13 +86,13 @@ import {
84
86
  rm,
85
87
  stat as stat2
86
88
  } from "node:fs/promises";
87
- import { delimiter, dirname, join as join3, relative as relative2 } from "node:path";
89
+ import { delimiter, dirname, join as join3, relative } from "node:path";
88
90
 
89
91
  // src/process/executable.ts
90
92
  import { spawn } from "node:child_process";
91
93
  import { constants } from "node:fs";
92
94
  import { access, realpath, stat } from "node:fs/promises";
93
- import { isAbsolute, join as join2, relative, resolve } from "node:path";
95
+ import { join as join2, resolve } from "node:path";
94
96
  var DEFAULT_OUTPUT_LIMIT_BYTES = 16 * 1024;
95
97
  var PROCESS_GRACEFUL_STOP_TIMEOUT_MILLISECONDS = 1e3;
96
98
  var PROCESS_FORCED_STOP_TIMEOUT_MILLISECONDS = 5e3;
@@ -114,9 +116,9 @@ var ExecutableIdentity = class {
114
116
  var ResolvedExecutable = class {
115
117
  identity;
116
118
  path;
117
- constructor(identity) {
118
- this.identity = identity;
119
- this.path = identity.realPath;
119
+ constructor(identity2) {
120
+ this.identity = identity2;
121
+ this.path = identity2.realPath;
120
122
  }
121
123
  };
122
124
  var ProcessExecutionError = class extends Error {
@@ -135,10 +137,6 @@ var ProcessResult = class {
135
137
  this.stdout = stdout;
136
138
  }
137
139
  };
138
- function isInside(root, candidate) {
139
- const difference = relative(root, candidate);
140
- return difference.length === 0 || !difference.startsWith("..") && !isAbsolute(difference);
141
- }
142
140
  function executableExtensions(executableName, environment, platform) {
143
141
  if (platform !== "win32") {
144
142
  return [""];
@@ -177,10 +175,9 @@ async function resolveExecutablePath(requestedPath, platform = process.platform)
177
175
  return null;
178
176
  }
179
177
  }
180
- async function findExecutableOnPath(executableName, environment, approvedRoot, platform = process.platform) {
178
+ async function findExecutableOnPath(executableName, environment, platform = process.platform) {
181
179
  const encodedPath = environment["PATH"] ?? "";
182
180
  const separator = platform === "win32" ? ";" : ":";
183
- const approvedRealPath = await realpath(resolve(approvedRoot));
184
181
  const extensions = executableExtensions(
185
182
  executableName,
186
183
  environment,
@@ -195,16 +192,16 @@ async function findExecutableOnPath(executableName, environment, approvedRoot, p
195
192
  join2(entry, `${executableName}${extension}`)
196
193
  );
197
194
  const executable = await resolveExecutablePath(requestedPath, platform);
198
- if (executable !== null && !isInside(approvedRealPath, requestedPath) && !isInside(approvedRealPath, executable.path)) {
195
+ if (executable !== null) {
199
196
  return executable;
200
197
  }
201
198
  }
202
199
  }
203
200
  return null;
204
201
  }
205
- async function revalidateExecutable(identity, platform = process.platform) {
206
- const current = await resolveExecutablePath(identity.requestedPath, platform);
207
- return current !== null && identity.matches(current.identity);
202
+ async function revalidateExecutable(identity2, platform = process.platform) {
203
+ const current = await resolveExecutablePath(identity2.requestedPath, platform);
204
+ return current !== null && identity2.matches(current.identity);
208
205
  }
209
206
  function appendBounded(chunks, chunk, currentBytes, limitBytes) {
210
207
  const nextBytes = currentBytes + chunk.byteLength;
@@ -444,8 +441,8 @@ function isRecord2(value) {
444
441
  function packagePath(root, packageName) {
445
442
  return join3(root, "node_modules", ...packageName.split("/"));
446
443
  }
447
- async function readPackageJson(path) {
448
- const handle = await open2(path, "r");
444
+ async function readPackageJson(path2) {
445
+ const handle = await open2(path2, "r");
449
446
  try {
450
447
  const metadata = await handle.stat();
451
448
  if (!metadata.isFile() || metadata.size > PACKAGE_METADATA_LIMIT_BYTES) {
@@ -486,7 +483,7 @@ async function validateManagedAdapter(root, specification) {
486
483
  const packageRoot = await realpath2(
487
484
  packagePath(rootPath, specification.adapterPackage)
488
485
  );
489
- const packageDifference = relative2(rootPath, packageRoot);
486
+ const packageDifference = relative(rootPath, packageRoot);
490
487
  if (packageDifference.startsWith("..") || packageDifference.length === 0) {
491
488
  throw new ManagedAdapterInstallError(
492
489
  "adapter_verification_failed",
@@ -510,7 +507,7 @@ async function validateManagedAdapter(root, specification) {
510
507
  "The managed adapter executable is invalid"
511
508
  );
512
509
  }
513
- const executableDifference = relative2(rootPath, executable.path);
510
+ const executableDifference = relative(rootPath, executable.path);
514
511
  if (executableDifference.startsWith("..") || executableDifference.length === 0) {
515
512
  throw new ManagedAdapterInstallError(
516
513
  "adapter_verification_failed",
@@ -520,11 +517,9 @@ async function validateManagedAdapter(root, specification) {
520
517
  return executable;
521
518
  }
522
519
  var ManagedAdapterInstaller = class {
523
- #approvedRoot;
524
520
  #environment;
525
521
  #stateDirectory;
526
522
  constructor(options) {
527
- this.#approvedRoot = options.approvedRoot;
528
523
  this.#environment = options.environment;
529
524
  this.#stateDirectory = options.stateDirectory;
530
525
  }
@@ -549,8 +544,7 @@ var ManagedAdapterInstaller = class {
549
544
  try {
550
545
  const npm = await findExecutableOnPath(
551
546
  process.platform === "win32" ? "npm.cmd" : "npm",
552
- this.#environment,
553
- this.#approvedRoot
547
+ this.#environment
554
548
  );
555
549
  if (npm === null) {
556
550
  throw new ManagedAdapterInstallError(
@@ -633,8 +627,8 @@ function readStateDirectory(environment = process.env) {
633
627
 
634
628
  // src/agent-catalog/local-agent.ts
635
629
  import { realpathSync, statSync } from "node:fs";
636
- function readTrustedIdentity(path) {
637
- const resolvedPath = realpathSync(path);
630
+ function readTrustedIdentity(path2) {
631
+ const resolvedPath = realpathSync(path2);
638
632
  const metadata = statSync(resolvedPath);
639
633
  return new ExecutableIdentity(
640
634
  resolvedPath,
@@ -894,23 +888,11 @@ var DiscoveryAcpHandler = class {
894
888
  onPermission = (_request, _signal) => Promise.reject(new Error("ACP probe does not grant permissions"));
895
889
  onUpdate = (_notification) => Promise.resolve();
896
890
  };
897
- async function authorizeManagedExecutable(managed, options) {
898
- const approvedRoot = await realpath3(resolve3(options.approvedRoot));
899
- const requestedDifference = relative3(
900
- approvedRoot,
901
- managed.identity.requestedPath
902
- );
903
- const resolvedDifference = relative3(approvedRoot, managed.path);
904
- const requestedInside = requestedDifference.length === 0 || !requestedDifference.startsWith("..") && !isAbsolute2(requestedDifference);
905
- const resolvedInside = resolvedDifference.length === 0 || !resolvedDifference.startsWith("..") && !isAbsolute2(resolvedDifference);
906
- return requestedInside || resolvedInside ? null : managed;
907
- }
908
891
  async function resolveManagedExecutable(managedExecutablePath, options) {
909
- const managed = await resolveExecutablePath(
892
+ return await resolveExecutablePath(
910
893
  join5(readStateDirectory(options.environment), ...managedExecutablePath),
911
894
  options.platform
912
895
  );
913
- return managed === null ? null : await authorizeManagedExecutable(managed, options);
914
896
  }
915
897
  async function resolveManagedAdapterExecutable(specification, options) {
916
898
  let managed;
@@ -927,12 +909,12 @@ async function resolveManagedAdapterExecutable(specification, options) {
927
909
  } catch {
928
910
  return null;
929
911
  }
930
- return await authorizeManagedExecutable(managed, options);
912
+ return managed;
931
913
  }
932
914
  async function resolveSpecificationExecutables(executableName, overrideVariable, resolveManaged, options) {
933
915
  const override = options.environment[overrideVariable];
934
916
  if (override !== void 0) {
935
- if (override.length === 0 || !isAbsolute2(override)) {
917
+ if (override.length === 0 || !isAbsolute(override)) {
936
918
  return [];
937
919
  }
938
920
  const executable = await resolveExecutablePath(override, options.platform);
@@ -941,7 +923,6 @@ async function resolveSpecificationExecutables(executableName, overrideVariable,
941
923
  const external = await findExecutableOnPath(
942
924
  executableName,
943
925
  options.environment,
944
- options.approvedRoot,
945
926
  options.platform
946
927
  );
947
928
  const managed = await resolveManaged();
@@ -983,11 +964,11 @@ async function resolveAdapterExecutable(specification, options) {
983
964
  function normalizedVersion(output2) {
984
965
  return output2.trim().split(/\r?\n/, 1)[0]?.trim().slice(0, 128) ?? "";
985
966
  }
986
- async function probeVersion(executable, environment, approvedRoot, signal) {
967
+ async function probeVersion(executable, environment, defaultWorkspace, signal) {
987
968
  const result = await runBoundedProcess({
988
969
  arguments: ["--version"],
989
970
  command: executable.path,
990
- cwd: approvedRoot,
971
+ cwd: defaultWorkspace,
991
972
  environment,
992
973
  signal,
993
974
  timeoutMilliseconds: VERSION_PROBE_TIMEOUT_MILLISECONDS
@@ -1016,7 +997,7 @@ async function findVerifiedAgentExecutable(specification, options) {
1016
997
  const version2 = await probeVersion(
1017
998
  executable,
1018
999
  options.environment,
1019
- options.approvedRoot,
1000
+ options.defaultWorkspace,
1020
1001
  options.signal
1021
1002
  );
1022
1003
  return new VerifiedAgentExecutable(executable, version2);
@@ -1083,7 +1064,7 @@ function writableAcpStream(output2) {
1083
1064
  }
1084
1065
  });
1085
1066
  }
1086
- async function probeAcp(agent, environment, approvedRoot, signal) {
1067
+ async function probeAcp(agent, environment, defaultWorkspace, signal) {
1087
1068
  signal.throwIfAborted();
1088
1069
  const controller = new AbortController();
1089
1070
  let timedOut = false;
@@ -1097,12 +1078,18 @@ async function probeAcp(agent, environment, approvedRoot, signal) {
1097
1078
  abort();
1098
1079
  }
1099
1080
  const child = spawn2(agent.adapterPath, [...agent.arguments], {
1100
- cwd: approvedRoot,
1081
+ cwd: defaultWorkspace,
1101
1082
  detached: process.platform !== "win32",
1102
1083
  env: { ...environment, ...agent.environment },
1103
1084
  shell: false,
1104
1085
  stdio: ["pipe", "pipe", "pipe"]
1105
1086
  });
1087
+ const processExit = new Promise((_resolve, reject) => {
1088
+ const rejectExit = () => reject(new Error("ACP adapter exited during initialization"));
1089
+ child.once("error", reject);
1090
+ child.once("exit", rejectExit);
1091
+ if (child.exitCode !== null || child.signalCode !== null) rejectExit();
1092
+ });
1106
1093
  const stdin = child.stdin;
1107
1094
  const stdout = child.stdout;
1108
1095
  const stderr = child.stderr;
@@ -1124,13 +1111,6 @@ async function probeAcp(agent, environment, approvedRoot, signal) {
1124
1111
  const connection = createAcpClient(new DiscoveryAcpHandler()).connect(
1125
1112
  ndJsonStream(writableAcpStream(stdin), readableAcpStream(stdout))
1126
1113
  );
1127
- const processExit = new Promise((_resolve, reject) => {
1128
- child.once("error", (error61) => reject(error61));
1129
- child.once(
1130
- "exit",
1131
- () => reject(new Error("ACP adapter exited during initialization"))
1132
- );
1133
- });
1134
1114
  try {
1135
1115
  const initialized = await Promise.race([
1136
1116
  connection.agent.request(
@@ -1215,7 +1195,7 @@ async function discoverSpecification(specification, options) {
1215
1195
  agentVersion = await probeVersion(
1216
1196
  agent,
1217
1197
  options.environment,
1218
- options.approvedRoot,
1198
+ options.defaultWorkspace,
1219
1199
  options.signal
1220
1200
  );
1221
1201
  } catch (error61) {
@@ -1234,7 +1214,7 @@ async function discoverSpecification(specification, options) {
1234
1214
  ...options.environment,
1235
1215
  [specification.adapterEnvironmentVariable]: agent.path
1236
1216
  },
1237
- options.approvedRoot,
1217
+ options.defaultWorkspace,
1238
1218
  options.signal
1239
1219
  );
1240
1220
  } catch (error61) {
@@ -1259,7 +1239,7 @@ async function discoverSpecification(specification, options) {
1259
1239
  await probeAcp(
1260
1240
  definition,
1261
1241
  options.environment,
1262
- options.approvedRoot,
1242
+ options.defaultWorkspace,
1263
1243
  options.signal
1264
1244
  );
1265
1245
  return new AvailableAgent(specification, definition);
@@ -1374,9 +1354,9 @@ import { dirname as dirname2 } from "node:path";
1374
1354
  import { DatabaseSync } from "node:sqlite";
1375
1355
  var AuditStore = class {
1376
1356
  database;
1377
- constructor(path) {
1378
- mkdirSync(dirname2(path), { recursive: true });
1379
- this.database = new DatabaseSync(path);
1357
+ constructor(path2) {
1358
+ mkdirSync(dirname2(path2), { recursive: true });
1359
+ this.database = new DatabaseSync(path2);
1380
1360
  this.database.exec(`
1381
1361
  CREATE TABLE IF NOT EXISTS audit_events (
1382
1362
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1419,13 +1399,15 @@ var AuditStore = class {
1419
1399
  };
1420
1400
 
1421
1401
  // src/config.ts
1422
- function loadApprovedRoot() {
1423
- return process.env["MOBIUS_APPROVED_ROOT"] ?? process.cwd();
1402
+ import { join as join6 } from "node:path";
1403
+ function loadDefaultWorkspace() {
1404
+ const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = process.env;
1405
+ return configuredWorkspace ?? join6(readStateDirectory(), "workspace");
1424
1406
  }
1425
1407
  function createDaemonConfig(connection, agents = [], installableAgents = []) {
1426
1408
  return {
1427
1409
  agents,
1428
- approvedRoot: connection.approvedRoot,
1410
+ defaultWorkspace: connection.defaultWorkspace,
1429
1411
  auditDatabasePath: connection.auditDatabasePath,
1430
1412
  bindingId: "workspace",
1431
1413
  catalog: new LocalAgentCatalog(agents, installableAgents),
@@ -1440,13 +1422,13 @@ import { randomUUID } from "node:crypto";
1440
1422
  import {
1441
1423
  mkdir as mkdir2,
1442
1424
  open as open3,
1443
- realpath as realpath4,
1425
+ realpath as realpath3,
1444
1426
  rename as rename2,
1445
1427
  stat as stat3,
1446
1428
  writeFile
1447
1429
  } from "node:fs/promises";
1448
1430
  import { hostname } from "node:os";
1449
- import { basename, join as join6, resolve as resolve4 } from "node:path";
1431
+ import { basename, join as join7, resolve as resolve3 } from "node:path";
1450
1432
  import { fetch } from "undici";
1451
1433
 
1452
1434
  // src/network/environment-proxy.ts
@@ -1461,7 +1443,7 @@ var TOKEN_PATTERN = /^[a-zA-Z0-9_-]{32,256}$/;
1461
1443
  var MAX_CONNECTION_FILE_LENGTH = 16 * 1024;
1462
1444
  var CLAIM_RESPONSE_KEYS = ["channelId", "daemonToken"];
1463
1445
  var SAVED_CONNECTION_KEYS = [
1464
- "approvedRoot",
1446
+ "defaultWorkspace",
1465
1447
  "channelId",
1466
1448
  "daemonToken",
1467
1449
  "relayOrigin"
@@ -1508,7 +1490,7 @@ function readSingleOption(arguments_, option) {
1508
1490
  function assertKnownArguments(arguments_) {
1509
1491
  for (let index = 0; index < arguments_.length; index += 2) {
1510
1492
  const option = arguments_[index];
1511
- if (option !== "--url" && option !== "--code" && option !== "--root") {
1493
+ if (option !== "--url" && option !== "--code" && option !== "--workspace") {
1512
1494
  throw new PairingClientError(
1513
1495
  "invalid_arguments",
1514
1496
  `Unknown pairing option: ${option ?? "missing option"}`
@@ -1565,7 +1547,7 @@ function parsePairCommand(arguments_) {
1565
1547
  return {
1566
1548
  code: parsePairingCode(readSingleOption(arguments_, "--code")),
1567
1549
  relayOrigin: parseRelayOrigin(readSingleOption(arguments_, "--url")),
1568
- root: readSingleOption(arguments_, "--root")
1550
+ workspace: arguments_.includes("--workspace") ? readSingleOption(arguments_, "--workspace") : loadDefaultWorkspace()
1569
1551
  };
1570
1552
  }
1571
1553
  function parseChannelArgument(arguments_) {
@@ -1584,25 +1566,26 @@ function parseChannelArgument(arguments_) {
1584
1566
  }
1585
1567
  return channelId;
1586
1568
  }
1587
- async function resolveApprovedRoot(path) {
1588
- const requestedRoot = resolve4(path);
1569
+ async function resolveDefaultWorkspace(path2) {
1570
+ const requestedWorkspace = resolve3(path2);
1589
1571
  try {
1590
- const approvedRoot = await realpath4(requestedRoot);
1591
- const metadata = await stat3(approvedRoot);
1572
+ await mkdir2(requestedWorkspace, { recursive: true, mode: 448 });
1573
+ const defaultWorkspace = await realpath3(requestedWorkspace);
1574
+ const metadata = await stat3(defaultWorkspace);
1592
1575
  if (!metadata.isDirectory()) {
1593
1576
  throw new PairingClientError(
1594
1577
  "invalid_workspace",
1595
- "The approved workspace root is not a directory"
1578
+ "The default workspace is not a directory"
1596
1579
  );
1597
1580
  }
1598
- return approvedRoot;
1581
+ return defaultWorkspace;
1599
1582
  } catch (error61) {
1600
1583
  if (error61 instanceof PairingClientError) {
1601
1584
  throw error61;
1602
1585
  }
1603
1586
  throw new PairingClientError(
1604
1587
  "invalid_workspace",
1605
- "The approved workspace root cannot be resolved",
1588
+ "The default workspace cannot be resolved",
1606
1589
  { cause: error61 }
1607
1590
  );
1608
1591
  }
@@ -1658,7 +1641,7 @@ function decodeClaimResponse(value) {
1658
1641
  }
1659
1642
  return { channelId, daemonToken };
1660
1643
  }
1661
- async function claimPairing(options, approvedRoot, localAgents, installableAgents, signal) {
1644
+ async function claimPairing(options, defaultWorkspace, localAgents, installableAgents, signal) {
1662
1645
  const agents = localAgents.map((agent) => ({
1663
1646
  id: agent.id,
1664
1647
  label: agent.label
@@ -1676,7 +1659,7 @@ async function claimPairing(options, approvedRoot, localAgents, installableAgent
1676
1659
  })),
1677
1660
  machineName: hostname(),
1678
1661
  pairingCode: options.code,
1679
- workspaceName: basename(approvedRoot) || "Local workspace"
1662
+ workspaceName: basename(defaultWorkspace) || "Local workspace"
1680
1663
  }),
1681
1664
  dispatcher: environmentProxyDispatcher,
1682
1665
  headers: { "content-type": "application/json" },
@@ -1699,11 +1682,11 @@ async function claimPairing(options, approvedRoot, localAgents, installableAgent
1699
1682
  }
1700
1683
  return decodeClaimResponse(value);
1701
1684
  }
1702
- async function persistConnection(channelId, relayOrigin, daemonToken, approvedRoot) {
1703
- const connectionsDirectory = join6(readStateDirectory(), "connections");
1685
+ async function persistConnection(channelId, relayOrigin, daemonToken, defaultWorkspace) {
1686
+ const connectionsDirectory = join7(readStateDirectory(), "connections");
1704
1687
  await mkdir2(connectionsDirectory, { mode: 448, recursive: true });
1705
- const connectionPath = join6(connectionsDirectory, `${channelId}.json`);
1706
- const temporaryPath = join6(
1688
+ const connectionPath = join7(connectionsDirectory, `${channelId}.json`);
1689
+ const temporaryPath = join7(
1707
1690
  connectionsDirectory,
1708
1691
  `.${channelId}.${randomUUID()}.tmp`
1709
1692
  );
@@ -1711,7 +1694,7 @@ async function persistConnection(channelId, relayOrigin, daemonToken, approvedRo
1711
1694
  temporaryPath,
1712
1695
  `${JSON.stringify(
1713
1696
  {
1714
- approvedRoot,
1697
+ defaultWorkspace,
1715
1698
  channelId,
1716
1699
  daemonToken,
1717
1700
  relayOrigin: relayOrigin.toString()
@@ -1732,25 +1715,25 @@ function decodeSavedConnection(value, expectedChannelId) {
1732
1715
  "The saved connection is invalid"
1733
1716
  );
1734
1717
  }
1735
- const approvedRoot = value["approvedRoot"];
1718
+ const defaultWorkspace = value["defaultWorkspace"];
1736
1719
  const channelId = value["channelId"];
1737
1720
  const daemonToken = value["daemonToken"];
1738
1721
  const relayOrigin = value["relayOrigin"];
1739
- if (typeof approvedRoot !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN.test(channelId) || typeof daemonToken !== "string" || !TOKEN_PATTERN.test(daemonToken) || typeof relayOrigin !== "string") {
1722
+ if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN.test(channelId) || typeof daemonToken !== "string" || !TOKEN_PATTERN.test(daemonToken) || typeof relayOrigin !== "string") {
1740
1723
  throw new PairingClientError(
1741
1724
  "invalid_connection",
1742
1725
  "The saved connection is invalid"
1743
1726
  );
1744
1727
  }
1745
1728
  return {
1746
- approvedRoot,
1729
+ defaultWorkspace,
1747
1730
  channelId,
1748
1731
  daemonToken,
1749
1732
  relayOrigin: parseRelayOrigin(relayOrigin)
1750
1733
  };
1751
1734
  }
1752
1735
  async function readSavedConnection(channelId) {
1753
- const connectionPath = join6(
1736
+ const connectionPath = join7(
1754
1737
  readStateDirectory(),
1755
1738
  "connections",
1756
1739
  `${channelId}.json`
@@ -1781,7 +1764,9 @@ async function readSavedConnection(channelId) {
1781
1764
  }
1782
1765
  const connection = decodeSavedConnection(value, channelId);
1783
1766
  return {
1784
- approvedRoot: await resolveApprovedRoot(connection.approvedRoot),
1767
+ defaultWorkspace: await resolveDefaultWorkspace(
1768
+ connection.defaultWorkspace
1769
+ ),
1785
1770
  channelId: connection.channelId,
1786
1771
  daemonToken: connection.daemonToken,
1787
1772
  relayOrigin: connection.relayOrigin
@@ -1803,7 +1788,7 @@ function buildPairedDaemon(connection, connectionPath, inventory) {
1803
1788
  );
1804
1789
  relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
1805
1790
  relayUrl.searchParams.set("token", connection.daemonToken);
1806
- const auditDatabasePath = join6(
1791
+ const auditDatabasePath = join7(
1807
1792
  readStateDirectory(),
1808
1793
  "audit",
1809
1794
  `${connection.channelId}.sqlite`
@@ -1815,7 +1800,7 @@ function buildPairedDaemon(connection, connectionPath, inventory) {
1815
1800
  ).toString(),
1816
1801
  config: createDaemonConfig(
1817
1802
  {
1818
- approvedRoot: connection.approvedRoot,
1803
+ defaultWorkspace: connection.defaultWorkspace,
1819
1804
  auditDatabasePath,
1820
1805
  channelId: connection.channelId,
1821
1806
  relayUrl
@@ -1828,9 +1813,9 @@ function buildPairedDaemon(connection, connectionPath, inventory) {
1828
1813
  }
1829
1814
  async function pairLocalMachine(arguments_, signal = new AbortController().signal) {
1830
1815
  const options = parsePairCommand(arguments_);
1831
- const approvedRoot = await resolveApprovedRoot(options.root);
1816
+ const defaultWorkspace = await resolveDefaultWorkspace(options.workspace);
1832
1817
  const inventory = await discoverLocalAgentInventory({
1833
- approvedRoot,
1818
+ defaultWorkspace,
1834
1819
  environment: process.env,
1835
1820
  signal
1836
1821
  });
@@ -1838,7 +1823,7 @@ async function pairLocalMachine(arguments_, signal = new AbortController().signa
1838
1823
  const agents = inventory.readyAgents();
1839
1824
  const claim2 = await claimPairing(
1840
1825
  options,
1841
- approvedRoot,
1826
+ defaultWorkspace,
1842
1827
  agents,
1843
1828
  inventory.installable,
1844
1829
  signal
@@ -1847,11 +1832,11 @@ async function pairLocalMachine(arguments_, signal = new AbortController().signa
1847
1832
  claim2.channelId,
1848
1833
  options.relayOrigin,
1849
1834
  claim2.daemonToken,
1850
- approvedRoot
1835
+ defaultWorkspace
1851
1836
  );
1852
1837
  return buildPairedDaemon(
1853
1838
  {
1854
- approvedRoot,
1839
+ defaultWorkspace,
1855
1840
  channelId: claim2.channelId,
1856
1841
  daemonToken: claim2.daemonToken,
1857
1842
  relayOrigin: options.relayOrigin
@@ -1862,14 +1847,14 @@ async function pairLocalMachine(arguments_, signal = new AbortController().signa
1862
1847
  }
1863
1848
  async function resumeLocalMachine(arguments_, signal = new AbortController().signal) {
1864
1849
  const channelId = parseChannelArgument(arguments_);
1865
- const connectionPath = join6(
1850
+ const connectionPath = join7(
1866
1851
  readStateDirectory(),
1867
1852
  "connections",
1868
1853
  `${channelId}.json`
1869
1854
  );
1870
1855
  const connection = await readSavedConnection(channelId);
1871
1856
  const inventory = await discoverLocalAgentInventory({
1872
- approvedRoot: connection.approvedRoot,
1857
+ defaultWorkspace: connection.defaultWorkspace,
1873
1858
  environment: process.env,
1874
1859
  signal
1875
1860
  });
@@ -1877,7 +1862,7 @@ async function resumeLocalMachine(arguments_, signal = new AbortController().sig
1877
1862
  }
1878
1863
 
1879
1864
  // src/run-daemon.ts
1880
- import { randomUUID as randomUUID4 } from "node:crypto";
1865
+ import { randomUUID as randomUUID5 } from "node:crypto";
1881
1866
  import { basename as basename3 } from "node:path";
1882
1867
 
1883
1868
  // ../../node_modules/.pnpm/zod@4.5.4/node_modules/zod/v4/classic/external.js
@@ -2624,10 +2609,10 @@ function mergeDefs(...defs) {
2624
2609
  function cloneDef(schema) {
2625
2610
  return mergeDefs(schema._zod.def);
2626
2611
  }
2627
- function getElementAtPath(obj, path) {
2628
- if (!path)
2612
+ function getElementAtPath(obj, path2) {
2613
+ if (!path2)
2629
2614
  return obj;
2630
- return path.reduce((acc, key) => acc?.[key], obj);
2615
+ return path2.reduce((acc, key) => acc?.[key], obj);
2631
2616
  }
2632
2617
  function promiseAllObject(promisesObj) {
2633
2618
  const keys = Object.keys(promisesObj);
@@ -3039,11 +3024,11 @@ function explicitlyAborted(x, startIndex = 0) {
3039
3024
  }
3040
3025
  return false;
3041
3026
  }
3042
- function prefixIssues(path, issues) {
3027
+ function prefixIssues(path2, issues) {
3043
3028
  return issues.map((iss) => {
3044
3029
  var _a3;
3045
3030
  (_a3 = iss).path ?? (_a3.path = []);
3046
- iss.path.unshift(path);
3031
+ iss.path.unshift(path2);
3047
3032
  return iss;
3048
3033
  });
3049
3034
  }
@@ -3476,16 +3461,16 @@ function flattenError(error61, mapper = (issue2) => issue2.message) {
3476
3461
  }
3477
3462
  function formatError(error61, mapper = (issue2) => issue2.message) {
3478
3463
  const fieldErrors = { _errors: [] };
3479
- const processError = (error62, path = []) => {
3464
+ const processError = (error62, path2 = []) => {
3480
3465
  for (const issue2 of error62.issues) {
3481
3466
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3482
- issue2.errors.map((issues) => processError({ issues }, [...path, ...issue2.path]));
3467
+ issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
3483
3468
  } else if (issue2.code === "invalid_key") {
3484
- processError({ issues: issue2.issues }, [...path, ...issue2.path]);
3469
+ processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
3485
3470
  } else if (issue2.code === "invalid_element") {
3486
- processError({ issues: issue2.issues }, [...path, ...issue2.path]);
3471
+ processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
3487
3472
  } else {
3488
- const fullpath = [...path, ...issue2.path];
3473
+ const fullpath = [...path2, ...issue2.path];
3489
3474
  if (fullpath.length === 0) {
3490
3475
  fieldErrors._errors.push(mapper(issue2));
3491
3476
  } else {
@@ -3524,17 +3509,17 @@ function formatError(error61, mapper = (issue2) => issue2.message) {
3524
3509
  }
3525
3510
  function treeifyError(error61, mapper = (issue2) => issue2.message) {
3526
3511
  const result = { errors: [] };
3527
- const processError = (error62, path = []) => {
3512
+ const processError = (error62, path2 = []) => {
3528
3513
  var _a3;
3529
3514
  for (const issue2 of error62.issues) {
3530
3515
  if (issue2.code === "invalid_union" && issue2.errors.length) {
3531
- issue2.errors.map((issues) => processError({ issues }, [...path, ...issue2.path]));
3516
+ issue2.errors.map((issues) => processError({ issues }, [...path2, ...issue2.path]));
3532
3517
  } else if (issue2.code === "invalid_key") {
3533
- processError({ issues: issue2.issues }, [...path, ...issue2.path]);
3518
+ processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
3534
3519
  } else if (issue2.code === "invalid_element") {
3535
- processError({ issues: issue2.issues }, [...path, ...issue2.path]);
3520
+ processError({ issues: issue2.issues }, [...path2, ...issue2.path]);
3536
3521
  } else {
3537
- const fullpath = [...path, ...issue2.path];
3522
+ const fullpath = [...path2, ...issue2.path];
3538
3523
  if (fullpath.length === 0) {
3539
3524
  result.errors.push(mapper(issue2));
3540
3525
  continue;
@@ -3573,8 +3558,8 @@ function treeifyError(error61, mapper = (issue2) => issue2.message) {
3573
3558
  }
3574
3559
  function toDotPath(_path) {
3575
3560
  const segs = [];
3576
- const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
3577
- for (const seg of path) {
3561
+ const path2 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
3562
+ for (const seg of path2) {
3578
3563
  if (typeof seg === "number")
3579
3564
  segs.push(`[${seg}]`);
3580
3565
  else if (typeof seg === "symbol")
@@ -20084,13 +20069,13 @@ function resolveRef(ref, ctx) {
20084
20069
  if (!ref.startsWith("#")) {
20085
20070
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
20086
20071
  }
20087
- const path = ref.slice(1).split("/").filter(Boolean);
20088
- if (path.length === 0) {
20072
+ const path2 = ref.slice(1).split("/").filter(Boolean);
20073
+ if (path2.length === 0) {
20089
20074
  return ctx.rootSchema;
20090
20075
  }
20091
20076
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
20092
- if (path[0] === defsKey) {
20093
- const key = path[1] === void 0 ? void 0 : decodeJSONPointerSegment(path[1]);
20077
+ if (path2[0] === defsKey) {
20078
+ const key = path2[1] === void 0 ? void 0 : decodeJSONPointerSegment(path2[1]);
20094
20079
  if (!key || !ctx.defs[key]) {
20095
20080
  throw new Error(`Reference not found: ${ref}`);
20096
20081
  }
@@ -20746,6 +20731,56 @@ function date4(params) {
20746
20731
  return _coercedDate(ZodDate, params);
20747
20732
  }
20748
20733
 
20734
+ // ../protocol/src/prompt-usage.ts
20735
+ var TOKEN_COUNT = external_exports.number().int().nonnegative();
20736
+ var promptUsageSchema = external_exports.object({
20737
+ cachedReadTokens: TOKEN_COUNT.nullish(),
20738
+ cachedWriteTokens: TOKEN_COUNT.nullish(),
20739
+ inputTokens: TOKEN_COUNT,
20740
+ outputTokens: TOKEN_COUNT,
20741
+ thoughtTokens: TOKEN_COUNT.nullish(),
20742
+ totalTokens: TOKEN_COUNT
20743
+ }).readonly();
20744
+ var quotaTokenCountSchema = external_exports.object({
20745
+ cachedInputTokens: TOKEN_COUNT,
20746
+ cachedWriteTokens: TOKEN_COUNT.optional(),
20747
+ inputTokens: TOKEN_COUNT,
20748
+ outputTokens: TOKEN_COUNT,
20749
+ reasoningOutputTokens: TOKEN_COUNT,
20750
+ totalTokens: TOKEN_COUNT
20751
+ }).readonly();
20752
+ var promptMetadataSchema = external_exports.object({
20753
+ quota: external_exports.object({
20754
+ model_usage: external_exports.array(
20755
+ external_exports.object({
20756
+ model: external_exports.string().min(1).max(255),
20757
+ token_count: quotaTokenCountSchema
20758
+ }).readonly()
20759
+ ).max(100).readonly(),
20760
+ token_count: quotaTokenCountSchema.nullable()
20761
+ }).readonly().optional()
20762
+ }).readonly();
20763
+ var agentPromptResponseSchema = external_exports.object({
20764
+ _meta: promptMetadataSchema.nullish(),
20765
+ stopReason: external_exports.enum([
20766
+ "end_turn",
20767
+ "max_tokens",
20768
+ "max_turn_requests",
20769
+ "refusal",
20770
+ "cancelled"
20771
+ ]),
20772
+ usage: promptUsageSchema.nullish()
20773
+ }).readonly();
20774
+ function decodeAgentPromptResponse(value) {
20775
+ const result = agentPromptResponseSchema.safeParse(value);
20776
+ if (!result.success) {
20777
+ throw new ProtocolMessageError("Invalid agent prompt response", {
20778
+ cause: result.error
20779
+ });
20780
+ }
20781
+ return result.data;
20782
+ }
20783
+
20749
20784
  // ../protocol/src/version.ts
20750
20785
  var MOBIUS_PROTOCOL_VERSION = 1;
20751
20786
 
@@ -20812,7 +20847,8 @@ var sessionCapabilitiesSchema = external_exports.strictObject({
20812
20847
  imagePrompt: external_exports.boolean(),
20813
20848
  listSessions: external_exports.boolean(),
20814
20849
  loadSession: external_exports.boolean(),
20815
- resumeSession: external_exports.boolean()
20850
+ resumeSession: external_exports.boolean(),
20851
+ unstableForkSession: external_exports.boolean().default(false)
20816
20852
  }).readonly();
20817
20853
  var sessionCommandSchema = external_exports.strictObject({
20818
20854
  description: SHORT_TEXT,
@@ -20953,7 +20989,7 @@ var sessionConfigUpdatedEventSchema = external_exports.strictObject({
20953
20989
  requestId: IDENTIFIER,
20954
20990
  sessionId: IDENTIFIER
20955
20991
  }).readonly();
20956
- var sessionUsageUpdatedEventSchema = external_exports.strictObject({
20992
+ var sessionContextUsageUpdatedEventSchema = external_exports.strictObject({
20957
20993
  costAmount: external_exports.number().finite().nonnegative(),
20958
20994
  costCurrency: external_exports.string().max(16),
20959
20995
  hasCost: external_exports.boolean(),
@@ -20961,8 +20997,22 @@ var sessionUsageUpdatedEventSchema = external_exports.strictObject({
20961
20997
  protocolVersion: PROTOCOL_VERSION2,
20962
20998
  sessionId: IDENTIFIER,
20963
20999
  size: external_exports.number().int().nonnegative(),
21000
+ source: external_exports.literal("context").default("context"),
20964
21001
  used: external_exports.number().int().nonnegative()
20965
21002
  }).readonly();
21003
+ var sessionPromptUsageUpdatedEventSchema = external_exports.strictObject({
21004
+ _meta: promptMetadataSchema.nullable(),
21005
+ commandId: IDENTIFIER,
21006
+ kind: external_exports.literal(SESSION_USAGE_UPDATED_KIND),
21007
+ protocolVersion: PROTOCOL_VERSION2,
21008
+ sessionId: IDENTIFIER,
21009
+ source: external_exports.literal("prompt"),
21010
+ usage: promptUsageSchema.nullable()
21011
+ }).refine((event) => event.usage !== null || event._meta?.quota !== void 0).readonly();
21012
+ var sessionUsageUpdatedEventSchema = external_exports.union([
21013
+ sessionContextUsageUpdatedEventSchema,
21014
+ sessionPromptUsageUpdatedEventSchema
21015
+ ]);
20966
21016
  var sessionCommandsUpdatedEventSchema = external_exports.strictObject({
20967
21017
  commands: external_exports.array(sessionCommandSchema).max(500).readonly(),
20968
21018
  kind: external_exports.literal(SESSION_COMMANDS_UPDATED_KIND),
@@ -20973,7 +21023,7 @@ var sessionInfoUpdatedEventSchema = external_exports.strictObject({
20973
21023
  kind: external_exports.literal(SESSION_INFO_UPDATED_KIND),
20974
21024
  protocolVersion: PROTOCOL_VERSION2,
20975
21025
  sessionId: IDENTIFIER,
20976
- title: external_exports.string().max(500),
21026
+ title: external_exports.string().max(500).nullable(),
20977
21027
  updatedAt: external_exports.string().max(128)
20978
21028
  }).readonly();
20979
21029
  var sessionPermissionRequestedEventSchema = external_exports.strictObject({
@@ -21550,6 +21600,160 @@ function encodeSessionFailedEvent(message) {
21550
21600
  return JSON.stringify(decodeSessionFailedEvent(JSON.stringify(message)));
21551
21601
  }
21552
21602
 
21603
+ // ../protocol/src/session-history.ts
21604
+ var SESSION_HISTORY_CHARACTER_LIMIT = 256e3;
21605
+ var SESSION_HISTORY_MESSAGE_LIMIT = 1e3;
21606
+ var SESSION_HISTORY_PREVIEW_MESSAGE_LIMIT = 10;
21607
+ var SESSION_HISTORY_IMAGE_CHARACTER_LIMIT = 4e6;
21608
+ var historyImage = external_exports.strictObject({
21609
+ data: external_exports.string().min(4).max(SESSION_HISTORY_IMAGE_CHARACTER_LIMIT).refine(
21610
+ (data) => data.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(data)
21611
+ ),
21612
+ mimeType: external_exports.enum(["image/png", "image/jpeg", "image/gif", "image/webp"]),
21613
+ name: external_exports.string().min(1).max(500)
21614
+ }).readonly();
21615
+ function parseNativeHistoryText(text) {
21616
+ const images = [];
21617
+ let imageCharacters = 0;
21618
+ const plainText = text.replace(
21619
+ /\[@image\]\(([^)\r\n]*)(?:\)|$)/g,
21620
+ (_marker, uri) => {
21621
+ const header = /^data:(image\/[a-z+]+);base64,/.exec(uri);
21622
+ if (header === null) return "[Image unavailable]";
21623
+ const parsed = historyImage.safeParse({
21624
+ data: uri.slice(header[0].length),
21625
+ mimeType: header[1],
21626
+ name: `Image ${(images.length + 1).toString()}`
21627
+ });
21628
+ if (!parsed.success || images.length >= 8 || imageCharacters + parsed.data.data.length > SESSION_HISTORY_IMAGE_CHARACTER_LIMIT)
21629
+ return "[Image unavailable]";
21630
+ images.push(parsed.data);
21631
+ imageCharacters += parsed.data.data.length;
21632
+ return "";
21633
+ }
21634
+ );
21635
+ return { text: plainText, images };
21636
+ }
21637
+ var historyMessage = external_exports.strictObject({
21638
+ role: external_exports.enum(["user", "assistant"]),
21639
+ text: external_exports.string().max(SESSION_HISTORY_CHARACTER_LIMIT),
21640
+ images: external_exports.array(historyImage).max(8).readonly().optional()
21641
+ }).readonly();
21642
+ var sessionHistorySchema = external_exports.discriminatedUnion("status", [
21643
+ external_exports.strictObject({
21644
+ status: external_exports.literal("available"),
21645
+ messages: external_exports.array(historyMessage).max(SESSION_HISTORY_MESSAGE_LIMIT).readonly(),
21646
+ truncated: external_exports.boolean(),
21647
+ coveredCommandIds: external_exports.array(external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/)).max(1e4).readonly()
21648
+ }).readonly(),
21649
+ external_exports.strictObject({ status: external_exports.literal("unavailable") }).readonly()
21650
+ ]).refine(
21651
+ (history) => history.status === "unavailable" || history.messages.reduce(
21652
+ (total, message) => total + message.text.length,
21653
+ 0
21654
+ ) <= SESSION_HISTORY_CHARACTER_LIMIT && history.messages.reduce(
21655
+ (total, message) => total + (message.images ?? []).reduce(
21656
+ (size, image) => size + image.data.length,
21657
+ 0
21658
+ ),
21659
+ 0
21660
+ ) <= SESSION_HISTORY_IMAGE_CHARACTER_LIMIT
21661
+ );
21662
+
21663
+ // ../protocol/src/native-sessions.ts
21664
+ var NATIVE_SESSION_REQUEST_KIND = "native.session.request";
21665
+ var NATIVE_SESSION_RESULT_KIND = "native.session.result";
21666
+ var NATIVE_SESSION_CANCEL_KIND = "native.session.cancel";
21667
+ var identifier = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/);
21668
+ var directoryId = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,2047}$/);
21669
+ var envelope = {
21670
+ protocolVersion: external_exports.literal(MOBIUS_PROTOCOL_VERSION),
21671
+ requestId: identifier,
21672
+ sessionId: identifier
21673
+ };
21674
+ var identity = {
21675
+ ...envelope,
21676
+ kind: external_exports.literal(NATIVE_SESSION_REQUEST_KIND),
21677
+ additionalDirectoryIds: external_exports.array(directoryId).max(16).readonly(),
21678
+ agentId: identifier,
21679
+ bindingId: identifier,
21680
+ directoryId
21681
+ };
21682
+ var nativeSessionCommandSchema = external_exports.discriminatedUnion("action", [
21683
+ external_exports.strictObject({
21684
+ ...identity,
21685
+ action: external_exports.literal("list"),
21686
+ cursor: identifier.nullable()
21687
+ }).readonly(),
21688
+ external_exports.strictObject({
21689
+ ...identity,
21690
+ action: external_exports.enum(["import", "fork"]),
21691
+ entryId: identifier,
21692
+ targetSessionId: identifier
21693
+ }).readonly(),
21694
+ external_exports.strictObject({
21695
+ ...identity,
21696
+ action: external_exports.literal("delete"),
21697
+ entryId: identifier
21698
+ }).readonly()
21699
+ ]);
21700
+ var nativeSessionEntrySchema = external_exports.strictObject({
21701
+ entryId: identifier,
21702
+ title: external_exports.string().max(500),
21703
+ directoryId,
21704
+ directoryLabel: external_exports.string().min(1).max(2048),
21705
+ updatedAt: external_exports.iso.datetime({ offset: true }).nullable(),
21706
+ additionalDirectories: external_exports.array(
21707
+ external_exports.strictObject({ directoryId, name: external_exports.string().min(1).max(255) }).readonly()
21708
+ ).max(16).readonly()
21709
+ }).readonly();
21710
+ var resultEnvelope = {
21711
+ ...envelope,
21712
+ kind: external_exports.literal(NATIVE_SESSION_RESULT_KIND)
21713
+ };
21714
+ var nativeSessionResultSchema = external_exports.discriminatedUnion("action", [
21715
+ external_exports.strictObject({
21716
+ ...resultEnvelope,
21717
+ action: external_exports.literal("list"),
21718
+ capabilities: sessionCapabilitiesSchema,
21719
+ entries: external_exports.array(nativeSessionEntrySchema).max(500).readonly(),
21720
+ nextCursor: identifier.nullable()
21721
+ }).readonly(),
21722
+ external_exports.strictObject({
21723
+ ...resultEnvelope,
21724
+ action: external_exports.enum(["import", "fork"]),
21725
+ ready: sessionReadyEventSchema,
21726
+ history: sessionHistorySchema,
21727
+ title: external_exports.string().max(500)
21728
+ }).readonly(),
21729
+ external_exports.strictObject({
21730
+ ...resultEnvelope,
21731
+ action: external_exports.literal("preview"),
21732
+ entryId: identifier,
21733
+ reason: external_exports.literal("continuation_unavailable"),
21734
+ history: sessionHistorySchema,
21735
+ title: external_exports.string().max(500)
21736
+ }).readonly(),
21737
+ external_exports.strictObject({
21738
+ ...resultEnvelope,
21739
+ action: external_exports.literal("delete"),
21740
+ entryId: identifier,
21741
+ deletedSessionIds: external_exports.array(identifier).max(500).readonly()
21742
+ }).readonly()
21743
+ ]);
21744
+ var nativeSessionCancelSchema = external_exports.strictObject({ ...envelope, kind: external_exports.literal(NATIVE_SESSION_CANCEL_KIND) }).readonly();
21745
+ function decode4(schema, encoded) {
21746
+ try {
21747
+ const value = JSON.parse(encoded);
21748
+ return schema.parse(value);
21749
+ } catch (cause) {
21750
+ throw new ProtocolMessageError("Invalid native session message", { cause });
21751
+ }
21752
+ }
21753
+ var decodeNativeSessionCommand = (encoded) => decode4(nativeSessionCommandSchema, encoded);
21754
+ var encodeNativeSessionResult = (value) => JSON.stringify(nativeSessionResultSchema.parse(value));
21755
+ var decodeNativeSessionCancel = (encoded) => decode4(nativeSessionCancelSchema, encoded);
21756
+
21553
21757
  // src/run-daemon.ts
21554
21758
  import { WebSocket as WebSocket2 } from "undici";
21555
21759
 
@@ -21799,15 +22003,15 @@ var AcpActivityTracker = class {
21799
22003
  await this.finishNarrative();
21800
22004
  }
21801
22005
  const previous = this.#activities.get(key);
21802
- const identity = this.#identity(key);
22006
+ const identity2 = this.#identity(key);
21803
22007
  const activity = new AcpActivity(
21804
- identity.id,
22008
+ identity2.id,
21805
22009
  activityType,
21806
22010
  "",
21807
22011
  [],
21808
22012
  "",
21809
22013
  truncate(`${previous?.output ?? ""}${text}`, AGENT_ACTIVITY_DETAIL_LIMIT),
21810
- identity.sequence,
22014
+ identity2.sequence,
21811
22015
  "running",
21812
22016
  activityType === "reasoning" ? "Reasoning" : "Progress update",
21813
22017
  activityType === "reasoning" ? "think" : ""
@@ -21827,18 +22031,18 @@ var AcpActivityTracker = class {
21827
22031
  await this.#recordPlanEntries(key, plan.entries);
21828
22032
  return;
21829
22033
  }
21830
- const identity = this.#identity(key);
22034
+ const identity2 = this.#identity(key);
21831
22035
  const output2 = plan.type === "markdown" ? plan.content : `Plan file: ${plan.uri}`;
21832
22036
  await this.#record(
21833
22037
  key,
21834
22038
  new AcpActivity(
21835
- identity.id,
22039
+ identity2.id,
21836
22040
  "plan",
21837
22041
  "",
21838
22042
  [],
21839
22043
  "",
21840
22044
  truncate(output2, AGENT_ACTIVITY_DETAIL_LIMIT),
21841
- identity.sequence,
22045
+ identity2.sequence,
21842
22046
  "running",
21843
22047
  "Plan",
21844
22048
  ""
@@ -21871,18 +22075,18 @@ var AcpActivityTracker = class {
21871
22075
  recordToolCall = async (update) => {
21872
22076
  await this.finishNarrative();
21873
22077
  const key = `tool:${update.toolCallId}`;
21874
- const identity = this.#identity(key);
22078
+ const identity2 = this.#identity(key);
21875
22079
  const output2 = update.rawOutput === void 0 ? readContentOutput(update.content) : formatUnknown(update.rawOutput);
21876
22080
  await this.#record(
21877
22081
  key,
21878
22082
  new AcpActivity(
21879
- identity.id,
22083
+ identity2.id,
21880
22084
  "tool",
21881
22085
  update.rawInput === void 0 ? "" : formatUnknown(update.rawInput),
21882
22086
  normalizeLocations(update.locations ?? []),
21883
22087
  truncate(update.name ?? "", 128),
21884
22088
  output2,
21885
- identity.sequence,
22089
+ identity2.sequence,
21886
22090
  normalizeToolStatus(update.status ?? "pending", "pending"),
21887
22091
  normalizeTitle(update.title, "Tool call"),
21888
22092
  update.kind ?? "other",
@@ -21894,19 +22098,19 @@ var AcpActivityTracker = class {
21894
22098
  await this.finishNarrative();
21895
22099
  const key = `tool:${update.toolCallId}`;
21896
22100
  const previous = this.#activities.get(key);
21897
- const identity = this.#identity(key);
22101
+ const identity2 = this.#identity(key);
21898
22102
  const output2 = update.rawOutput !== void 0 ? formatUnknown(update.rawOutput) : update.content !== void 0 && update.content !== null ? readContentOutput(update.content) : previous?.output ?? "";
21899
22103
  const content = update.content === void 0 || update.content === null ? previous?.content ?? [] : normalizeToolContent(update.content);
21900
22104
  await this.#record(
21901
22105
  key,
21902
22106
  new AcpActivity(
21903
- identity.id,
22107
+ identity2.id,
21904
22108
  "tool",
21905
22109
  update.rawInput === void 0 ? previous?.input ?? "" : formatUnknown(update.rawInput),
21906
22110
  update.locations === void 0 || update.locations === null ? previous?.locations ?? [] : normalizeLocations(update.locations),
21907
22111
  update.name === void 0 || update.name === null ? previous?.name ?? "" : truncate(update.name, 128),
21908
22112
  output2,
21909
- identity.sequence,
22113
+ identity2.sequence,
21910
22114
  update.status === void 0 || update.status === null ? previous?.status ?? "pending" : normalizeToolStatus(update.status, previous?.status ?? "pending"),
21911
22115
  update.title === void 0 || update.title === null ? previous?.title ?? "Tool call" : normalizeTitle(update.title, "Tool call"),
21912
22116
  update.kind === void 0 || update.kind === null ? previous?.toolKind ?? "other" : update.kind,
@@ -21946,30 +22150,30 @@ var AcpActivityTracker = class {
21946
22150
  if (existing !== void 0) {
21947
22151
  return existing;
21948
22152
  }
21949
- const identity = new ActivityIdentity(
22153
+ const identity2 = new ActivityIdentity(
21950
22154
  `activity-${crypto.randomUUID()}`,
21951
22155
  this.#nextSequence
21952
22156
  );
21953
22157
  this.#nextSequence += 1;
21954
- this.#identities.set(key, identity);
21955
- return identity;
22158
+ this.#identities.set(key, identity2);
22159
+ return identity2;
21956
22160
  };
21957
22161
  #record = async (key, activity) => {
21958
22162
  this.#activities.set(key, activity);
21959
22163
  await this.#onActivity(activity);
21960
22164
  };
21961
22165
  #recordPlanEntries = async (key, entries) => {
21962
- const identity = this.#identity(key);
22166
+ const identity2 = this.#identity(key);
21963
22167
  await this.#record(
21964
22168
  key,
21965
22169
  new AcpActivity(
21966
- identity.id,
22170
+ identity2.id,
21967
22171
  "plan",
21968
22172
  "",
21969
22173
  [],
21970
22174
  "",
21971
22175
  formatPlanEntries(entries),
21972
- identity.sequence,
22176
+ identity2.sequence,
21973
22177
  planStatus(entries),
21974
22178
  "Plan",
21975
22179
  ""
@@ -21978,6 +22182,88 @@ var AcpActivityTracker = class {
21978
22182
  };
21979
22183
  };
21980
22184
 
22185
+ // src/acp/native-session.ts
22186
+ var path = external_exports.string().min(1).max(2048).refine((value) => !value.includes("\0"));
22187
+ var nativeSessionIdSchema = external_exports.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,255}$/);
22188
+ var nativeSessionInfoSchema = external_exports.object({
22189
+ sessionId: nativeSessionIdSchema,
22190
+ cwd: path,
22191
+ additionalDirectories: external_exports.array(path).max(16).default([]).readonly(),
22192
+ title: external_exports.string().max(500).nullish(),
22193
+ updatedAt: external_exports.iso.datetime({ offset: true }).nullish()
22194
+ }).readonly();
22195
+ var nativeSessionPageSchema = external_exports.object({
22196
+ sessions: external_exports.array(nativeSessionInfoSchema).max(500).readonly(),
22197
+ nextCursor: external_exports.string().min(1).max(4096).nullish()
22198
+ }).readonly();
22199
+
22200
+ // src/acp/session-history.ts
22201
+ var AcpHistoryReplay = class {
22202
+ #messages = [];
22203
+ #lastMessageId = null;
22204
+ #characters = 0;
22205
+ #imageCharacters = 0;
22206
+ #truncated = false;
22207
+ record = (update) => {
22208
+ if (update.sessionUpdate !== "user_message_chunk" && update.sessionUpdate !== "agent_message_chunk")
22209
+ return;
22210
+ if (update.sessionUpdate === "agent_message_chunk" && isProgressMessage(update))
22211
+ return;
22212
+ const role = update.sessionUpdate === "user_message_chunk" ? "user" : "assistant";
22213
+ const content = update.content;
22214
+ const parsed = parseNativeHistoryText(
22215
+ content.type === "text" ? content.text : content.type === "image" ? `[@image](data:${content.mimeType};base64,${content.data})` : `[${content.type} attachment]`
22216
+ );
22217
+ const text = parsed.text;
22218
+ if (text.length === 0 && parsed.images.length === 0) return;
22219
+ const previous = this.#messages.at(-1);
22220
+ const id = update.messageId ?? null;
22221
+ const continuation = previous?.role === role && id === this.#lastMessageId;
22222
+ const combined = continuation ? `${previous.text}${text}` : text;
22223
+ const allImages = continuation ? [...previous.images ?? [], ...parsed.images] : parsed.images;
22224
+ const images = allImages.slice(0, 8);
22225
+ this.#truncated ||= images.length < allImages.length;
22226
+ if (continuation) {
22227
+ this.#messages.pop();
22228
+ this.#characters -= previous.text.length;
22229
+ this.#imageCharacters -= (previous.images ?? []).reduce(
22230
+ (size, image) => size + image.data.length,
22231
+ 0
22232
+ );
22233
+ }
22234
+ const bounded = combined.slice(-SESSION_HISTORY_CHARACTER_LIMIT);
22235
+ this.#truncated ||= bounded.length < combined.length;
22236
+ this.#messages.push({
22237
+ role,
22238
+ text: bounded,
22239
+ ...images.length > 0 ? { images } : {}
22240
+ });
22241
+ this.#characters += bounded.length;
22242
+ this.#imageCharacters += images.reduce(
22243
+ (size, image) => size + image.data.length,
22244
+ 0
22245
+ );
22246
+ this.#lastMessageId = id;
22247
+ while (this.#messages.length > SESSION_HISTORY_PREVIEW_MESSAGE_LIMIT || this.#characters > SESSION_HISTORY_CHARACTER_LIMIT || this.#imageCharacters > SESSION_HISTORY_IMAGE_CHARACTER_LIMIT) {
22248
+ const removed = this.#messages.shift();
22249
+ if (removed !== void 0) {
22250
+ this.#characters -= removed.text.length;
22251
+ this.#imageCharacters -= (removed.images ?? []).reduce(
22252
+ (size, image) => size + image.data.length,
22253
+ 0
22254
+ );
22255
+ }
22256
+ this.#truncated = true;
22257
+ }
22258
+ };
22259
+ snapshot = () => ({
22260
+ status: "available",
22261
+ messages: [...this.#messages],
22262
+ truncated: this.#truncated,
22263
+ coveredCommandIds: []
22264
+ });
22265
+ };
22266
+
21981
22267
  // src/acp/session-store.ts
21982
22268
  import { mkdirSync as mkdirSync2 } from "node:fs";
21983
22269
  import { dirname as dirname3 } from "node:path";
@@ -22011,6 +22297,9 @@ var FoundAcpStoredSession = class extends AcpStoredSessionLookup {
22011
22297
  this.session = session;
22012
22298
  }
22013
22299
  };
22300
+ var DeletedAcpStoredSession = class extends AcpStoredSessionLookup {
22301
+ found = false;
22302
+ };
22014
22303
  function readString2(value, field) {
22015
22304
  if (typeof value !== "string" || value.length === 0) {
22016
22305
  throw new Error(`Stored ACP session has an invalid ${field}`);
@@ -22051,7 +22340,22 @@ function decodeSessionRow(row) {
22051
22340
  }
22052
22341
  var MemoryAcpSessionStore = class {
22053
22342
  #sessions = /* @__PURE__ */ new Map();
22343
+ #deleted = /* @__PURE__ */ new Set();
22344
+ findNative = (agentId, nativeSessionId) => [...this.#sessions.values()].filter(
22345
+ (session) => session.agentId === agentId && session.nativeSessionId === nativeSessionId
22346
+ );
22347
+ markDeleted = (agentId, nativeSessionId) => {
22348
+ const ids = this.findNative(agentId, nativeSessionId).map(
22349
+ (session) => session.sessionId
22350
+ );
22351
+ for (const id of ids) {
22352
+ this.#sessions.delete(id);
22353
+ this.#deleted.add(id);
22354
+ }
22355
+ return ids;
22356
+ };
22054
22357
  read = (sessionId) => {
22358
+ if (this.#deleted.has(sessionId)) return new DeletedAcpStoredSession();
22055
22359
  const session = this.#sessions.get(sessionId);
22056
22360
  return session === void 0 ? new MissingAcpStoredSession() : new FoundAcpStoredSession(session);
22057
22361
  };
@@ -22064,10 +22368,11 @@ var MemoryAcpSessionStore = class {
22064
22368
  };
22065
22369
  var SqliteAcpSessionStore = class {
22066
22370
  #database;
22067
- constructor(path) {
22068
- mkdirSync2(dirname3(path), { recursive: true });
22069
- this.#database = new DatabaseSync2(path);
22371
+ constructor(path2) {
22372
+ mkdirSync2(dirname3(path2), { recursive: true });
22373
+ this.#database = new DatabaseSync2(path2);
22070
22374
  this.#database.exec(`
22375
+ CREATE TABLE IF NOT EXISTS acp_deleted_sessions (session_id TEXT PRIMARY KEY) STRICT;
22071
22376
  CREATE TABLE IF NOT EXISTS acp_sessions (
22072
22377
  session_id TEXT PRIMARY KEY,
22073
22378
  agent_id TEXT NOT NULL,
@@ -22090,6 +22395,10 @@ var SqliteAcpSessionStore = class {
22090
22395
  }
22091
22396
  }
22092
22397
  read = (sessionId) => {
22398
+ if (this.#database.prepare(
22399
+ "SELECT session_id FROM acp_deleted_sessions WHERE session_id = ?"
22400
+ ).get(sessionId) !== void 0)
22401
+ return new DeletedAcpStoredSession();
22093
22402
  const row = this.#database.prepare(
22094
22403
  `
22095
22404
  SELECT
@@ -22108,6 +22417,28 @@ var SqliteAcpSessionStore = class {
22108
22417
  remove = (sessionId) => {
22109
22418
  this.#database.prepare("DELETE FROM acp_sessions WHERE session_id = ?").run(sessionId);
22110
22419
  };
22420
+ findNative = (agentId, nativeSessionId) => this.#database.prepare(
22421
+ "SELECT * FROM acp_sessions WHERE agent_id = ? AND native_session_id = ?"
22422
+ ).all(agentId, nativeSessionId).map(decodeSessionRow);
22423
+ markDeleted = (agentId, nativeSessionId) => {
22424
+ const ids = this.findNative(agentId, nativeSessionId).map(
22425
+ (session) => session.sessionId
22426
+ );
22427
+ this.#database.exec("BEGIN");
22428
+ try {
22429
+ for (const id of ids) {
22430
+ this.#database.prepare(
22431
+ "INSERT OR IGNORE INTO acp_deleted_sessions (session_id) VALUES (?)"
22432
+ ).run(id);
22433
+ this.remove(id);
22434
+ }
22435
+ this.#database.exec("COMMIT");
22436
+ return ids;
22437
+ } catch (error61) {
22438
+ this.#database.exec("ROLLBACK");
22439
+ throw error61;
22440
+ }
22441
+ };
22111
22442
  write = (session) => {
22112
22443
  this.#database.prepare(
22113
22444
  `
@@ -22237,7 +22568,7 @@ function createWritableAcpStream(output2) {
22237
22568
  if (outputFailure !== void 0) {
22238
22569
  return Promise.reject(outputFailure);
22239
22570
  }
22240
- return new Promise((resolve6, reject) => {
22571
+ return new Promise((resolve5, reject) => {
22241
22572
  pendingWriteFailures.add(reject);
22242
22573
  output2.write(chunk, (error61) => {
22243
22574
  pendingWriteFailures.delete(reject);
@@ -22250,7 +22581,7 @@ function createWritableAcpStream(output2) {
22250
22581
  reject(outputFailure);
22251
22582
  return;
22252
22583
  }
22253
- resolve6();
22584
+ resolve5();
22254
22585
  });
22255
22586
  });
22256
22587
  }
@@ -22411,7 +22742,8 @@ function normalizeCapabilities(capabilities) {
22411
22742
  imagePrompt: prompt?.image === true,
22412
22743
  listSessions: session?.list != null,
22413
22744
  loadSession: capabilities.loadSession === true,
22414
- resumeSession: session?.resume != null
22745
+ resumeSession: session?.resume != null,
22746
+ unstableForkSession: session?.fork != null
22415
22747
  };
22416
22748
  }
22417
22749
  function elicitationFieldType(value) {
@@ -22759,15 +23091,18 @@ var AcpProcessSession = class _AcpProcessSession {
22759
23091
  imagePrompt: false,
22760
23092
  listSessions: false,
22761
23093
  loadSession: false,
22762
- resumeSession: false
23094
+ resumeSession: false,
23095
+ unstableForkSession: false
22763
23096
  };
22764
23097
  #commands = [];
22765
23098
  #configOptions = [];
22766
23099
  #nativeSessionId = "";
22767
23100
  #prompted = false;
22768
- #replaying = false;
23101
+ #replay = {
23102
+ kind: "idle"
23103
+ };
22769
23104
  #restored = false;
22770
- #sessionTitle = "";
23105
+ #sessionTitle = null;
22771
23106
  #sessionUpdatedAt = "";
22772
23107
  #turn = new IdleAcpTurn();
22773
23108
  #updateTail = Promise.resolve();
@@ -22796,9 +23131,9 @@ var AcpProcessSession = class _AcpProcessSession {
22796
23131
  stderr.on("data", (chunk) => {
22797
23132
  process.stderr.write(`[${agent.id}-acp] ${chunk}`);
22798
23133
  });
22799
- this.#processExit = new Promise((resolve6) => {
22800
- this.#child.once("error", resolve6);
22801
- this.#child.once("exit", resolve6);
23134
+ this.#processExit = new Promise((resolve5) => {
23135
+ this.#child.once("error", resolve5);
23136
+ this.#child.once("exit", resolve5);
22802
23137
  });
22803
23138
  const stream = ndJsonStream2(
22804
23139
  createWritableAcpStream(stdin),
@@ -22829,10 +23164,11 @@ var AcpProcessSession = class _AcpProcessSession {
22829
23164
  store
22830
23165
  );
22831
23166
  try {
22832
- await runtime.initialize(options.signal);
23167
+ await runtime.initialize(options.signal, options.origin);
22833
23168
  return runtime;
22834
23169
  } catch (error61) {
22835
23170
  await runtime.closeProcess();
23171
+ options.signal.throwIfAborted();
22836
23172
  if (error61 instanceof AcpExecutionError) {
22837
23173
  throw error61;
22838
23174
  }
@@ -22851,7 +23187,33 @@ var AcpProcessSession = class _AcpProcessSession {
22851
23187
  this.#configOptions,
22852
23188
  this.#restored
22853
23189
  );
22854
- initialize = async (signal) => {
23190
+ loadHistory = async (signal) => {
23191
+ if (!this.#capabilities.loadSession) return { status: "unavailable" };
23192
+ const history = new AcpHistoryReplay();
23193
+ this.#replay = { kind: "capture", history };
23194
+ try {
23195
+ await this.race(
23196
+ this.#connection.agent.request(
23197
+ methods3.agent.session.load,
23198
+ {
23199
+ additionalDirectories: [...this.#additionalDirectories],
23200
+ cwd: this.#cwd,
23201
+ mcpServers: [],
23202
+ sessionId: this.#nativeSessionId
23203
+ },
23204
+ { cancellationSignal: signal }
23205
+ ),
23206
+ "ACP agent exited while loading history",
23207
+ signal
23208
+ );
23209
+ await this.#updateTail;
23210
+ signal.throwIfAborted();
23211
+ return history.snapshot();
23212
+ } finally {
23213
+ this.#replay = { kind: "idle" };
23214
+ }
23215
+ };
23216
+ initialize = async (signal, origin) => {
22855
23217
  const initialized = await this.race(
22856
23218
  this.#connection.agent.request(
22857
23219
  methods3.agent.initialize,
@@ -22883,9 +23245,56 @@ var AcpProcessSession = class _AcpProcessSession {
22883
23245
  255
22884
23246
  );
22885
23247
  this.#agentVersion = truncate2(initialized.agentInfo?.version ?? "", 128);
22886
- const stored = this.#store.read(this.#mobiusSessionId);
23248
+ const stored = origin?.kind === "import" ? new FoundAcpStoredSession(
23249
+ new AcpStoredSession(
23250
+ this.#mobiusSessionId,
23251
+ this.#agentId,
23252
+ nativeSessionIdSchema.parse(origin.nativeSessionId),
23253
+ this.#cwd,
23254
+ this.#additionalDirectories,
23255
+ true
23256
+ )
23257
+ ) : this.#store.read(this.#mobiusSessionId);
23258
+ if (stored instanceof DeletedAcpStoredSession)
23259
+ throw new AcpExecutionError(
23260
+ "The native Agent session was deleted. Start a new chat.",
23261
+ {},
23262
+ "native_session_deleted"
23263
+ );
22887
23264
  let lifecycleResponse;
22888
- if (stored instanceof FoundAcpStoredSession && stored.session.agentId === this.#agentId && stored.session.cwd === this.#cwd && arraysEqual(
23265
+ if (origin?.kind === "fork") {
23266
+ if (!this.#capabilities.unstableForkSession)
23267
+ throw new AcpExecutionError(
23268
+ "This Agent does not support session forking",
23269
+ {},
23270
+ "native_session_unsupported"
23271
+ );
23272
+ const forked = await this.race(
23273
+ this.#connection.agent.request(
23274
+ methods3.agent.session.fork,
23275
+ {
23276
+ sessionId: nativeSessionIdSchema.parse(origin.nativeSessionId),
23277
+ cwd: this.#cwd,
23278
+ additionalDirectories: [...this.#additionalDirectories],
23279
+ mcpServers: []
23280
+ },
23281
+ { cancellationSignal: signal }
23282
+ ),
23283
+ "ACP agent exited while forking a session",
23284
+ signal
23285
+ );
23286
+ this.#nativeSessionId = nativeSessionIdSchema.parse(forked.sessionId);
23287
+ if (this.#nativeSessionId === origin.nativeSessionId || this.#store.findNative(this.#agentId, this.#nativeSessionId).length > 0) {
23288
+ throw new AcpExecutionError(
23289
+ "The Agent did not create an independent fork",
23290
+ {},
23291
+ "invalid_native_session_response"
23292
+ );
23293
+ }
23294
+ this.#prompted = true;
23295
+ this.#restored = true;
23296
+ lifecycleResponse = forked;
23297
+ } else if (stored instanceof FoundAcpStoredSession && stored.session.agentId === this.#agentId && stored.session.cwd === this.#cwd && arraysEqual(
22889
23298
  stored.session.additionalDirectories,
22890
23299
  this.#additionalDirectories
22891
23300
  )) {
@@ -22908,7 +23317,7 @@ var AcpProcessSession = class _AcpProcessSession {
22908
23317
  signal
22909
23318
  );
22910
23319
  } else if (this.#capabilities.loadSession) {
22911
- this.#replaying = true;
23320
+ this.#replay = { kind: "discard" };
22912
23321
  try {
22913
23322
  lifecycleResponse = await this.race(
22914
23323
  this.#connection.agent.request(
@@ -22925,7 +23334,8 @@ var AcpProcessSession = class _AcpProcessSession {
22925
23334
  signal
22926
23335
  );
22927
23336
  } finally {
22928
- this.#replaying = false;
23337
+ await this.#updateTail;
23338
+ this.#replay = { kind: "idle" };
22929
23339
  }
22930
23340
  } else {
22931
23341
  throw new AcpExecutionError(
@@ -22978,7 +23388,60 @@ var AcpProcessSession = class _AcpProcessSession {
22978
23388
  );
22979
23389
  await this.#updateTail;
22980
23390
  signal.throwIfAborted();
23391
+ this.#store.write(
23392
+ new AcpStoredSession(
23393
+ this.#mobiusSessionId,
23394
+ this.#agentId,
23395
+ this.#nativeSessionId,
23396
+ this.#cwd,
23397
+ this.#additionalDirectories,
23398
+ this.#prompted
23399
+ )
23400
+ );
23401
+ };
23402
+ listNativeSessions = async (cursor, signal) => {
23403
+ if (!this.#capabilities.listSessions)
23404
+ throw new AcpExecutionError(
23405
+ "This Agent does not support native session listing",
23406
+ {},
23407
+ "native_session_unsupported"
23408
+ );
23409
+ const response = await this.race(
23410
+ this.#connection.agent.request(
23411
+ methods3.agent.session.list,
23412
+ cursor === null ? {} : { cursor },
23413
+ { cancellationSignal: signal }
23414
+ ),
23415
+ "ACP agent exited while listing sessions",
23416
+ signal
23417
+ );
23418
+ const decoded = nativeSessionPageSchema.safeParse(response);
23419
+ if (!decoded.success)
23420
+ throw new AcpExecutionError(
23421
+ "The Agent returned an invalid native session list",
23422
+ { cause: decoded.error },
23423
+ "invalid_native_session_response"
23424
+ );
23425
+ return decoded.data;
23426
+ };
23427
+ deleteNativeSession = async (nativeSessionId, signal) => {
23428
+ if (!this.#capabilities.deleteSession || !this.#capabilities.listSessions)
23429
+ throw new AcpExecutionError(
23430
+ "This Agent does not support native session deletion",
23431
+ {},
23432
+ "native_session_unsupported"
23433
+ );
23434
+ await this.race(
23435
+ this.#connection.agent.request(
23436
+ methods3.agent.session.delete,
23437
+ { sessionId: nativeSessionIdSchema.parse(nativeSessionId) },
23438
+ { cancellationSignal: signal }
23439
+ ),
23440
+ "ACP agent exited while deleting a session",
23441
+ signal
23442
+ );
22981
23443
  };
23444
+ nativeSessionId = () => this.#nativeSessionId;
22982
23445
  configure = async (options) => {
22983
23446
  const existing = this.#configOptions.find(
22984
23447
  (option) => option.id === options.configId
@@ -23141,7 +23604,28 @@ var AcpProcessSession = class _AcpProcessSession {
23141
23604
  );
23142
23605
  await this.#updateTail;
23143
23606
  await running.activities.finish();
23144
- return { stopReason: response.stopReason };
23607
+ let result;
23608
+ try {
23609
+ result = decodeAgentPromptResponse(response);
23610
+ } catch (cause) {
23611
+ throw new AcpExecutionError(
23612
+ "ACP agent returned an invalid prompt response",
23613
+ { cause },
23614
+ "acp_invalid_prompt_response"
23615
+ );
23616
+ }
23617
+ if (result.usage != null || result._meta?.quota !== void 0) {
23618
+ await this.#getObserver().onUsage({
23619
+ _meta: result._meta ?? null,
23620
+ commandId: options.commandId,
23621
+ kind: SESSION_USAGE_UPDATED_KIND,
23622
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
23623
+ sessionId: this.#mobiusSessionId,
23624
+ source: "prompt",
23625
+ usage: result.usage ?? null
23626
+ });
23627
+ }
23628
+ return result;
23145
23629
  } catch (error61) {
23146
23630
  await running.activities.finish();
23147
23631
  if (error61 instanceof AcpExecutionError) {
@@ -23361,7 +23845,8 @@ var AcpProcessSession = class _AcpProcessSession {
23361
23845
  processUpdate = async (notification) => {
23362
23846
  this.assertNativeSession(notification.sessionId);
23363
23847
  const update = notification.update;
23364
- if (this.#replaying && (update.sessionUpdate === "user_message_chunk" || update.sessionUpdate === "agent_message_chunk" || update.sessionUpdate === "agent_thought_chunk" || update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update" || update.sessionUpdate === "plan" || update.sessionUpdate === "plan_update" || update.sessionUpdate === "plan_removed")) {
23848
+ if (this.#replay.kind === "capture") this.#replay.history.record(update);
23849
+ if (this.#replay.kind !== "idle" && (update.sessionUpdate === "user_message_chunk" || update.sessionUpdate === "agent_message_chunk" || update.sessionUpdate === "agent_thought_chunk" || update.sessionUpdate === "tool_call" || update.sessionUpdate === "tool_call_update" || update.sessionUpdate === "plan" || update.sessionUpdate === "plan_update" || update.sessionUpdate === "plan_removed")) {
23365
23850
  return;
23366
23851
  }
23367
23852
  if (update.sessionUpdate === "agent_message_chunk") {
@@ -23475,6 +23960,7 @@ var AcpProcessSession = class _AcpProcessSession {
23475
23960
  protocolVersion: MOBIUS_PROTOCOL_VERSION,
23476
23961
  sessionId: this.#mobiusSessionId,
23477
23962
  size: Math.max(0, Math.trunc(update.size)),
23963
+ source: "context",
23478
23964
  used: Math.max(0, Math.trunc(update.used))
23479
23965
  });
23480
23966
  }
@@ -23596,6 +24082,7 @@ var AcpProcessSession = class _AcpProcessSession {
23596
24082
  };
23597
24083
  };
23598
24084
  var AcpSessionManager = class {
24085
+ #nativeOperations = /* @__PURE__ */ new Set();
23599
24086
  #activeSessionIds = /* @__PURE__ */ new Set();
23600
24087
  #cancellationRequests = /* @__PURE__ */ new Set();
23601
24088
  #preparing = /* @__PURE__ */ new Map();
@@ -23647,6 +24134,21 @@ var AcpSessionManager = class {
23647
24134
  return await operation;
23648
24135
  }
23649
24136
  prepareSession = async (options) => {
24137
+ const stored = this.#store.read(options.sessionId);
24138
+ if (stored instanceof DeletedAcpStoredSession)
24139
+ throw new AcpExecutionError(
24140
+ "The native Agent session was deleted. Start a new chat.",
24141
+ {},
24142
+ "native_session_deleted"
24143
+ );
24144
+ if (stored instanceof FoundAcpStoredSession && this.#nativeOperations.has(
24145
+ `${stored.session.agentId}:${stored.session.nativeSessionId}`
24146
+ ))
24147
+ throw new AcpExecutionError(
24148
+ "The native session is being managed",
24149
+ {},
24150
+ "session_busy"
24151
+ );
23650
24152
  const pending = this.#preparing.get(options.sessionId);
23651
24153
  if (pending !== void 0) {
23652
24154
  const runtime2 = await pending;
@@ -23693,6 +24195,16 @@ var AcpSessionManager = class {
23693
24195
  }
23694
24196
  };
23695
24197
  runPrompt = async (options) => {
24198
+ const stored = this.#store.read(options.sessionId);
24199
+ if (stored instanceof FoundAcpStoredSession && this.#nativeOperations.has(
24200
+ `${stored.session.agentId}:${stored.session.nativeSessionId}`
24201
+ )) {
24202
+ throw new AcpExecutionError(
24203
+ "The native session is being managed",
24204
+ {},
24205
+ "session_busy"
24206
+ );
24207
+ }
23696
24208
  if (this.#activeSessionIds.has(options.sessionId)) {
23697
24209
  throw new AcpExecutionError(
23698
24210
  "Another prompt is already running in this conversation",
@@ -23706,6 +24218,14 @@ var AcpSessionManager = class {
23706
24218
  return { stopReason: "cancelled" };
23707
24219
  }
23708
24220
  const runtime = await this.ensureSession(options);
24221
+ if (this.#nativeOperations.has(
24222
+ `${options.agent.id}:${runtime.nativeSessionId()}`
24223
+ ))
24224
+ throw new AcpExecutionError(
24225
+ "The native session is being managed",
24226
+ {},
24227
+ "session_busy"
24228
+ );
23709
24229
  if (this.#cancellationRequests.has(options.sessionId)) {
23710
24230
  return { stopReason: "cancelled" };
23711
24231
  }
@@ -23777,6 +24297,124 @@ var AcpSessionManager = class {
23777
24297
  }
23778
24298
  return started;
23779
24299
  };
24300
+ listNativeSessions = async (options, cursor) => await (await this.ensureSession(options)).listNativeSessions(
24301
+ cursor,
24302
+ options.signal
24303
+ );
24304
+ openNativeSession = async (options, origin) => {
24305
+ const key = `${options.agent.id}:${origin.nativeSessionId}`;
24306
+ if (this.#nativeOperations.has(key))
24307
+ throw new AcpExecutionError(
24308
+ "The native session is being managed",
24309
+ {},
24310
+ "session_busy"
24311
+ );
24312
+ const linked = this.#store.findNative(
24313
+ options.agent.id,
24314
+ origin.nativeSessionId
24315
+ );
24316
+ if (linked.some(
24317
+ (entry) => this.#activeSessionIds.has(entry.sessionId) || this.#preparing.has(entry.sessionId)
24318
+ ))
24319
+ throw new AcpExecutionError(
24320
+ "Wait for the native session's running operation to finish",
24321
+ {},
24322
+ "session_busy"
24323
+ );
24324
+ const existing = linked[0];
24325
+ if (origin.kind === "import" && existing !== void 0) {
24326
+ const runtime = await this.ensureSession({
24327
+ ...options,
24328
+ sessionId: existing.sessionId
24329
+ });
24330
+ if (this.#activeSessionIds.has(existing.sessionId) || this.#nativeOperations.has(key))
24331
+ throw new AcpExecutionError(
24332
+ "Wait for the native session's running operation to finish",
24333
+ {},
24334
+ "session_busy"
24335
+ );
24336
+ this.#nativeOperations.add(key);
24337
+ try {
24338
+ const history = await runtime.loadHistory(options.signal);
24339
+ return {
24340
+ prepared: runtime.prepared(),
24341
+ sessionId: existing.sessionId,
24342
+ history
24343
+ };
24344
+ } catch (error61) {
24345
+ this.#sessions.delete(existing.sessionId);
24346
+ await runtime.closeProcess();
24347
+ throw error61;
24348
+ } finally {
24349
+ this.#nativeOperations.delete(key);
24350
+ }
24351
+ }
24352
+ if (this.#sessions.has(options.sessionId) || this.#preparing.has(options.sessionId) || this.#store.read(options.sessionId) instanceof FoundAcpStoredSession || this.#store.read(options.sessionId) instanceof DeletedAcpStoredSession)
24353
+ throw new AcpExecutionError(
24354
+ "The destination conversation already exists",
24355
+ {},
24356
+ "session_identity_mismatch"
24357
+ );
24358
+ this.#nativeOperations.add(key);
24359
+ try {
24360
+ const runtime = await this.#startPreparation({ ...options, origin });
24361
+ const openedKey = `${options.agent.id}:${runtime.nativeSessionId()}`;
24362
+ this.#nativeOperations.add(openedKey);
24363
+ try {
24364
+ const history = await runtime.loadHistory(options.signal);
24365
+ return {
24366
+ sessionId: options.sessionId,
24367
+ prepared: runtime.prepared(),
24368
+ history
24369
+ };
24370
+ } catch (error61) {
24371
+ this.#sessions.delete(options.sessionId);
24372
+ this.#store.remove(options.sessionId);
24373
+ await runtime.closeProcess();
24374
+ throw error61;
24375
+ } finally {
24376
+ this.#nativeOperations.delete(openedKey);
24377
+ }
24378
+ } finally {
24379
+ this.#nativeOperations.delete(key);
24380
+ }
24381
+ };
24382
+ deleteNativeSession = async (options, nativeSessionId) => {
24383
+ const runtime = await this.ensureSession(options);
24384
+ const key = `${options.agent.id}:${nativeSessionId}`;
24385
+ const linked = this.#store.findNative(options.agent.id, nativeSessionId);
24386
+ if (this.#nativeOperations.has(key) || linked.some(
24387
+ (entry) => this.#activeSessionIds.has(entry.sessionId) || this.#preparing.has(entry.sessionId)
24388
+ ))
24389
+ throw new AcpExecutionError(
24390
+ "Wait for the native session's running operation to finish",
24391
+ {},
24392
+ "session_busy"
24393
+ );
24394
+ this.#nativeOperations.add(key);
24395
+ try {
24396
+ for (const entry of linked) {
24397
+ const active = this.#sessions.get(entry.sessionId);
24398
+ if (active !== void 0 && active !== runtime) {
24399
+ this.#sessions.delete(entry.sessionId);
24400
+ await active.close();
24401
+ }
24402
+ }
24403
+ options.signal.throwIfAborted();
24404
+ await runtime.deleteNativeSession(nativeSessionId, options.signal);
24405
+ const deleted = this.#store.markDeleted(
24406
+ options.agent.id,
24407
+ nativeSessionId
24408
+ );
24409
+ if (runtime.nativeSessionId() === nativeSessionId) {
24410
+ this.#sessions.delete(options.sessionId);
24411
+ await runtime.close();
24412
+ }
24413
+ return deleted;
24414
+ } finally {
24415
+ this.#nativeOperations.delete(key);
24416
+ }
24417
+ };
23780
24418
  };
23781
24419
 
23782
24420
  // src/agent-catalog/publish-agent-catalog.ts
@@ -24224,7 +24862,7 @@ function stableFailureCode(error61) {
24224
24862
  function discoveryOptions(options, environment, signal, specifications) {
24225
24863
  return {
24226
24864
  ...options.architecture === void 0 ? {} : { architecture: options.architecture },
24227
- approvedRoot: options.approvedRoot,
24865
+ defaultWorkspace: options.defaultWorkspace,
24228
24866
  environment,
24229
24867
  ...options.platform === void 0 ? {} : { platform: options.platform },
24230
24868
  signal,
@@ -24380,11 +25018,11 @@ var AutomaticAdapterProvisioner = class {
24380
25018
 
24381
25019
  // src/agent-installation/fixed-installation-executor.ts
24382
25020
  import { realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
24383
- import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5 } from "node:path";
25021
+ import { resolve as resolve4 } from "node:path";
24384
25022
 
24385
25023
  // src/agent-installation/fixed-agent-install-recipes.ts
24386
25024
  import { mkdir as mkdir3, mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
24387
- import { join as join7 } from "node:path";
25025
+ import { join as join8 } from "node:path";
24388
25026
 
24389
25027
  // src/agent-installation/verified-download.ts
24390
25028
  import { createHash } from "node:crypto";
@@ -24591,8 +25229,8 @@ var FixedAgentInstallRecipe = class {
24591
25229
  );
24592
25230
  }
24593
25231
  };
24594
- requireInstalledExecutable = async (path) => {
24595
- const executable = await resolveExecutablePath(path);
25232
+ requireInstalledExecutable = async (path2) => {
25233
+ const executable = await resolveExecutablePath(path2);
24596
25234
  if (executable === null) {
24597
25235
  throw new InstallationManagerError(
24598
25236
  "agent_verification_failed",
@@ -24608,18 +25246,18 @@ var CodexInstallRecipe = class extends FixedAgentInstallRecipe {
24608
25246
  releaseChannel = "latest";
24609
25247
  install = async (options) => {
24610
25248
  this.assertSupportedPlatform();
24611
- const root = join7(options.stateDirectory, "agents", this.id);
24612
- const temporaryRoot = join7(options.stateDirectory, "temporary");
25249
+ const root = join8(options.stateDirectory, "agents", this.id);
25250
+ const temporaryRoot = join8(options.stateDirectory, "temporary");
24613
25251
  await Promise.all([
24614
25252
  mkdir3(root, { mode: 448, recursive: true }),
24615
25253
  mkdir3(temporaryRoot, { mode: 448, recursive: true })
24616
25254
  ]);
24617
25255
  const temporaryDirectory = await mkdtemp2(
24618
- join7(temporaryRoot, ".codex-install-")
25256
+ join8(temporaryRoot, ".codex-install-")
24619
25257
  );
24620
- const installerPath = join7(temporaryDirectory, "install.sh");
24621
- const installDirectory = join7(root, "bin");
24622
- const codexHome = join7(root, "home");
25258
+ const installerPath = join8(temporaryDirectory, "install.sh");
25259
+ const installDirectory = join8(root, "bin");
25260
+ const codexHome = join8(root, "home");
24623
25261
  try {
24624
25262
  await Promise.all([
24625
25263
  mkdir3(installDirectory, { mode: 448, recursive: true }),
@@ -24663,7 +25301,7 @@ var CodexInstallRecipe = class extends FixedAgentInstallRecipe {
24663
25301
  timeoutMilliseconds: AGENT_INSTALL_TIMEOUT_MILLISECONDS
24664
25302
  });
24665
25303
  return await this.requireInstalledExecutable(
24666
- join7(installDirectory, "codex")
25304
+ join8(installDirectory, "codex")
24667
25305
  );
24668
25306
  } catch (error61) {
24669
25307
  if (options.signal.aborted) {
@@ -24687,17 +25325,17 @@ var ClaudeCodeInstallRecipe = class extends FixedAgentInstallRecipe {
24687
25325
  releaseChannel = "stable";
24688
25326
  install = async (options) => {
24689
25327
  this.assertSupportedPlatform();
24690
- const root = join7(options.stateDirectory, "agents", this.id);
24691
- const managedHome = join7(root, "home");
24692
- const temporaryRoot = join7(options.stateDirectory, "temporary");
25328
+ const root = join8(options.stateDirectory, "agents", this.id);
25329
+ const managedHome = join8(root, "home");
25330
+ const temporaryRoot = join8(options.stateDirectory, "temporary");
24693
25331
  await Promise.all([
24694
25332
  mkdir3(managedHome, { mode: 448, recursive: true }),
24695
25333
  mkdir3(temporaryRoot, { mode: 448, recursive: true })
24696
25334
  ]);
24697
25335
  const temporaryDirectory = await mkdtemp2(
24698
- join7(temporaryRoot, ".claude-install-")
25336
+ join8(temporaryRoot, ".claude-install-")
24699
25337
  );
24700
- const installerPath = join7(temporaryDirectory, "install.sh");
25338
+ const installerPath = join8(temporaryDirectory, "install.sh");
24701
25339
  try {
24702
25340
  await options.report("downloading");
24703
25341
  const download = await downloadVerifiedFile({
@@ -24734,7 +25372,7 @@ var ClaudeCodeInstallRecipe = class extends FixedAgentInstallRecipe {
24734
25372
  timeoutMilliseconds: AGENT_INSTALL_TIMEOUT_MILLISECONDS
24735
25373
  });
24736
25374
  return await this.requireInstalledExecutable(
24737
- join7(managedHome, ".local", "bin", "claude")
25375
+ join8(managedHome, ".local", "bin", "claude")
24738
25376
  );
24739
25377
  } catch (error61) {
24740
25378
  if (options.signal.aborted) {
@@ -24782,28 +25420,24 @@ function findRecipe(recipes, agentId) {
24782
25420
  "The requested Agent does not have an approved installation recipe"
24783
25421
  );
24784
25422
  }
24785
- function isInside2(root, candidate) {
24786
- const difference = relative4(resolve5(root), resolve5(candidate));
24787
- return difference.length === 0 || !difference.startsWith("..") && !isAbsolute3(difference);
24788
- }
24789
- function resolveApprovedRoot2(path) {
25423
+ function resolveDefaultWorkspace2(path2) {
24790
25424
  try {
24791
- const directory = realpathSync2(resolve5(path));
25425
+ const directory = realpathSync2(resolve4(path2));
24792
25426
  if (!statSync2(directory).isDirectory()) {
24793
- throw new Error("Approved root is not a directory");
25427
+ throw new Error("Default workspace is not a directory");
24794
25428
  }
24795
25429
  return directory;
24796
25430
  } catch (error61) {
24797
25431
  throw new InstallationManagerError(
24798
25432
  "unsafe_installation_root",
24799
- "The approved workspace is unavailable or unsafe",
25433
+ "The default workspace is unavailable or unsafe",
24800
25434
  { cause: error61 }
24801
25435
  );
24802
25436
  }
24803
25437
  }
24804
- function resolveUserInstallationDirectory(path) {
25438
+ function resolveUserInstallationDirectory(path2) {
24805
25439
  try {
24806
- const directory = realpathSync2(resolve5(path));
25440
+ const directory = realpathSync2(resolve4(path2));
24807
25441
  const metadata = statSync2(directory);
24808
25442
  if (!metadata.isDirectory() || typeof process.getuid === "function" && metadata.uid !== process.getuid()) {
24809
25443
  throw new Error("Installation directory is not user-owned");
@@ -24840,7 +25474,7 @@ function requiresAdapterReplacement(code) {
24840
25474
  }
24841
25475
  var FixedInstallationExecutor = class {
24842
25476
  #adapterInstaller;
24843
- #approvedRoot;
25477
+ #defaultWorkspace;
24844
25478
  #audit;
24845
25479
  #catalog;
24846
25480
  #environment;
@@ -24849,17 +25483,11 @@ var FixedInstallationExecutor = class {
24849
25483
  #specifications;
24850
25484
  #stateDirectory;
24851
25485
  constructor(options, specifications = supportedLocalAgentSpecifications(), recipes = fixedAgentInstallRecipes(), adapterInstaller) {
24852
- const approvedRoot = resolveApprovedRoot2(options.approvedRoot);
25486
+ const defaultWorkspace = resolveDefaultWorkspace2(options.defaultWorkspace);
24853
25487
  const stateDirectory = resolveUserInstallationDirectory(
24854
25488
  options.stateDirectory
24855
25489
  );
24856
- if (isInside2(approvedRoot, stateDirectory)) {
24857
- throw new InstallationManagerError(
24858
- "unsafe_installation_root",
24859
- "The Mobius state directory cannot be inside the approved workspace"
24860
- );
24861
- }
24862
- this.#approvedRoot = approvedRoot;
25490
+ this.#defaultWorkspace = defaultWorkspace;
24863
25491
  this.#audit = options.audit;
24864
25492
  this.#catalog = options.catalog;
24865
25493
  this.#environment = Object.freeze({ ...options.environment });
@@ -24868,7 +25496,6 @@ var FixedInstallationExecutor = class {
24868
25496
  this.#specifications = Object.freeze([...specifications]);
24869
25497
  this.#stateDirectory = stateDirectory;
24870
25498
  this.#adapterInstaller = adapterInstaller ?? new ManagedAdapterInstaller({
24871
- approvedRoot,
24872
25499
  environment: this.#environment,
24873
25500
  stateDirectory
24874
25501
  });
@@ -24887,7 +25514,7 @@ var FixedInstallationExecutor = class {
24887
25514
  }
24888
25515
  );
24889
25516
  const discoveryOptions2 = {
24890
- approvedRoot: this.#approvedRoot,
25517
+ defaultWorkspace: this.#defaultWorkspace,
24891
25518
  environment: this.#environment,
24892
25519
  signal
24893
25520
  };
@@ -24922,7 +25549,7 @@ var FixedInstallationExecutor = class {
24922
25549
  environment[specification.agentOverrideVariable] = agent.path;
24923
25550
  environment[specification.adapterOverrideVariable] = adapter.path;
24924
25551
  const inventory = await discoverLocalAgentInventory({
24925
- approvedRoot: this.#approvedRoot,
25552
+ defaultWorkspace: this.#defaultWorkspace,
24926
25553
  environment,
24927
25554
  signal,
24928
25555
  specifications: this.#specifications
@@ -25034,7 +25661,7 @@ Mobius Web requested installation of ${label} (${requestId}). Allow this fixed u
25034
25661
  };
25035
25662
  };
25036
25663
 
25037
- // src/authorization/authorize-binding.ts
25664
+ // src/authorization/local-authorization-error.ts
25038
25665
  var LocalAuthorizationError = class extends Error {
25039
25666
  code = "local_authorization_failed";
25040
25667
  constructor(message, options = {}) {
@@ -25044,18 +25671,16 @@ var LocalAuthorizationError = class extends Error {
25044
25671
  };
25045
25672
 
25046
25673
  // src/authorization/workspace-directory.ts
25047
- import { readdir, realpath as realpath5, stat as stat4 } from "node:fs/promises";
25674
+ import { readdir, realpath as realpath4, stat as stat4 } from "node:fs/promises";
25675
+ import { homedir as homedir2 } from "node:os";
25048
25676
  import {
25049
25677
  basename as basename2,
25050
25678
  dirname as dirname4,
25051
- isAbsolute as isAbsolute4,
25052
- join as join8,
25679
+ isAbsolute as isAbsolute2,
25680
+ join as join9,
25053
25681
  normalize,
25054
- relative as relative5
25682
+ relative as relative2
25055
25683
  } from "node:path";
25056
- var ROOT_DIRECTORY_ID = "root";
25057
- var DIRECTORY_ID_PREFIX = "dir-";
25058
- var DIRECTORY_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
25059
25684
  var WorkspaceDirectoryListing = class {
25060
25685
  directoryId;
25061
25686
  directoryLabel;
@@ -25068,150 +25693,690 @@ var WorkspaceDirectoryListing = class {
25068
25693
  this.parentDirectoryId = parentDirectoryId;
25069
25694
  }
25070
25695
  };
25071
- function directoryId(relativePath) {
25072
- if (relativePath.length === 0) {
25073
- return ROOT_DIRECTORY_ID;
25696
+ function encodePath(path2) {
25697
+ const encoded = Buffer.from(path2, "utf8").toString("base64url");
25698
+ if (encoded.length === 0 || encoded.length > 2e3) {
25699
+ throw new LocalAuthorizationError(
25700
+ "The local directory path is too long to select"
25701
+ );
25074
25702
  }
25075
- return `${DIRECTORY_ID_PREFIX}${Buffer.from(relativePath, "utf8").toString("base64url")}`;
25703
+ return `path-${encoded}`;
25076
25704
  }
25077
- function decodeDirectoryId(value) {
25078
- if (value === ROOT_DIRECTORY_ID) {
25079
- return "";
25080
- }
25081
- if (!value.startsWith(DIRECTORY_ID_PREFIX)) {
25082
- throw new LocalAuthorizationError("The workspace directory is invalid");
25083
- }
25084
- const encoded = value.slice(DIRECTORY_ID_PREFIX.length);
25085
- if (encoded.length === 0 || encoded.length > 2e3 || !DIRECTORY_ID_PATTERN.test(encoded)) {
25086
- throw new LocalAuthorizationError("The workspace directory is invalid");
25087
- }
25705
+ function decodePath(value, prefix) {
25706
+ const encoded = value.slice(prefix.length);
25707
+ if (encoded.length === 0 || encoded.length > 2e3 || !/^[a-zA-Z0-9_-]+$/.test(encoded))
25708
+ throw new LocalAuthorizationError("The directory identifier is invalid");
25088
25709
  const decoded = Buffer.from(encoded, "base64url").toString("utf8");
25089
- if (Buffer.from(decoded, "utf8").toString("base64url") !== encoded) {
25090
- throw new LocalAuthorizationError("The workspace directory is invalid");
25091
- }
25092
- const normalized = normalize(decoded);
25093
- if (decoded.includes("\0") || isAbsolute4(decoded) || normalized === ".." || normalized.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || normalized !== decoded) {
25094
- throw new LocalAuthorizationError("The workspace directory is invalid");
25095
- }
25096
- return normalized;
25097
- }
25098
- function isContained(root, target) {
25099
- const remainder = relative5(root, target);
25100
- return remainder.length === 0 || !isAbsolute4(remainder) && remainder !== ".." && !remainder.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
25710
+ if (Buffer.from(decoded, "utf8").toString("base64url") !== encoded || decoded.includes("\0") || normalize(decoded) !== decoded)
25711
+ throw new LocalAuthorizationError("The directory identifier is invalid");
25712
+ return decoded;
25101
25713
  }
25102
25714
  var WorkspaceDirectoryService = class {
25103
- #approvedBindingId;
25104
- #approvedRoot;
25105
- constructor(approvedBindingId, approvedRoot) {
25106
- this.#approvedBindingId = approvedBindingId;
25107
- this.#approvedRoot = approvedRoot;
25108
- }
25109
- resolve = async (requestedBindingId, requestedDirectoryId) => {
25110
- if (requestedBindingId !== this.#approvedBindingId) {
25715
+ #bindingId;
25716
+ #defaultWorkspace;
25717
+ constructor(bindingId, defaultWorkspace) {
25718
+ this.#bindingId = bindingId;
25719
+ this.#defaultWorkspace = defaultWorkspace;
25720
+ }
25721
+ authorizeMachine = (bindingId) => {
25722
+ if (bindingId !== this.#bindingId)
25111
25723
  throw new LocalAuthorizationError(
25112
25724
  "The runtime binding is not authorized"
25113
25725
  );
25726
+ };
25727
+ requestedPath = (id) => {
25728
+ if (id === "default") return this.#defaultWorkspace;
25729
+ if (id === "home") return homedir2();
25730
+ if (id.startsWith("path-")) {
25731
+ const path2 = decodePath(id, "path-");
25732
+ if (isAbsolute2(path2)) return path2;
25114
25733
  }
25115
- const root = await this.resolveRoot();
25116
- const relativePath = decodeDirectoryId(requestedDirectoryId);
25734
+ throw new LocalAuthorizationError("The directory identifier is invalid");
25735
+ };
25736
+ resolvePath = async (requested, signal) => {
25737
+ signal.throwIfAborted();
25738
+ if (!isAbsolute2(requested) || requested.includes("\0") || normalize(requested) !== requested)
25739
+ throw new LocalAuthorizationError("The local directory path is invalid");
25117
25740
  try {
25118
- const target = await realpath5(join8(root, relativePath));
25741
+ const target = await realpath4(requested);
25742
+ signal.throwIfAborted();
25119
25743
  const metadata = await stat4(target);
25120
- if (!metadata.isDirectory() || !isContained(root, target)) {
25744
+ signal.throwIfAborted();
25745
+ if (!isAbsolute2(target) || normalize(target) !== target || !metadata.isDirectory())
25121
25746
  throw new LocalAuthorizationError(
25122
- "The workspace directory is not authorized"
25747
+ "The selected location is not a directory"
25123
25748
  );
25124
- }
25125
25749
  return target;
25126
- } catch (error61) {
25127
- if (error61 instanceof LocalAuthorizationError) {
25128
- throw error61;
25129
- }
25750
+ } catch (cause) {
25751
+ signal.throwIfAborted();
25752
+ if (cause instanceof LocalAuthorizationError) throw cause;
25130
25753
  throw new LocalAuthorizationError(
25131
- "The workspace directory cannot be resolved",
25132
- { cause: error61 }
25754
+ "This directory is missing or unavailable to your user account",
25755
+ { cause }
25133
25756
  );
25134
25757
  }
25135
25758
  };
25136
- list = async (requestedBindingId, requestedDirectoryId) => {
25137
- const root = await this.resolveRoot();
25138
- const logicalRelativePath = decodeDirectoryId(requestedDirectoryId);
25139
- const target = await this.resolve(requestedBindingId, requestedDirectoryId);
25759
+ label = (path2) => {
25760
+ const suffix = relative2(homedir2(), path2);
25761
+ return suffix === "" ? "~" : !isAbsolute2(suffix) && suffix !== ".." && !suffix.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) ? `~/${suffix.replaceAll("\\", "/")}` : path2;
25762
+ };
25763
+ identify = async (bindingId, requestedPath, signal) => {
25764
+ this.authorizeMachine(bindingId);
25765
+ const path2 = await this.resolvePath(requestedPath, signal);
25766
+ return {
25767
+ path: path2,
25768
+ directoryId: encodePath(path2),
25769
+ name: basename2(path2) || path2,
25770
+ label: this.label(path2)
25771
+ };
25772
+ };
25773
+ resolve = async (bindingId, id, signal) => {
25774
+ this.authorizeMachine(bindingId);
25775
+ return await this.resolvePath(this.requestedPath(id), signal);
25776
+ };
25777
+ list = async (bindingId, id, signal) => {
25778
+ const target = await this.resolve(bindingId, id, signal);
25140
25779
  try {
25141
- const entries = [];
25142
25780
  const children = await readdir(target, { withFileTypes: true });
25781
+ signal.throwIfAborted();
25782
+ const entries = [];
25143
25783
  for (const child of children) {
25144
- if (entries.length === 500) {
25145
- break;
25784
+ if (entries.length === 500) break;
25785
+ if (child.isFile() || child.isBlockDevice() || child.isCharacterDevice() || child.isFIFO() || child.isSocket()) {
25786
+ continue;
25146
25787
  }
25147
- const logicalChild = logicalRelativePath.length === 0 ? child.name : join8(logicalRelativePath, child.name);
25148
25788
  try {
25149
- const resolvedChild = await realpath5(join8(target, child.name));
25150
- const metadata = await stat4(resolvedChild);
25151
- if (metadata.isDirectory() && isContained(root, resolvedChild)) {
25152
- entries.push({
25153
- directoryId: directoryId(logicalChild),
25154
- name: child.name
25155
- });
25156
- }
25157
- } catch {
25789
+ const path2 = await this.resolvePath(join9(target, child.name), signal);
25790
+ entries.push({ directoryId: encodePath(path2), name: child.name });
25791
+ } catch (cause) {
25792
+ signal.throwIfAborted();
25793
+ if (!(cause instanceof LocalAuthorizationError)) throw cause;
25158
25794
  }
25159
25795
  }
25160
- entries.sort((left, right) => left.name.localeCompare(right.name));
25161
- const labelSuffix = logicalRelativePath.replaceAll("\\", "/");
25162
- const parentRelative = logicalRelativePath.length === 0 || dirname4(logicalRelativePath) === "." ? "" : dirname4(logicalRelativePath);
25796
+ entries.sort((a, b) => a.name.localeCompare(b.name));
25797
+ const parent = dirname4(target);
25163
25798
  return new WorkspaceDirectoryListing(
25164
- requestedDirectoryId,
25165
- labelSuffix.length === 0 ? basename2(root) : `${basename2(root)}/${labelSuffix}`,
25799
+ id,
25800
+ this.label(target),
25166
25801
  entries,
25167
- requestedDirectoryId === ROOT_DIRECTORY_ID ? "" : directoryId(parentRelative)
25802
+ parent === target ? "" : encodePath(parent)
25168
25803
  );
25169
- } catch (error61) {
25170
- if (error61 instanceof LocalAuthorizationError) {
25171
- throw error61;
25172
- }
25804
+ } catch (cause) {
25805
+ signal.throwIfAborted();
25806
+ if (cause instanceof LocalAuthorizationError) throw cause;
25173
25807
  throw new LocalAuthorizationError(
25174
- "The workspace directory cannot be listed",
25175
- { cause: error61 }
25808
+ "This directory cannot be browsed by your user account",
25809
+ { cause }
25176
25810
  );
25177
25811
  }
25178
25812
  };
25179
- resolveMany = async (requestedBindingId, directoryIds) => {
25180
- const resolved = [];
25181
- const unique = /* @__PURE__ */ new Set();
25182
- for (const requestedDirectoryId of directoryIds) {
25183
- const directory = await this.resolve(
25184
- requestedBindingId,
25185
- requestedDirectoryId
25813
+ resolveMany = async (bindingId, ids, signal) => {
25814
+ const paths = /* @__PURE__ */ new Set();
25815
+ for (const id of ids) paths.add(await this.resolve(bindingId, id, signal));
25816
+ return [...paths];
25817
+ };
25818
+ };
25819
+
25820
+ // src/native-sessions/service.ts
25821
+ import { randomUUID as randomUUID3 } from "node:crypto";
25822
+
25823
+ // src/native-sessions/codex-history.ts
25824
+ import { spawn as spawn4 } from "node:child_process";
25825
+ var responseSchema = external_exports.union([
25826
+ external_exports.object({
25827
+ id: external_exports.number(),
25828
+ error: external_exports.object({ code: external_exports.number(), message: external_exports.string() })
25829
+ }),
25830
+ external_exports.object({ id: external_exports.number(), result: external_exports.unknown() }),
25831
+ external_exports.object({ method: external_exports.string() })
25832
+ ]);
25833
+ var threadSchema = external_exports.object({
25834
+ thread: external_exports.object({
25835
+ id: nativeSessionIdSchema,
25836
+ cwd: external_exports.string().min(1).max(2048).refine((path2) => !path2.includes("\0"))
25837
+ })
25838
+ });
25839
+ var turnsSchema = external_exports.object({
25840
+ data: external_exports.array(external_exports.object({ items: external_exports.array(external_exports.unknown()).max(1e5) })).max(SESSION_HISTORY_PREVIEW_MESSAGE_LIMIT),
25841
+ nextCursor: external_exports.string().nullish()
25842
+ });
25843
+ var itemTypeSchema = external_exports.object({ type: external_exports.string().min(1).max(80) });
25844
+ var userMessageSchema = external_exports.object({
25845
+ content: external_exports.array(external_exports.unknown()).max(1e4)
25846
+ });
25847
+ var textSchema = external_exports.object({ text: external_exports.string() });
25848
+ var imageSchema = external_exports.object({ url: external_exports.string() });
25849
+ var agentMessageSchema = textSchema.extend({ phase: external_exports.string().nullish() });
25850
+ function decodeHistory(thread, value) {
25851
+ const page = turnsSchema.parse(value);
25852
+ const messages = [];
25853
+ let characters = 0;
25854
+ let imageCharacters = 0;
25855
+ let truncated = page.nextCursor != null;
25856
+ for (const turn of page.data.toReversed()) {
25857
+ for (const item of turn.items) {
25858
+ const { type } = itemTypeSchema.parse(item);
25859
+ let message;
25860
+ if (type === "userMessage") {
25861
+ const { content } = userMessageSchema.parse(item);
25862
+ const parsed = parseNativeHistoryText(
25863
+ content.map((block) => {
25864
+ const { type: type2 } = itemTypeSchema.parse(block);
25865
+ if (type2 === "text") return textSchema.parse(block).text;
25866
+ if (type2 === "image")
25867
+ return `[@image](${imageSchema.parse(block).url})`;
25868
+ return "[Attachment]";
25869
+ }).join("")
25870
+ );
25871
+ message = {
25872
+ role: "user",
25873
+ text: parsed.text,
25874
+ ...parsed.images.length > 0 ? { images: parsed.images } : {}
25875
+ };
25876
+ } else if (type === "agentMessage") {
25877
+ const agent = agentMessageSchema.parse(item);
25878
+ if (agent.phase === "commentary") continue;
25879
+ message = { role: "assistant", text: agent.text };
25880
+ } else {
25881
+ continue;
25882
+ }
25883
+ const text = message.text.slice(-SESSION_HISTORY_CHARACTER_LIMIT);
25884
+ if (text.length === 0 && (message.images?.length ?? 0) === 0) continue;
25885
+ truncated ||= text.length < message.text.length;
25886
+ messages.push({ ...message, text });
25887
+ characters += text.length;
25888
+ imageCharacters += (message.images ?? []).reduce(
25889
+ (size, image) => size + image.data.length,
25890
+ 0
25186
25891
  );
25187
- if (!unique.has(directory)) {
25188
- unique.add(directory);
25189
- resolved.push(directory);
25892
+ while (messages.length > SESSION_HISTORY_PREVIEW_MESSAGE_LIMIT || characters > SESSION_HISTORY_CHARACTER_LIMIT || imageCharacters > SESSION_HISTORY_IMAGE_CHARACTER_LIMIT) {
25893
+ const removed = messages.shift();
25894
+ if (removed !== void 0) {
25895
+ characters -= removed.text.length;
25896
+ imageCharacters -= (removed.images ?? []).reduce(
25897
+ (size, image) => size + image.data.length,
25898
+ 0
25899
+ );
25900
+ }
25901
+ truncated = true;
25190
25902
  }
25191
25903
  }
25192
- return resolved;
25904
+ }
25905
+ return {
25906
+ nativeSessionId: thread.id,
25907
+ cwd: thread.cwd,
25908
+ history: {
25909
+ status: "available",
25910
+ messages,
25911
+ truncated,
25912
+ coveredCommandIds: []
25913
+ }
25193
25914
  };
25194
- resolveRoot = async () => {
25915
+ }
25916
+ async function readCodexHistory(agent, cwd, nativeSessionId, signal) {
25917
+ signal.throwIfAborted();
25918
+ nativeSessionIdSchema.parse(nativeSessionId);
25919
+ if (agent.id !== "codex" || !await revalidateExecutable(agent.agentIdentity))
25920
+ throw new AcpExecutionError(
25921
+ "The local Codex executable changed. Restart Mobius to discover it again.",
25922
+ {},
25923
+ "agent_executable_changed"
25924
+ );
25925
+ signal.throwIfAborted();
25926
+ const child = spawn4(agent.agentIdentity.realPath, ["app-server"], {
25927
+ cwd,
25928
+ env: { ...process.env, ...agent.environment },
25929
+ detached: process.platform !== "win32",
25930
+ shell: false,
25931
+ stdio: ["pipe", "pipe", "ignore"]
25932
+ });
25933
+ const response = Promise.withResolvers();
25934
+ let state = { kind: "initializing", requestId: 1 };
25935
+ let pending = "";
25936
+ let receivedBytes = 0;
25937
+ const fail = (cause) => response.reject(
25938
+ cause instanceof AcpExecutionError ? cause : new AcpExecutionError(
25939
+ "Saved Codex history could not be read.",
25940
+ { cause },
25941
+ "native_history_unavailable"
25942
+ )
25943
+ );
25944
+ const send2 = (message) => {
25945
+ child.stdin.write(`${JSON.stringify(message)}
25946
+ `);
25947
+ };
25948
+ const onData = (chunk) => {
25195
25949
  try {
25196
- const root = await realpath5(this.#approvedRoot);
25197
- const metadata = await stat4(root);
25198
- if (!metadata.isDirectory()) {
25199
- throw new LocalAuthorizationError(
25200
- "The approved root is not a directory"
25950
+ receivedBytes += Buffer.byteLength(chunk);
25951
+ if (receivedBytes > 32 * 1024 * 1024)
25952
+ throw new AcpExecutionError(
25953
+ "This saved conversation is too large to preview.",
25954
+ {},
25955
+ "native_history_too_large"
25201
25956
  );
25957
+ pending += chunk;
25958
+ let newline = pending.indexOf("\n");
25959
+ while (newline >= 0) {
25960
+ const line = pending.slice(0, newline);
25961
+ pending = pending.slice(newline + 1);
25962
+ newline = pending.indexOf("\n");
25963
+ if (line.trim().length === 0) continue;
25964
+ const value = JSON.parse(line);
25965
+ const message = responseSchema.parse(value);
25966
+ if (!("id" in message)) continue;
25967
+ if (message.id !== state.requestId)
25968
+ throw new AcpExecutionError(
25969
+ "Codex returned an invalid history response.",
25970
+ {},
25971
+ "invalid_native_session_response"
25972
+ );
25973
+ if ("error" in message) {
25974
+ fail(
25975
+ new AcpExecutionError(
25976
+ "Saved Codex history could not be read.",
25977
+ { cause: message.error },
25978
+ "native_history_unavailable"
25979
+ )
25980
+ );
25981
+ return;
25982
+ }
25983
+ switch (state.kind) {
25984
+ case "initializing":
25985
+ state = { kind: "metadata", requestId: 2 };
25986
+ send2({ method: "initialized", params: {} });
25987
+ send2({
25988
+ id: state.requestId,
25989
+ method: "thread/read",
25990
+ params: { threadId: nativeSessionId, includeTurns: false }
25991
+ });
25992
+ break;
25993
+ case "metadata": {
25994
+ const { thread } = threadSchema.parse(message.result);
25995
+ state = { kind: "turns", requestId: 3, thread };
25996
+ send2({
25997
+ id: state.requestId,
25998
+ method: "thread/turns/list",
25999
+ params: {
26000
+ threadId: nativeSessionId,
26001
+ limit: SESSION_HISTORY_PREVIEW_MESSAGE_LIMIT,
26002
+ sortDirection: "desc",
26003
+ itemsView: "summary"
26004
+ }
26005
+ });
26006
+ break;
26007
+ }
26008
+ case "turns":
26009
+ response.resolve(decodeHistory(state.thread, message.result));
26010
+ return;
26011
+ default: {
26012
+ const unhandled = state;
26013
+ throw new Error(`Unknown history read state: ${String(unhandled)}`);
26014
+ }
26015
+ }
25202
26016
  }
25203
- return root;
25204
- } catch (error61) {
25205
- if (error61 instanceof LocalAuthorizationError) {
25206
- throw error61;
26017
+ } catch (cause) {
26018
+ fail(cause);
26019
+ }
26020
+ };
26021
+ const abort = () => response.reject(signal.reason);
26022
+ const onExit = () => fail(
26023
+ new AcpExecutionError(
26024
+ "Codex stopped before its saved history was available.",
26025
+ {},
26026
+ "native_history_unavailable"
26027
+ )
26028
+ );
26029
+ const timeout = setTimeout(
26030
+ () => fail(
26031
+ new AcpExecutionError(
26032
+ "Reading saved Codex history timed out.",
26033
+ {},
26034
+ "native_history_timeout"
26035
+ )
26036
+ ),
26037
+ 15e3
26038
+ );
26039
+ child.stdout.setEncoding("utf8");
26040
+ child.stdout.on("data", onData);
26041
+ child.stdin.on("error", fail);
26042
+ child.on("error", fail);
26043
+ child.on("exit", onExit);
26044
+ signal.addEventListener("abort", abort, { once: true });
26045
+ try {
26046
+ if (signal.aborted) abort();
26047
+ else
26048
+ send2({
26049
+ id: 1,
26050
+ method: "initialize",
26051
+ params: { clientInfo: { name: "mobius_history", version: "1.0.0" } }
26052
+ });
26053
+ return await response.promise;
26054
+ } finally {
26055
+ clearTimeout(timeout);
26056
+ signal.removeEventListener("abort", abort);
26057
+ child.stdout.removeListener("data", onData);
26058
+ if (child.pid !== void 0) await stopProcessTree(child);
26059
+ child.stdin.removeListener("error", fail);
26060
+ child.removeListener("error", fail);
26061
+ child.removeListener("exit", onExit);
26062
+ }
26063
+ }
26064
+
26065
+ // src/native-sessions/service.ts
26066
+ var NativeSessionService = class {
26067
+ #entries = /* @__PURE__ */ new Map();
26068
+ #cursors = /* @__PURE__ */ new Map();
26069
+ #config;
26070
+ #directories;
26071
+ #sessions;
26072
+ #audit;
26073
+ constructor(config2, directories, sessions, audit) {
26074
+ this.#config = config2;
26075
+ this.#directories = directories;
26076
+ this.#sessions = sessions;
26077
+ this.#audit = audit;
26078
+ }
26079
+ execute = async (command, signal) => {
26080
+ signal.throwIfAborted();
26081
+ const cwd = await this.#directories.resolve(
26082
+ command.bindingId,
26083
+ command.directoryId,
26084
+ signal
26085
+ );
26086
+ const additionalDirectories = await this.#directories.resolveMany(
26087
+ command.bindingId,
26088
+ command.additionalDirectoryIds,
26089
+ signal
26090
+ );
26091
+ signal.throwIfAborted();
26092
+ const options = {
26093
+ additionalDirectories,
26094
+ agent: this.#config.catalog.find(command.agentId),
26095
+ cwd,
26096
+ sessionId: command.sessionId,
26097
+ signal
26098
+ };
26099
+ const prepared = await this.#sessions.prepareSession(options);
26100
+ if (!prepared.capabilities.listSessions)
26101
+ throw new AcpExecutionError(
26102
+ "This Agent does not support native session listing",
26103
+ {},
26104
+ "native_session_unsupported"
26105
+ );
26106
+ const scope = JSON.stringify([
26107
+ command.agentId,
26108
+ command.bindingId,
26109
+ command.sessionId,
26110
+ cwd
26111
+ ]);
26112
+ const envelope2 = {
26113
+ kind: "native.session.result",
26114
+ protocolVersion: 1,
26115
+ requestId: command.requestId,
26116
+ sessionId: command.sessionId
26117
+ };
26118
+ if (command.action === "list") {
26119
+ let cursor = null;
26120
+ if (command.cursor !== null) {
26121
+ const known = this.#cursors.get(command.cursor);
26122
+ if (known?.scope !== scope)
26123
+ throw new AcpExecutionError(
26124
+ "Refresh the native session list before continuing",
26125
+ {},
26126
+ "native_session_cursor_expired"
26127
+ );
26128
+ cursor = known.cursor;
25207
26129
  }
25208
- throw new LocalAuthorizationError(
25209
- "The approved root cannot be resolved",
25210
- {
25211
- cause: error61
25212
- }
26130
+ const page = await this.#sessions.listNativeSessions(options, cursor);
26131
+ const entries = [];
26132
+ for (const info of page.sessions) {
26133
+ const authorized = await this.authorizeEntry(command, options, info);
26134
+ if (authorized === null) continue;
26135
+ const entryId = randomUUID3();
26136
+ if (this.#entries.size >= 5e3) this.#entries.clear();
26137
+ this.#entries.set(entryId, {
26138
+ scope,
26139
+ nativeSessionId: info.sessionId,
26140
+ cwd: authorized.cwd,
26141
+ additionalDirectories: authorized.additionalDirectories
26142
+ });
26143
+ entries.push({
26144
+ entryId,
26145
+ title: info.title ?? "Untitled session",
26146
+ directoryId: authorized.directoryId,
26147
+ directoryLabel: authorized.directoryLabel,
26148
+ updatedAt: info.updatedAt ?? null,
26149
+ additionalDirectories: authorized.additionalEntries
26150
+ });
26151
+ }
26152
+ let nextCursor = null;
26153
+ if (page.nextCursor != null) {
26154
+ if (page.nextCursor === cursor)
26155
+ throw new AcpExecutionError(
26156
+ "The Agent returned a repeated session cursor",
26157
+ {},
26158
+ "invalid_native_session_response"
26159
+ );
26160
+ nextCursor = randomUUID3();
26161
+ if (this.#cursors.size >= 500) this.#cursors.clear();
26162
+ this.#cursors.set(nextCursor, { scope, cursor: page.nextCursor });
26163
+ }
26164
+ this.record(command, "native_sessions_listed");
26165
+ return {
26166
+ ...envelope2,
26167
+ action: "list",
26168
+ capabilities: prepared.capabilities,
26169
+ entries,
26170
+ nextCursor
26171
+ };
26172
+ }
26173
+ const selected = this.#entries.get(command.entryId);
26174
+ if (selected?.scope !== scope)
26175
+ throw new AcpExecutionError(
26176
+ "Select a session from a fresh native session list",
26177
+ {},
26178
+ "native_session_not_found"
26179
+ );
26180
+ const target = await this.resolveTarget(command, options, selected);
26181
+ if (command.action === "delete") {
26182
+ if (!prepared.capabilities.deleteSession)
26183
+ throw new AcpExecutionError(
26184
+ "This Agent does not support native session deletion",
26185
+ {},
26186
+ "native_session_unsupported"
26187
+ );
26188
+ signal.throwIfAborted();
26189
+ this.record(command, "native_session_delete_authorized");
26190
+ const deletedSessionIds = await this.#sessions.deleteNativeSession(
26191
+ options,
26192
+ target.info.sessionId
26193
+ );
26194
+ this.record(command, "native_session_deleted");
26195
+ return {
26196
+ ...envelope2,
26197
+ action: "delete",
26198
+ entryId: command.entryId,
26199
+ deletedSessionIds
26200
+ };
26201
+ }
26202
+ if (command.action === "import" && !prepared.capabilities.loadSession && !prepared.capabilities.resumeSession)
26203
+ throw new AcpExecutionError(
26204
+ "This Agent cannot import native sessions",
26205
+ {},
26206
+ "native_session_unsupported"
26207
+ );
26208
+ if (command.action === "fork" && !prepared.capabilities.unstableForkSession)
26209
+ throw new AcpExecutionError(
26210
+ "This Agent does not support session forking",
26211
+ {},
26212
+ "native_session_unsupported"
26213
+ );
26214
+ if (target.additionalDirectories.length > 0 && !prepared.capabilities.additionalDirectories)
26215
+ throw new AcpExecutionError(
26216
+ "This Agent cannot restore additional working directories",
26217
+ {},
26218
+ "native_session_unsupported"
26219
+ );
26220
+ const opening = this.#sessions.openNativeSession(
26221
+ {
26222
+ ...options,
26223
+ cwd: target.cwd,
26224
+ sessionId: command.targetSessionId,
26225
+ additionalDirectories: target.additionalDirectories
26226
+ },
26227
+ { kind: command.action, nativeSessionId: target.info.sessionId }
26228
+ );
26229
+ const opened = await opening.catch(async (cause) => {
26230
+ signal.throwIfAborted();
26231
+ if (command.action !== "import" || options.agent.id !== "codex" || !(cause instanceof AcpExecutionError) || cause.code !== "acp_execution_failed")
26232
+ throw cause;
26233
+ const snapshot = await readCodexHistory(
26234
+ options.agent,
26235
+ target.cwd,
26236
+ target.info.sessionId,
26237
+ signal
26238
+ );
26239
+ const resolved = await this.#directories.identify(
26240
+ command.bindingId,
26241
+ snapshot.cwd,
26242
+ signal
26243
+ );
26244
+ if (snapshot.nativeSessionId !== target.info.sessionId || resolved.path !== target.cwd)
26245
+ throw new AcpExecutionError(
26246
+ "Codex returned history for a different session or workspace.",
26247
+ { cause },
26248
+ "invalid_native_session_response"
26249
+ );
26250
+ signal.throwIfAborted();
26251
+ this.record(command, "native_session_previewed");
26252
+ return { kind: "preview", history: snapshot.history };
26253
+ });
26254
+ if ("kind" in opened)
26255
+ return {
26256
+ ...envelope2,
26257
+ action: "preview",
26258
+ entryId: command.entryId,
26259
+ reason: "continuation_unavailable",
26260
+ title: target.info.title ?? "Untitled session",
26261
+ history: opened.history
26262
+ };
26263
+ signal.throwIfAborted();
26264
+ const ready = {
26265
+ additionalDirectories: target.additionalEntries,
26266
+ agentId: command.agentId,
26267
+ agentName: opened.prepared.agentName,
26268
+ agentVersion: opened.prepared.agentVersion,
26269
+ capabilities: opened.prepared.capabilities,
26270
+ commands: opened.prepared.commands,
26271
+ configOptions: opened.prepared.configOptions,
26272
+ directoryId: target.directoryId,
26273
+ directoryLabel: target.directoryLabel,
26274
+ kind: "session.ready",
26275
+ protocolVersion: 1,
26276
+ requestId: command.requestId,
26277
+ restored: true,
26278
+ sessionId: opened.sessionId
26279
+ };
26280
+ this.record(
26281
+ command,
26282
+ command.action === "fork" ? "native_session_forked" : "native_session_imported"
26283
+ );
26284
+ return {
26285
+ ...envelope2,
26286
+ action: command.action,
26287
+ ready,
26288
+ title: target.info.title ?? "Untitled session",
26289
+ history: opened.history
26290
+ };
26291
+ };
26292
+ authorizeEntry = async (command, options, info) => {
26293
+ try {
26294
+ const primary = await this.#directories.identify(
26295
+ command.bindingId,
26296
+ info.cwd,
26297
+ options.signal
26298
+ );
26299
+ const additionalDirectories = [];
26300
+ const additionalEntries = [];
26301
+ const paths = /* @__PURE__ */ new Set([primary.path]);
26302
+ for (const path2 of info.additionalDirectories) {
26303
+ const directory = await this.#directories.identify(
26304
+ command.bindingId,
26305
+ path2,
26306
+ options.signal
26307
+ );
26308
+ if (paths.has(directory.path))
26309
+ throw new LocalAuthorizationError(
26310
+ "Native session roots must be distinct"
26311
+ );
26312
+ paths.add(directory.path);
26313
+ additionalDirectories.push(directory.path);
26314
+ additionalEntries.push({
26315
+ directoryId: directory.directoryId,
26316
+ name: directory.name
26317
+ });
26318
+ }
26319
+ return {
26320
+ info,
26321
+ cwd: primary.path,
26322
+ directoryId: primary.directoryId,
26323
+ directoryLabel: primary.label,
26324
+ additionalDirectories,
26325
+ additionalEntries
26326
+ };
26327
+ } catch (error61) {
26328
+ options.signal.throwIfAborted();
26329
+ if (error61 instanceof LocalAuthorizationError) return null;
26330
+ throw error61;
26331
+ }
26332
+ };
26333
+ resolveTarget = async (command, options, selected) => {
26334
+ let cursor = null;
26335
+ const visited = /* @__PURE__ */ new Set();
26336
+ for (let index = 0; index < 100; index += 1) {
26337
+ const page = await this.#sessions.listNativeSessions(options, cursor);
26338
+ const info = page.sessions.find(
26339
+ (entry) => entry.sessionId === selected.nativeSessionId
25213
26340
  );
26341
+ if (info !== void 0) {
26342
+ const target = await this.authorizeEntry(command, options, info);
26343
+ if (target === null)
26344
+ throw new LocalAuthorizationError(
26345
+ "The selected native session is not authorized"
26346
+ );
26347
+ if (target.cwd !== selected.cwd || target.additionalDirectories.length !== selected.additionalDirectories.length || target.additionalDirectories.some(
26348
+ (path2, index2) => path2 !== selected.additionalDirectories[index2]
26349
+ ))
26350
+ throw new AcpExecutionError(
26351
+ "This session's directory changed. Refresh the list before continuing.",
26352
+ {},
26353
+ "native_session_target_changed"
26354
+ );
26355
+ return target;
26356
+ }
26357
+ cursor = page.nextCursor ?? null;
26358
+ if (cursor === null) break;
26359
+ if (visited.has(cursor))
26360
+ throw new AcpExecutionError(
26361
+ "The Agent returned a repeated session cursor",
26362
+ {},
26363
+ "invalid_native_session_response"
26364
+ );
26365
+ visited.add(cursor);
25214
26366
  }
26367
+ throw new AcpExecutionError(
26368
+ "The selected native session no longer exists. Refresh the list.",
26369
+ {},
26370
+ "native_session_not_found"
26371
+ );
26372
+ };
26373
+ record = (command, event) => {
26374
+ this.#audit.record({
26375
+ commandId: command.requestId,
26376
+ sessionId: command.sessionId,
26377
+ event,
26378
+ details: { agentId: command.agentId }
26379
+ });
25215
26380
  };
25216
26381
  };
25217
26382
 
@@ -25228,7 +26393,7 @@ var RelayConnectionError = class extends Error {
25228
26393
  };
25229
26394
  function connectRelay(url2, signal) {
25230
26395
  signal.throwIfAborted();
25231
- return new Promise((resolve6, reject) => {
26396
+ return new Promise((resolve5, reject) => {
25232
26397
  const socket = new WebSocket(url2, {
25233
26398
  dispatcher: environmentProxyDispatcher
25234
26399
  });
@@ -25281,7 +26446,7 @@ function connectRelay(url2, signal) {
25281
26446
  }
25282
26447
  finished = true;
25283
26448
  cleanup();
25284
- resolve6(socket);
26449
+ resolve5(socket);
25285
26450
  };
25286
26451
  function onTimeout() {
25287
26452
  fail(
@@ -25302,7 +26467,7 @@ function connectRelay(url2, signal) {
25302
26467
  }
25303
26468
 
25304
26469
  // src/relay-connection/liveness.ts
25305
- import { randomUUID as randomUUID3 } from "node:crypto";
26470
+ import { randomUUID as randomUUID4 } from "node:crypto";
25306
26471
  var HEARTBEAT_ACK_TIMEOUT_MILLISECONDS = 1e4;
25307
26472
  var HEARTBEAT_INTERVAL_MILLISECONDS = 2e4;
25308
26473
  var RelayLivenessState = class {
@@ -25393,7 +26558,7 @@ var RelayConnectionLiveness = class {
25393
26558
  }
25394
26559
  throw new Error("Heartbeat was scheduled in an invalid state");
25395
26560
  }
25396
- const heartbeatId = randomUUID3();
26561
+ const heartbeatId = randomUUID4();
25397
26562
  this.#state = new AwaitingHeartbeatAckState(
25398
26563
  heartbeatId,
25399
26564
  this.#timer.schedule(
@@ -25720,15 +26885,16 @@ function reportOperationFailure(socket, requestId, sessionId, error61) {
25720
26885
  })
25721
26886
  );
25722
26887
  }
25723
- async function resolveSessionDirectories(directories, bindingId, directoryId2, additionalDirectoryIds) {
25724
- const cwd = await directories.resolve(bindingId, directoryId2);
26888
+ async function resolveSessionDirectories(directories, bindingId, directoryId2, additionalDirectoryIds, signal) {
26889
+ const cwd = await directories.resolve(bindingId, directoryId2, signal);
25725
26890
  const additionalDirectories = [];
25726
26891
  const additionalDirectoryEntries = [];
25727
26892
  const unique = /* @__PURE__ */ new Set([cwd]);
25728
26893
  for (const additionalDirectoryId of additionalDirectoryIds) {
25729
26894
  const directory = await directories.resolve(
25730
26895
  bindingId,
25731
- additionalDirectoryId
26896
+ additionalDirectoryId,
26897
+ signal
25732
26898
  );
25733
26899
  if (unique.has(directory)) {
25734
26900
  throw new LocalAuthorizationError(
@@ -25748,11 +26914,12 @@ async function resolveSessionDirectories(directories, bindingId, directoryId2, a
25748
26914
  additionalDirectoryEntries
25749
26915
  );
25750
26916
  }
25751
- async function executeWorkspaceList(socket, command, directories, audit) {
26917
+ async function executeWorkspaceList(socket, command, directories, audit, signal) {
25752
26918
  try {
25753
26919
  const listing = await directories.list(
25754
26920
  command.bindingId,
25755
- command.directoryId
26921
+ command.directoryId,
26922
+ signal
25756
26923
  );
25757
26924
  audit.record({
25758
26925
  commandId: command.requestId,
@@ -25782,7 +26949,8 @@ async function executeSessionPrepare(socket, command, config2, directories, audi
25782
26949
  directories,
25783
26950
  command.bindingId,
25784
26951
  command.directoryId,
25785
- command.additionalDirectoryIds
26952
+ command.additionalDirectoryIds,
26953
+ signal
25786
26954
  );
25787
26955
  const agent = config2.catalog.find(command.agentId);
25788
26956
  const prepared = await sessions.prepareSession({
@@ -25794,7 +26962,8 @@ async function executeSessionPrepare(socket, command, config2, directories, audi
25794
26962
  });
25795
26963
  const listing = await directories.list(
25796
26964
  command.bindingId,
25797
- command.directoryId
26965
+ command.directoryId,
26966
+ signal
25798
26967
  );
25799
26968
  audit.record({
25800
26969
  commandId: command.requestId,
@@ -25830,7 +26999,8 @@ async function executeSessionConfigure(socket, command, config2, directories, au
25830
26999
  directories,
25831
27000
  command.bindingId,
25832
27001
  command.directoryId,
25833
- command.additionalDirectoryIds
27002
+ command.additionalDirectoryIds,
27003
+ signal
25834
27004
  );
25835
27005
  const configOptions = await sessions.configureSession({
25836
27006
  additionalDirectories: resolved.additionalDirectories,
@@ -25869,10 +27039,11 @@ async function executePrompt(socket, command, config2, directories, audit, sessi
25869
27039
  directories,
25870
27040
  command.bindingId,
25871
27041
  command.directoryId,
25872
- command.additionalDirectoryIds
27042
+ command.additionalDirectoryIds,
27043
+ signal
25873
27044
  );
25874
27045
  const agent = config2.catalog.find(command.agentId);
25875
- const processAttemptId = randomUUID4();
27046
+ const processAttemptId = randomUUID5();
25876
27047
  audit.record({
25877
27048
  commandId: command.commandId,
25878
27049
  event: "session_authorized",
@@ -25970,262 +27141,383 @@ function waitForRelayRetry(signal, timer) {
25970
27141
  if (signal.aborted) {
25971
27142
  return Promise.resolve();
25972
27143
  }
25973
- return new Promise((resolve6) => {
27144
+ return new Promise((resolve5) => {
25974
27145
  const retry = timer.schedule(finish, RELAY_RECONNECT_DELAY_MILLISECONDS);
25975
27146
  function finish() {
25976
27147
  retry.cancel();
25977
27148
  signal.removeEventListener("abort", finish);
25978
- resolve6();
27149
+ resolve5();
25979
27150
  }
25980
27151
  signal.addEventListener("abort", finish, { once: true });
25981
27152
  });
25982
27153
  }
25983
- async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, signal, timer) {
27154
+ async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, nativeSessions, signal, timer) {
25984
27155
  const relayUrl = new URL(config2.relayUrl);
25985
27156
  relayUrl.searchParams.set("liveness", "1");
25986
27157
  const socket = await connectRelay(relayUrl, signal);
25987
27158
  const activeTurns = /* @__PURE__ */ new Map();
25988
27159
  const connectionController = new AbortController();
27160
+ const nativeRequests = /* @__PURE__ */ new Map();
25989
27161
  sessions.setObserver(new DaemonAcpObserver(socket));
25990
27162
  installationObserver.connect(socket);
25991
- await new Promise((resolve6, reject) => {
25992
- let finished = false;
25993
- const cleanup = () => {
25994
- connectionRefresh.close();
25995
- connectionLiveness.close();
25996
- signal.removeEventListener("abort", stop);
25997
- socket.removeEventListener("close", handleClose);
25998
- socket.removeEventListener("error", handleError);
25999
- socket.removeEventListener("message", handleMessage);
26000
- installationObserver.disconnect(socket);
26001
- connectionController.abort();
26002
- for (const turn of activeTurns.values()) {
26003
- turn.abortConnection();
26004
- }
26005
- activeTurns.clear();
26006
- };
26007
- const resolveConnection = () => {
26008
- if (finished) {
26009
- return;
26010
- }
26011
- finished = true;
26012
- cleanup();
26013
- resolve6();
26014
- };
26015
- const rejectConnection = () => {
26016
- if (finished) {
26017
- return;
26018
- }
26019
- finished = true;
26020
- cleanup();
26021
- reject(new Error("Relay WebSocket failed"));
26022
- };
26023
- const stop = () => {
26024
- socket.close(1e3, "Daemon stopped");
26025
- resolveConnection();
26026
- };
26027
- const refreshConnection = () => {
26028
- socket.close(1e3, "Refreshing idle relay connection");
26029
- resolveConnection();
26030
- };
26031
- const connectionLiveness = new RelayConnectionLiveness(timer, {
26032
- send: (message) => send(socket, message),
26033
- unresponsive: () => {
26034
- socket.close(4001, "Relay liveness check timed out");
27163
+ try {
27164
+ await new Promise((resolve5, reject) => {
27165
+ let finished = false;
27166
+ const cleanup = () => {
27167
+ connectionRefresh.close();
27168
+ connectionLiveness.close();
27169
+ signal.removeEventListener("abort", stop);
27170
+ socket.removeEventListener("close", handleClose);
27171
+ socket.removeEventListener("error", handleError);
27172
+ socket.removeEventListener("message", handleMessage);
27173
+ installationObserver.disconnect(socket);
27174
+ connectionController.abort();
27175
+ for (const turn of activeTurns.values()) {
27176
+ turn.abortConnection();
27177
+ }
27178
+ activeTurns.clear();
27179
+ };
27180
+ const resolveConnection = () => {
27181
+ if (finished) {
27182
+ return;
27183
+ }
27184
+ finished = true;
27185
+ cleanup();
27186
+ resolve5();
27187
+ };
27188
+ const rejectConnection = () => {
27189
+ if (finished) {
27190
+ return;
27191
+ }
27192
+ finished = true;
27193
+ cleanup();
27194
+ reject(new Error("Relay WebSocket failed"));
27195
+ };
27196
+ const stop = () => {
27197
+ socket.close(1e3, "Daemon stopped");
26035
27198
  resolveConnection();
26036
- }
26037
- });
26038
- const connectionRefresh = new RelayConnectionRefresh(
26039
- timer,
26040
- () => activeTurns.size === 0,
26041
- refreshConnection
26042
- );
26043
- signal.addEventListener("abort", stop, { once: true });
26044
- const handleClose = () => {
26045
- resolveConnection();
26046
- };
26047
- const handleError = () => {
26048
- if (signal.aborted) {
27199
+ };
27200
+ const refreshConnection = () => {
27201
+ socket.close(1e3, "Refreshing idle relay connection");
26049
27202
  resolveConnection();
26050
- return;
26051
- }
26052
- rejectConnection();
26053
- };
26054
- const handleMessage = (event) => {
26055
- const encoded = event.data;
26056
- if (typeof encoded !== "string") {
26057
- socket.close(4003, "Text message required");
26058
- rejectConnection();
26059
- return;
26060
- }
26061
- try {
26062
- const kind = decodeMessageKind(encoded);
26063
- if (kind === RUNTIME_LIVENESS_READY_KIND) {
26064
- decodeRuntimeLivenessReadyEvent(encoded);
26065
- connectionLiveness.start();
27203
+ };
27204
+ const connectionLiveness = new RelayConnectionLiveness(timer, {
27205
+ send: (message) => send(socket, message),
27206
+ unresponsive: () => {
27207
+ socket.close(4001, "Relay liveness check timed out");
27208
+ resolveConnection();
27209
+ }
27210
+ });
27211
+ const connectionRefresh = new RelayConnectionRefresh(
27212
+ timer,
27213
+ () => activeTurns.size === 0,
27214
+ refreshConnection
27215
+ );
27216
+ signal.addEventListener("abort", stop, { once: true });
27217
+ const handleClose = () => {
27218
+ resolveConnection();
27219
+ };
27220
+ const handleError = () => {
27221
+ if (signal.aborted) {
27222
+ resolveConnection();
26066
27223
  return;
26067
27224
  }
26068
- if (kind === RUNTIME_HEARTBEAT_ACK_KIND) {
26069
- connectionLiveness.acknowledge(
26070
- decodeRuntimeHeartbeatAckEvent(encoded)
26071
- );
27225
+ rejectConnection();
27226
+ };
27227
+ const handleMessage = (event) => {
27228
+ const encoded = event.data;
27229
+ if (typeof encoded !== "string") {
27230
+ socket.close(4003, "Text message required");
27231
+ rejectConnection();
26072
27232
  return;
26073
27233
  }
26074
- if (kind === SESSION_CANCEL_KIND) {
26075
- const cancellation = decodeSessionCancelCommand(encoded);
26076
- const turn2 = activeTurns.get(cancellation.commandId);
26077
- if (turn2 === void 0 || !turn2.matches(cancellation)) {
26078
- console.warn(
26079
- "Mobius daemon ignored cancellation for an inactive turn"
27234
+ try {
27235
+ const kind = decodeMessageKind(encoded);
27236
+ if (kind === NATIVE_SESSION_CANCEL_KIND) {
27237
+ const command2 = decodeNativeSessionCancel(encoded);
27238
+ const pending = nativeRequests.get(command2.requestId);
27239
+ if (pending?.sessionId === command2.sessionId)
27240
+ pending.controller.abort(
27241
+ new AcpExecutionError(
27242
+ "Native session operation cancelled",
27243
+ {},
27244
+ "native_session_cancelled"
27245
+ )
27246
+ );
27247
+ return;
27248
+ }
27249
+ if (kind === NATIVE_SESSION_REQUEST_KIND) {
27250
+ const command2 = decodeNativeSessionCommand(encoded);
27251
+ const previous = [...nativeRequests.values()].filter(
27252
+ (request) => request.agentId === command2.agentId
26080
27253
  );
27254
+ if (nativeRequests.has(command2.requestId) || nativeRequests.size >= 4 || previous.some((request) => !request.controller.signal.aborted)) {
27255
+ reportOperationFailure(
27256
+ socket,
27257
+ command2.requestId,
27258
+ command2.sessionId,
27259
+ new AcpExecutionError(
27260
+ "Another native session operation is running",
27261
+ {},
27262
+ "native_session_busy"
27263
+ )
27264
+ );
27265
+ return;
27266
+ }
27267
+ const controller = new AbortController();
27268
+ const operationSignal = AbortSignal.any([
27269
+ connectionController.signal,
27270
+ controller.signal
27271
+ ]);
27272
+ const timeout = timer.schedule(
27273
+ () => controller.abort(
27274
+ new AcpExecutionError(
27275
+ "Native session operation timed out",
27276
+ {},
27277
+ "native_session_timeout"
27278
+ )
27279
+ ),
27280
+ 9e4
27281
+ );
27282
+ const completion = (async () => {
27283
+ try {
27284
+ operationSignal.throwIfAborted();
27285
+ if (previous.length > 0) {
27286
+ await new Promise((resolve6, reject2) => {
27287
+ const cancel = () => reject2(operationSignal.reason);
27288
+ operationSignal.addEventListener("abort", cancel, {
27289
+ once: true
27290
+ });
27291
+ void Promise.all(
27292
+ previous.map((request) => request.completion)
27293
+ ).then(
27294
+ () => {
27295
+ operationSignal.removeEventListener("abort", cancel);
27296
+ resolve6();
27297
+ },
27298
+ (error61) => {
27299
+ operationSignal.removeEventListener("abort", cancel);
27300
+ reject2(error61);
27301
+ }
27302
+ );
27303
+ });
27304
+ }
27305
+ operationSignal.throwIfAborted();
27306
+ const result = await nativeSessions.execute(
27307
+ command2,
27308
+ operationSignal
27309
+ );
27310
+ if (socket.readyState === WebSocket2.OPEN)
27311
+ send(socket, encodeNativeSessionResult(result));
27312
+ } catch (error61) {
27313
+ const failure = operationSignal.aborted ? controller.signal.reason ?? new AcpExecutionError(
27314
+ "Native session operation cancelled",
27315
+ {},
27316
+ "native_session_cancelled"
27317
+ ) : error61;
27318
+ audit.record({
27319
+ commandId: command2.requestId,
27320
+ sessionId: command2.sessionId,
27321
+ event: "native_session_operation_failed",
27322
+ details: { code: operationErrorCode(failure) }
27323
+ });
27324
+ reportOperationFailure(
27325
+ socket,
27326
+ command2.requestId,
27327
+ command2.sessionId,
27328
+ failure
27329
+ );
27330
+ } finally {
27331
+ timeout.cancel();
27332
+ nativeRequests.delete(command2.requestId);
27333
+ }
27334
+ })();
27335
+ nativeRequests.set(command2.requestId, {
27336
+ agentId: command2.agentId,
27337
+ sessionId: command2.sessionId,
27338
+ controller,
27339
+ completion
27340
+ });
26081
27341
  return;
26082
27342
  }
26083
- if (!turn2.requestCancellation()) {
27343
+ if (kind === RUNTIME_LIVENESS_READY_KIND) {
27344
+ decodeRuntimeLivenessReadyEvent(encoded);
27345
+ connectionLiveness.start();
26084
27346
  return;
26085
27347
  }
26086
- audit.record({
26087
- commandId: cancellation.commandId,
26088
- event: "session_cancellation_requested",
26089
- sessionId: cancellation.sessionId
26090
- });
26091
- void sessions.cancelPrompt(cancellation.sessionId).catch((error61) => {
26092
- console.error(
26093
- "Mobius daemon could not cancel the agent turn",
26094
- error61
27348
+ if (kind === RUNTIME_HEARTBEAT_ACK_KIND) {
27349
+ connectionLiveness.acknowledge(
27350
+ decodeRuntimeHeartbeatAckEvent(encoded)
26095
27351
  );
26096
- });
26097
- return;
26098
- }
26099
- if (kind === WORKSPACE_LIST_KIND) {
26100
- const command2 = decodeWorkspaceListCommand(encoded);
26101
- void executeWorkspaceList(socket, command2, directories, audit);
26102
- return;
26103
- }
26104
- if (kind === SESSION_PREPARE_KIND) {
26105
- const command2 = decodeSessionPrepareCommand(encoded);
26106
- void executeSessionPrepare(
26107
- socket,
26108
- command2,
26109
- config2,
26110
- directories,
26111
- audit,
26112
- sessions,
26113
- connectionController.signal
26114
- );
26115
- return;
26116
- }
26117
- if (kind === SESSION_CONFIGURE_KIND) {
26118
- const command2 = decodeSessionConfigureCommand(encoded);
26119
- void executeSessionConfigure(
26120
- socket,
26121
- command2,
26122
- config2,
26123
- directories,
26124
- audit,
26125
- sessions,
26126
- connectionController.signal
26127
- );
26128
- return;
26129
- }
26130
- if (kind === SESSION_PERMISSION_RESPOND_KIND) {
26131
- const command2 = decodeSessionPermissionRespondCommand(encoded);
26132
- void sessions.respondPermission(command2).then(() => {
27352
+ return;
27353
+ }
27354
+ if (kind === SESSION_CANCEL_KIND) {
27355
+ const cancellation = decodeSessionCancelCommand(encoded);
27356
+ const turn2 = activeTurns.get(cancellation.commandId);
27357
+ if (turn2 === void 0 || !turn2.matches(cancellation)) {
27358
+ console.warn(
27359
+ "Mobius daemon ignored cancellation for an inactive turn"
27360
+ );
27361
+ return;
27362
+ }
27363
+ if (!turn2.requestCancellation()) {
27364
+ return;
27365
+ }
26133
27366
  audit.record({
26134
- commandId: command2.interactionId,
26135
- event: "session_permission_resolved",
26136
- sessionId: command2.sessionId
27367
+ commandId: cancellation.commandId,
27368
+ event: "session_cancellation_requested",
27369
+ sessionId: cancellation.sessionId
27370
+ });
27371
+ void sessions.cancelPrompt(cancellation.sessionId).catch((error61) => {
27372
+ console.error(
27373
+ "Mobius daemon could not cancel the agent turn",
27374
+ error61
27375
+ );
26137
27376
  });
26138
- }).catch((error61) => {
26139
- reportOperationFailure(
27377
+ return;
27378
+ }
27379
+ if (kind === WORKSPACE_LIST_KIND) {
27380
+ const command2 = decodeWorkspaceListCommand(encoded);
27381
+ void executeWorkspaceList(
26140
27382
  socket,
26141
- command2.interactionId,
26142
- command2.sessionId,
26143
- error61
27383
+ command2,
27384
+ directories,
27385
+ audit,
27386
+ connectionController.signal
26144
27387
  );
26145
- });
26146
- return;
26147
- }
26148
- if (kind === SESSION_ELICITATION_RESPOND_KIND) {
26149
- const command2 = decodeSessionElicitationRespondCommand(encoded);
26150
- void sessions.respondElicitation(command2).then(() => {
26151
- audit.record({
26152
- commandId: command2.interactionId,
26153
- event: "session_elicitation_resolved",
26154
- sessionId: command2.sessionId
27388
+ return;
27389
+ }
27390
+ if (kind === SESSION_PREPARE_KIND) {
27391
+ const command2 = decodeSessionPrepareCommand(encoded);
27392
+ void executeSessionPrepare(
27393
+ socket,
27394
+ command2,
27395
+ config2,
27396
+ directories,
27397
+ audit,
27398
+ sessions,
27399
+ connectionController.signal
27400
+ );
27401
+ return;
27402
+ }
27403
+ if (kind === SESSION_CONFIGURE_KIND) {
27404
+ const command2 = decodeSessionConfigureCommand(encoded);
27405
+ void executeSessionConfigure(
27406
+ socket,
27407
+ command2,
27408
+ config2,
27409
+ directories,
27410
+ audit,
27411
+ sessions,
27412
+ connectionController.signal
27413
+ );
27414
+ return;
27415
+ }
27416
+ if (kind === SESSION_PERMISSION_RESPOND_KIND) {
27417
+ const command2 = decodeSessionPermissionRespondCommand(encoded);
27418
+ void sessions.respondPermission(command2).then(() => {
27419
+ audit.record({
27420
+ commandId: command2.interactionId,
27421
+ event: "session_permission_resolved",
27422
+ sessionId: command2.sessionId
27423
+ });
27424
+ }).catch((error61) => {
27425
+ reportOperationFailure(
27426
+ socket,
27427
+ command2.interactionId,
27428
+ command2.sessionId,
27429
+ error61
27430
+ );
27431
+ });
27432
+ return;
27433
+ }
27434
+ if (kind === SESSION_ELICITATION_RESPOND_KIND) {
27435
+ const command2 = decodeSessionElicitationRespondCommand(encoded);
27436
+ void sessions.respondElicitation(command2).then(() => {
27437
+ audit.record({
27438
+ commandId: command2.interactionId,
27439
+ event: "session_elicitation_resolved",
27440
+ sessionId: command2.sessionId
27441
+ });
27442
+ }).catch((error61) => {
27443
+ reportOperationFailure(
27444
+ socket,
27445
+ command2.interactionId,
27446
+ command2.sessionId,
27447
+ error61
27448
+ );
27449
+ });
27450
+ return;
27451
+ }
27452
+ if (kind === AGENT_INSTALL_REQUEST_KIND) {
27453
+ const command2 = decodeAgentInstallRequestCommand(encoded);
27454
+ void installations.request(command2, signal).catch((error61) => {
27455
+ console.error("Mobius Agent installation failed", error61);
27456
+ });
27457
+ return;
27458
+ }
27459
+ if (kind === AGENT_INSTALL_CANCEL_KIND) {
27460
+ const command2 = decodeAgentInstallCancelCommand(encoded);
27461
+ void installations.cancel(command2.requestId).catch((error61) => {
27462
+ reportOperationFailure(socket, command2.requestId, "", error61);
26155
27463
  });
26156
- }).catch((error61) => {
26157
- reportOperationFailure(
27464
+ return;
27465
+ }
27466
+ if (kind !== SESSION_PROMPT_KIND) {
27467
+ throw new Error("Unsupported protocol message");
27468
+ }
27469
+ const command = decodeSessionPromptCommand(encoded);
27470
+ if (activeTurns.has(command.commandId)) {
27471
+ reportFailure(
26158
27472
  socket,
26159
- command2.interactionId,
26160
- command2.sessionId,
26161
- error61
27473
+ command,
27474
+ "duplicate_command",
27475
+ "The command is already running"
26162
27476
  );
26163
- });
26164
- return;
26165
- }
26166
- if (kind === AGENT_INSTALL_REQUEST_KIND) {
26167
- const command2 = decodeAgentInstallRequestCommand(encoded);
26168
- void installations.request(command2, signal).catch((error61) => {
26169
- console.error("Mobius Agent installation failed", error61);
26170
- });
26171
- return;
26172
- }
26173
- if (kind === AGENT_INSTALL_CANCEL_KIND) {
26174
- const command2 = decodeAgentInstallCancelCommand(encoded);
26175
- void installations.cancel(command2.requestId).catch((error61) => {
26176
- reportOperationFailure(socket, command2.requestId, "", error61);
26177
- });
26178
- return;
26179
- }
26180
- if (kind !== SESSION_PROMPT_KIND) {
26181
- throw new Error("Unsupported protocol message");
26182
- }
26183
- const command = decodeSessionPromptCommand(encoded);
26184
- if (activeTurns.has(command.commandId)) {
26185
- reportFailure(
27477
+ return;
27478
+ }
27479
+ const turn = new ActiveDaemonTurn(command, new AbortController());
27480
+ activeTurns.set(command.commandId, turn);
27481
+ void executePrompt(
26186
27482
  socket,
26187
27483
  command,
26188
- "duplicate_command",
26189
- "The command is already running"
26190
- );
26191
- return;
27484
+ config2,
27485
+ directories,
27486
+ audit,
27487
+ sessions,
27488
+ turn.signal
27489
+ ).catch((error61) => {
27490
+ console.error("Mobius daemon turn failed", error61);
27491
+ }).finally(() => {
27492
+ activeTurns.delete(command.commandId);
27493
+ connectionRefresh.afterTurnFinished();
27494
+ });
27495
+ } catch (error61) {
27496
+ console.error("Mobius daemon rejected a message", error61);
27497
+ socket.close(4008, "Invalid protocol message");
27498
+ rejectConnection();
26192
27499
  }
26193
- const turn = new ActiveDaemonTurn(command, new AbortController());
26194
- activeTurns.set(command.commandId, turn);
26195
- void executePrompt(
26196
- socket,
26197
- command,
26198
- config2,
26199
- directories,
26200
- audit,
26201
- sessions,
26202
- turn.signal
26203
- ).catch((error61) => {
26204
- console.error("Mobius daemon turn failed", error61);
26205
- }).finally(() => {
26206
- activeTurns.delete(command.commandId);
26207
- connectionRefresh.afterTurnFinished();
26208
- });
26209
- } catch (error61) {
26210
- console.error("Mobius daemon rejected a message", error61);
26211
- socket.close(4008, "Invalid protocol message");
26212
- rejectConnection();
27500
+ };
27501
+ socket.addEventListener("close", handleClose);
27502
+ socket.addEventListener("error", handleError);
27503
+ socket.addEventListener("message", handleMessage);
27504
+ if (signal.aborted) {
27505
+ stop();
26213
27506
  }
26214
- };
26215
- socket.addEventListener("close", handleClose);
26216
- socket.addEventListener("error", handleError);
26217
- socket.addEventListener("message", handleMessage);
26218
- if (signal.aborted) {
26219
- stop();
26220
- }
26221
- });
27507
+ });
27508
+ } finally {
27509
+ connectionController.abort();
27510
+ await Promise.all(
27511
+ [...nativeRequests.values()].map((request) => request.completion)
27512
+ );
27513
+ }
26222
27514
  }
26223
27515
  async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
26224
27516
  const installationAudit = new AuditInstallationEvents(audit);
26225
27517
  const stateDirectory = readStateDirectory();
26226
27518
  const specifications = supportedLocalAgentSpecifications();
26227
27519
  const installationExecutor = new FixedInstallationExecutor({
26228
- approvedRoot: config2.approvedRoot,
27520
+ defaultWorkspace: config2.defaultWorkspace,
26229
27521
  audit: installationAudit,
26230
27522
  catalog: config2.catalog,
26231
27523
  environment: process.env,
@@ -26234,13 +27526,12 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
26234
27526
  });
26235
27527
  await new AutomaticAdapterProvisioner(
26236
27528
  {
26237
- approvedRoot: config2.approvedRoot,
27529
+ defaultWorkspace: config2.defaultWorkspace,
26238
27530
  audit: installationAudit,
26239
27531
  catalog: config2.catalog,
26240
27532
  environment: process.env
26241
27533
  },
26242
27534
  new ManagedAdapterInstaller({
26243
- approvedRoot: config2.approvedRoot,
26244
27535
  environment: process.env,
26245
27536
  stateDirectory
26246
27537
  }),
@@ -26253,9 +27544,15 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
26253
27544
  );
26254
27545
  const directories = new WorkspaceDirectoryService(
26255
27546
  config2.bindingId,
26256
- config2.approvedRoot
27547
+ config2.defaultWorkspace
26257
27548
  );
26258
27549
  const installationObserver = new DaemonInstallationObserver();
27550
+ const nativeSessions = new NativeSessionService(
27551
+ config2,
27552
+ directories,
27553
+ sessions,
27554
+ audit
27555
+ );
26259
27556
  const installations = new AgentInstallationManager(
26260
27557
  specifications,
26261
27558
  new ConsoleInstallationAuthorizer(stateDirectory),
@@ -26275,6 +27572,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
26275
27572
  sessions,
26276
27573
  installations,
26277
27574
  installationObserver,
27575
+ nativeSessions,
26278
27576
  signal,
26279
27577
  timer
26280
27578
  );
@@ -26310,7 +27608,7 @@ process.once("SIGTERM", () => shutdown.abort());
26310
27608
  var USAGE = `Mobius CLI
26311
27609
 
26312
27610
  Usage:
26313
- mobius pair --url <relay-url> --code <pairing-code> --root <workspace>
27611
+ mobius pair --url <relay-url> --code <pairing-code> [--workspace <directory>]
26314
27612
  mobius start --channel <channel-id>
26315
27613
  mobius agents [--json]
26316
27614
 
@@ -26318,6 +27616,9 @@ Commands:
26318
27616
  pair Pair this machine and start its local daemon
26319
27617
  start Start the local daemon for a saved channel
26320
27618
  agents Show locally available and installable Agents
27619
+
27620
+ Pairing grants this browser access to the machine with your user account's permissions.
27621
+ New conversations default to ~/.mobius/workspace; choose other directories in the browser.
26321
27622
  `;
26322
27623
  async function main() {
26323
27624
  const command = process.argv[2];
@@ -26330,8 +27631,9 @@ async function main() {
26330
27631
  if (arguments_.length > 1 || arguments_.length === 1 && arguments_[0] !== "--json") {
26331
27632
  throw new Error("Usage: mobius agents [--json]");
26332
27633
  }
27634
+ await mkdir4(loadDefaultWorkspace(), { recursive: true, mode: 448 });
26333
27635
  const inventory = await discoverLocalAgentInventory({
26334
- approvedRoot: loadApprovedRoot(),
27636
+ defaultWorkspace: loadDefaultWorkspace(),
26335
27637
  environment: process.env,
26336
27638
  signal: shutdown.signal
26337
27639
  });
@@ -26373,6 +27675,12 @@ async function main() {
26373
27675
  process.stdout.write(
26374
27676
  "Starting the local daemon. Keep this terminal open.\n\n"
26375
27677
  );
27678
+ process.stdout.write(
27679
+ "This paired browser can use the machine with your user account's permissions.\n"
27680
+ );
27681
+ process.stdout.write(`Default workspace: ${config2.defaultWorkspace}
27682
+
27683
+ `);
26376
27684
  const audit = new AuditStore(config2.auditDatabasePath);
26377
27685
  try {
26378
27686
  await runDaemon(config2, audit, shutdown.signal);