@moxt-ai/mobius 0.0.4 → 0.0.5

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 (80) hide show
  1. package/dist/acp/session-manager.d.ts.map +1 -1
  2. package/dist/acp/session-manager.js +15 -29
  3. package/dist/acp/session-manager.js.map +1 -1
  4. package/dist/agent-catalog/agent-diagnostics.d.ts +3 -0
  5. package/dist/agent-catalog/agent-diagnostics.d.ts.map +1 -0
  6. package/dist/agent-catalog/agent-diagnostics.js +62 -0
  7. package/dist/agent-catalog/agent-diagnostics.js.map +1 -0
  8. package/dist/agent-catalog/discover-local-agents.d.ts +77 -0
  9. package/dist/agent-catalog/discover-local-agents.d.ts.map +1 -0
  10. package/dist/agent-catalog/discover-local-agents.js +478 -0
  11. package/dist/agent-catalog/discover-local-agents.js.map +1 -0
  12. package/dist/agent-catalog/local-agent.d.ts +38 -0
  13. package/dist/agent-catalog/local-agent.d.ts.map +1 -0
  14. package/dist/agent-catalog/local-agent.js +93 -0
  15. package/dist/agent-catalog/local-agent.js.map +1 -0
  16. package/dist/agent-catalog/publish-agent-catalog.d.ts +3 -0
  17. package/dist/agent-catalog/publish-agent-catalog.d.ts.map +1 -0
  18. package/dist/agent-catalog/publish-agent-catalog.js +58 -0
  19. package/dist/agent-catalog/publish-agent-catalog.js.map +1 -0
  20. package/dist/agent-installation/agent-installation-manager.d.ts +60 -0
  21. package/dist/agent-installation/agent-installation-manager.d.ts.map +1 -0
  22. package/dist/agent-installation/agent-installation-manager.js +242 -0
  23. package/dist/agent-installation/agent-installation-manager.js.map +1 -0
  24. package/dist/agent-installation/automatic-adapter-provisioner.d.ts +18 -0
  25. package/dist/agent-installation/automatic-adapter-provisioner.d.ts.map +1 -0
  26. package/dist/agent-installation/automatic-adapter-provisioner.js +116 -0
  27. package/dist/agent-installation/automatic-adapter-provisioner.js.map +1 -0
  28. package/dist/agent-installation/fixed-agent-install-recipes.d.ts +20 -0
  29. package/dist/agent-installation/fixed-agent-install-recipes.d.ts.map +1 -0
  30. package/dist/agent-installation/fixed-agent-install-recipes.js +163 -0
  31. package/dist/agent-installation/fixed-agent-install-recipes.js.map +1 -0
  32. package/dist/agent-installation/fixed-installation-executor.d.ts +20 -0
  33. package/dist/agent-installation/fixed-installation-executor.d.ts.map +1 -0
  34. package/dist/agent-installation/fixed-installation-executor.js +213 -0
  35. package/dist/agent-installation/fixed-installation-executor.js.map +1 -0
  36. package/dist/agent-installation/installation-grants.d.ts +2 -0
  37. package/dist/agent-installation/installation-grants.d.ts.map +1 -0
  38. package/dist/agent-installation/installation-grants.js +52 -0
  39. package/dist/agent-installation/installation-grants.js.map +1 -0
  40. package/dist/agent-installation/local-installation-authorizer.d.ts +7 -0
  41. package/dist/agent-installation/local-installation-authorizer.d.ts.map +1 -0
  42. package/dist/agent-installation/local-installation-authorizer.js +32 -0
  43. package/dist/agent-installation/local-installation-authorizer.js.map +1 -0
  44. package/dist/agent-installation/managed-adapter-installer.d.ts +21 -0
  45. package/dist/agent-installation/managed-adapter-installer.d.ts.map +1 -0
  46. package/dist/agent-installation/managed-adapter-installer.js +161 -0
  47. package/dist/agent-installation/managed-adapter-installer.js.map +1 -0
  48. package/dist/agent-installation/verified-download.d.ts +24 -0
  49. package/dist/agent-installation/verified-download.d.ts.map +1 -0
  50. package/dist/agent-installation/verified-download.js +158 -0
  51. package/dist/agent-installation/verified-download.js.map +1 -0
  52. package/dist/audit/audit-store.d.ts +1 -0
  53. package/dist/audit/audit-store.d.ts.map +1 -1
  54. package/dist/audit/audit-store.js +12 -4
  55. package/dist/audit/audit-store.js.map +1 -1
  56. package/dist/cli.js +4430 -1573
  57. package/dist/cli.js.map +4 -4
  58. package/dist/config.d.ts +6 -10
  59. package/dist/config.d.ts.map +1 -1
  60. package/dist/config.js +10 -53
  61. package/dist/config.js.map +1 -1
  62. package/dist/index.js +3233 -464
  63. package/dist/index.js.map +4 -4
  64. package/dist/pairing/pair-local-machine.d.ts +2 -2
  65. package/dist/pairing/pair-local-machine.d.ts.map +1 -1
  66. package/dist/pairing/pair-local-machine.js +41 -20
  67. package/dist/pairing/pair-local-machine.js.map +1 -1
  68. package/dist/process/executable.d.ts +40 -0
  69. package/dist/process/executable.d.ts.map +1 -0
  70. package/dist/process/executable.js +313 -0
  71. package/dist/process/executable.js.map +1 -0
  72. package/dist/run-daemon.d.ts +1 -1
  73. package/dist/run-daemon.d.ts.map +1 -1
  74. package/dist/run-daemon.js +182 -12
  75. package/dist/run-daemon.js.map +1 -1
  76. package/dist/storage/state-directory.d.ts +2 -0
  77. package/dist/storage/state-directory.d.ts.map +1 -0
  78. package/dist/storage/state-directory.js +10 -0
  79. package/dist/storage/state-directory.js.map +1 -0
  80. package/package.json +3 -5
package/dist/index.js CHANGED
@@ -23,65 +23,466 @@ var createAcpClient = (handler) => client({ name: "mobius" }).onRequest(
23
23
 
24
24
  // src/config.ts
25
25
  import { fileURLToPath } from "node:url";
26
- var repositoryRoot = fileURLToPath(new URL("../../../", import.meta.url));
27
- function adapterEntrypoint(packageName) {
28
- return fileURLToPath(new URL("./index.js", import.meta.resolve(packageName)));
26
+
27
+ // src/agent-catalog/local-agent.ts
28
+ import { realpathSync, statSync } from "node:fs";
29
+
30
+ // src/process/executable.ts
31
+ import { spawn } from "node:child_process";
32
+ import { constants } from "node:fs";
33
+ import { access, realpath, stat } from "node:fs/promises";
34
+ import { isAbsolute, join, relative, resolve } from "node:path";
35
+ var DEFAULT_OUTPUT_LIMIT_BYTES = 16 * 1024;
36
+ var PROCESS_GRACEFUL_STOP_TIMEOUT_MILLISECONDS = 1e3;
37
+ var PROCESS_FORCED_STOP_TIMEOUT_MILLISECONDS = 5e3;
38
+ var ExecutableIdentity = class {
39
+ changedAtMilliseconds;
40
+ device;
41
+ inode;
42
+ realPath;
43
+ requestedPath;
44
+ size;
45
+ constructor(requestedPath, realPath_, device, inode, size, changedAtMilliseconds) {
46
+ this.changedAtMilliseconds = changedAtMilliseconds;
47
+ this.device = device;
48
+ this.inode = inode;
49
+ this.realPath = realPath_;
50
+ this.requestedPath = requestedPath;
51
+ this.size = size;
52
+ }
53
+ matches = (candidate) => this.requestedPath === candidate.requestedPath && this.realPath === candidate.realPath && this.device === candidate.device && this.inode === candidate.inode && this.size === candidate.size && this.changedAtMilliseconds === candidate.changedAtMilliseconds;
54
+ };
55
+ var ResolvedExecutable = class {
56
+ identity;
57
+ path;
58
+ constructor(identity) {
59
+ this.identity = identity;
60
+ this.path = identity.realPath;
61
+ }
62
+ };
63
+ var ProcessExecutionError = class extends Error {
64
+ code;
65
+ constructor(code, message, options = {}) {
66
+ super(message, options);
67
+ this.code = code;
68
+ this.name = "ProcessExecutionError";
69
+ }
70
+ };
71
+ var ProcessResult = class {
72
+ stderr;
73
+ stdout;
74
+ constructor(stdout, stderr) {
75
+ this.stderr = stderr;
76
+ this.stdout = stdout;
77
+ }
78
+ };
79
+ function isInside(root, candidate) {
80
+ const difference = relative(root, candidate);
81
+ return difference.length === 0 || !difference.startsWith("..") && !isAbsolute(difference);
82
+ }
83
+ function executableExtensions(executableName, environment, platform) {
84
+ if (platform !== "win32") {
85
+ return [""];
86
+ }
87
+ if (/\.[a-zA-Z0-9]+$/.test(executableName)) {
88
+ return [""];
89
+ }
90
+ const configured = environment["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD";
91
+ const extensions = configured.split(";").map((extension) => extension.trim()).filter((extension) => extension.length > 0).map(
92
+ (extension) => extension.startsWith(".") ? extension : `.${extension}`
93
+ );
94
+ return extensions.length > 0 ? extensions : [".EXE"];
95
+ }
96
+ async function resolveExecutablePath(requestedPath, platform = process.platform) {
97
+ const absoluteRequestedPath = resolve(requestedPath);
98
+ try {
99
+ const resolvedPath = await realpath(absoluteRequestedPath);
100
+ const metadata = await stat(resolvedPath);
101
+ if (!metadata.isFile()) {
102
+ return null;
103
+ }
104
+ if (platform !== "win32") {
105
+ await access(resolvedPath, constants.X_OK);
106
+ }
107
+ return new ResolvedExecutable(
108
+ new ExecutableIdentity(
109
+ absoluteRequestedPath,
110
+ resolvedPath,
111
+ metadata.dev,
112
+ metadata.ino,
113
+ metadata.size,
114
+ metadata.ctimeMs
115
+ )
116
+ );
117
+ } catch {
118
+ return null;
119
+ }
120
+ }
121
+ async function findExecutableOnPath(executableName, environment, approvedRoot, platform = process.platform) {
122
+ const encodedPath = environment["PATH"] ?? "";
123
+ const separator = platform === "win32" ? ";" : ":";
124
+ const approvedRealPath = await realpath(resolve(approvedRoot));
125
+ const extensions = executableExtensions(
126
+ executableName,
127
+ environment,
128
+ platform
129
+ );
130
+ for (const entry of encodedPath.split(separator)) {
131
+ if (entry.length === 0) {
132
+ continue;
133
+ }
134
+ for (const extension of extensions) {
135
+ const requestedPath = resolve(
136
+ join(entry, `${executableName}${extension}`)
137
+ );
138
+ const executable = await resolveExecutablePath(requestedPath, platform);
139
+ if (executable !== null && !isInside(approvedRealPath, requestedPath) && !isInside(approvedRealPath, executable.path)) {
140
+ return executable;
141
+ }
142
+ }
143
+ }
144
+ return null;
145
+ }
146
+ async function revalidateExecutable(identity, platform = process.platform) {
147
+ const current = await resolveExecutablePath(identity.requestedPath, platform);
148
+ return current !== null && identity.matches(current.identity);
149
+ }
150
+ function appendBounded(chunks, chunk, currentBytes, limitBytes) {
151
+ const nextBytes = currentBytes + chunk.byteLength;
152
+ if (nextBytes > limitBytes) {
153
+ throw new ProcessExecutionError(
154
+ "process_output_limit",
155
+ "The executable produced too much output"
156
+ );
157
+ }
158
+ chunks.push(chunk);
159
+ return nextBytes;
160
+ }
161
+ function terminateProcess(child) {
162
+ if (child.exitCode !== null || child.signalCode !== null || child.pid === void 0) {
163
+ return;
164
+ }
165
+ if (process.platform !== "win32") {
166
+ try {
167
+ process.kill(-child.pid, "SIGTERM");
168
+ return;
169
+ } catch {
170
+ }
171
+ }
172
+ child.kill("SIGTERM");
173
+ }
174
+ function forceTerminateProcess(child) {
175
+ if (child.exitCode !== null || child.signalCode !== null || child.pid === void 0) {
176
+ return;
177
+ }
178
+ if (process.platform !== "win32") {
179
+ try {
180
+ process.kill(-child.pid, "SIGKILL");
181
+ return;
182
+ } catch {
183
+ }
184
+ }
185
+ child.kill("SIGKILL");
186
+ }
187
+ async function stopProcessTree(child) {
188
+ if (child.exitCode !== null || child.signalCode !== null) {
189
+ return;
190
+ }
191
+ terminateProcess(child);
192
+ const exitedAfterTermination = await waitForProcessExit(
193
+ child,
194
+ PROCESS_GRACEFUL_STOP_TIMEOUT_MILLISECONDS
195
+ );
196
+ if (exitedAfterTermination) {
197
+ return;
198
+ }
199
+ forceTerminateProcess(child);
200
+ const exitedAfterForce = await waitForProcessExit(
201
+ child,
202
+ PROCESS_FORCED_STOP_TIMEOUT_MILLISECONDS
203
+ );
204
+ if (!exitedAfterForce) {
205
+ throw new ProcessExecutionError(
206
+ "process_termination_timeout",
207
+ "The executable process could not be stopped"
208
+ );
209
+ }
210
+ }
211
+ function waitForProcessExit(child, timeoutMilliseconds) {
212
+ if (child.exitCode !== null || child.signalCode !== null) {
213
+ return Promise.resolve(true);
214
+ }
215
+ return new Promise((resolveExit) => {
216
+ let settled = false;
217
+ const finish = (exited) => {
218
+ if (settled) {
219
+ return;
220
+ }
221
+ settled = true;
222
+ clearTimeout(timeout);
223
+ child.removeListener("exit", handleExit);
224
+ resolveExit(exited);
225
+ };
226
+ const handleExit = () => finish(true);
227
+ const timeout = setTimeout(() => finish(false), timeoutMilliseconds);
228
+ child.once("exit", handleExit);
229
+ if (child.exitCode !== null || child.signalCode !== null) {
230
+ finish(true);
231
+ }
232
+ });
233
+ }
234
+ async function runBoundedProcess(options) {
235
+ options.signal.throwIfAborted();
236
+ const outputLimitBytes = options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES;
237
+ const child = spawn(options.command, [...options.arguments], {
238
+ cwd: options.cwd,
239
+ detached: process.platform !== "win32",
240
+ env: { ...options.environment },
241
+ shell: false,
242
+ stdio: ["ignore", "pipe", "pipe"]
243
+ });
244
+ const stdout = [];
245
+ const stderr = [];
246
+ let stdoutBytes = 0;
247
+ let stderrBytes = 0;
248
+ let interruptionFailure = null;
249
+ let interruptionCleanupFailure = null;
250
+ let stopping = false;
251
+ const interrupted = Promise.withResolvers();
252
+ const interrupt = (failure) => {
253
+ if (stopping) {
254
+ return;
255
+ }
256
+ stopping = true;
257
+ interruptionFailure = failure;
258
+ const stop = async () => {
259
+ try {
260
+ await stopProcessTree(child);
261
+ } catch (error61) {
262
+ interruptionCleanupFailure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
263
+ "process_termination_failed",
264
+ "The executable process could not be stopped",
265
+ { cause: error61 }
266
+ );
267
+ } finally {
268
+ interrupted.resolve();
269
+ }
270
+ };
271
+ void stop();
272
+ };
273
+ const collectStdout = (chunk) => {
274
+ if (interruptionFailure !== null) {
275
+ return;
276
+ }
277
+ try {
278
+ stdoutBytes = appendBounded(stdout, chunk, stdoutBytes, outputLimitBytes);
279
+ } catch (error61) {
280
+ const failure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
281
+ "process_output_limit",
282
+ "The executable produced too much output",
283
+ { cause: error61 }
284
+ );
285
+ interrupt(failure);
286
+ }
287
+ };
288
+ const collectStderr = (chunk) => {
289
+ if (interruptionFailure !== null) {
290
+ return;
291
+ }
292
+ try {
293
+ stderrBytes = appendBounded(stderr, chunk, stderrBytes, outputLimitBytes);
294
+ } catch (error61) {
295
+ const failure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
296
+ "process_output_limit",
297
+ "The executable produced too much output",
298
+ { cause: error61 }
299
+ );
300
+ interrupt(failure);
301
+ }
302
+ };
303
+ child.stdout?.on("data", collectStdout);
304
+ child.stderr?.on("data", collectStderr);
305
+ const timeout = setTimeout(() => {
306
+ interrupt(
307
+ new ProcessExecutionError(
308
+ "process_timeout",
309
+ "The executable probe timed out"
310
+ )
311
+ );
312
+ }, options.timeoutMilliseconds);
313
+ const abort = () => interrupt(
314
+ new ProcessExecutionError(
315
+ "process_cancelled",
316
+ "The executable probe was cancelled",
317
+ { cause: options.signal.reason }
318
+ )
319
+ );
320
+ options.signal.addEventListener("abort", abort, { once: true });
321
+ if (options.signal.aborted) {
322
+ abort();
323
+ }
324
+ try {
325
+ const exit = new Promise((resolveExit, rejectExit) => {
326
+ child.once("error", rejectExit);
327
+ child.once("exit", (code) => resolveExit(code ?? 1));
328
+ });
329
+ const exitCode = await Promise.race([
330
+ exit,
331
+ interrupted.promise.then(() => -1)
332
+ ]);
333
+ if (interruptionCleanupFailure !== null) {
334
+ throw interruptionCleanupFailure;
335
+ }
336
+ if (interruptionFailure !== null) {
337
+ throw interruptionFailure;
338
+ }
339
+ if (exitCode !== 0) {
340
+ throw new ProcessExecutionError(
341
+ "process_exit_failed",
342
+ "The executable probe failed"
343
+ );
344
+ }
345
+ return new ProcessResult(
346
+ Buffer.concat(stdout).toString("utf8"),
347
+ Buffer.concat(stderr).toString("utf8")
348
+ );
349
+ } catch (error61) {
350
+ if (error61 instanceof ProcessExecutionError) {
351
+ throw error61;
352
+ }
353
+ throw new ProcessExecutionError(
354
+ "process_spawn_failed",
355
+ "The executable could not be started",
356
+ { cause: error61 }
357
+ );
358
+ } finally {
359
+ clearTimeout(timeout);
360
+ options.signal.removeEventListener("abort", abort);
361
+ child.stdout?.removeListener("data", collectStdout);
362
+ child.stderr?.removeListener("data", collectStderr);
363
+ if (stopping) {
364
+ await interrupted.promise;
365
+ } else {
366
+ await stopProcessTree(child);
367
+ }
368
+ }
369
+ }
370
+
371
+ // src/agent-catalog/local-agent.ts
372
+ function readTrustedIdentity(path) {
373
+ const resolvedPath = realpathSync(path);
374
+ const metadata = statSync(resolvedPath);
375
+ return new ExecutableIdentity(
376
+ resolvedPath,
377
+ resolvedPath,
378
+ metadata.dev,
379
+ metadata.ino,
380
+ metadata.size,
381
+ metadata.ctimeMs
382
+ );
29
383
  }
30
384
  var LocalAgentDefinition = class {
385
+ adapterIdentity;
386
+ adapterPath;
387
+ adapterVersion;
388
+ agentIdentity;
389
+ agentVersion;
31
390
  arguments;
32
391
  command;
392
+ environment;
33
393
  id;
34
394
  label;
35
- constructor(id, label, command, arguments_) {
395
+ constructor(id, label, command, arguments_, environment = {}, adapterVersion = "", agentVersion = "", adapterIdentity = readTrustedIdentity(command), agentIdentity = adapterIdentity) {
396
+ this.adapterIdentity = adapterIdentity;
397
+ this.adapterPath = adapterIdentity.realPath;
398
+ this.adapterVersion = adapterVersion;
399
+ this.agentIdentity = agentIdentity;
400
+ this.agentVersion = agentVersion;
36
401
  this.id = id;
37
402
  this.label = label;
38
- this.command = command;
39
- this.arguments = arguments_;
403
+ this.command = adapterIdentity.realPath;
404
+ this.arguments = Object.freeze([...arguments_]);
405
+ this.environment = Object.freeze({ ...environment });
40
406
  }
41
407
  };
42
- function loadLocalAgents() {
43
- const overridePath = process.env["MOBIUS_ACP_AGENT_PATH"]?.trim();
44
- if (overridePath !== void 0 && overridePath.length > 0) {
45
- return [
46
- new LocalAgentDefinition("codex", "Codex", process.execPath, [
47
- overridePath,
48
- "--agent-name",
49
- "Codex"
50
- ]),
51
- new LocalAgentDefinition("claude", "Claude Code", process.execPath, [
52
- overridePath,
53
- "--agent-name",
54
- "Claude Code"
55
- ])
56
- ];
408
+ var InstallableAgentDefinition = class {
409
+ id;
410
+ label;
411
+ requiresLocalApproval;
412
+ constructor(id, label, requiresLocalApproval = true) {
413
+ this.id = id;
414
+ this.label = label;
415
+ this.requiresLocalApproval = requiresLocalApproval;
57
416
  }
58
- return [
59
- new LocalAgentDefinition("codex", "Codex", process.execPath, [
60
- adapterEntrypoint("@agentclientprotocol/codex-acp")
61
- ]),
62
- new LocalAgentDefinition("claude", "Claude Code", process.execPath, [
63
- adapterEntrypoint("@agentclientprotocol/claude-agent-acp")
64
- ])
65
- ];
417
+ };
418
+ var LocalAgentCatalogSnapshot = class {
419
+ agents;
420
+ installableAgents;
421
+ constructor(agents, installableAgents) {
422
+ this.agents = Object.freeze([...agents]);
423
+ this.installableAgents = Object.freeze([...installableAgents]);
424
+ }
425
+ };
426
+ var LocalAgentCatalogError = class extends Error {
427
+ code = "agent_not_ready";
428
+ constructor() {
429
+ super("The requested local Agent is not ready");
430
+ this.name = "LocalAgentCatalogError";
431
+ }
432
+ };
433
+ var LocalAgentCatalog = class {
434
+ #snapshot;
435
+ constructor(agents, installableAgents) {
436
+ this.#snapshot = this.validate(agents, installableAgents);
437
+ }
438
+ find = (agentId) => {
439
+ for (const agent of this.#snapshot.agents) {
440
+ if (agent.id === agentId) {
441
+ return agent;
442
+ }
443
+ }
444
+ throw new LocalAgentCatalogError();
445
+ };
446
+ read = () => this.#snapshot;
447
+ transition = (agents, installableAgents) => {
448
+ const next = this.validate(agents, installableAgents);
449
+ this.#snapshot = next;
450
+ return next;
451
+ };
452
+ validate = (agents, installableAgents) => {
453
+ const identifiers = /* @__PURE__ */ new Set();
454
+ for (const agent of agents) {
455
+ if (identifiers.has(agent.id)) {
456
+ throw new Error("The local Agent catalog contains duplicate entries");
457
+ }
458
+ identifiers.add(agent.id);
459
+ }
460
+ for (const agent of installableAgents) {
461
+ if (identifiers.has(agent.id)) {
462
+ throw new Error("A local Agent cannot be ready and installable");
463
+ }
464
+ identifiers.add(agent.id);
465
+ }
466
+ return new LocalAgentCatalogSnapshot(agents, installableAgents);
467
+ };
468
+ };
469
+
470
+ // src/config.ts
471
+ function loadApprovedRoot() {
472
+ return process.env["MOBIUS_APPROVED_ROOT"] ?? process.cwd();
66
473
  }
67
- function createDaemonConfig(connection) {
474
+ function createDaemonConfig(connection, agents = [], installableAgents = []) {
68
475
  return {
69
- agents: loadLocalAgents(),
476
+ agents,
70
477
  approvedRoot: connection.approvedRoot,
71
478
  auditDatabasePath: connection.auditDatabasePath,
72
479
  bindingId: "workspace",
480
+ catalog: new LocalAgentCatalog(agents, installableAgents),
73
481
  channelId: connection.channelId,
482
+ installableAgents,
74
483
  relayUrl: connection.relayUrl
75
484
  };
76
485
  }
77
- function findLocalAgent(agents, agentId) {
78
- for (const agent of agents) {
79
- if (agent.id === agentId) {
80
- return agent;
81
- }
82
- }
83
- throw new Error("The requested local agent is not approved");
84
- }
85
486
  function loadDaemonConfig() {
86
487
  const relayUrl = new URL(
87
488
  // biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
@@ -95,22 +496,26 @@ function loadDaemonConfig() {
95
496
  if (relayUrl.protocol !== "ws:" && relayUrl.protocol !== "wss:") {
96
497
  throw new Error("MOBIUS_RELAY_URL must use ws or wss");
97
498
  }
98
- return createDaemonConfig({
99
- // biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
100
- approvedRoot: process.env["MOBIUS_APPROVED_ROOT"] ?? repositoryRoot,
101
- auditDatabasePath: (
499
+ return createDaemonConfig(
500
+ {
501
+ approvedRoot: loadApprovedRoot(),
502
+ auditDatabasePath: (
503
+ // biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
504
+ process.env["MOBIUS_AUDIT_DATABASE"] ?? fileURLToPath(
505
+ new URL("../../../.mobius/daemon.sqlite", import.meta.url)
506
+ )
507
+ ),
102
508
  // biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
103
- process.env["MOBIUS_AUDIT_DATABASE"] ?? fileURLToPath(new URL("../../../.mobius/daemon.sqlite", import.meta.url))
104
- ),
105
- // biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
106
- channelId: process.env["MOBIUS_CHANNEL_ID"] ?? "local",
107
- relayUrl
108
- });
509
+ channelId: process.env["MOBIUS_CHANNEL_ID"] ?? "local",
510
+ relayUrl
511
+ },
512
+ [],
513
+ []
514
+ );
109
515
  }
110
516
 
111
517
  // src/acp/session-manager.ts
112
- import { spawn } from "node:child_process";
113
- import { once } from "node:events";
518
+ import { spawn as spawn2 } from "node:child_process";
114
519
  import {
115
520
  methods as methods2,
116
521
  ndJsonStream,
@@ -2077,8 +2482,8 @@ function emoji() {
2077
2482
  }
2078
2483
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
2079
2484
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
2080
- var mac = (delimiter) => {
2081
- const escapedDelim = escapeRegex(delimiter ?? ":");
2485
+ var mac = (delimiter2) => {
2486
+ const escapedDelim = escapeRegex(delimiter2 ?? ":");
2082
2487
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
2083
2488
  };
2084
2489
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -19004,6 +19409,14 @@ var SESSION_ELICITATION_RESPOND_KIND = "session.elicitation.respond";
19004
19409
  var SESSION_ELICITATION_RESOLVED_KIND = "session.elicitation.resolved";
19005
19410
  var SESSION_OPERATION_FAILED_KIND = "session.operation.failed";
19006
19411
  var AGENT_CONTENT_KIND = "agent.content";
19412
+ var AGENT_INSTALL_REQUEST_KIND = "agent.install.request";
19413
+ var AGENT_INSTALL_CANCEL_KIND = "agent.install.cancel";
19414
+ var AGENT_INSTALL_AWAITING_APPROVAL_KIND = "agent.install.awaiting_approval";
19415
+ var AGENT_INSTALL_STARTED_KIND = "agent.install.started";
19416
+ var AGENT_INSTALL_PROGRESS_KIND = "agent.install.progress";
19417
+ var AGENT_INSTALL_COMPLETED_KIND = "agent.install.completed";
19418
+ var AGENT_INSTALL_FAILED_KIND = "agent.install.failed";
19419
+ var AGENT_INSTALL_CANCELLED_KIND = "agent.install.cancelled";
19007
19420
  var IDENTIFIER_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
19008
19421
  var DIRECTORY_IDENTIFIER_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,2047}$/;
19009
19422
  var IDENTIFIER = external_exports.string().min(1).max(128).regex(IDENTIFIER_PATTERN);
@@ -19277,6 +19690,55 @@ var agentContentEventSchema = external_exports.strictObject({
19277
19690
  protocolVersion: PROTOCOL_VERSION,
19278
19691
  sessionId: IDENTIFIER
19279
19692
  }).readonly();
19693
+ var agentInstallIdentityFields = {
19694
+ agentId: IDENTIFIER,
19695
+ protocolVersion: PROTOCOL_VERSION,
19696
+ requestId: IDENTIFIER
19697
+ };
19698
+ var agentInstallRequestCommandSchema = external_exports.strictObject({
19699
+ ...agentInstallIdentityFields,
19700
+ kind: external_exports.literal(AGENT_INSTALL_REQUEST_KIND)
19701
+ }).readonly();
19702
+ var agentInstallCancelCommandSchema = external_exports.strictObject({
19703
+ kind: external_exports.literal(AGENT_INSTALL_CANCEL_KIND),
19704
+ protocolVersion: PROTOCOL_VERSION,
19705
+ requestId: IDENTIFIER
19706
+ }).readonly();
19707
+ var agentInstallAwaitingApprovalEventSchema = external_exports.strictObject({
19708
+ ...agentInstallIdentityFields,
19709
+ kind: external_exports.literal(AGENT_INSTALL_AWAITING_APPROVAL_KIND)
19710
+ }).readonly();
19711
+ var agentInstallStartedEventSchema = external_exports.strictObject({
19712
+ ...agentInstallIdentityFields,
19713
+ kind: external_exports.literal(AGENT_INSTALL_STARTED_KIND)
19714
+ }).readonly();
19715
+ var installationStageSchema = external_exports.enum([
19716
+ "downloading",
19717
+ "verifying",
19718
+ "installing_agent",
19719
+ "installing_adapter",
19720
+ "checking_acp"
19721
+ ]);
19722
+ var agentInstallProgressEventSchema = external_exports.strictObject({
19723
+ ...agentInstallIdentityFields,
19724
+ kind: external_exports.literal(AGENT_INSTALL_PROGRESS_KIND),
19725
+ stage: installationStageSchema
19726
+ }).readonly();
19727
+ var agentInstallCompletedEventSchema = external_exports.strictObject({
19728
+ ...agentInstallIdentityFields,
19729
+ authenticationMayBeRequired: external_exports.boolean(),
19730
+ kind: external_exports.literal(AGENT_INSTALL_COMPLETED_KIND)
19731
+ }).readonly();
19732
+ var agentInstallFailedEventSchema = external_exports.strictObject({
19733
+ ...agentInstallIdentityFields,
19734
+ code: IDENTIFIER,
19735
+ kind: external_exports.literal(AGENT_INSTALL_FAILED_KIND),
19736
+ message: external_exports.string().min(1).max(500)
19737
+ }).readonly();
19738
+ var agentInstallCancelledEventSchema = external_exports.strictObject({
19739
+ ...agentInstallIdentityFields,
19740
+ kind: external_exports.literal(AGENT_INSTALL_CANCELLED_KIND)
19741
+ }).readonly();
19280
19742
  function parseJson(encoded) {
19281
19743
  try {
19282
19744
  return JSON.parse(encoded);
@@ -19325,6 +19787,14 @@ var decodeSessionElicitationRespondCommand = (encoded) => decode3(encoded, sessi
19325
19787
  var encodeSessionElicitationResolvedEvent = (message) => encode3(message, sessionElicitationResolvedEventSchema);
19326
19788
  var encodeSessionOperationFailedEvent = (message) => encode3(message, sessionOperationFailedEventSchema);
19327
19789
  var encodeAgentContentEvent = (message) => encode3(message, agentContentEventSchema);
19790
+ var decodeAgentInstallRequestCommand = (encoded) => decode3(encoded, agentInstallRequestCommandSchema);
19791
+ var decodeAgentInstallCancelCommand = (encoded) => decode3(encoded, agentInstallCancelCommandSchema);
19792
+ var encodeAgentInstallAwaitingApprovalEvent = (message) => encode3(message, agentInstallAwaitingApprovalEventSchema);
19793
+ var encodeAgentInstallStartedEvent = (message) => encode3(message, agentInstallStartedEventSchema);
19794
+ var encodeAgentInstallProgressEvent = (message) => encode3(message, agentInstallProgressEventSchema);
19795
+ var encodeAgentInstallCompletedEvent = (message) => encode3(message, agentInstallCompletedEventSchema);
19796
+ var encodeAgentInstallFailedEvent = (message) => encode3(message, agentInstallFailedEventSchema);
19797
+ var encodeAgentInstallCancelledEvent = (message) => encode3(message, agentInstallCancelledEventSchema);
19328
19798
 
19329
19799
  // ../protocol/src/messages.ts
19330
19800
  var SESSION_PROMPT_KIND = "session.prompt";
@@ -20398,7 +20868,7 @@ function createWritableAcpStream(output2) {
20398
20868
  if (outputFailure !== void 0) {
20399
20869
  return Promise.reject(outputFailure);
20400
20870
  }
20401
- return new Promise((resolve2, reject) => {
20871
+ return new Promise((resolve6, reject) => {
20402
20872
  pendingWriteFailures.add(reject);
20403
20873
  output2.write(chunk, (error61) => {
20404
20874
  pendingWriteFailures.delete(reject);
@@ -20411,34 +20881,12 @@ function createWritableAcpStream(output2) {
20411
20881
  reject(outputFailure);
20412
20882
  return;
20413
20883
  }
20414
- resolve2();
20884
+ resolve6();
20415
20885
  });
20416
20886
  });
20417
20887
  }
20418
20888
  });
20419
20889
  }
20420
- async function stopAgentProcess(child) {
20421
- child.stdin?.end();
20422
- if (child.exitCode !== null) {
20423
- return;
20424
- }
20425
- child.kill("SIGTERM");
20426
- try {
20427
- await once(child, "exit", {
20428
- signal: AbortSignal.timeout(PROCESS_STOP_TIMEOUT_MILLISECONDS)
20429
- });
20430
- } catch {
20431
- if (child.exitCode === null) {
20432
- child.kill("SIGKILL");
20433
- try {
20434
- await once(child, "exit", {
20435
- signal: AbortSignal.timeout(PROCESS_STOP_TIMEOUT_MILLISECONDS)
20436
- });
20437
- } catch {
20438
- }
20439
- }
20440
- }
20441
- }
20442
20890
  function truncate2(value, maximumLength) {
20443
20891
  return value.length <= maximumLength ? value : `${value.slice(0, Math.max(0, maximumLength - 1))}\u2026`;
20444
20892
  }
@@ -20961,8 +21409,11 @@ var AcpProcessSession = class _AcpProcessSession {
20961
21409
  this.#getObserver = getObserver;
20962
21410
  this.#mobiusSessionId = mobiusSessionId;
20963
21411
  this.#store = store;
20964
- this.#child = spawn(agent.command, [...agent.arguments], {
21412
+ this.#child = spawn2(agent.adapterPath, [...agent.arguments], {
20965
21413
  cwd,
21414
+ detached: process.platform !== "win32",
21415
+ env: { ...process.env, ...agent.environment },
21416
+ shell: false,
20966
21417
  stdio: ["pipe", "pipe", "pipe"]
20967
21418
  });
20968
21419
  const stdin = this.#child.stdin;
@@ -20976,9 +21427,9 @@ var AcpProcessSession = class _AcpProcessSession {
20976
21427
  stderr.on("data", (chunk) => {
20977
21428
  process.stderr.write(`[${agent.id}-acp] ${chunk}`);
20978
21429
  });
20979
- this.#processExit = new Promise((resolve2) => {
20980
- this.#child.once("error", resolve2);
20981
- this.#child.once("exit", resolve2);
21430
+ this.#processExit = new Promise((resolve6) => {
21431
+ this.#child.once("error", resolve6);
21432
+ this.#child.once("exit", resolve6);
20982
21433
  });
20983
21434
  const stream = ndJsonStream(
20984
21435
  createWritableAcpStream(stdin),
@@ -20988,6 +21439,18 @@ var AcpProcessSession = class _AcpProcessSession {
20988
21439
  }
20989
21440
  static start = async (options, getObserver, store) => {
20990
21441
  options.signal.throwIfAborted();
21442
+ const [adapterUnchanged, agentUnchanged] = await Promise.all([
21443
+ revalidateExecutable(options.agent.adapterIdentity),
21444
+ revalidateExecutable(options.agent.agentIdentity)
21445
+ ]);
21446
+ options.signal.throwIfAborted();
21447
+ if (!adapterUnchanged || !agentUnchanged) {
21448
+ throw new AcpExecutionError(
21449
+ "The local Agent runtime changed; restart Mobius before starting a new conversation",
21450
+ {},
21451
+ "agent_runtime_changed"
21452
+ );
21453
+ }
20991
21454
  const runtime = new _AcpProcessSession(
20992
21455
  options.agent,
20993
21456
  options.cwd,
@@ -21029,7 +21492,7 @@ var AcpProcessSession = class _AcpProcessSession {
21029
21492
  plan: {},
21030
21493
  session: { configOptions: { boolean: {} } }
21031
21494
  },
21032
- clientInfo: { name: "mobius", version: "0.0.2" },
21495
+ clientInfo: { name: "mobius", version: "0.0.3" },
21033
21496
  protocolVersion: PROTOCOL_VERSION2
21034
21497
  },
21035
21498
  { cancellationSignal: signal }
@@ -21667,7 +22130,7 @@ var AcpProcessSession = class _AcpProcessSession {
21667
22130
  };
21668
22131
  closeProcess = async () => {
21669
22132
  this.#connection.close();
21670
- await stopAgentProcess(this.#child);
22133
+ await stopProcessTree(this.#child);
21671
22134
  };
21672
22135
  race = async (operation, exitMessage, signal) => {
21673
22136
  signal.throwIfAborted();
@@ -21989,9 +22452,16 @@ var AuditStore = class {
21989
22452
  recorded_at TEXT NOT NULL,
21990
22453
  event TEXT NOT NULL,
21991
22454
  command_id TEXT NOT NULL,
21992
- session_id TEXT NOT NULL
22455
+ session_id TEXT NOT NULL,
22456
+ details_json TEXT NOT NULL DEFAULT '{}'
21993
22457
  ) STRICT
21994
22458
  `);
22459
+ const columns = this.database.prepare("PRAGMA table_info(audit_events)").all();
22460
+ if (!columns.some((column) => Reflect.get(column, "name") === "details_json")) {
22461
+ this.database.exec(
22462
+ "ALTER TABLE audit_events ADD COLUMN details_json TEXT NOT NULL DEFAULT '{}'"
22463
+ );
22464
+ }
21995
22465
  }
21996
22466
  record(record2) {
21997
22467
  this.database.prepare(
@@ -22000,14 +22470,16 @@ var AuditStore = class {
22000
22470
  recorded_at,
22001
22471
  event,
22002
22472
  command_id,
22003
- session_id
22004
- ) VALUES (?, ?, ?, ?)
22473
+ session_id,
22474
+ details_json
22475
+ ) VALUES (?, ?, ?, ?, ?)
22005
22476
  `
22006
22477
  ).run(
22007
22478
  (/* @__PURE__ */ new Date()).toISOString(),
22008
22479
  record2.event,
22009
22480
  record2.commandId,
22010
- record2.sessionId
22481
+ record2.sessionId,
22482
+ JSON.stringify(record2.details ?? {})
22011
22483
  );
22012
22484
  }
22013
22485
  close() {
@@ -22016,7 +22488,7 @@ var AuditStore = class {
22016
22488
  };
22017
22489
 
22018
22490
  // src/authorization/authorize-binding.ts
22019
- import { realpath, stat } from "node:fs/promises";
22491
+ import { realpath as realpath2, stat as stat2 } from "node:fs/promises";
22020
22492
  var LocalAuthorizationError = class extends Error {
22021
22493
  code = "local_authorization_failed";
22022
22494
  constructor(message, options = {}) {
@@ -22029,8 +22501,8 @@ async function authorizeBinding(requestedBindingId, approvedBindingId, approvedR
22029
22501
  throw new LocalAuthorizationError("The runtime binding is not authorized");
22030
22502
  }
22031
22503
  try {
22032
- const resolvedRoot = await realpath(approvedRoot);
22033
- const metadata = await stat(resolvedRoot);
22504
+ const resolvedRoot = await realpath2(approvedRoot);
22505
+ const metadata = await stat2(resolvedRoot);
22034
22506
  if (!metadata.isDirectory()) {
22035
22507
  throw new LocalAuthorizationError("The approved root is not a directory");
22036
22508
  }
@@ -22048,427 +22520,2543 @@ async function authorizeBinding(requestedBindingId, approvedBindingId, approvedR
22048
22520
  // src/pairing/pair-local-machine.ts
22049
22521
  import { randomUUID } from "node:crypto";
22050
22522
  import {
22051
- mkdir,
22052
- open as open2,
22053
- realpath as realpath2,
22054
- rename,
22055
- stat as stat2,
22523
+ mkdir as mkdir2,
22524
+ open as open4,
22525
+ realpath as realpath5,
22526
+ rename as rename2,
22527
+ stat as stat4,
22056
22528
  writeFile
22057
22529
  } from "node:fs/promises";
22058
- import { homedir, hostname as hostname3 } from "node:os";
22059
- import { basename, join, resolve } from "node:path";
22530
+ import { hostname as hostname3 } from "node:os";
22531
+ import { basename, join as join6, resolve as resolve4 } from "node:path";
22060
22532
  import { fetch } from "undici";
22061
22533
 
22062
- // src/network/environment-proxy.ts
22063
- import { EnvHttpProxyAgent } from "undici";
22064
- var environmentProxyDispatcher = new EnvHttpProxyAgent();
22534
+ // src/agent-catalog/discover-local-agents.ts
22535
+ import { spawn as spawn3 } from "node:child_process";
22536
+ import { realpath as realpath4 } from "node:fs/promises";
22537
+ import { isAbsolute as isAbsolute2, join as join5, relative as relative3, resolve as resolve3 } from "node:path";
22538
+ import {
22539
+ methods as methods3,
22540
+ ndJsonStream as ndJsonStream2,
22541
+ PROTOCOL_VERSION as PROTOCOL_VERSION3
22542
+ } from "@agentclientprotocol/sdk";
22065
22543
 
22066
- // src/pairing/pair-local-machine.ts
22067
- var PAIRING_TIMEOUT_MILLISECONDS = 2e4;
22068
- var PAIRING_CODE_PATTERN = /^[A-HJ-NP-Z2-9]{4}(?:-[A-HJ-NP-Z2-9]{4}){4}$/;
22069
- var IDENTIFIER_PATTERN3 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
22070
- var TOKEN_PATTERN = /^[a-zA-Z0-9_-]{32,256}$/;
22071
- var MAX_CONNECTION_FILE_LENGTH = 16 * 1024;
22072
- var CLAIM_RESPONSE_KEYS = ["channelId", "daemonToken"];
22073
- var SAVED_CONNECTION_KEYS = [
22074
- "approvedRoot",
22075
- "channelId",
22076
- "daemonToken",
22077
- "relayOrigin"
22078
- ];
22079
- var PairingClientError = class extends Error {
22544
+ // src/agent-installation/installation-grants.ts
22545
+ import { open as open2 } from "node:fs/promises";
22546
+ import { join as join2 } from "node:path";
22547
+ var MAX_GRANT_FILE_LENGTH = 16 * 1024;
22548
+ var AGENT_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
22549
+ function isRecord4(value) {
22550
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22551
+ }
22552
+ async function readGrantedAgents(stateDirectory) {
22553
+ const path = join2(stateDirectory, "installation-grants.json");
22554
+ let handle;
22555
+ try {
22556
+ handle = await open2(path, "r");
22557
+ } catch (error61) {
22558
+ if (error61 instanceof Error && "code" in error61 && error61.code === "ENOENT") {
22559
+ return /* @__PURE__ */ new Set();
22560
+ }
22561
+ throw error61;
22562
+ }
22563
+ try {
22564
+ const metadata = await handle.stat();
22565
+ if (!metadata.isFile() || metadata.size > MAX_GRANT_FILE_LENGTH || typeof process.getuid === "function" && metadata.uid !== process.getuid() || process.platform !== "win32" && (metadata.mode & 63) !== 0) {
22566
+ throw new Error("The local installation grant file is not private");
22567
+ }
22568
+ const encoded = await handle.readFile({ encoding: "utf8" });
22569
+ const value = JSON.parse(encoded);
22570
+ if (!isRecord4(value) || Object.keys(value).length !== 2 || value["version"] !== 1 || !Array.isArray(value["agents"])) {
22571
+ throw new Error("The local installation grant file is invalid");
22572
+ }
22573
+ const agents = /* @__PURE__ */ new Set();
22574
+ for (const candidate of value["agents"]) {
22575
+ if (typeof candidate !== "string" || !AGENT_ID_PATTERN.test(candidate) || agents.has(candidate)) {
22576
+ throw new Error("The local installation grant file is invalid");
22577
+ }
22578
+ agents.add(candidate);
22579
+ }
22580
+ return agents;
22581
+ } finally {
22582
+ await handle.close();
22583
+ }
22584
+ }
22585
+
22586
+ // src/agent-installation/managed-adapter-installer.ts
22587
+ import {
22588
+ mkdir,
22589
+ mkdtemp,
22590
+ open as open3,
22591
+ realpath as realpath3,
22592
+ rename,
22593
+ rm,
22594
+ stat as stat3
22595
+ } from "node:fs/promises";
22596
+ import { delimiter, dirname as dirname3, join as join3, relative as relative2 } from "node:path";
22597
+ var ADAPTER_INSTALL_TIMEOUT_MILLISECONDS = 2 * 60 * 1e3;
22598
+ var PACKAGE_METADATA_LIMIT_BYTES = 64 * 1024;
22599
+ var ManagedAdapterInstallError = class extends Error {
22080
22600
  code;
22081
22601
  constructor(code, message, options = {}) {
22082
22602
  super(message, options);
22083
22603
  this.code = code;
22084
- this.name = "PairingClientError";
22604
+ this.name = "ManagedAdapterInstallError";
22085
22605
  }
22086
22606
  };
22087
- function isRecord4(value) {
22607
+ function isRecord5(value) {
22088
22608
  return typeof value === "object" && value !== null && !Array.isArray(value);
22089
22609
  }
22090
- function hasExactKeys(value, expectedKeys) {
22091
- const keys = Object.keys(value);
22092
- return keys.length === expectedKeys.length && expectedKeys.every((key) => Object.hasOwn(value, key));
22610
+ function packagePath(root, packageName) {
22611
+ return join3(root, "node_modules", ...packageName.split("/"));
22093
22612
  }
22094
- function readSingleOption(arguments_, option) {
22095
- const values = [];
22096
- for (let index = 0; index < arguments_.length; index += 1) {
22097
- if (arguments_[index] !== option) {
22098
- continue;
22099
- }
22100
- const value = arguments_[index + 1];
22101
- if (value === void 0 || value.startsWith("--")) {
22102
- throw new PairingClientError(
22103
- "invalid_arguments",
22104
- `${option} requires a value`
22613
+ async function readPackageJson(path) {
22614
+ const handle = await open3(path, "r");
22615
+ try {
22616
+ const metadata = await handle.stat();
22617
+ if (!metadata.isFile() || metadata.size > PACKAGE_METADATA_LIMIT_BYTES) {
22618
+ throw new ManagedAdapterInstallError(
22619
+ "adapter_verification_failed",
22620
+ "The managed adapter package metadata is invalid"
22105
22621
  );
22106
22622
  }
22107
- values.push(value);
22108
- index += 1;
22109
- }
22110
- if (values.length !== 1) {
22111
- throw new PairingClientError(
22112
- "invalid_arguments",
22113
- `${option} must be provided exactly once`
22623
+ const value = JSON.parse(
22624
+ await handle.readFile({ encoding: "utf8" })
22114
22625
  );
22115
- }
22116
- return values[0] ?? "";
22117
- }
22118
- function assertKnownArguments(arguments_) {
22119
- for (let index = 0; index < arguments_.length; index += 2) {
22120
- const option = arguments_[index];
22121
- if (option !== "--url" && option !== "--code" && option !== "--root") {
22122
- throw new PairingClientError(
22123
- "invalid_arguments",
22124
- `Unknown pairing option: ${option ?? "missing option"}`
22626
+ if (!isRecord5(value)) {
22627
+ throw new ManagedAdapterInstallError(
22628
+ "adapter_verification_failed",
22629
+ "The managed adapter package metadata is invalid"
22125
22630
  );
22126
22631
  }
22632
+ return value;
22633
+ } finally {
22634
+ await handle.close();
22127
22635
  }
22128
22636
  }
22129
- function parseRelayOrigin(value) {
22130
- let url2;
22131
- try {
22132
- url2 = new URL(value);
22133
- } catch (error61) {
22134
- throw new PairingClientError(
22135
- "invalid_relay_url",
22136
- "The relay URL is invalid",
22137
- { cause: error61 }
22637
+ function readBinPath(packageMetadata, executableName) {
22638
+ const bin = packageMetadata["bin"];
22639
+ if (typeof bin === "string") {
22640
+ return bin;
22641
+ }
22642
+ if (isRecord5(bin) && typeof bin[executableName] === "string") {
22643
+ return bin[executableName];
22644
+ }
22645
+ throw new ManagedAdapterInstallError(
22646
+ "adapter_verification_failed",
22647
+ "The managed adapter executable declaration is invalid"
22648
+ );
22649
+ }
22650
+ async function validateManagedAdapter(root, specification) {
22651
+ const rootPath = await realpath3(root);
22652
+ const packageRoot = await realpath3(
22653
+ packagePath(rootPath, specification.adapterPackage)
22654
+ );
22655
+ const packageDifference = relative2(rootPath, packageRoot);
22656
+ if (packageDifference.startsWith("..") || packageDifference.length === 0) {
22657
+ throw new ManagedAdapterInstallError(
22658
+ "adapter_verification_failed",
22659
+ "The managed adapter package escaped its private cache"
22138
22660
  );
22139
22661
  }
22140
- if (url2.protocol !== "https:" && url2.protocol !== "http:") {
22141
- throw new PairingClientError(
22142
- "invalid_relay_url",
22143
- "The relay URL must use http or https"
22662
+ const metadata = await readPackageJson(join3(packageRoot, "package.json"));
22663
+ if (metadata["name"] !== specification.adapterPackage || metadata["version"] !== specification.adapterVersion) {
22664
+ throw new ManagedAdapterInstallError(
22665
+ "adapter_verification_failed",
22666
+ "The managed adapter package identity is invalid"
22144
22667
  );
22145
22668
  }
22146
- if (url2.username.length > 0 || url2.password.length > 0) {
22147
- throw new PairingClientError(
22148
- "invalid_relay_url",
22149
- "The relay URL cannot contain credentials"
22669
+ const declaredBin = readBinPath(metadata, specification.adapterExecutable);
22670
+ const executable = await resolveExecutablePath(
22671
+ join3(packageRoot, declaredBin)
22672
+ );
22673
+ if (executable === null) {
22674
+ throw new ManagedAdapterInstallError(
22675
+ "adapter_verification_failed",
22676
+ "The managed adapter executable is invalid"
22150
22677
  );
22151
22678
  }
22152
- if (url2.protocol === "http:" && url2.hostname !== "127.0.0.1" && url2.hostname !== "localhost") {
22153
- throw new PairingClientError(
22154
- "invalid_relay_url",
22155
- "A remote relay must use https"
22679
+ const executableDifference = relative2(rootPath, executable.path);
22680
+ if (executableDifference.startsWith("..") || executableDifference.length === 0) {
22681
+ throw new ManagedAdapterInstallError(
22682
+ "adapter_verification_failed",
22683
+ "The managed adapter executable escaped its private cache"
22156
22684
  );
22157
22685
  }
22158
- url2.hash = "";
22159
- url2.pathname = "/";
22160
- url2.search = "";
22161
- return url2;
22686
+ return executable;
22162
22687
  }
22163
- function parsePairingCode(value) {
22164
- const code = value.trim().toUpperCase();
22165
- if (!PAIRING_CODE_PATTERN.test(code)) {
22166
- throw new PairingClientError(
22167
- "invalid_pairing_code",
22168
- "The pairing code must match the code shown in the browser"
22688
+ var ManagedAdapterInstaller = class {
22689
+ #approvedRoot;
22690
+ #environment;
22691
+ #stateDirectory;
22692
+ constructor(options) {
22693
+ this.#approvedRoot = options.approvedRoot;
22694
+ this.#environment = options.environment;
22695
+ this.#stateDirectory = options.stateDirectory;
22696
+ }
22697
+ install = async (specification, signal) => {
22698
+ signal.throwIfAborted();
22699
+ const agentCache = join3(this.#stateDirectory, "adapters", specification.id);
22700
+ const published = join3(agentCache, specification.adapterVersion);
22701
+ try {
22702
+ const metadata = await stat3(published);
22703
+ if (metadata.isDirectory()) {
22704
+ return await validateManagedAdapter(published, specification);
22705
+ }
22706
+ } catch (error61) {
22707
+ if (!(error61 instanceof Error) || !("code" in error61) || error61.code !== "ENOENT") {
22708
+ throw error61;
22709
+ }
22710
+ }
22711
+ await mkdir(agentCache, { mode: 448, recursive: true });
22712
+ const staging = await mkdtemp(
22713
+ join3(agentCache, `.${specification.adapterVersion}.staging-`)
22169
22714
  );
22715
+ try {
22716
+ const npm = await findExecutableOnPath(
22717
+ process.platform === "win32" ? "npm.cmd" : "npm",
22718
+ this.#environment,
22719
+ this.#approvedRoot
22720
+ );
22721
+ if (npm === null) {
22722
+ throw new ManagedAdapterInstallError(
22723
+ "package_manager_not_found",
22724
+ "A supported npm executable is required to install the ACP adapter"
22725
+ );
22726
+ }
22727
+ await runBoundedProcess({
22728
+ arguments: [
22729
+ "install",
22730
+ "--prefix",
22731
+ staging,
22732
+ "--omit=optional",
22733
+ "--ignore-scripts",
22734
+ "--no-audit",
22735
+ "--no-fund",
22736
+ "--package-lock=false",
22737
+ "--save=false",
22738
+ "--registry=https://registry.npmjs.org/",
22739
+ `${specification.adapterPackage}@${specification.adapterVersion}`
22740
+ ],
22741
+ command: npm.path,
22742
+ cwd: staging,
22743
+ environment: {
22744
+ HOME: join3(staging, "home"),
22745
+ NPM_CONFIG_AUDIT: "false",
22746
+ NPM_CONFIG_FUND: "false",
22747
+ NPM_CONFIG_IGNORE_SCRIPTS: "true",
22748
+ NPM_CONFIG_OMIT: "optional",
22749
+ NPM_CONFIG_REGISTRY: "https://registry.npmjs.org/",
22750
+ PATH: `${dirname3(process.execPath)}${delimiter}/usr/bin${delimiter}/bin`
22751
+ },
22752
+ outputLimitBytes: 64 * 1024,
22753
+ signal,
22754
+ timeoutMilliseconds: ADAPTER_INSTALL_TIMEOUT_MILLISECONDS
22755
+ });
22756
+ await validateManagedAdapter(staging, specification);
22757
+ try {
22758
+ await rename(staging, published);
22759
+ } catch (error61) {
22760
+ try {
22761
+ return await validateManagedAdapter(published, specification);
22762
+ } catch {
22763
+ throw new ManagedAdapterInstallError(
22764
+ "adapter_publish_failed",
22765
+ "The managed adapter could not be published",
22766
+ { cause: error61 }
22767
+ );
22768
+ }
22769
+ }
22770
+ return await validateManagedAdapter(published, specification);
22771
+ } catch (error61) {
22772
+ if (signal.aborted) {
22773
+ signal.throwIfAborted();
22774
+ }
22775
+ if (error61 instanceof ManagedAdapterInstallError) {
22776
+ throw error61;
22777
+ }
22778
+ throw new ManagedAdapterInstallError(
22779
+ "adapter_install_failed",
22780
+ "The pinned ACP adapter could not be installed",
22781
+ { cause: error61 }
22782
+ );
22783
+ } finally {
22784
+ await rm(staging, { force: true, recursive: true });
22785
+ }
22786
+ };
22787
+ };
22788
+
22789
+ // src/storage/state-directory.ts
22790
+ import { homedir } from "node:os";
22791
+ import { join as join4, resolve as resolve2 } from "node:path";
22792
+ function readStateDirectory(environment = process.env) {
22793
+ const configured = environment["MOBIUS_STATE_DIRECTORY"]?.trim();
22794
+ if (configured !== void 0 && configured.length > 0) {
22795
+ return resolve2(configured);
22170
22796
  }
22171
- return code;
22797
+ return join4(homedir(), ".mobius");
22172
22798
  }
22173
- function parsePairCommand(arguments_) {
22174
- assertKnownArguments(arguments_);
22175
- return {
22176
- code: parsePairingCode(readSingleOption(arguments_, "--code")),
22177
- relayOrigin: parseRelayOrigin(readSingleOption(arguments_, "--url")),
22178
- root: readSingleOption(arguments_, "--root")
22179
- };
22799
+
22800
+ // src/agent-catalog/discover-local-agents.ts
22801
+ var VERSION_PROBE_TIMEOUT_MILLISECONDS = 3e3;
22802
+ var ACP_PROBE_TIMEOUT_MILLISECONDS = 5e3;
22803
+ var ACP_OUTPUT_LIMIT_BYTES = 1024 * 1024;
22804
+ var LocalAgentSpecification = class {
22805
+ recoveryCommand = () => `npm install -g --omit=optional ${this.adapterPackage}@${this.adapterVersion}`;
22806
+ supportsInstallation = (platform, architecture) => (platform === "darwin" || platform === "linux") && (architecture === "arm64" || architecture === "x64");
22807
+ };
22808
+ var CodexAgentSpecification = class extends LocalAgentSpecification {
22809
+ adapterEnvironmentVariable = "CODEX_PATH";
22810
+ adapterExecutable = "codex-acp";
22811
+ adapterOverrideVariable = "MOBIUS_CODEX_ACP_PATH";
22812
+ adapterPackage = "@agentclientprotocol/codex-acp";
22813
+ adapterVersion = "1.8.0";
22814
+ agentExecutable = "codex";
22815
+ agentOverrideVariable = "MOBIUS_CODEX_PATH";
22816
+ id = "codex";
22817
+ label = "Codex";
22818
+ managedAdapterExecutablePath = [
22819
+ "adapters",
22820
+ "codex",
22821
+ "1.8.0",
22822
+ "node_modules",
22823
+ "@agentclientprotocol",
22824
+ "codex-acp",
22825
+ "dist",
22826
+ "index.js"
22827
+ ];
22828
+ managedAgentExecutablePath = ["agents", "codex", "bin", "codex"];
22829
+ };
22830
+ var ClaudeCodeAgentSpecification = class extends LocalAgentSpecification {
22831
+ adapterEnvironmentVariable = "CLAUDE_CODE_EXECUTABLE";
22832
+ adapterExecutable = "claude-agent-acp";
22833
+ adapterOverrideVariable = "MOBIUS_CLAUDE_ACP_PATH";
22834
+ adapterPackage = "@agentclientprotocol/claude-agent-acp";
22835
+ adapterVersion = "0.73.0";
22836
+ agentExecutable = "claude";
22837
+ agentOverrideVariable = "MOBIUS_CLAUDE_PATH";
22838
+ id = "claude";
22839
+ label = "Claude Code";
22840
+ managedAdapterExecutablePath = [
22841
+ "adapters",
22842
+ "claude",
22843
+ "0.73.0",
22844
+ "node_modules",
22845
+ "@agentclientprotocol",
22846
+ "claude-agent-acp",
22847
+ "dist",
22848
+ "index.js"
22849
+ ];
22850
+ managedAgentExecutablePath = [
22851
+ "agents",
22852
+ "claude",
22853
+ "home",
22854
+ ".local",
22855
+ "bin",
22856
+ "claude"
22857
+ ];
22858
+ };
22859
+ function supportedLocalAgentSpecifications() {
22860
+ return Object.freeze([
22861
+ new CodexAgentSpecification(),
22862
+ new ClaudeCodeAgentSpecification()
22863
+ ]);
22180
22864
  }
22181
- function parseChannelArgument(arguments_) {
22182
- if (arguments_.length !== 2 || arguments_[0] !== "--channel") {
22183
- throw new PairingClientError(
22184
- "invalid_arguments",
22185
- "Usage: mobius start --channel <channel-id>"
22186
- );
22865
+ var AgentDiscovery = class {
22866
+ specification;
22867
+ constructor(specification) {
22868
+ this.specification = specification;
22187
22869
  }
22188
- const channelId = arguments_[1] ?? "";
22189
- if (!IDENTIFIER_PATTERN3.test(channelId)) {
22190
- throw new PairingClientError(
22191
- "invalid_channel_id",
22192
- "The saved channel ID is invalid"
22193
- );
22870
+ };
22871
+ var AvailableAgent = class extends AgentDiscovery {
22872
+ agent;
22873
+ status = "ready";
22874
+ constructor(specification, agent) {
22875
+ super(specification);
22876
+ this.agent = agent;
22194
22877
  }
22195
- return channelId;
22196
- }
22197
- async function resolveApprovedRoot(path) {
22198
- const requestedRoot = resolve(path);
22199
- try {
22200
- const approvedRoot = await realpath2(requestedRoot);
22201
- const metadata = await stat2(approvedRoot);
22202
- if (!metadata.isDirectory()) {
22203
- throw new PairingClientError(
22204
- "invalid_workspace",
22205
- "The approved workspace root is not a directory"
22206
- );
22207
- }
22208
- return approvedRoot;
22209
- } catch (error61) {
22210
- if (error61 instanceof PairingClientError) {
22211
- throw error61;
22212
- }
22213
- throw new PairingClientError(
22214
- "invalid_workspace",
22215
- "The approved workspace root cannot be resolved",
22216
- { cause: error61 }
22217
- );
22878
+ };
22879
+ var UnavailableAgent = class extends AgentDiscovery {
22880
+ };
22881
+ var MissingAdapter = class extends UnavailableAgent {
22882
+ code = "adapter_not_found";
22883
+ message = "ACP adapter not found";
22884
+ status = "missing_adapter";
22885
+ constructor(specification) {
22886
+ super(specification);
22218
22887
  }
22219
- }
22220
- async function readJsonResponse(response) {
22221
- if (response.headers.get("cf-mitigated") === "challenge") {
22222
- throw new PairingClientError(
22223
- "relay_challenge",
22224
- "Cloudflare is protecting this temporary preview. Claim the deployment or use an authenticated deployment, then pair again"
22888
+ };
22889
+ var MissingAgent = class extends UnavailableAgent {
22890
+ code = "agent_not_found";
22891
+ message = "Agent executable not found";
22892
+ status = "missing_agent";
22893
+ constructor(specification) {
22894
+ super(specification);
22895
+ }
22896
+ };
22897
+ var ProbeFailed = class extends UnavailableAgent {
22898
+ code;
22899
+ message;
22900
+ status = "probe_failed";
22901
+ constructor(specification, code, message) {
22902
+ super(specification);
22903
+ this.code = code;
22904
+ this.message = message;
22905
+ }
22906
+ };
22907
+ var IncompatibleAdapter = class extends UnavailableAgent {
22908
+ code = "acp_incompatible";
22909
+ message = "ACP adapter is incompatible";
22910
+ status = "incompatible_adapter";
22911
+ constructor(specification) {
22912
+ super(specification);
22913
+ }
22914
+ };
22915
+ var LocalAgentInventory = class {
22916
+ available;
22917
+ discoveries;
22918
+ installable;
22919
+ unavailable;
22920
+ constructor(discoveries, grantedAgents = /* @__PURE__ */ new Set(), platform = process.platform, architecture = process.arch) {
22921
+ const available = [];
22922
+ const unavailable = [];
22923
+ for (const discovery of discoveries) {
22924
+ if (discovery instanceof AvailableAgent) {
22925
+ available.push(discovery);
22926
+ } else if (discovery instanceof UnavailableAgent) {
22927
+ unavailable.push(discovery);
22928
+ } else {
22929
+ throw new Error("Unsupported Agent discovery outcome");
22930
+ }
22931
+ }
22932
+ this.available = Object.freeze(available);
22933
+ this.discoveries = Object.freeze([...discoveries]);
22934
+ this.installable = Object.freeze(
22935
+ unavailable.filter(
22936
+ (discovery) => discovery.specification.supportsInstallation(platform, architecture)
22937
+ ).map(
22938
+ (discovery) => new InstallableAgentDefinition(
22939
+ discovery.specification.id,
22940
+ discovery.specification.label,
22941
+ !grantedAgents.has(discovery.specification.id)
22942
+ )
22943
+ )
22225
22944
  );
22945
+ this.unavailable = Object.freeze(unavailable);
22226
22946
  }
22227
- const encoded = await response.text();
22947
+ readyAgents = () => this.available.map((discovery) => discovery.agent);
22948
+ };
22949
+ var AgentProbeError = class extends Error {
22950
+ code;
22951
+ constructor(code, message, options = {}) {
22952
+ super(message, options);
22953
+ this.code = code;
22954
+ this.name = "AgentProbeError";
22955
+ }
22956
+ };
22957
+ var DiscoveryAcpHandler = class {
22958
+ onElicitation = (_request, _signal) => Promise.reject(new Error("ACP probe does not accept elicitations"));
22959
+ onElicitationComplete = (_notification) => Promise.resolve();
22960
+ onPermission = (_request, _signal) => Promise.reject(new Error("ACP probe does not grant permissions"));
22961
+ onUpdate = (_notification) => Promise.resolve();
22962
+ };
22963
+ async function authorizeManagedExecutable(managed, options) {
22964
+ const approvedRoot = await realpath4(resolve3(options.approvedRoot));
22965
+ const requestedDifference = relative3(
22966
+ approvedRoot,
22967
+ managed.identity.requestedPath
22968
+ );
22969
+ const resolvedDifference = relative3(approvedRoot, managed.path);
22970
+ const requestedInside = requestedDifference.length === 0 || !requestedDifference.startsWith("..") && !isAbsolute2(requestedDifference);
22971
+ const resolvedInside = resolvedDifference.length === 0 || !resolvedDifference.startsWith("..") && !isAbsolute2(resolvedDifference);
22972
+ return requestedInside || resolvedInside ? null : managed;
22973
+ }
22974
+ async function resolveManagedExecutable(managedExecutablePath, options) {
22975
+ const managed = await resolveExecutablePath(
22976
+ join5(readStateDirectory(options.environment), ...managedExecutablePath),
22977
+ options.platform
22978
+ );
22979
+ return managed === null ? null : await authorizeManagedExecutable(managed, options);
22980
+ }
22981
+ async function resolveManagedAdapterExecutable(specification, options) {
22982
+ let managed;
22228
22983
  try {
22229
- return JSON.parse(encoded);
22230
- } catch (error61) {
22231
- throw new PairingClientError(
22232
- "invalid_relay_response",
22233
- "The relay returned an invalid response",
22234
- { cause: error61 }
22984
+ managed = await validateManagedAdapter(
22985
+ join5(
22986
+ readStateDirectory(options.environment),
22987
+ "adapters",
22988
+ specification.id,
22989
+ specification.adapterVersion
22990
+ ),
22991
+ specification
22235
22992
  );
22993
+ } catch {
22994
+ return null;
22236
22995
  }
22996
+ return await authorizeManagedExecutable(managed, options);
22237
22997
  }
22238
- function readRelayError(value) {
22239
- if (isRecord4(value)) {
22240
- const error61 = value["error"];
22241
- if (isRecord4(error61)) {
22242
- const code = error61["code"];
22243
- const message = error61["message"];
22244
- if (typeof code === "string" && typeof message === "string") {
22245
- return new PairingClientError(code, message);
22246
- }
22998
+ async function resolveSpecificationExecutables(executableName, overrideVariable, resolveManaged, options) {
22999
+ const override = options.environment[overrideVariable];
23000
+ if (override !== void 0) {
23001
+ if (override.length === 0 || !isAbsolute2(override)) {
23002
+ return [];
22247
23003
  }
23004
+ const executable = await resolveExecutablePath(override, options.platform);
23005
+ return executable === null ? [] : [executable];
22248
23006
  }
22249
- return new PairingClientError(
22250
- "pairing_failed",
22251
- "The relay could not pair this machine"
23007
+ const external = await findExecutableOnPath(
23008
+ executableName,
23009
+ options.environment,
23010
+ options.approvedRoot,
23011
+ options.platform
23012
+ );
23013
+ const managed = await resolveManaged();
23014
+ if (external === null) {
23015
+ return managed === null ? [] : [managed];
23016
+ }
23017
+ if (managed === null || managed.path === external.path) {
23018
+ return [external];
23019
+ }
23020
+ return [external, managed];
23021
+ }
23022
+ async function resolveAgentExecutables(specification, options) {
23023
+ return await resolveSpecificationExecutables(
23024
+ specification.agentExecutable,
23025
+ specification.agentOverrideVariable,
23026
+ async () => await resolveManagedExecutable(
23027
+ specification.managedAgentExecutablePath,
23028
+ options
23029
+ ),
23030
+ options
22252
23031
  );
22253
23032
  }
22254
- function decodeClaimResponse(value) {
22255
- if (!isRecord4(value) || !hasExactKeys(value, CLAIM_RESPONSE_KEYS)) {
22256
- throw new PairingClientError(
22257
- "invalid_relay_response",
22258
- "The relay returned an invalid pairing response"
22259
- );
22260
- }
22261
- const channelId = value["channelId"];
22262
- const daemonToken = value["daemonToken"];
22263
- if (typeof channelId !== "string" || !IDENTIFIER_PATTERN3.test(channelId) || typeof daemonToken !== "string" || !TOKEN_PATTERN.test(daemonToken)) {
22264
- throw new PairingClientError(
22265
- "invalid_relay_response",
22266
- "The relay returned an invalid pairing response"
23033
+ async function resolveAdapterExecutables(specification, options) {
23034
+ return await resolveSpecificationExecutables(
23035
+ specification.adapterExecutable,
23036
+ specification.adapterOverrideVariable,
23037
+ async () => await resolveManagedAdapterExecutable(specification, options),
23038
+ options
23039
+ );
23040
+ }
23041
+ async function resolveAgentExecutable(specification, options) {
23042
+ const executables = await resolveAgentExecutables(specification, options);
23043
+ return executables[0] ?? null;
23044
+ }
23045
+ async function resolveAdapterExecutable(specification, options) {
23046
+ const executables = await resolveAdapterExecutables(specification, options);
23047
+ return executables[0] ?? null;
23048
+ }
23049
+ function normalizedVersion(output2) {
23050
+ return output2.trim().split(/\r?\n/, 1)[0]?.trim().slice(0, 128) ?? "";
23051
+ }
23052
+ async function probeVersion(executable, environment, approvedRoot, signal) {
23053
+ const result = await runBoundedProcess({
23054
+ arguments: ["--version"],
23055
+ command: executable.path,
23056
+ cwd: approvedRoot,
23057
+ environment,
23058
+ signal,
23059
+ timeoutMilliseconds: VERSION_PROBE_TIMEOUT_MILLISECONDS
23060
+ });
23061
+ const version2 = normalizedVersion(result.stdout || result.stderr);
23062
+ if (version2.length === 0) {
23063
+ throw new ProcessExecutionError(
23064
+ "process_exit_failed",
23065
+ "The executable did not report a version"
22267
23066
  );
22268
23067
  }
22269
- return { channelId, daemonToken };
23068
+ return version2;
22270
23069
  }
22271
- async function claimPairing(options, approvedRoot) {
22272
- const agents = loadLocalAgents().map((agent) => ({
22273
- id: agent.id,
22274
- label: agent.label
22275
- }));
22276
- const endpoint = new URL("/v1/pairings/claim", options.relayOrigin);
22277
- let response;
22278
- try {
22279
- response = await fetch(endpoint, {
22280
- body: JSON.stringify({
22281
- agents,
22282
- machineName: hostname3(),
22283
- pairingCode: options.code,
22284
- workspaceName: basename(approvedRoot) || "Local workspace"
22285
- }),
22286
- dispatcher: environmentProxyDispatcher,
22287
- headers: { "content-type": "application/json" },
22288
- method: "POST",
22289
- signal: AbortSignal.timeout(PAIRING_TIMEOUT_MILLISECONDS)
22290
- });
22291
- } catch (error61) {
22292
- throw new PairingClientError(
22293
- "relay_unavailable",
22294
- "The pairing relay could not be reached",
22295
- { cause: error61 }
22296
- );
23070
+ var VerifiedAgentExecutable = class {
23071
+ executable;
23072
+ version;
23073
+ constructor(executable, version2) {
23074
+ this.executable = executable;
23075
+ this.version = version2;
22297
23076
  }
22298
- const value = await readJsonResponse(response);
22299
- if (!response.ok) {
22300
- throw readRelayError(value);
23077
+ };
23078
+ async function findVerifiedAgentExecutable(specification, options) {
23079
+ const executables = await resolveAgentExecutables(specification, options);
23080
+ for (const executable of executables) {
23081
+ try {
23082
+ const version2 = await probeVersion(
23083
+ executable,
23084
+ options.environment,
23085
+ options.approvedRoot,
23086
+ options.signal
23087
+ );
23088
+ return new VerifiedAgentExecutable(executable, version2);
23089
+ } catch {
23090
+ if (options.signal.aborted) {
23091
+ options.signal.throwIfAborted();
23092
+ }
23093
+ }
22301
23094
  }
22302
- return decodeClaimResponse(value);
23095
+ return null;
23096
+ }
23097
+ function readableAcpStream(input2) {
23098
+ let byteLength = 0;
23099
+ let open6 = true;
23100
+ return new ReadableStream({
23101
+ start(controller) {
23102
+ input2.on("data", (chunk) => {
23103
+ if (!open6) {
23104
+ return;
23105
+ }
23106
+ if (!(chunk instanceof Uint8Array)) {
23107
+ open6 = false;
23108
+ controller.error(new Error("ACP adapter produced invalid output"));
23109
+ return;
23110
+ }
23111
+ byteLength += chunk.byteLength;
23112
+ if (byteLength > ACP_OUTPUT_LIMIT_BYTES) {
23113
+ open6 = false;
23114
+ controller.error(new Error("ACP adapter output limit exceeded"));
23115
+ return;
23116
+ }
23117
+ controller.enqueue(new Uint8Array(chunk));
23118
+ });
23119
+ input2.once("end", () => {
23120
+ if (open6) {
23121
+ open6 = false;
23122
+ controller.close();
23123
+ }
23124
+ });
23125
+ input2.once("error", (error61) => {
23126
+ if (open6) {
23127
+ open6 = false;
23128
+ controller.error(error61);
23129
+ }
23130
+ });
23131
+ },
23132
+ cancel() {
23133
+ open6 = false;
23134
+ }
23135
+ });
23136
+ }
23137
+ function writableAcpStream(output2) {
23138
+ return new WritableStream({
23139
+ write(chunk) {
23140
+ return new Promise((resolveWrite, rejectWrite) => {
23141
+ output2.write(chunk, (error61) => {
23142
+ if (error61) {
23143
+ rejectWrite(error61);
23144
+ return;
23145
+ }
23146
+ resolveWrite();
23147
+ });
23148
+ });
23149
+ }
23150
+ });
23151
+ }
23152
+ async function probeAcp(agent, environment, approvedRoot, signal) {
23153
+ signal.throwIfAborted();
23154
+ const controller = new AbortController();
23155
+ let timedOut = false;
23156
+ const timeout = setTimeout(() => {
23157
+ timedOut = true;
23158
+ controller.abort(new Error("ACP probe timed out"));
23159
+ }, ACP_PROBE_TIMEOUT_MILLISECONDS);
23160
+ const abort = () => controller.abort(signal.reason);
23161
+ signal.addEventListener("abort", abort, { once: true });
23162
+ if (signal.aborted) {
23163
+ abort();
23164
+ }
23165
+ const child = spawn3(agent.adapterPath, [...agent.arguments], {
23166
+ cwd: approvedRoot,
23167
+ detached: process.platform !== "win32",
23168
+ env: { ...environment, ...agent.environment },
23169
+ shell: false,
23170
+ stdio: ["pipe", "pipe", "pipe"]
23171
+ });
23172
+ const stdin = child.stdin;
23173
+ const stdout = child.stdout;
23174
+ const stderr = child.stderr;
23175
+ if (stdin === null || stdout === null || stderr === null) {
23176
+ await stopProcessTree(child);
23177
+ throw new AgentProbeError(
23178
+ "acp_incompatible",
23179
+ "ACP adapter streams are unavailable"
23180
+ );
23181
+ }
23182
+ let stderrBytes = 0;
23183
+ const consumeStderr = (chunk) => {
23184
+ stderrBytes += chunk.byteLength;
23185
+ if (stderrBytes > ACP_OUTPUT_LIMIT_BYTES) {
23186
+ controller.abort(new Error("ACP adapter output limit exceeded"));
23187
+ }
23188
+ };
23189
+ stderr.on("data", consumeStderr);
23190
+ const connection = createAcpClient(new DiscoveryAcpHandler()).connect(
23191
+ ndJsonStream2(writableAcpStream(stdin), readableAcpStream(stdout))
23192
+ );
23193
+ const processExit = new Promise((_resolve, reject) => {
23194
+ child.once("error", (error61) => reject(error61));
23195
+ child.once(
23196
+ "exit",
23197
+ () => reject(new Error("ACP adapter exited during initialization"))
23198
+ );
23199
+ });
23200
+ try {
23201
+ const initialized = await Promise.race([
23202
+ connection.agent.request(
23203
+ methods3.agent.initialize,
23204
+ {
23205
+ clientCapabilities: {},
23206
+ clientInfo: { name: "mobius", version: "0.0.3" },
23207
+ protocolVersion: PROTOCOL_VERSION3
23208
+ },
23209
+ { cancellationSignal: controller.signal }
23210
+ ),
23211
+ processExit
23212
+ ]);
23213
+ if (initialized.protocolVersion !== PROTOCOL_VERSION3) {
23214
+ throw new AgentProbeError(
23215
+ "acp_incompatible",
23216
+ "ACP adapter selected an unsupported protocol version"
23217
+ );
23218
+ }
23219
+ } catch (error61) {
23220
+ if (signal.aborted) {
23221
+ signal.throwIfAborted();
23222
+ }
23223
+ if (timedOut) {
23224
+ throw new AgentProbeError(
23225
+ "adapter_probe_timeout",
23226
+ "ACP adapter probe timed out",
23227
+ { cause: error61 }
23228
+ );
23229
+ }
23230
+ if (error61 instanceof AgentProbeError) {
23231
+ throw error61;
23232
+ }
23233
+ throw new AgentProbeError(
23234
+ "acp_incompatible",
23235
+ "ACP adapter initialization failed",
23236
+ { cause: error61 }
23237
+ );
23238
+ } finally {
23239
+ clearTimeout(timeout);
23240
+ signal.removeEventListener("abort", abort);
23241
+ connection.close();
23242
+ stdin.end();
23243
+ stderr.removeListener("data", consumeStderr);
23244
+ await stopProcessTree(child);
23245
+ }
23246
+ }
23247
+ function probeFailure(specification, executableKind, error61) {
23248
+ if (error61 instanceof AgentProbeError) {
23249
+ return error61.code === "acp_incompatible" ? new IncompatibleAdapter(specification) : new ProbeFailed(specification, error61.code, error61.message);
23250
+ }
23251
+ if (error61 instanceof ProcessExecutionError) {
23252
+ const code = error61.code === "process_timeout" ? `${executableKind}_probe_timeout` : `${executableKind}_probe_failed`;
23253
+ return new ProbeFailed(
23254
+ specification,
23255
+ code,
23256
+ `${executableKind === "agent" ? "Agent" : "Adapter"} version probe failed`
23257
+ );
23258
+ }
23259
+ return new ProbeFailed(
23260
+ specification,
23261
+ `${executableKind}_probe_failed`,
23262
+ `${executableKind === "agent" ? "Agent" : "Adapter"} probe failed`
23263
+ );
23264
+ }
23265
+ async function discoverSpecification(specification, options) {
23266
+ options.signal.throwIfAborted();
23267
+ const [adapters, agents] = await Promise.all([
23268
+ resolveAdapterExecutables(specification, options),
23269
+ resolveAgentExecutables(specification, options)
23270
+ ]);
23271
+ if (adapters.length === 0) {
23272
+ return new MissingAdapter(specification);
23273
+ }
23274
+ if (agents.length === 0) {
23275
+ return new MissingAgent(specification);
23276
+ }
23277
+ let lastFailure = null;
23278
+ for (const agent of agents) {
23279
+ let agentVersion = "";
23280
+ try {
23281
+ agentVersion = await probeVersion(
23282
+ agent,
23283
+ options.environment,
23284
+ options.approvedRoot,
23285
+ options.signal
23286
+ );
23287
+ } catch (error61) {
23288
+ if (options.signal.aborted) {
23289
+ options.signal.throwIfAborted();
23290
+ }
23291
+ lastFailure = probeFailure(specification, "agent", error61);
23292
+ continue;
23293
+ }
23294
+ for (const adapter of adapters) {
23295
+ let adapterVersion = "";
23296
+ try {
23297
+ adapterVersion = await probeVersion(
23298
+ adapter,
23299
+ {
23300
+ ...options.environment,
23301
+ [specification.adapterEnvironmentVariable]: agent.path
23302
+ },
23303
+ options.approvedRoot,
23304
+ options.signal
23305
+ );
23306
+ } catch (error61) {
23307
+ if (options.signal.aborted) {
23308
+ options.signal.throwIfAborted();
23309
+ }
23310
+ lastFailure = probeFailure(specification, "adapter", error61);
23311
+ continue;
23312
+ }
23313
+ const definition = new LocalAgentDefinition(
23314
+ specification.id,
23315
+ specification.label,
23316
+ adapter.path,
23317
+ [],
23318
+ { [specification.adapterEnvironmentVariable]: agent.path },
23319
+ adapterVersion,
23320
+ agentVersion,
23321
+ adapter.identity,
23322
+ agent.identity
23323
+ );
23324
+ try {
23325
+ await probeAcp(
23326
+ definition,
23327
+ options.environment,
23328
+ options.approvedRoot,
23329
+ options.signal
23330
+ );
23331
+ return new AvailableAgent(specification, definition);
23332
+ } catch (error61) {
23333
+ if (options.signal.aborted) {
23334
+ options.signal.throwIfAborted();
23335
+ }
23336
+ lastFailure = probeFailure(specification, "adapter", error61);
23337
+ }
23338
+ }
23339
+ }
23340
+ return lastFailure ?? new IncompatibleAdapter(specification);
23341
+ }
23342
+ async function discoverLocalAgentInventory(options) {
23343
+ const specifications = options.specifications ?? supportedLocalAgentSpecifications();
23344
+ const discoveries = await Promise.all(
23345
+ specifications.map(
23346
+ async (specification) => await discoverSpecification(specification, options)
23347
+ )
23348
+ );
23349
+ let grantedAgents = /* @__PURE__ */ new Set();
23350
+ try {
23351
+ grantedAgents = await readGrantedAgents(
23352
+ readStateDirectory(options.environment)
23353
+ );
23354
+ } catch {
23355
+ }
23356
+ return new LocalAgentInventory(
23357
+ discoveries,
23358
+ grantedAgents,
23359
+ options.platform,
23360
+ options.architecture
23361
+ );
23362
+ }
23363
+
23364
+ // src/network/environment-proxy.ts
23365
+ import { EnvHttpProxyAgent } from "undici";
23366
+ var environmentProxyDispatcher = new EnvHttpProxyAgent();
23367
+
23368
+ // src/pairing/pair-local-machine.ts
23369
+ var PAIRING_TIMEOUT_MILLISECONDS = 2e4;
23370
+ var PAIRING_CODE_PATTERN = /^[A-HJ-NP-Z2-9]{4}(?:-[A-HJ-NP-Z2-9]{4}){4}$/;
23371
+ var IDENTIFIER_PATTERN3 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
23372
+ var TOKEN_PATTERN = /^[a-zA-Z0-9_-]{32,256}$/;
23373
+ var MAX_CONNECTION_FILE_LENGTH = 16 * 1024;
23374
+ var CLAIM_RESPONSE_KEYS = ["channelId", "daemonToken"];
23375
+ var SAVED_CONNECTION_KEYS = [
23376
+ "approvedRoot",
23377
+ "channelId",
23378
+ "daemonToken",
23379
+ "relayOrigin"
23380
+ ];
23381
+ var PairingClientError = class extends Error {
23382
+ code;
23383
+ constructor(code, message, options = {}) {
23384
+ super(message, options);
23385
+ this.code = code;
23386
+ this.name = "PairingClientError";
23387
+ }
23388
+ };
23389
+ function isRecord6(value) {
23390
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23391
+ }
23392
+ function hasExactKeys(value, expectedKeys) {
23393
+ const keys = Object.keys(value);
23394
+ return keys.length === expectedKeys.length && expectedKeys.every((key) => Object.hasOwn(value, key));
23395
+ }
23396
+ function readSingleOption(arguments_, option) {
23397
+ const values = [];
23398
+ for (let index = 0; index < arguments_.length; index += 1) {
23399
+ if (arguments_[index] !== option) {
23400
+ continue;
23401
+ }
23402
+ const value = arguments_[index + 1];
23403
+ if (value === void 0 || value.startsWith("--")) {
23404
+ throw new PairingClientError(
23405
+ "invalid_arguments",
23406
+ `${option} requires a value`
23407
+ );
23408
+ }
23409
+ values.push(value);
23410
+ index += 1;
23411
+ }
23412
+ if (values.length !== 1) {
23413
+ throw new PairingClientError(
23414
+ "invalid_arguments",
23415
+ `${option} must be provided exactly once`
23416
+ );
23417
+ }
23418
+ return values[0] ?? "";
23419
+ }
23420
+ function assertKnownArguments(arguments_) {
23421
+ for (let index = 0; index < arguments_.length; index += 2) {
23422
+ const option = arguments_[index];
23423
+ if (option !== "--url" && option !== "--code" && option !== "--root") {
23424
+ throw new PairingClientError(
23425
+ "invalid_arguments",
23426
+ `Unknown pairing option: ${option ?? "missing option"}`
23427
+ );
23428
+ }
23429
+ }
23430
+ }
23431
+ function parseRelayOrigin(value) {
23432
+ let url2;
23433
+ try {
23434
+ url2 = new URL(value);
23435
+ } catch (error61) {
23436
+ throw new PairingClientError(
23437
+ "invalid_relay_url",
23438
+ "The relay URL is invalid",
23439
+ { cause: error61 }
23440
+ );
23441
+ }
23442
+ if (url2.protocol !== "https:" && url2.protocol !== "http:") {
23443
+ throw new PairingClientError(
23444
+ "invalid_relay_url",
23445
+ "The relay URL must use http or https"
23446
+ );
23447
+ }
23448
+ if (url2.username.length > 0 || url2.password.length > 0) {
23449
+ throw new PairingClientError(
23450
+ "invalid_relay_url",
23451
+ "The relay URL cannot contain credentials"
23452
+ );
23453
+ }
23454
+ if (url2.protocol === "http:" && url2.hostname !== "127.0.0.1" && url2.hostname !== "localhost") {
23455
+ throw new PairingClientError(
23456
+ "invalid_relay_url",
23457
+ "A remote relay must use https"
23458
+ );
23459
+ }
23460
+ url2.hash = "";
23461
+ url2.pathname = "/";
23462
+ url2.search = "";
23463
+ return url2;
23464
+ }
23465
+ function parsePairingCode(value) {
23466
+ const code = value.trim().toUpperCase();
23467
+ if (!PAIRING_CODE_PATTERN.test(code)) {
23468
+ throw new PairingClientError(
23469
+ "invalid_pairing_code",
23470
+ "The pairing code must match the code shown in the browser"
23471
+ );
23472
+ }
23473
+ return code;
23474
+ }
23475
+ function parsePairCommand(arguments_) {
23476
+ assertKnownArguments(arguments_);
23477
+ return {
23478
+ code: parsePairingCode(readSingleOption(arguments_, "--code")),
23479
+ relayOrigin: parseRelayOrigin(readSingleOption(arguments_, "--url")),
23480
+ root: readSingleOption(arguments_, "--root")
23481
+ };
23482
+ }
23483
+ function parseChannelArgument(arguments_) {
23484
+ if (arguments_.length !== 2 || arguments_[0] !== "--channel") {
23485
+ throw new PairingClientError(
23486
+ "invalid_arguments",
23487
+ "Usage: mobius start --channel <channel-id>"
23488
+ );
23489
+ }
23490
+ const channelId = arguments_[1] ?? "";
23491
+ if (!IDENTIFIER_PATTERN3.test(channelId)) {
23492
+ throw new PairingClientError(
23493
+ "invalid_channel_id",
23494
+ "The saved channel ID is invalid"
23495
+ );
23496
+ }
23497
+ return channelId;
23498
+ }
23499
+ async function resolveApprovedRoot(path) {
23500
+ const requestedRoot = resolve4(path);
23501
+ try {
23502
+ const approvedRoot = await realpath5(requestedRoot);
23503
+ const metadata = await stat4(approvedRoot);
23504
+ if (!metadata.isDirectory()) {
23505
+ throw new PairingClientError(
23506
+ "invalid_workspace",
23507
+ "The approved workspace root is not a directory"
23508
+ );
23509
+ }
23510
+ return approvedRoot;
23511
+ } catch (error61) {
23512
+ if (error61 instanceof PairingClientError) {
23513
+ throw error61;
23514
+ }
23515
+ throw new PairingClientError(
23516
+ "invalid_workspace",
23517
+ "The approved workspace root cannot be resolved",
23518
+ { cause: error61 }
23519
+ );
23520
+ }
23521
+ }
23522
+ async function readJsonResponse(response) {
23523
+ if (response.headers.get("cf-mitigated") === "challenge") {
23524
+ throw new PairingClientError(
23525
+ "relay_challenge",
23526
+ "Cloudflare is protecting this temporary preview. Claim the deployment or use an authenticated deployment, then pair again"
23527
+ );
23528
+ }
23529
+ const encoded = await response.text();
23530
+ try {
23531
+ return JSON.parse(encoded);
23532
+ } catch (error61) {
23533
+ throw new PairingClientError(
23534
+ "invalid_relay_response",
23535
+ "The relay returned an invalid response",
23536
+ { cause: error61 }
23537
+ );
23538
+ }
23539
+ }
23540
+ function readRelayError(value) {
23541
+ if (isRecord6(value)) {
23542
+ const error61 = value["error"];
23543
+ if (isRecord6(error61)) {
23544
+ const code = error61["code"];
23545
+ const message = error61["message"];
23546
+ if (typeof code === "string" && typeof message === "string") {
23547
+ return new PairingClientError(code, message);
23548
+ }
23549
+ }
23550
+ }
23551
+ return new PairingClientError(
23552
+ "pairing_failed",
23553
+ "The relay could not pair this machine"
23554
+ );
23555
+ }
23556
+ function decodeClaimResponse(value) {
23557
+ if (!isRecord6(value) || !hasExactKeys(value, CLAIM_RESPONSE_KEYS)) {
23558
+ throw new PairingClientError(
23559
+ "invalid_relay_response",
23560
+ "The relay returned an invalid pairing response"
23561
+ );
23562
+ }
23563
+ const channelId = value["channelId"];
23564
+ const daemonToken = value["daemonToken"];
23565
+ if (typeof channelId !== "string" || !IDENTIFIER_PATTERN3.test(channelId) || typeof daemonToken !== "string" || !TOKEN_PATTERN.test(daemonToken)) {
23566
+ throw new PairingClientError(
23567
+ "invalid_relay_response",
23568
+ "The relay returned an invalid pairing response"
23569
+ );
23570
+ }
23571
+ return { channelId, daemonToken };
23572
+ }
23573
+ async function claimPairing(options, approvedRoot, localAgents, installableAgents, signal) {
23574
+ const agents = localAgents.map((agent) => ({
23575
+ id: agent.id,
23576
+ label: agent.label
23577
+ }));
23578
+ const endpoint = new URL("/v1/pairings/claim", options.relayOrigin);
23579
+ let response;
23580
+ try {
23581
+ response = await fetch(endpoint, {
23582
+ body: JSON.stringify({
23583
+ agents,
23584
+ installableAgents: installableAgents.map((agent) => ({
23585
+ id: agent.id,
23586
+ label: agent.label,
23587
+ requiresLocalApproval: agent.requiresLocalApproval
23588
+ })),
23589
+ machineName: hostname3(),
23590
+ pairingCode: options.code,
23591
+ workspaceName: basename(approvedRoot) || "Local workspace"
23592
+ }),
23593
+ dispatcher: environmentProxyDispatcher,
23594
+ headers: { "content-type": "application/json" },
23595
+ method: "POST",
23596
+ signal: AbortSignal.any([
23597
+ signal,
23598
+ AbortSignal.timeout(PAIRING_TIMEOUT_MILLISECONDS)
23599
+ ])
23600
+ });
23601
+ } catch (error61) {
23602
+ throw new PairingClientError(
23603
+ "relay_unavailable",
23604
+ "The pairing relay could not be reached",
23605
+ { cause: error61 }
23606
+ );
23607
+ }
23608
+ const value = await readJsonResponse(response);
23609
+ if (!response.ok) {
23610
+ throw readRelayError(value);
23611
+ }
23612
+ return decodeClaimResponse(value);
23613
+ }
23614
+ async function persistConnection(channelId, relayOrigin, daemonToken, approvedRoot) {
23615
+ const connectionsDirectory = join6(readStateDirectory(), "connections");
23616
+ await mkdir2(connectionsDirectory, { mode: 448, recursive: true });
23617
+ const connectionPath = join6(connectionsDirectory, `${channelId}.json`);
23618
+ const temporaryPath = join6(
23619
+ connectionsDirectory,
23620
+ `.${channelId}.${randomUUID()}.tmp`
23621
+ );
23622
+ await writeFile(
23623
+ temporaryPath,
23624
+ `${JSON.stringify(
23625
+ {
23626
+ approvedRoot,
23627
+ channelId,
23628
+ daemonToken,
23629
+ relayOrigin: relayOrigin.toString()
23630
+ },
23631
+ null,
23632
+ 2
23633
+ )}
23634
+ `,
23635
+ { flag: "wx", mode: 384 }
23636
+ );
23637
+ await rename2(temporaryPath, connectionPath);
23638
+ return connectionPath;
23639
+ }
23640
+ function decodeSavedConnection(value, expectedChannelId) {
23641
+ if (!isRecord6(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS)) {
23642
+ throw new PairingClientError(
23643
+ "invalid_connection",
23644
+ "The saved connection is invalid"
23645
+ );
23646
+ }
23647
+ const approvedRoot = value["approvedRoot"];
23648
+ const channelId = value["channelId"];
23649
+ const daemonToken = value["daemonToken"];
23650
+ const relayOrigin = value["relayOrigin"];
23651
+ if (typeof approvedRoot !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN3.test(channelId) || typeof daemonToken !== "string" || !TOKEN_PATTERN.test(daemonToken) || typeof relayOrigin !== "string") {
23652
+ throw new PairingClientError(
23653
+ "invalid_connection",
23654
+ "The saved connection is invalid"
23655
+ );
23656
+ }
23657
+ return {
23658
+ approvedRoot,
23659
+ channelId,
23660
+ daemonToken,
23661
+ relayOrigin: parseRelayOrigin(relayOrigin)
23662
+ };
23663
+ }
23664
+ async function readSavedConnection(channelId) {
23665
+ const connectionPath = join6(
23666
+ readStateDirectory(),
23667
+ "connections",
23668
+ `${channelId}.json`
23669
+ );
23670
+ let encoded = "";
23671
+ const handle = await open4(connectionPath, "r");
23672
+ try {
23673
+ const metadata = await handle.stat();
23674
+ if (!metadata.isFile() || metadata.size > MAX_CONNECTION_FILE_LENGTH) {
23675
+ throw new PairingClientError(
23676
+ "invalid_connection",
23677
+ "The saved connection is invalid"
23678
+ );
23679
+ }
23680
+ encoded = await handle.readFile({ encoding: "utf8" });
23681
+ } finally {
23682
+ await handle.close();
23683
+ }
23684
+ let value;
23685
+ try {
23686
+ value = JSON.parse(encoded);
23687
+ } catch (error61) {
23688
+ throw new PairingClientError(
23689
+ "invalid_connection",
23690
+ "The saved connection is invalid",
23691
+ { cause: error61 }
23692
+ );
23693
+ }
23694
+ const connection = decodeSavedConnection(value, channelId);
23695
+ return {
23696
+ approvedRoot: await resolveApprovedRoot(connection.approvedRoot),
23697
+ channelId: connection.channelId,
23698
+ daemonToken: connection.daemonToken,
23699
+ relayOrigin: connection.relayOrigin
23700
+ };
23701
+ }
23702
+ function assertInventoryUsable(inventory) {
23703
+ if (inventory.available.length + inventory.installable.length === 0) {
23704
+ throw new PairingClientError(
23705
+ "no_supported_agent",
23706
+ "No supported Agent is ready or safely installable on this machine"
23707
+ );
23708
+ }
23709
+ }
23710
+ function buildPairedDaemon(connection, connectionPath, inventory) {
23711
+ assertInventoryUsable(inventory);
23712
+ const relayUrl = new URL(
23713
+ `/v1/channels/${encodeURIComponent(connection.channelId)}/daemon`,
23714
+ connection.relayOrigin
23715
+ );
23716
+ relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
23717
+ relayUrl.searchParams.set("token", connection.daemonToken);
23718
+ const auditDatabasePath = join6(
23719
+ readStateDirectory(),
23720
+ "audit",
23721
+ `${connection.channelId}.sqlite`
23722
+ );
23723
+ return {
23724
+ channelUrl: new URL(
23725
+ `/channels/${encodeURIComponent(connection.channelId)}`,
23726
+ connection.relayOrigin
23727
+ ).toString(),
23728
+ config: createDaemonConfig(
23729
+ {
23730
+ approvedRoot: connection.approvedRoot,
23731
+ auditDatabasePath,
23732
+ channelId: connection.channelId,
23733
+ relayUrl
23734
+ },
23735
+ inventory.readyAgents(),
23736
+ inventory.installable
23737
+ ),
23738
+ connectionPath
23739
+ };
23740
+ }
23741
+ async function pairLocalMachine(arguments_, signal = new AbortController().signal) {
23742
+ const options = parsePairCommand(arguments_);
23743
+ const approvedRoot = await resolveApprovedRoot(options.root);
23744
+ const inventory = await discoverLocalAgentInventory({
23745
+ approvedRoot,
23746
+ environment: process.env,
23747
+ signal
23748
+ });
23749
+ assertInventoryUsable(inventory);
23750
+ const agents = inventory.readyAgents();
23751
+ const claim2 = await claimPairing(
23752
+ options,
23753
+ approvedRoot,
23754
+ agents,
23755
+ inventory.installable,
23756
+ signal
23757
+ );
23758
+ const connectionPath = await persistConnection(
23759
+ claim2.channelId,
23760
+ options.relayOrigin,
23761
+ claim2.daemonToken,
23762
+ approvedRoot
23763
+ );
23764
+ return buildPairedDaemon(
23765
+ {
23766
+ approvedRoot,
23767
+ channelId: claim2.channelId,
23768
+ daemonToken: claim2.daemonToken,
23769
+ relayOrigin: options.relayOrigin
23770
+ },
23771
+ connectionPath,
23772
+ inventory
23773
+ );
23774
+ }
23775
+ async function resumeLocalMachine(arguments_, signal = new AbortController().signal) {
23776
+ const channelId = parseChannelArgument(arguments_);
23777
+ const connectionPath = join6(
23778
+ readStateDirectory(),
23779
+ "connections",
23780
+ `${channelId}.json`
23781
+ );
23782
+ const connection = await readSavedConnection(channelId);
23783
+ const inventory = await discoverLocalAgentInventory({
23784
+ approvedRoot: connection.approvedRoot,
23785
+ environment: process.env,
23786
+ signal
23787
+ });
23788
+ return buildPairedDaemon(connection, connectionPath, inventory);
23789
+ }
23790
+
23791
+ // src/run-daemon.ts
23792
+ import { randomUUID as randomUUID4 } from "node:crypto";
23793
+ import { basename as basename3 } from "node:path";
23794
+ import { WebSocket as WebSocket2 } from "undici";
23795
+
23796
+ // src/agent-catalog/publish-agent-catalog.ts
23797
+ import { fetch as fetch2 } from "undici";
23798
+ var CATALOG_UPDATE_TIMEOUT_MILLISECONDS = 2e4;
23799
+ var AgentCatalogPublishError = class extends Error {
23800
+ code = "agent_catalog_update_failed";
23801
+ constructor(message, options = {}) {
23802
+ super(message, options);
23803
+ this.name = "AgentCatalogPublishError";
23804
+ }
23805
+ };
23806
+ function catalogEndpoint(relayUrl) {
23807
+ const endpoint = new URL(relayUrl);
23808
+ endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
23809
+ if (!endpoint.pathname.endsWith("/daemon")) {
23810
+ throw new AgentCatalogPublishError("The Relay URL is invalid");
23811
+ }
23812
+ endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
23813
+ return endpoint;
23814
+ }
23815
+ async function publishAgentCatalog(config2, signal) {
23816
+ signal.throwIfAborted();
23817
+ const timeoutSignal = AbortSignal.timeout(
23818
+ CATALOG_UPDATE_TIMEOUT_MILLISECONDS
23819
+ );
23820
+ const requestSignal = AbortSignal.any([signal, timeoutSignal]);
23821
+ let response;
23822
+ const catalog = config2.catalog.read();
23823
+ try {
23824
+ response = await fetch2(catalogEndpoint(config2.relayUrl), {
23825
+ body: JSON.stringify({
23826
+ agents: catalog.agents.map((agent) => ({
23827
+ id: agent.id,
23828
+ label: agent.label
23829
+ })),
23830
+ installableAgents: catalog.installableAgents.map((agent) => ({
23831
+ id: agent.id,
23832
+ label: agent.label,
23833
+ requiresLocalApproval: agent.requiresLocalApproval
23834
+ }))
23835
+ }),
23836
+ dispatcher: environmentProxyDispatcher,
23837
+ headers: { "content-type": "application/json" },
23838
+ method: "PUT",
23839
+ signal: requestSignal
23840
+ });
23841
+ } catch (error61) {
23842
+ if (signal.aborted) {
23843
+ signal.throwIfAborted();
23844
+ }
23845
+ throw new AgentCatalogPublishError(
23846
+ timeoutSignal.aborted ? "The Agent catalog update timed out" : "The Agent catalog could not be updated",
23847
+ { cause: error61 }
23848
+ );
23849
+ }
23850
+ await response.body?.cancel();
23851
+ if (!response.ok) {
23852
+ throw new AgentCatalogPublishError(
23853
+ "The Relay rejected the Agent catalog update"
23854
+ );
23855
+ }
23856
+ }
23857
+
23858
+ // src/agent-installation/agent-installation-manager.ts
23859
+ var INSTALLATION_DEADLINE_MILLISECONDS = 10 * 60 * 1e3;
23860
+ var AgentInstallation = class {
23861
+ agentId;
23862
+ requestId;
23863
+ constructor(requestId, agentId) {
23864
+ this.agentId = agentId;
23865
+ this.requestId = requestId;
23866
+ }
23867
+ };
23868
+ var RequestedInstallation = class extends AgentInstallation {
23869
+ status = "requested";
23870
+ constructor(requestId, agentId) {
23871
+ super(requestId, agentId);
23872
+ }
23873
+ };
23874
+ var AwaitingLocalApproval = class extends AgentInstallation {
23875
+ status = "awaiting_local_approval";
23876
+ constructor(requestId, agentId) {
23877
+ super(requestId, agentId);
23878
+ }
23879
+ };
23880
+ var RunningInstallation = class extends AgentInstallation {
23881
+ stage;
23882
+ status = "running";
23883
+ constructor(requestId, agentId, stage) {
23884
+ super(requestId, agentId);
23885
+ this.stage = stage;
23886
+ }
23887
+ };
23888
+ var SucceededInstallation = class extends AgentInstallation {
23889
+ authenticationMayBeRequired = true;
23890
+ status = "succeeded";
23891
+ constructor(requestId, agentId) {
23892
+ super(requestId, agentId);
23893
+ }
23894
+ };
23895
+ var FailedInstallation = class extends AgentInstallation {
23896
+ code;
23897
+ message;
23898
+ status = "failed";
23899
+ constructor(requestId, agentId, code, message) {
23900
+ super(requestId, agentId);
23901
+ this.code = code;
23902
+ this.message = message;
23903
+ }
23904
+ };
23905
+ var CancelledInstallation = class extends AgentInstallation {
23906
+ status = "cancelled";
23907
+ constructor(requestId, agentId) {
23908
+ super(requestId, agentId);
23909
+ }
23910
+ };
23911
+ var InstallationManagerError = class extends Error {
23912
+ code;
23913
+ constructor(code, message, options = {}) {
23914
+ super(message, options);
23915
+ this.code = code;
23916
+ this.name = "InstallationManagerError";
23917
+ }
23918
+ };
23919
+ var NullInstallationAudit = class {
23920
+ record = () => void 0;
23921
+ };
23922
+ var InstallationJob = class {
23923
+ agentId;
23924
+ completion = Promise.withResolvers();
23925
+ controller = new AbortController();
23926
+ requestId;
23927
+ state;
23928
+ constructor(requestId, agentId) {
23929
+ this.agentId = agentId;
23930
+ this.requestId = requestId;
23931
+ this.state = new RequestedInstallation(requestId, agentId);
23932
+ }
23933
+ };
23934
+ function specificationFor(specifications, agentId) {
23935
+ for (const specification of specifications) {
23936
+ if (specification.id === agentId) {
23937
+ return specification;
23938
+ }
23939
+ }
23940
+ return null;
23941
+ }
23942
+ function executionFailure(error61) {
23943
+ if (error61 instanceof InstallationManagerError) {
23944
+ return error61;
23945
+ }
23946
+ return new InstallationManagerError(
23947
+ "installation_failed",
23948
+ "The local Agent installation failed",
23949
+ { cause: error61 }
23950
+ );
23951
+ }
23952
+ var AgentInstallationManager = class {
23953
+ #activeAgents = /* @__PURE__ */ new Map();
23954
+ #audit;
23955
+ #authorizer;
23956
+ #executor;
23957
+ #jobs = /* @__PURE__ */ new Map();
23958
+ #observer;
23959
+ #specifications;
23960
+ constructor(specifications, authorizer, executor, observer, audit = new NullInstallationAudit()) {
23961
+ this.#audit = audit;
23962
+ this.#authorizer = authorizer;
23963
+ this.#executor = executor;
23964
+ this.#observer = observer;
23965
+ this.#specifications = Object.freeze([...specifications]);
23966
+ }
23967
+ request = async (command, signal) => {
23968
+ const existing = this.#jobs.get(command.requestId);
23969
+ if (existing !== void 0) {
23970
+ if (existing.agentId !== command.agentId) {
23971
+ const conflict = new FailedInstallation(
23972
+ command.requestId,
23973
+ command.agentId,
23974
+ "installation_request_conflict",
23975
+ "The installation request identifier is already in use"
23976
+ );
23977
+ this.#audit.record(
23978
+ "agent_installation_failed_installation_request_conflict",
23979
+ command.requestId,
23980
+ command.agentId
23981
+ );
23982
+ await this.#observer.onState(conflict);
23983
+ return conflict;
23984
+ }
23985
+ await this.#observer.onState(existing.state);
23986
+ return await existing.completion.promise;
23987
+ }
23988
+ this.#audit.record(
23989
+ "agent_installation_requested",
23990
+ command.requestId,
23991
+ command.agentId
23992
+ );
23993
+ const specification = specificationFor(
23994
+ this.#specifications,
23995
+ command.agentId
23996
+ );
23997
+ if (specification === null) {
23998
+ const unknown2 = new FailedInstallation(
23999
+ command.requestId,
24000
+ command.agentId,
24001
+ "unknown_agent",
24002
+ "The requested Agent is not supported"
24003
+ );
24004
+ const job2 = new InstallationJob(command.requestId, command.agentId);
24005
+ job2.state = unknown2;
24006
+ job2.completion.resolve(unknown2);
24007
+ this.#jobs.set(command.requestId, job2);
24008
+ this.#audit.record(
24009
+ "agent_installation_failed_unknown_agent",
24010
+ command.requestId,
24011
+ command.agentId
24012
+ );
24013
+ await this.#observer.onState(unknown2);
24014
+ return unknown2;
24015
+ }
24016
+ if (this.#activeAgents.has(command.agentId)) {
24017
+ const busy = new FailedInstallation(
24018
+ command.requestId,
24019
+ command.agentId,
24020
+ "installation_in_progress",
24021
+ "An installation for this Agent is already running"
24022
+ );
24023
+ const job2 = new InstallationJob(command.requestId, command.agentId);
24024
+ job2.state = busy;
24025
+ job2.completion.resolve(busy);
24026
+ this.#jobs.set(command.requestId, job2);
24027
+ this.#audit.record(
24028
+ "agent_installation_failed_installation_in_progress",
24029
+ command.requestId,
24030
+ command.agentId
24031
+ );
24032
+ await this.#observer.onState(busy);
24033
+ return busy;
24034
+ }
24035
+ const job = new InstallationJob(command.requestId, command.agentId);
24036
+ this.#jobs.set(command.requestId, job);
24037
+ this.#activeAgents.set(command.agentId, command.requestId);
24038
+ void this.#run(job, specification, signal);
24039
+ return await job.completion.promise;
24040
+ };
24041
+ cancel = async (requestId) => {
24042
+ const job = this.#jobs.get(requestId);
24043
+ if (job === void 0) {
24044
+ throw new InstallationManagerError(
24045
+ "installation_not_found",
24046
+ "The installation request was not found"
24047
+ );
24048
+ }
24049
+ if (job.state instanceof SucceededInstallation || job.state instanceof FailedInstallation || job.state instanceof CancelledInstallation) {
24050
+ await this.#observer.onState(job.state);
24051
+ return job.state;
24052
+ }
24053
+ this.#audit.record(
24054
+ "agent_installation_cancellation_requested",
24055
+ job.requestId,
24056
+ job.agentId
24057
+ );
24058
+ job.controller.abort(new Error("Agent installation cancelled"));
24059
+ return await job.completion.promise;
24060
+ };
24061
+ close = async () => {
24062
+ const completions = [];
24063
+ for (const requestId of this.#activeAgents.values()) {
24064
+ const job = this.#jobs.get(requestId);
24065
+ if (job === void 0) {
24066
+ throw new InstallationManagerError(
24067
+ "installation_state_invalid",
24068
+ "An active Agent installation is unavailable"
24069
+ );
24070
+ }
24071
+ job.controller.abort(new Error("Mobius daemon stopped"));
24072
+ completions.push(job.completion.promise);
24073
+ }
24074
+ await Promise.all(completions);
24075
+ };
24076
+ #transition = async (job, state) => {
24077
+ job.state = state;
24078
+ await this.#observer.onState(state);
24079
+ };
24080
+ #run = async (job, specification, externalSignal) => {
24081
+ const abort = () => job.controller.abort(externalSignal.reason);
24082
+ externalSignal.addEventListener("abort", abort, { once: true });
24083
+ if (externalSignal.aborted) {
24084
+ abort();
24085
+ }
24086
+ const deadline = setTimeout(() => {
24087
+ job.controller.abort(
24088
+ new InstallationManagerError(
24089
+ "installation_timeout",
24090
+ "The local Agent installation exceeded its deadline"
24091
+ )
24092
+ );
24093
+ }, INSTALLATION_DEADLINE_MILLISECONDS);
24094
+ const signal = job.controller.signal;
24095
+ try {
24096
+ await this.#transition(
24097
+ job,
24098
+ new AwaitingLocalApproval(job.requestId, job.agentId)
24099
+ );
24100
+ this.#audit.record(
24101
+ "agent_installation_awaiting_approval",
24102
+ job.requestId,
24103
+ job.agentId
24104
+ );
24105
+ const approved = await this.#authorizer.authorize(
24106
+ specification.id,
24107
+ specification.label,
24108
+ job.requestId,
24109
+ signal
24110
+ );
24111
+ signal.throwIfAborted();
24112
+ if (!approved) {
24113
+ const denied = new FailedInstallation(
24114
+ job.requestId,
24115
+ job.agentId,
24116
+ "installation_not_authorized",
24117
+ "Installation was not approved on the local machine"
24118
+ );
24119
+ await this.#transition(job, denied);
24120
+ this.#audit.record(
24121
+ "agent_installation_denied",
24122
+ job.requestId,
24123
+ job.agentId
24124
+ );
24125
+ job.completion.resolve(denied);
24126
+ return;
24127
+ }
24128
+ this.#audit.record(
24129
+ "agent_installation_authorized",
24130
+ job.requestId,
24131
+ job.agentId
24132
+ );
24133
+ await this.#executor.execute(
24134
+ job.agentId,
24135
+ job.requestId,
24136
+ async (stage) => {
24137
+ signal.throwIfAborted();
24138
+ await this.#transition(
24139
+ job,
24140
+ new RunningInstallation(job.requestId, job.agentId, stage)
24141
+ );
24142
+ this.#audit.record(
24143
+ `agent_installation_${stage}`,
24144
+ job.requestId,
24145
+ job.agentId
24146
+ );
24147
+ },
24148
+ signal
24149
+ );
24150
+ signal.throwIfAborted();
24151
+ const succeeded = new SucceededInstallation(job.requestId, job.agentId);
24152
+ await this.#transition(job, succeeded);
24153
+ this.#audit.record(
24154
+ "agent_installation_completed",
24155
+ job.requestId,
24156
+ job.agentId
24157
+ );
24158
+ job.completion.resolve(succeeded);
24159
+ } catch (error61) {
24160
+ if (signal.aborted) {
24161
+ if (signal.reason instanceof InstallationManagerError && signal.reason.code === "installation_timeout") {
24162
+ const timedOut = new FailedInstallation(
24163
+ job.requestId,
24164
+ job.agentId,
24165
+ signal.reason.code,
24166
+ signal.reason.message
24167
+ );
24168
+ await this.#transition(job, timedOut);
24169
+ this.#audit.record(
24170
+ "agent_installation_failed_installation_timeout",
24171
+ job.requestId,
24172
+ job.agentId
24173
+ );
24174
+ job.completion.resolve(timedOut);
24175
+ return;
24176
+ }
24177
+ const cancelled = new CancelledInstallation(job.requestId, job.agentId);
24178
+ await this.#transition(job, cancelled);
24179
+ this.#audit.record(
24180
+ "agent_installation_cancelled",
24181
+ job.requestId,
24182
+ job.agentId
24183
+ );
24184
+ job.completion.resolve(cancelled);
24185
+ } else {
24186
+ const failure = executionFailure(error61);
24187
+ const failed = new FailedInstallation(
24188
+ job.requestId,
24189
+ job.agentId,
24190
+ failure.code,
24191
+ failure.message
24192
+ );
24193
+ await this.#transition(job, failed);
24194
+ this.#audit.record(
24195
+ `agent_installation_failed_${failure.code}`,
24196
+ job.requestId,
24197
+ job.agentId
24198
+ );
24199
+ job.completion.resolve(failed);
24200
+ }
24201
+ } finally {
24202
+ clearTimeout(deadline);
24203
+ externalSignal.removeEventListener("abort", abort);
24204
+ if (this.#activeAgents.get(job.agentId) === job.requestId) {
24205
+ this.#activeAgents.delete(job.agentId);
24206
+ }
24207
+ }
24208
+ };
24209
+ };
24210
+
24211
+ // src/agent-installation/automatic-adapter-provisioner.ts
24212
+ import { randomUUID as randomUUID2 } from "node:crypto";
24213
+ var AUTOMATIC_ADAPTER_FAILURE_CODES = /* @__PURE__ */ new Set([
24214
+ "adapter_not_found",
24215
+ "adapter_probe_failed",
24216
+ "adapter_probe_timeout",
24217
+ "acp_incompatible"
24218
+ ]);
24219
+ var InstalledAutomaticAdapter = class {
24220
+ executable;
24221
+ requestId;
24222
+ specification;
24223
+ constructor(specification, requestId, executable) {
24224
+ this.executable = executable;
24225
+ this.requestId = requestId;
24226
+ this.specification = specification;
24227
+ }
24228
+ };
24229
+ function shouldProvisionAdapter(unavailable, options) {
24230
+ return AUTOMATIC_ADAPTER_FAILURE_CODES.has(unavailable.code) && options.environment[unavailable.specification.adapterOverrideVariable] === void 0 && unavailable.specification.supportsInstallation(
24231
+ options.platform ?? process.platform,
24232
+ options.architecture ?? process.arch
24233
+ );
24234
+ }
24235
+ function stableFailureCode(error61) {
24236
+ return error61 instanceof ManagedAdapterInstallError ? error61.code : "adapter_install_failed";
24237
+ }
24238
+ function discoveryOptions(options, environment, signal, specifications) {
24239
+ return {
24240
+ ...options.architecture === void 0 ? {} : { architecture: options.architecture },
24241
+ approvedRoot: options.approvedRoot,
24242
+ environment,
24243
+ ...options.platform === void 0 ? {} : { platform: options.platform },
24244
+ signal,
24245
+ specifications
24246
+ };
24247
+ }
24248
+ var AutomaticAdapterProvisioner = class {
24249
+ #adapterInstaller;
24250
+ #options;
24251
+ #specifications;
24252
+ constructor(options, adapterInstaller, specifications = supportedLocalAgentSpecifications()) {
24253
+ this.#adapterInstaller = adapterInstaller;
24254
+ this.#options = options;
24255
+ this.#specifications = Object.freeze([...specifications]);
24256
+ }
24257
+ prepare = async (signal) => {
24258
+ signal.throwIfAborted();
24259
+ let inventory = await discoverLocalAgentInventory(
24260
+ discoveryOptions(
24261
+ this.#options,
24262
+ this.#options.environment,
24263
+ signal,
24264
+ this.#specifications
24265
+ )
24266
+ );
24267
+ const candidates = inventory.unavailable.filter(
24268
+ (unavailable) => shouldProvisionAdapter(unavailable, this.#options)
24269
+ );
24270
+ const attempts = [];
24271
+ for (const unavailable of candidates) {
24272
+ signal.throwIfAborted();
24273
+ const specification = unavailable.specification;
24274
+ const verifiedAgent = await findVerifiedAgentExecutable(
24275
+ specification,
24276
+ discoveryOptions(
24277
+ this.#options,
24278
+ this.#options.environment,
24279
+ signal,
24280
+ this.#specifications
24281
+ )
24282
+ );
24283
+ if (verifiedAgent === null) {
24284
+ attempts.push(null);
24285
+ continue;
24286
+ }
24287
+ const requestId = `automatic-adapter-${randomUUID2()}`;
24288
+ this.#options.audit.record(
24289
+ "agent_adapter_auto_install_started",
24290
+ requestId,
24291
+ specification.id,
24292
+ {
24293
+ agentVersion: verifiedAgent.version,
24294
+ adapterVersion: specification.adapterVersion
24295
+ }
24296
+ );
24297
+ process.stdout.write(
24298
+ `Installing the pinned ACP adapter for ${specification.label}.
24299
+ `
24300
+ );
24301
+ try {
24302
+ const executable = await this.#adapterInstaller.install(
24303
+ specification,
24304
+ signal
24305
+ );
24306
+ this.#options.audit.record(
24307
+ "agent_adapter_auto_install_completed",
24308
+ requestId,
24309
+ specification.id,
24310
+ { adapterVersion: specification.adapterVersion }
24311
+ );
24312
+ attempts.push(
24313
+ new InstalledAutomaticAdapter(specification, requestId, executable)
24314
+ );
24315
+ } catch (error61) {
24316
+ if (signal.aborted) {
24317
+ this.#options.audit.record(
24318
+ "agent_adapter_auto_install_cancelled",
24319
+ requestId,
24320
+ specification.id
24321
+ );
24322
+ signal.throwIfAborted();
24323
+ }
24324
+ this.#options.audit.record(
24325
+ "agent_adapter_auto_install_failed",
24326
+ requestId,
24327
+ specification.id,
24328
+ { code: stableFailureCode(error61) }
24329
+ );
24330
+ process.stderr.write(
24331
+ `The pinned ACP adapter for ${specification.label} could not be installed automatically; the daemon will continue.
24332
+ `
24333
+ );
24334
+ attempts.push(null);
24335
+ }
24336
+ }
24337
+ signal.throwIfAborted();
24338
+ const installed = attempts.filter(
24339
+ (attempt) => attempt !== null
24340
+ );
24341
+ if (installed.length > 0) {
24342
+ const verificationEnvironment = {
24343
+ ...this.#options.environment
24344
+ };
24345
+ for (const attempt of installed) {
24346
+ verificationEnvironment[attempt.specification.adapterOverrideVariable] = attempt.executable.path;
24347
+ }
24348
+ inventory = await discoverLocalAgentInventory(
24349
+ discoveryOptions(
24350
+ this.#options,
24351
+ verificationEnvironment,
24352
+ signal,
24353
+ this.#specifications
24354
+ )
24355
+ );
24356
+ }
24357
+ this.#options.catalog.transition(
24358
+ inventory.readyAgents(),
24359
+ inventory.installable
24360
+ );
24361
+ for (const attempt of installed) {
24362
+ const available = inventory.available.find(
24363
+ (candidate) => candidate instanceof AvailableAgent && candidate.specification.id === attempt.specification.id
24364
+ );
24365
+ if (available === void 0) {
24366
+ const unavailable = inventory.unavailable.find(
24367
+ (candidate) => candidate.specification.id === attempt.specification.id
24368
+ );
24369
+ this.#options.audit.record(
24370
+ "agent_adapter_auto_install_verification_failed",
24371
+ attempt.requestId,
24372
+ attempt.specification.id,
24373
+ { code: unavailable?.code ?? "agent_verification_failed" }
24374
+ );
24375
+ continue;
24376
+ }
24377
+ this.#options.audit.record(
24378
+ "agent_adapter_auto_install_verified",
24379
+ attempt.requestId,
24380
+ attempt.specification.id,
24381
+ {
24382
+ adapterVersion: available.agent.adapterVersion,
24383
+ agentVersion: available.agent.agentVersion
24384
+ }
24385
+ );
24386
+ process.stdout.write(
24387
+ `${attempt.specification.label} ACP integration is ready.
24388
+ `
24389
+ );
24390
+ }
24391
+ return inventory;
24392
+ };
24393
+ };
24394
+
24395
+ // src/agent-installation/fixed-installation-executor.ts
24396
+ import { realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
24397
+ import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5 } from "node:path";
24398
+
24399
+ // src/agent-installation/fixed-agent-install-recipes.ts
24400
+ import { mkdir as mkdir3, mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
24401
+ import { join as join7 } from "node:path";
24402
+
24403
+ // src/agent-installation/verified-download.ts
24404
+ import { createHash } from "node:crypto";
24405
+ import { open as open5, unlink } from "node:fs/promises";
24406
+ import { fetch as fetch3 } from "undici";
24407
+ var MAX_REDIRECTS = 4;
24408
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
24409
+ var InstallerDownloadError = class extends Error {
24410
+ code;
24411
+ constructor(code, message, options = {}) {
24412
+ super(message, options);
24413
+ this.code = code;
24414
+ this.name = "InstallerDownloadError";
24415
+ }
24416
+ };
24417
+ var VerifiedDownload = class {
24418
+ bytes;
24419
+ sha256;
24420
+ url;
24421
+ constructor(url2, bytes, sha256) {
24422
+ this.bytes = bytes;
24423
+ this.sha256 = sha256;
24424
+ this.url = url2;
24425
+ }
24426
+ };
24427
+ var UndiciInstallerDownloadTransport = class {
24428
+ get = async (url2, signal) => await fetch3(url2, {
24429
+ dispatcher: environmentProxyDispatcher,
24430
+ headers: {
24431
+ accept: "text/plain, application/octet-stream;q=0.9",
24432
+ "user-agent": "Mobius-Agent-Installer/0.0.3"
24433
+ },
24434
+ method: "GET",
24435
+ redirect: "manual",
24436
+ signal
24437
+ });
24438
+ };
24439
+ function validateUrl(url2, allowedHosts) {
24440
+ if (url2.protocol !== "https:" || url2.username.length > 0 || url2.password.length > 0 || url2.port.length > 0 || !allowedHosts.has(url2.hostname)) {
24441
+ throw new InstallerDownloadError(
24442
+ "installer_host_not_allowed",
24443
+ "The installer download target is not allowed"
24444
+ );
24445
+ }
24446
+ }
24447
+ async function fetchInstaller(options, signal, transport) {
24448
+ let url2 = new URL(options.url);
24449
+ for (let redirectCount = 0; redirectCount <= MAX_REDIRECTS; redirectCount += 1) {
24450
+ validateUrl(url2, options.allowedHosts);
24451
+ const response = await transport.get(url2, signal);
24452
+ if (!REDIRECT_STATUSES.has(response.status)) {
24453
+ return { response, url: url2 };
24454
+ }
24455
+ await response.body?.cancel();
24456
+ const location = response.headers.get("location");
24457
+ if (location === null || redirectCount === MAX_REDIRECTS) {
24458
+ throw new InstallerDownloadError(
24459
+ "installer_redirect_invalid",
24460
+ "The installer returned an invalid redirect"
24461
+ );
24462
+ }
24463
+ let redirected;
24464
+ try {
24465
+ redirected = new URL(location, url2);
24466
+ } catch (error61) {
24467
+ throw new InstallerDownloadError(
24468
+ "installer_redirect_invalid",
24469
+ "The installer returned an invalid redirect",
24470
+ { cause: error61 }
24471
+ );
24472
+ }
24473
+ try {
24474
+ validateUrl(redirected, options.allowedHosts);
24475
+ } catch (error61) {
24476
+ throw new InstallerDownloadError(
24477
+ "installer_redirect_not_allowed",
24478
+ "The installer redirect left the approved host allowlist",
24479
+ { cause: error61 }
24480
+ );
24481
+ }
24482
+ url2 = redirected;
24483
+ }
24484
+ throw new InstallerDownloadError(
24485
+ "installer_redirect_invalid",
24486
+ "The installer returned too many redirects"
24487
+ );
24488
+ }
24489
+ async function writeCompleteChunk(handle, chunk) {
24490
+ let offset = 0;
24491
+ while (offset < chunk.byteLength) {
24492
+ const written = await handle.write(
24493
+ chunk,
24494
+ offset,
24495
+ chunk.byteLength - offset
24496
+ );
24497
+ if (written.bytesWritten < 1) {
24498
+ throw new InstallerDownloadError(
24499
+ "installer_write_failed",
24500
+ "The verified installer could not be stored"
24501
+ );
24502
+ }
24503
+ offset += written.bytesWritten;
24504
+ }
24505
+ }
24506
+ async function downloadVerifiedFile(options, transport = new UndiciInstallerDownloadTransport()) {
24507
+ options.signal.throwIfAborted();
24508
+ const timeoutSignal = AbortSignal.timeout(options.timeoutMilliseconds);
24509
+ const signal = AbortSignal.any([options.signal, timeoutSignal]);
24510
+ let handle = null;
24511
+ let responseBody = null;
24512
+ try {
24513
+ const fetched = await fetchInstaller(options, signal, transport);
24514
+ responseBody = fetched.response.body;
24515
+ if (!fetched.response.ok || fetched.response.body === null) {
24516
+ throw new InstallerDownloadError(
24517
+ "installer_download_failed",
24518
+ "The approved installer could not be downloaded"
24519
+ );
24520
+ }
24521
+ const contentLength = fetched.response.headers.get("content-length");
24522
+ if (contentLength !== null) {
24523
+ const declaredLength = Number(contentLength);
24524
+ if (!Number.isSafeInteger(declaredLength) || declaredLength < 1 || declaredLength > options.maximumBytes) {
24525
+ throw new InstallerDownloadError(
24526
+ "installer_size_invalid",
24527
+ "The installer size is outside the approved limit"
24528
+ );
24529
+ }
24530
+ }
24531
+ handle = await open5(options.destination, "wx", 384);
24532
+ const digest = createHash("sha256");
24533
+ const reader = fetched.response.body.getReader();
24534
+ let bytes = 0;
24535
+ try {
24536
+ while (true) {
24537
+ signal.throwIfAborted();
24538
+ const chunk = await reader.read();
24539
+ if (chunk.done) {
24540
+ break;
24541
+ }
24542
+ bytes += chunk.value.byteLength;
24543
+ if (bytes > options.maximumBytes) {
24544
+ throw new InstallerDownloadError(
24545
+ "installer_size_invalid",
24546
+ "The installer exceeded the approved size limit"
24547
+ );
24548
+ }
24549
+ digest.update(chunk.value);
24550
+ await writeCompleteChunk(handle, chunk.value);
24551
+ }
24552
+ } finally {
24553
+ await reader.cancel().catch(() => void 0);
24554
+ reader.releaseLock();
24555
+ }
24556
+ if (bytes === 0) {
24557
+ throw new InstallerDownloadError(
24558
+ "installer_verification_failed",
24559
+ "The downloaded installer is empty"
24560
+ );
24561
+ }
24562
+ await handle.sync();
24563
+ await handle.close();
24564
+ handle = null;
24565
+ return new VerifiedDownload(fetched.url, bytes, digest.digest("hex"));
24566
+ } catch (error61) {
24567
+ if (handle !== null) {
24568
+ await handle.close().catch(() => void 0);
24569
+ }
24570
+ await unlink(options.destination).catch(() => void 0);
24571
+ if (options.signal.aborted) {
24572
+ options.signal.throwIfAborted();
24573
+ }
24574
+ if (timeoutSignal.aborted) {
24575
+ throw new InstallerDownloadError(
24576
+ "installer_download_timeout",
24577
+ "The installer download timed out",
24578
+ { cause: error61 }
24579
+ );
24580
+ }
24581
+ if (error61 instanceof InstallerDownloadError) {
24582
+ throw error61;
24583
+ }
24584
+ throw new InstallerDownloadError(
24585
+ "installer_download_failed",
24586
+ "The approved installer could not be downloaded",
24587
+ { cause: error61 }
24588
+ );
24589
+ } finally {
24590
+ await responseBody?.cancel().catch(() => void 0);
24591
+ }
24592
+ }
24593
+
24594
+ // src/agent-installation/fixed-agent-install-recipes.ts
24595
+ var INSTALLER_DOWNLOAD_LIMIT_BYTES = 1024 * 1024;
24596
+ var INSTALLER_DOWNLOAD_TIMEOUT_MILLISECONDS = 6e4;
24597
+ var AGENT_INSTALL_TIMEOUT_MILLISECONDS = 5 * 60 * 1e3;
24598
+ var MINIMAL_PATH = "/usr/bin:/bin";
24599
+ var FixedAgentInstallRecipe = class {
24600
+ assertSupportedPlatform = () => {
24601
+ if (process.platform !== "darwin" && process.platform !== "linux" || process.arch !== "arm64" && process.arch !== "x64") {
24602
+ throw new InstallationManagerError(
24603
+ "unsupported_install_platform",
24604
+ "This Agent installation recipe does not support the local platform"
24605
+ );
24606
+ }
24607
+ };
24608
+ requireInstalledExecutable = async (path) => {
24609
+ const executable = await resolveExecutablePath(path);
24610
+ if (executable === null) {
24611
+ throw new InstallationManagerError(
24612
+ "agent_verification_failed",
24613
+ "The Agent installer did not produce a valid executable"
24614
+ );
24615
+ }
24616
+ return executable;
24617
+ };
24618
+ };
24619
+ var CodexInstallRecipe = class extends FixedAgentInstallRecipe {
24620
+ id = "codex";
24621
+ label = "Codex";
24622
+ releaseChannel = "latest";
24623
+ install = async (options) => {
24624
+ this.assertSupportedPlatform();
24625
+ const root = join7(options.stateDirectory, "agents", this.id);
24626
+ const temporaryRoot = join7(options.stateDirectory, "temporary");
24627
+ await Promise.all([
24628
+ mkdir3(root, { mode: 448, recursive: true }),
24629
+ mkdir3(temporaryRoot, { mode: 448, recursive: true })
24630
+ ]);
24631
+ const temporaryDirectory = await mkdtemp2(
24632
+ join7(temporaryRoot, ".codex-install-")
24633
+ );
24634
+ const installerPath = join7(temporaryDirectory, "install.sh");
24635
+ const installDirectory = join7(root, "bin");
24636
+ const codexHome = join7(root, "home");
24637
+ try {
24638
+ await Promise.all([
24639
+ mkdir3(installDirectory, { mode: 448, recursive: true }),
24640
+ mkdir3(codexHome, { mode: 448, recursive: true })
24641
+ ]);
24642
+ await options.report("downloading");
24643
+ const download = await downloadVerifiedFile({
24644
+ allowedHosts: /* @__PURE__ */ new Set(["chatgpt.com", "releases.openai.com"]),
24645
+ destination: installerPath,
24646
+ maximumBytes: INSTALLER_DOWNLOAD_LIMIT_BYTES,
24647
+ signal: options.signal,
24648
+ timeoutMilliseconds: INSTALLER_DOWNLOAD_TIMEOUT_MILLISECONDS,
24649
+ url: new URL("https://chatgpt.com/codex/install.sh")
24650
+ });
24651
+ options.audit.record(
24652
+ "agent_installer_download_verified",
24653
+ options.requestId,
24654
+ this.id,
24655
+ {
24656
+ bytes: download.bytes.toString(),
24657
+ releaseChannel: this.releaseChannel,
24658
+ sha256: download.sha256
24659
+ }
24660
+ );
24661
+ await options.report("verifying");
24662
+ await options.report("installing_agent");
24663
+ await runBoundedProcess({
24664
+ arguments: [installerPath],
24665
+ command: "/bin/sh",
24666
+ cwd: temporaryDirectory,
24667
+ environment: {
24668
+ CODEX_HOME: codexHome,
24669
+ CODEX_INSTALL_DIR: installDirectory,
24670
+ CODEX_NON_INTERACTIVE: "1",
24671
+ CODEX_RELEASE: this.releaseChannel,
24672
+ HOME: codexHome,
24673
+ PATH: MINIMAL_PATH
24674
+ },
24675
+ outputLimitBytes: 64 * 1024,
24676
+ signal: options.signal,
24677
+ timeoutMilliseconds: AGENT_INSTALL_TIMEOUT_MILLISECONDS
24678
+ });
24679
+ return await this.requireInstalledExecutable(
24680
+ join7(installDirectory, "codex")
24681
+ );
24682
+ } catch (error61) {
24683
+ if (options.signal.aborted) {
24684
+ options.signal.throwIfAborted();
24685
+ }
24686
+ if (error61 instanceof InstallationManagerError || error61 instanceof Error && "code" in error61) {
24687
+ throw error61;
24688
+ }
24689
+ throw new InstallationManagerError(
24690
+ "agent_install_failed",
24691
+ "The fixed Codex installation recipe failed"
24692
+ );
24693
+ } finally {
24694
+ await rm2(temporaryDirectory, { force: true, recursive: true });
24695
+ }
24696
+ };
24697
+ };
24698
+ var ClaudeCodeInstallRecipe = class extends FixedAgentInstallRecipe {
24699
+ id = "claude";
24700
+ label = "Claude Code";
24701
+ releaseChannel = "stable";
24702
+ install = async (options) => {
24703
+ this.assertSupportedPlatform();
24704
+ const root = join7(options.stateDirectory, "agents", this.id);
24705
+ const managedHome = join7(root, "home");
24706
+ const temporaryRoot = join7(options.stateDirectory, "temporary");
24707
+ await Promise.all([
24708
+ mkdir3(managedHome, { mode: 448, recursive: true }),
24709
+ mkdir3(temporaryRoot, { mode: 448, recursive: true })
24710
+ ]);
24711
+ const temporaryDirectory = await mkdtemp2(
24712
+ join7(temporaryRoot, ".claude-install-")
24713
+ );
24714
+ const installerPath = join7(temporaryDirectory, "install.sh");
24715
+ try {
24716
+ await options.report("downloading");
24717
+ const download = await downloadVerifiedFile({
24718
+ allowedHosts: /* @__PURE__ */ new Set(["claude.ai"]),
24719
+ destination: installerPath,
24720
+ maximumBytes: INSTALLER_DOWNLOAD_LIMIT_BYTES,
24721
+ signal: options.signal,
24722
+ timeoutMilliseconds: INSTALLER_DOWNLOAD_TIMEOUT_MILLISECONDS,
24723
+ url: new URL("https://claude.ai/install.sh")
24724
+ });
24725
+ options.audit.record(
24726
+ "agent_installer_download_verified",
24727
+ options.requestId,
24728
+ this.id,
24729
+ {
24730
+ bytes: download.bytes.toString(),
24731
+ releaseChannel: this.releaseChannel,
24732
+ sha256: download.sha256
24733
+ }
24734
+ );
24735
+ await options.report("verifying");
24736
+ await options.report("installing_agent");
24737
+ await runBoundedProcess({
24738
+ arguments: [installerPath, "stable"],
24739
+ command: "/bin/bash",
24740
+ cwd: temporaryDirectory,
24741
+ environment: {
24742
+ HOME: managedHome,
24743
+ PATH: MINIMAL_PATH,
24744
+ SHELL: "/bin/bash"
24745
+ },
24746
+ outputLimitBytes: 64 * 1024,
24747
+ signal: options.signal,
24748
+ timeoutMilliseconds: AGENT_INSTALL_TIMEOUT_MILLISECONDS
24749
+ });
24750
+ return await this.requireInstalledExecutable(
24751
+ join7(managedHome, ".local", "bin", "claude")
24752
+ );
24753
+ } catch (error61) {
24754
+ if (options.signal.aborted) {
24755
+ options.signal.throwIfAborted();
24756
+ }
24757
+ if (error61 instanceof InstallationManagerError || error61 instanceof Error && "code" in error61) {
24758
+ throw error61;
24759
+ }
24760
+ throw new InstallationManagerError(
24761
+ "agent_install_failed",
24762
+ "The fixed Claude Code installation recipe failed"
24763
+ );
24764
+ } finally {
24765
+ await rm2(temporaryDirectory, { force: true, recursive: true });
24766
+ }
24767
+ };
24768
+ };
24769
+ function fixedAgentInstallRecipes() {
24770
+ return Object.freeze([
24771
+ new CodexInstallRecipe(),
24772
+ new ClaudeCodeInstallRecipe()
24773
+ ]);
22303
24774
  }
22304
- function readStateDirectory() {
22305
- const configured = process.env["MOBIUS_STATE_DIRECTORY"]?.trim();
22306
- if (configured !== void 0 && configured.length > 0) {
22307
- return resolve(configured);
24775
+
24776
+ // src/agent-installation/fixed-installation-executor.ts
24777
+ function findSpecification(specifications, agentId) {
24778
+ for (const specification of specifications) {
24779
+ if (specification.id === agentId) {
24780
+ return specification;
24781
+ }
22308
24782
  }
22309
- return join(homedir(), ".mobius");
22310
- }
22311
- async function persistConnection(channelId, relayOrigin, daemonToken, approvedRoot) {
22312
- const connectionsDirectory = join(readStateDirectory(), "connections");
22313
- await mkdir(connectionsDirectory, { mode: 448, recursive: true });
22314
- const connectionPath = join(connectionsDirectory, `${channelId}.json`);
22315
- const temporaryPath = join(
22316
- connectionsDirectory,
22317
- `.${channelId}.${randomUUID()}.tmp`
22318
- );
22319
- await writeFile(
22320
- temporaryPath,
22321
- `${JSON.stringify(
22322
- {
22323
- approvedRoot,
22324
- channelId,
22325
- daemonToken,
22326
- relayOrigin: relayOrigin.toString()
22327
- },
22328
- null,
22329
- 2
22330
- )}
22331
- `,
22332
- { flag: "wx", mode: 384 }
24783
+ throw new InstallationManagerError(
24784
+ "unknown_agent",
24785
+ "The requested Agent is not supported"
22333
24786
  );
22334
- await rename(temporaryPath, connectionPath);
22335
- return connectionPath;
22336
24787
  }
22337
- function decodeSavedConnection(value, expectedChannelId) {
22338
- if (!isRecord4(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS)) {
22339
- throw new PairingClientError(
22340
- "invalid_connection",
22341
- "The saved connection is invalid"
22342
- );
22343
- }
22344
- const approvedRoot = value["approvedRoot"];
22345
- const channelId = value["channelId"];
22346
- const daemonToken = value["daemonToken"];
22347
- const relayOrigin = value["relayOrigin"];
22348
- if (typeof approvedRoot !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN3.test(channelId) || typeof daemonToken !== "string" || !TOKEN_PATTERN.test(daemonToken) || typeof relayOrigin !== "string") {
22349
- throw new PairingClientError(
22350
- "invalid_connection",
22351
- "The saved connection is invalid"
22352
- );
24788
+ function findRecipe(recipes, agentId) {
24789
+ for (const recipe of recipes) {
24790
+ if (recipe.id === agentId) {
24791
+ return recipe;
24792
+ }
22353
24793
  }
22354
- return {
22355
- approvedRoot,
22356
- channelId,
22357
- daemonToken,
22358
- relayOrigin: parseRelayOrigin(relayOrigin)
22359
- };
22360
- }
22361
- async function readSavedConnection(channelId) {
22362
- const connectionPath = join(
22363
- readStateDirectory(),
22364
- "connections",
22365
- `${channelId}.json`
24794
+ throw new InstallationManagerError(
24795
+ "agent_not_installable",
24796
+ "The requested Agent does not have an approved installation recipe"
22366
24797
  );
22367
- let encoded = "";
22368
- const handle = await open2(connectionPath, "r");
24798
+ }
24799
+ function isInside2(root, candidate) {
24800
+ const difference = relative4(resolve5(root), resolve5(candidate));
24801
+ return difference.length === 0 || !difference.startsWith("..") && !isAbsolute3(difference);
24802
+ }
24803
+ function resolveApprovedRoot2(path) {
22369
24804
  try {
22370
- const metadata = await handle.stat();
22371
- if (!metadata.isFile() || metadata.size > MAX_CONNECTION_FILE_LENGTH) {
22372
- throw new PairingClientError(
22373
- "invalid_connection",
22374
- "The saved connection is invalid"
22375
- );
24805
+ const directory = realpathSync2(resolve5(path));
24806
+ if (!statSync2(directory).isDirectory()) {
24807
+ throw new Error("Approved root is not a directory");
22376
24808
  }
22377
- encoded = await handle.readFile({ encoding: "utf8" });
22378
- } finally {
22379
- await handle.close();
24809
+ return directory;
24810
+ } catch (error61) {
24811
+ throw new InstallationManagerError(
24812
+ "unsafe_installation_root",
24813
+ "The approved workspace is unavailable or unsafe",
24814
+ { cause: error61 }
24815
+ );
22380
24816
  }
22381
- let value;
24817
+ }
24818
+ function resolveUserInstallationDirectory(path) {
22382
24819
  try {
22383
- value = JSON.parse(encoded);
24820
+ const directory = realpathSync2(resolve5(path));
24821
+ const metadata = statSync2(directory);
24822
+ if (!metadata.isDirectory() || typeof process.getuid === "function" && metadata.uid !== process.getuid()) {
24823
+ throw new Error("Installation directory is not user-owned");
24824
+ }
24825
+ return directory;
22384
24826
  } catch (error61) {
22385
- throw new PairingClientError(
22386
- "invalid_connection",
22387
- "The saved connection is invalid",
24827
+ throw new InstallationManagerError(
24828
+ "unsafe_installation_root",
24829
+ "The local Agent installation root is unavailable or unsafe",
22388
24830
  { cause: error61 }
22389
24831
  );
22390
24832
  }
22391
- const connection = decodeSavedConnection(value, channelId);
22392
- return {
22393
- approvedRoot: await resolveApprovedRoot(connection.approvedRoot),
22394
- channelId: connection.channelId,
22395
- daemonToken: connection.daemonToken,
22396
- relayOrigin: connection.relayOrigin
22397
- };
22398
24833
  }
22399
- function buildPairedDaemon(connection, connectionPath) {
22400
- const relayUrl = new URL(
22401
- `/v1/channels/${encodeURIComponent(connection.channelId)}/daemon`,
22402
- connection.relayOrigin
22403
- );
22404
- relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
22405
- relayUrl.searchParams.set("token", connection.daemonToken);
22406
- const auditDatabasePath = join(
22407
- readStateDirectory(),
22408
- "audit",
22409
- `${connection.channelId}.sqlite`
24834
+ function translateExecutionError(error61) {
24835
+ if (error61 instanceof InstallationManagerError) {
24836
+ return error61;
24837
+ }
24838
+ if (error61 instanceof ManagedAdapterInstallError || error61 instanceof InstallerDownloadError || error61 instanceof ProcessExecutionError) {
24839
+ return new InstallationManagerError(error61.code, error61.message, {
24840
+ cause: error61
24841
+ });
24842
+ }
24843
+ return new InstallationManagerError(
24844
+ "installation_failed",
24845
+ "The fixed local Agent installation failed",
24846
+ { cause: error61 }
22410
24847
  );
22411
- return {
22412
- channelUrl: new URL(
22413
- `/channels/${encodeURIComponent(connection.channelId)}`,
22414
- connection.relayOrigin
22415
- ).toString(),
22416
- config: createDaemonConfig({
22417
- approvedRoot: connection.approvedRoot,
22418
- auditDatabasePath,
22419
- channelId: connection.channelId,
22420
- relayUrl
22421
- }),
22422
- connectionPath
22423
- };
22424
24848
  }
22425
- async function pairLocalMachine(arguments_) {
22426
- const options = parsePairCommand(arguments_);
22427
- const approvedRoot = await resolveApprovedRoot(options.root);
22428
- const claim2 = await claimPairing(options, approvedRoot);
22429
- const connectionPath = await persistConnection(
22430
- claim2.channelId,
22431
- options.relayOrigin,
22432
- claim2.daemonToken,
22433
- approvedRoot
22434
- );
22435
- return buildPairedDaemon(
22436
- {
22437
- approvedRoot,
22438
- channelId: claim2.channelId,
22439
- daemonToken: claim2.daemonToken,
22440
- relayOrigin: options.relayOrigin
22441
- },
22442
- connectionPath
22443
- );
24849
+ function requiresAgentReplacement(code) {
24850
+ return code === "agent_not_found" || code === "agent_probe_failed" || code === "agent_probe_timeout";
22444
24851
  }
22445
- async function resumeLocalMachine(arguments_) {
22446
- const channelId = parseChannelArgument(arguments_);
22447
- const connectionPath = join(
22448
- readStateDirectory(),
22449
- "connections",
22450
- `${channelId}.json`
22451
- );
22452
- return buildPairedDaemon(
22453
- await readSavedConnection(channelId),
22454
- connectionPath
22455
- );
24852
+ function requiresAdapterReplacement(code) {
24853
+ return code === "adapter_not_found" || code === "adapter_probe_failed" || code === "adapter_probe_timeout" || code === "acp_incompatible";
22456
24854
  }
24855
+ var FixedInstallationExecutor = class {
24856
+ #adapterInstaller;
24857
+ #approvedRoot;
24858
+ #audit;
24859
+ #catalog;
24860
+ #environment;
24861
+ #publishCatalog;
24862
+ #recipes;
24863
+ #specifications;
24864
+ #stateDirectory;
24865
+ constructor(options, specifications = supportedLocalAgentSpecifications(), recipes = fixedAgentInstallRecipes(), adapterInstaller) {
24866
+ const approvedRoot = resolveApprovedRoot2(options.approvedRoot);
24867
+ const stateDirectory = resolveUserInstallationDirectory(
24868
+ options.stateDirectory
24869
+ );
24870
+ if (isInside2(approvedRoot, stateDirectory)) {
24871
+ throw new InstallationManagerError(
24872
+ "unsafe_installation_root",
24873
+ "The Mobius state directory cannot be inside the approved workspace"
24874
+ );
24875
+ }
24876
+ this.#approvedRoot = approvedRoot;
24877
+ this.#audit = options.audit;
24878
+ this.#catalog = options.catalog;
24879
+ this.#environment = Object.freeze({ ...options.environment });
24880
+ this.#publishCatalog = options.publishCatalog;
24881
+ this.#recipes = Object.freeze([...recipes]);
24882
+ this.#specifications = Object.freeze([...specifications]);
24883
+ this.#stateDirectory = stateDirectory;
24884
+ this.#adapterInstaller = adapterInstaller ?? new ManagedAdapterInstaller({
24885
+ approvedRoot,
24886
+ environment: this.#environment,
24887
+ stateDirectory
24888
+ });
24889
+ }
24890
+ execute = async (agentId, requestId, report, signal) => {
24891
+ try {
24892
+ const specification = findSpecification(this.#specifications, agentId);
24893
+ const recipe = findRecipe(this.#recipes, agentId);
24894
+ this.#audit.record(
24895
+ "agent_installation_recipe_resolved",
24896
+ requestId,
24897
+ agentId,
24898
+ {
24899
+ adapterVersion: specification.adapterVersion,
24900
+ releaseChannel: recipe.releaseChannel
24901
+ }
24902
+ );
24903
+ const discoveryOptions2 = {
24904
+ approvedRoot: this.#approvedRoot,
24905
+ environment: this.#environment,
24906
+ signal
24907
+ };
24908
+ let agent = await resolveAgentExecutable(specification, discoveryOptions2);
24909
+ let installedAgent = false;
24910
+ if (agent === null) {
24911
+ agent = await recipe.install({
24912
+ audit: this.#audit,
24913
+ report,
24914
+ requestId,
24915
+ signal,
24916
+ stateDirectory: this.#stateDirectory
24917
+ });
24918
+ installedAgent = true;
24919
+ }
24920
+ let adapter = await resolveAdapterExecutable(
24921
+ specification,
24922
+ discoveryOptions2
24923
+ );
24924
+ let installedAdapter = false;
24925
+ if (adapter === null) {
24926
+ await report("installing_adapter");
24927
+ adapter = await this.#adapterInstaller.install(specification, signal);
24928
+ installedAdapter = true;
24929
+ this.#audit.record("agent_adapter_verified", requestId, agentId, {
24930
+ version: specification.adapterVersion
24931
+ });
24932
+ }
24933
+ while (true) {
24934
+ await report("checking_acp");
24935
+ const environment = { ...this.#environment };
24936
+ environment[specification.agentOverrideVariable] = agent.path;
24937
+ environment[specification.adapterOverrideVariable] = adapter.path;
24938
+ const inventory = await discoverLocalAgentInventory({
24939
+ approvedRoot: this.#approvedRoot,
24940
+ environment,
24941
+ signal,
24942
+ specifications: this.#specifications
24943
+ });
24944
+ const verified = inventory.available.find(
24945
+ (candidate) => candidate.specification.id === agentId
24946
+ );
24947
+ if (verified instanceof AvailableAgent) {
24948
+ this.#audit.record(
24949
+ "agent_installation_acp_verified",
24950
+ requestId,
24951
+ agentId,
24952
+ {
24953
+ adapterVersion: verified.agent.adapterVersion,
24954
+ agentVersion: verified.agent.agentVersion
24955
+ }
24956
+ );
24957
+ const previous = this.#catalog.read();
24958
+ this.#catalog.transition(
24959
+ inventory.readyAgents(),
24960
+ inventory.installable
24961
+ );
24962
+ try {
24963
+ await this.#publishCatalog(signal);
24964
+ } catch (error61) {
24965
+ this.#catalog.transition(
24966
+ previous.agents,
24967
+ previous.installableAgents
24968
+ );
24969
+ throw error61;
24970
+ }
24971
+ return;
24972
+ }
24973
+ const unavailable = inventory.unavailable.find(
24974
+ (candidate) => candidate.specification.id === agentId
24975
+ );
24976
+ if (unavailable === void 0) {
24977
+ throw new InstallationManagerError(
24978
+ "agent_verification_failed",
24979
+ "The complete Agent ACP path did not pass verification"
24980
+ );
24981
+ }
24982
+ if (!installedAdapter && requiresAdapterReplacement(unavailable.code)) {
24983
+ await report("installing_adapter");
24984
+ adapter = await this.#adapterInstaller.install(specification, signal);
24985
+ installedAdapter = true;
24986
+ this.#audit.record("agent_adapter_verified", requestId, agentId, {
24987
+ version: specification.adapterVersion
24988
+ });
24989
+ continue;
24990
+ }
24991
+ if (!installedAgent && (requiresAgentReplacement(unavailable.code) || unavailable.code === "acp_incompatible")) {
24992
+ agent = await recipe.install({
24993
+ audit: this.#audit,
24994
+ report,
24995
+ requestId,
24996
+ signal,
24997
+ stateDirectory: this.#stateDirectory
24998
+ });
24999
+ installedAgent = true;
25000
+ continue;
25001
+ }
25002
+ throw new InstallationManagerError(
25003
+ unavailable.code,
25004
+ unavailable.message
25005
+ );
25006
+ }
25007
+ } catch (error61) {
25008
+ if (signal.aborted) {
25009
+ signal.throwIfAborted();
25010
+ }
25011
+ throw translateExecutionError(error61);
25012
+ }
25013
+ };
25014
+ };
22457
25015
 
22458
- // src/run-daemon.ts
22459
- import { randomUUID as randomUUID3 } from "node:crypto";
22460
- import { basename as basename3 } from "node:path";
22461
- import { WebSocket as WebSocket2 } from "undici";
25016
+ // src/agent-installation/local-installation-authorizer.ts
25017
+ import { createInterface } from "node:readline/promises";
25018
+ var ConsoleInstallationAuthorizer = class {
25019
+ #stateDirectory;
25020
+ constructor(stateDirectory) {
25021
+ this.#stateDirectory = stateDirectory;
25022
+ }
25023
+ authorize = async (agentId, label, requestId, signal) => {
25024
+ signal.throwIfAborted();
25025
+ const grants = await readGrantedAgents(this.#stateDirectory);
25026
+ signal.throwIfAborted();
25027
+ if (grants.has(agentId)) {
25028
+ return true;
25029
+ }
25030
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
25031
+ return false;
25032
+ }
25033
+ const prompt = createInterface({
25034
+ input: process.stdin,
25035
+ output: process.stdout
25036
+ });
25037
+ try {
25038
+ const answer = await prompt.question(
25039
+ `
25040
+ Mobius Web requested installation of ${label} (${requestId}). Allow this fixed user-level recipe? [y/N] `,
25041
+ { signal }
25042
+ );
25043
+ const normalized = answer.trim().toLowerCase();
25044
+ return normalized === "y" || normalized === "yes";
25045
+ } finally {
25046
+ prompt.close();
25047
+ }
25048
+ };
25049
+ };
22462
25050
 
22463
25051
  // src/authorization/workspace-directory.ts
22464
- import { readdir, realpath as realpath3, stat as stat3 } from "node:fs/promises";
25052
+ import { readdir, realpath as realpath6, stat as stat5 } from "node:fs/promises";
22465
25053
  import {
22466
25054
  basename as basename2,
22467
- dirname as dirname3,
22468
- isAbsolute,
22469
- join as join2,
25055
+ dirname as dirname4,
25056
+ isAbsolute as isAbsolute4,
25057
+ join as join8,
22470
25058
  normalize,
22471
- relative
25059
+ relative as relative5
22472
25060
  } from "node:path";
22473
25061
  var ROOT_DIRECTORY_ID = "root";
22474
25062
  var DIRECTORY_ID_PREFIX = "dir-";
@@ -22507,14 +25095,14 @@ function decodeDirectoryId(value) {
22507
25095
  throw new LocalAuthorizationError("The workspace directory is invalid");
22508
25096
  }
22509
25097
  const normalized = normalize(decoded);
22510
- if (decoded.includes("\0") || isAbsolute(decoded) || normalized === ".." || normalized.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || normalized !== decoded) {
25098
+ if (decoded.includes("\0") || isAbsolute4(decoded) || normalized === ".." || normalized.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || normalized !== decoded) {
22511
25099
  throw new LocalAuthorizationError("The workspace directory is invalid");
22512
25100
  }
22513
25101
  return normalized;
22514
25102
  }
22515
25103
  function isContained(root, target) {
22516
- const remainder = relative(root, target);
22517
- return remainder.length === 0 || !isAbsolute(remainder) && remainder !== ".." && !remainder.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
25104
+ const remainder = relative5(root, target);
25105
+ return remainder.length === 0 || !isAbsolute4(remainder) && remainder !== ".." && !remainder.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
22518
25106
  }
22519
25107
  var WorkspaceDirectoryService = class {
22520
25108
  #approvedBindingId;
@@ -22532,8 +25120,8 @@ var WorkspaceDirectoryService = class {
22532
25120
  const root = await this.resolveRoot();
22533
25121
  const relativePath = decodeDirectoryId(requestedDirectoryId);
22534
25122
  try {
22535
- const target = await realpath3(join2(root, relativePath));
22536
- const metadata = await stat3(target);
25123
+ const target = await realpath6(join8(root, relativePath));
25124
+ const metadata = await stat5(target);
22537
25125
  if (!metadata.isDirectory() || !isContained(root, target)) {
22538
25126
  throw new LocalAuthorizationError(
22539
25127
  "The workspace directory is not authorized"
@@ -22561,10 +25149,10 @@ var WorkspaceDirectoryService = class {
22561
25149
  if (entries.length === 500) {
22562
25150
  break;
22563
25151
  }
22564
- const logicalChild = logicalRelativePath.length === 0 ? child.name : join2(logicalRelativePath, child.name);
25152
+ const logicalChild = logicalRelativePath.length === 0 ? child.name : join8(logicalRelativePath, child.name);
22565
25153
  try {
22566
- const resolvedChild = await realpath3(join2(target, child.name));
22567
- const metadata = await stat3(resolvedChild);
25154
+ const resolvedChild = await realpath6(join8(target, child.name));
25155
+ const metadata = await stat5(resolvedChild);
22568
25156
  if (metadata.isDirectory() && isContained(root, resolvedChild)) {
22569
25157
  entries.push({
22570
25158
  directoryId: directoryId(logicalChild),
@@ -22576,7 +25164,7 @@ var WorkspaceDirectoryService = class {
22576
25164
  }
22577
25165
  entries.sort((left, right) => left.name.localeCompare(right.name));
22578
25166
  const labelSuffix = logicalRelativePath.replaceAll("\\", "/");
22579
- const parentRelative = logicalRelativePath.length === 0 || dirname3(logicalRelativePath) === "." ? "" : dirname3(logicalRelativePath);
25167
+ const parentRelative = logicalRelativePath.length === 0 || dirname4(logicalRelativePath) === "." ? "" : dirname4(logicalRelativePath);
22580
25168
  return new WorkspaceDirectoryListing(
22581
25169
  requestedDirectoryId,
22582
25170
  labelSuffix.length === 0 ? basename2(root) : `${basename2(root)}/${labelSuffix}`,
@@ -22610,8 +25198,8 @@ var WorkspaceDirectoryService = class {
22610
25198
  };
22611
25199
  resolveRoot = async () => {
22612
25200
  try {
22613
- const root = await realpath3(this.#approvedRoot);
22614
- const metadata = await stat3(root);
25201
+ const root = await realpath6(this.#approvedRoot);
25202
+ const metadata = await stat5(root);
22615
25203
  if (!metadata.isDirectory()) {
22616
25204
  throw new LocalAuthorizationError(
22617
25205
  "The approved root is not a directory"
@@ -22645,7 +25233,7 @@ var RelayConnectionError = class extends Error {
22645
25233
  };
22646
25234
  function connectRelay(url2, signal) {
22647
25235
  signal.throwIfAborted();
22648
- return new Promise((resolve2, reject) => {
25236
+ return new Promise((resolve6, reject) => {
22649
25237
  const socket = new WebSocket(url2, {
22650
25238
  dispatcher: environmentProxyDispatcher
22651
25239
  });
@@ -22698,7 +25286,7 @@ function connectRelay(url2, signal) {
22698
25286
  }
22699
25287
  finished = true;
22700
25288
  cleanup();
22701
- resolve2(socket);
25289
+ resolve6(socket);
22702
25290
  };
22703
25291
  function onTimeout() {
22704
25292
  fail(
@@ -22719,7 +25307,7 @@ function connectRelay(url2, signal) {
22719
25307
  }
22720
25308
 
22721
25309
  // src/relay-connection/liveness.ts
22722
- import { randomUUID as randomUUID2 } from "node:crypto";
25310
+ import { randomUUID as randomUUID3 } from "node:crypto";
22723
25311
  var HEARTBEAT_ACK_TIMEOUT_MILLISECONDS = 1e4;
22724
25312
  var HEARTBEAT_INTERVAL_MILLISECONDS = 2e4;
22725
25313
  var RelayLivenessState = class {
@@ -22810,7 +25398,7 @@ var RelayConnectionLiveness = class {
22810
25398
  }
22811
25399
  throw new Error("Heartbeat was scheduled in an invalid state");
22812
25400
  }
22813
- const heartbeatId = randomUUID2();
25401
+ const heartbeatId = randomUUID3();
22814
25402
  this.#state = new AwaitingHeartbeatAckState(
22815
25403
  heartbeatId,
22816
25404
  this.#timer.schedule(
@@ -22933,6 +25521,127 @@ var DaemonAcpObserver = class {
22933
25521
  return Promise.resolve();
22934
25522
  };
22935
25523
  };
25524
+ var AuditInstallationEvents = class {
25525
+ #audit;
25526
+ constructor(audit) {
25527
+ this.#audit = audit;
25528
+ }
25529
+ record = (event, requestId, agentId, details = {}) => {
25530
+ this.#audit.record({
25531
+ commandId: requestId,
25532
+ details,
25533
+ event,
25534
+ sessionId: `agent-installation:${agentId}`
25535
+ });
25536
+ };
25537
+ };
25538
+ var DaemonInstallationObserver = class {
25539
+ #started = /* @__PURE__ */ new Set();
25540
+ #socket = null;
25541
+ connect = (socket) => {
25542
+ this.#socket = socket;
25543
+ };
25544
+ disconnect = (socket) => {
25545
+ if (this.#socket === socket) {
25546
+ this.#socket = null;
25547
+ }
25548
+ };
25549
+ #send = (socket, message) => {
25550
+ try {
25551
+ send(socket, message);
25552
+ } catch {
25553
+ if (this.#socket === socket) {
25554
+ this.#socket = null;
25555
+ }
25556
+ }
25557
+ };
25558
+ onState = (state) => {
25559
+ const socket = this.#socket;
25560
+ if (socket === null || socket.readyState !== WebSocket2.OPEN) {
25561
+ return Promise.resolve();
25562
+ }
25563
+ if (state instanceof RequestedInstallation) {
25564
+ return Promise.resolve();
25565
+ }
25566
+ if (state instanceof AwaitingLocalApproval) {
25567
+ this.#send(
25568
+ socket,
25569
+ encodeAgentInstallAwaitingApprovalEvent({
25570
+ agentId: state.agentId,
25571
+ kind: AGENT_INSTALL_AWAITING_APPROVAL_KIND,
25572
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
25573
+ requestId: state.requestId
25574
+ })
25575
+ );
25576
+ return Promise.resolve();
25577
+ }
25578
+ if (state instanceof RunningInstallation) {
25579
+ if (!this.#started.has(state.requestId)) {
25580
+ this.#started.add(state.requestId);
25581
+ this.#send(
25582
+ socket,
25583
+ encodeAgentInstallStartedEvent({
25584
+ agentId: state.agentId,
25585
+ kind: AGENT_INSTALL_STARTED_KIND,
25586
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
25587
+ requestId: state.requestId
25588
+ })
25589
+ );
25590
+ }
25591
+ this.#send(
25592
+ socket,
25593
+ encodeAgentInstallProgressEvent({
25594
+ agentId: state.agentId,
25595
+ kind: AGENT_INSTALL_PROGRESS_KIND,
25596
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
25597
+ requestId: state.requestId,
25598
+ stage: state.stage
25599
+ })
25600
+ );
25601
+ return Promise.resolve();
25602
+ }
25603
+ if (state instanceof SucceededInstallation) {
25604
+ this.#send(
25605
+ socket,
25606
+ encodeAgentInstallCompletedEvent({
25607
+ agentId: state.agentId,
25608
+ authenticationMayBeRequired: state.authenticationMayBeRequired,
25609
+ kind: AGENT_INSTALL_COMPLETED_KIND,
25610
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
25611
+ requestId: state.requestId
25612
+ })
25613
+ );
25614
+ return Promise.resolve();
25615
+ }
25616
+ if (state instanceof FailedInstallation) {
25617
+ this.#send(
25618
+ socket,
25619
+ encodeAgentInstallFailedEvent({
25620
+ agentId: state.agentId,
25621
+ code: state.code,
25622
+ kind: AGENT_INSTALL_FAILED_KIND,
25623
+ message: state.message,
25624
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
25625
+ requestId: state.requestId
25626
+ })
25627
+ );
25628
+ return Promise.resolve();
25629
+ }
25630
+ if (state instanceof CancelledInstallation) {
25631
+ this.#send(
25632
+ socket,
25633
+ encodeAgentInstallCancelledEvent({
25634
+ agentId: state.agentId,
25635
+ kind: AGENT_INSTALL_CANCELLED_KIND,
25636
+ protocolVersion: MOBIUS_PROTOCOL_VERSION,
25637
+ requestId: state.requestId
25638
+ })
25639
+ );
25640
+ return Promise.resolve();
25641
+ }
25642
+ throw new Error("Unsupported Agent installation state");
25643
+ };
25644
+ };
22936
25645
  var ResolvedSessionDirectories = class {
22937
25646
  additionalDirectories;
22938
25647
  additionalDirectoryEntries;
@@ -22989,13 +25698,13 @@ function reportFailure(socket, command, code, message) {
22989
25698
  );
22990
25699
  }
22991
25700
  function operationErrorCode(error61) {
22992
- if (error61 instanceof AcpExecutionError || error61 instanceof LocalAuthorizationError) {
25701
+ if (error61 instanceof AcpExecutionError || error61 instanceof LocalAuthorizationError || error61 instanceof LocalAgentCatalogError) {
22993
25702
  return error61.code;
22994
25703
  }
22995
25704
  return "session_operation_failed";
22996
25705
  }
22997
25706
  function operationErrorMessage(error61) {
22998
- if (error61 instanceof AcpExecutionError || error61 instanceof LocalAuthorizationError) {
25707
+ if (error61 instanceof AcpExecutionError || error61 instanceof LocalAuthorizationError || error61 instanceof LocalAgentCatalogError) {
22999
25708
  return error61.message;
23000
25709
  }
23001
25710
  return "The local agent operation could not be completed";
@@ -23080,7 +25789,7 @@ async function executeSessionPrepare(socket, command, config2, directories, audi
23080
25789
  command.directoryId,
23081
25790
  command.additionalDirectoryIds
23082
25791
  );
23083
- const agent = findLocalAgent(config2.agents, command.agentId);
25792
+ const agent = config2.catalog.find(command.agentId);
23084
25793
  const prepared = await sessions.prepareSession({
23085
25794
  additionalDirectories: resolved.additionalDirectories,
23086
25795
  agent,
@@ -23130,7 +25839,7 @@ async function executeSessionConfigure(socket, command, config2, directories, au
23130
25839
  );
23131
25840
  const configOptions = await sessions.configureSession({
23132
25841
  additionalDirectories: resolved.additionalDirectories,
23133
- agent: findLocalAgent(config2.agents, command.agentId),
25842
+ agent: config2.catalog.find(command.agentId),
23134
25843
  booleanValue: command.booleanValue,
23135
25844
  configId: command.configId,
23136
25845
  cwd: resolved.cwd,
@@ -23167,8 +25876,8 @@ async function executePrompt(socket, command, config2, directories, audit, sessi
23167
25876
  command.directoryId,
23168
25877
  command.additionalDirectoryIds
23169
25878
  );
23170
- const agent = findLocalAgent(config2.agents, command.agentId);
23171
- const processAttemptId = randomUUID3();
25879
+ const agent = config2.catalog.find(command.agentId);
25880
+ const processAttemptId = randomUUID4();
23172
25881
  audit.record({
23173
25882
  commandId: command.commandId,
23174
25883
  event: "session_authorized",
@@ -23252,11 +25961,12 @@ async function executePrompt(socket, command, config2, directories, audit, sessi
23252
25961
  event: "session_failed",
23253
25962
  sessionId: command.sessionId
23254
25963
  });
25964
+ const code = operationErrorCode(error61);
23255
25965
  reportFailure(
23256
25966
  socket,
23257
25967
  command,
23258
- "agent_execution_failed",
23259
- "The local agent could not complete the request"
25968
+ code === "session_operation_failed" ? "agent_execution_failed" : code,
25969
+ code === "session_operation_failed" ? "The local agent could not complete the request" : operationErrorMessage(error61)
23260
25970
  );
23261
25971
  console.error("Mobius daemon command failed", error61);
23262
25972
  }
@@ -23265,24 +25975,25 @@ function waitForRelayRetry(signal, timer) {
23265
25975
  if (signal.aborted) {
23266
25976
  return Promise.resolve();
23267
25977
  }
23268
- return new Promise((resolve2) => {
25978
+ return new Promise((resolve6) => {
23269
25979
  const retry = timer.schedule(finish, RELAY_RECONNECT_DELAY_MILLISECONDS);
23270
25980
  function finish() {
23271
25981
  retry.cancel();
23272
25982
  signal.removeEventListener("abort", finish);
23273
- resolve2();
25983
+ resolve6();
23274
25984
  }
23275
25985
  signal.addEventListener("abort", finish, { once: true });
23276
25986
  });
23277
25987
  }
23278
- async function runRelayConnection(config2, directories, audit, sessions, signal, timer) {
25988
+ async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, signal, timer) {
23279
25989
  const relayUrl = new URL(config2.relayUrl);
23280
25990
  relayUrl.searchParams.set("liveness", "1");
23281
25991
  const socket = await connectRelay(relayUrl, signal);
23282
25992
  const activeTurns = /* @__PURE__ */ new Map();
23283
25993
  const connectionController = new AbortController();
23284
25994
  sessions.setObserver(new DaemonAcpObserver(socket));
23285
- await new Promise((resolve2, reject) => {
25995
+ installationObserver.connect(socket);
25996
+ await new Promise((resolve6, reject) => {
23286
25997
  let finished = false;
23287
25998
  const cleanup = () => {
23288
25999
  connectionRefresh.close();
@@ -23291,6 +26002,7 @@ async function runRelayConnection(config2, directories, audit, sessions, signal,
23291
26002
  socket.removeEventListener("close", handleClose);
23292
26003
  socket.removeEventListener("error", handleError);
23293
26004
  socket.removeEventListener("message", handleMessage);
26005
+ installationObserver.disconnect(socket);
23294
26006
  connectionController.abort();
23295
26007
  for (const turn of activeTurns.values()) {
23296
26008
  turn.abortConnection();
@@ -23303,7 +26015,7 @@ async function runRelayConnection(config2, directories, audit, sessions, signal,
23303
26015
  }
23304
26016
  finished = true;
23305
26017
  cleanup();
23306
- resolve2();
26018
+ resolve6();
23307
26019
  };
23308
26020
  const rejectConnection = () => {
23309
26021
  if (finished) {
@@ -23456,6 +26168,20 @@ async function runRelayConnection(config2, directories, audit, sessions, signal,
23456
26168
  });
23457
26169
  return;
23458
26170
  }
26171
+ if (kind === AGENT_INSTALL_REQUEST_KIND) {
26172
+ const command2 = decodeAgentInstallRequestCommand(encoded);
26173
+ void installations.request(command2, signal).catch((error61) => {
26174
+ console.error("Mobius Agent installation failed", error61);
26175
+ });
26176
+ return;
26177
+ }
26178
+ if (kind === AGENT_INSTALL_CANCEL_KIND) {
26179
+ const command2 = decodeAgentInstallCancelCommand(encoded);
26180
+ void installations.cancel(command2.requestId).catch((error61) => {
26181
+ reportOperationFailure(socket, command2.requestId, "", error61);
26182
+ });
26183
+ return;
26184
+ }
23459
26185
  if (kind !== SESSION_PROMPT_KIND) {
23460
26186
  throw new Error("Unsupported protocol message");
23461
26187
  }
@@ -23500,6 +26226,31 @@ async function runRelayConnection(config2, directories, audit, sessions, signal,
23500
26226
  });
23501
26227
  }
23502
26228
  async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
26229
+ const installationAudit = new AuditInstallationEvents(audit);
26230
+ const stateDirectory = readStateDirectory();
26231
+ const specifications = supportedLocalAgentSpecifications();
26232
+ const installationExecutor = new FixedInstallationExecutor({
26233
+ approvedRoot: config2.approvedRoot,
26234
+ audit: installationAudit,
26235
+ catalog: config2.catalog,
26236
+ environment: process.env,
26237
+ publishCatalog: async (installationSignal) => await publishAgentCatalog(config2, installationSignal),
26238
+ stateDirectory
26239
+ });
26240
+ await new AutomaticAdapterProvisioner(
26241
+ {
26242
+ approvedRoot: config2.approvedRoot,
26243
+ audit: installationAudit,
26244
+ catalog: config2.catalog,
26245
+ environment: process.env
26246
+ },
26247
+ new ManagedAdapterInstaller({
26248
+ approvedRoot: config2.approvedRoot,
26249
+ environment: process.env,
26250
+ stateDirectory
26251
+ }),
26252
+ specifications
26253
+ ).prepare(signal);
23503
26254
  const sessionStore = new SqliteAcpSessionStore(config2.auditDatabasePath);
23504
26255
  const sessions = new AcpSessionManager(
23505
26256
  new NullAcpSessionObserver(),
@@ -23509,15 +26260,26 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
23509
26260
  config2.bindingId,
23510
26261
  config2.approvedRoot
23511
26262
  );
26263
+ const installationObserver = new DaemonInstallationObserver();
26264
+ const installations = new AgentInstallationManager(
26265
+ specifications,
26266
+ new ConsoleInstallationAuthorizer(stateDirectory),
26267
+ installationExecutor,
26268
+ installationObserver,
26269
+ installationAudit
26270
+ );
23512
26271
  let retryAnnounced = false;
23513
26272
  try {
23514
26273
  while (!signal.aborted) {
23515
26274
  try {
26275
+ await publishAgentCatalog(config2, signal);
23516
26276
  await runRelayConnection(
23517
26277
  config2,
23518
26278
  directories,
23519
26279
  audit,
23520
26280
  sessions,
26281
+ installations,
26282
+ installationObserver,
23521
26283
  signal,
23522
26284
  timer
23523
26285
  );
@@ -23534,8 +26296,15 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
23534
26296
  await waitForRelayRetry(signal, timer);
23535
26297
  }
23536
26298
  } finally {
23537
- await sessions.close();
23538
- sessionStore.close();
26299
+ try {
26300
+ await installations.close();
26301
+ } finally {
26302
+ try {
26303
+ await sessions.close();
26304
+ } finally {
26305
+ sessionStore.close();
26306
+ }
26307
+ }
23539
26308
  }
23540
26309
  }
23541
26310
  export {