@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.
- package/dist/acp/session-manager.d.ts.map +1 -1
- package/dist/acp/session-manager.js +15 -29
- package/dist/acp/session-manager.js.map +1 -1
- package/dist/agent-catalog/agent-diagnostics.d.ts +3 -0
- package/dist/agent-catalog/agent-diagnostics.d.ts.map +1 -0
- package/dist/agent-catalog/agent-diagnostics.js +62 -0
- package/dist/agent-catalog/agent-diagnostics.js.map +1 -0
- package/dist/agent-catalog/discover-local-agents.d.ts +77 -0
- package/dist/agent-catalog/discover-local-agents.d.ts.map +1 -0
- package/dist/agent-catalog/discover-local-agents.js +478 -0
- package/dist/agent-catalog/discover-local-agents.js.map +1 -0
- package/dist/agent-catalog/local-agent.d.ts +38 -0
- package/dist/agent-catalog/local-agent.d.ts.map +1 -0
- package/dist/agent-catalog/local-agent.js +93 -0
- package/dist/agent-catalog/local-agent.js.map +1 -0
- package/dist/agent-catalog/publish-agent-catalog.d.ts +3 -0
- package/dist/agent-catalog/publish-agent-catalog.d.ts.map +1 -0
- package/dist/agent-catalog/publish-agent-catalog.js +58 -0
- package/dist/agent-catalog/publish-agent-catalog.js.map +1 -0
- package/dist/agent-installation/agent-installation-manager.d.ts +60 -0
- package/dist/agent-installation/agent-installation-manager.d.ts.map +1 -0
- package/dist/agent-installation/agent-installation-manager.js +242 -0
- package/dist/agent-installation/agent-installation-manager.js.map +1 -0
- package/dist/agent-installation/automatic-adapter-provisioner.d.ts +18 -0
- package/dist/agent-installation/automatic-adapter-provisioner.d.ts.map +1 -0
- package/dist/agent-installation/automatic-adapter-provisioner.js +116 -0
- package/dist/agent-installation/automatic-adapter-provisioner.js.map +1 -0
- package/dist/agent-installation/fixed-agent-install-recipes.d.ts +20 -0
- package/dist/agent-installation/fixed-agent-install-recipes.d.ts.map +1 -0
- package/dist/agent-installation/fixed-agent-install-recipes.js +163 -0
- package/dist/agent-installation/fixed-agent-install-recipes.js.map +1 -0
- package/dist/agent-installation/fixed-installation-executor.d.ts +20 -0
- package/dist/agent-installation/fixed-installation-executor.d.ts.map +1 -0
- package/dist/agent-installation/fixed-installation-executor.js +213 -0
- package/dist/agent-installation/fixed-installation-executor.js.map +1 -0
- package/dist/agent-installation/installation-grants.d.ts +2 -0
- package/dist/agent-installation/installation-grants.d.ts.map +1 -0
- package/dist/agent-installation/installation-grants.js +52 -0
- package/dist/agent-installation/installation-grants.js.map +1 -0
- package/dist/agent-installation/local-installation-authorizer.d.ts +7 -0
- package/dist/agent-installation/local-installation-authorizer.d.ts.map +1 -0
- package/dist/agent-installation/local-installation-authorizer.js +32 -0
- package/dist/agent-installation/local-installation-authorizer.js.map +1 -0
- package/dist/agent-installation/managed-adapter-installer.d.ts +21 -0
- package/dist/agent-installation/managed-adapter-installer.d.ts.map +1 -0
- package/dist/agent-installation/managed-adapter-installer.js +161 -0
- package/dist/agent-installation/managed-adapter-installer.js.map +1 -0
- package/dist/agent-installation/verified-download.d.ts +24 -0
- package/dist/agent-installation/verified-download.d.ts.map +1 -0
- package/dist/agent-installation/verified-download.js +158 -0
- package/dist/agent-installation/verified-download.js.map +1 -0
- package/dist/audit/audit-store.d.ts +1 -0
- package/dist/audit/audit-store.d.ts.map +1 -1
- package/dist/audit/audit-store.js +12 -4
- package/dist/audit/audit-store.js.map +1 -1
- package/dist/cli.js +4430 -1573
- package/dist/cli.js.map +4 -4
- package/dist/config.d.ts +6 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -53
- package/dist/config.js.map +1 -1
- package/dist/index.js +3233 -464
- package/dist/index.js.map +4 -4
- package/dist/pairing/pair-local-machine.d.ts +2 -2
- package/dist/pairing/pair-local-machine.d.ts.map +1 -1
- package/dist/pairing/pair-local-machine.js +41 -20
- package/dist/pairing/pair-local-machine.js.map +1 -1
- package/dist/process/executable.d.ts +40 -0
- package/dist/process/executable.d.ts.map +1 -0
- package/dist/process/executable.js +313 -0
- package/dist/process/executable.js.map +1 -0
- package/dist/run-daemon.d.ts +1 -1
- package/dist/run-daemon.d.ts.map +1 -1
- package/dist/run-daemon.js +182 -12
- package/dist/run-daemon.js.map +1 -1
- package/dist/storage/state-directory.d.ts +2 -0
- package/dist/storage/state-directory.d.ts.map +1 -0
- package/dist/storage/state-directory.js +10 -0
- package/dist/storage/state-directory.js.map +1 -0
- 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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 =
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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["
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 = (
|
|
2081
|
-
const escapedDelim = escapeRegex(
|
|
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((
|
|
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
|
-
|
|
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 =
|
|
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((
|
|
20980
|
-
this.#child.once("error",
|
|
20981
|
-
this.#child.once("exit",
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
22033
|
-
const metadata = await
|
|
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
|
|
22053
|
-
realpath as
|
|
22054
|
-
rename,
|
|
22055
|
-
stat as
|
|
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 {
|
|
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/
|
|
22063
|
-
import {
|
|
22064
|
-
|
|
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/
|
|
22067
|
-
|
|
22068
|
-
|
|
22069
|
-
var
|
|
22070
|
-
var
|
|
22071
|
-
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
"
|
|
22076
|
-
|
|
22077
|
-
|
|
22078
|
-
|
|
22079
|
-
|
|
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 = "
|
|
22604
|
+
this.name = "ManagedAdapterInstallError";
|
|
22085
22605
|
}
|
|
22086
22606
|
};
|
|
22087
|
-
function
|
|
22607
|
+
function isRecord5(value) {
|
|
22088
22608
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22089
22609
|
}
|
|
22090
|
-
function
|
|
22091
|
-
|
|
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
|
|
22095
|
-
const
|
|
22096
|
-
|
|
22097
|
-
|
|
22098
|
-
|
|
22099
|
-
|
|
22100
|
-
|
|
22101
|
-
|
|
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
|
-
|
|
22108
|
-
|
|
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
|
-
|
|
22117
|
-
|
|
22118
|
-
|
|
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
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
|
|
22133
|
-
}
|
|
22134
|
-
|
|
22135
|
-
|
|
22136
|
-
|
|
22137
|
-
|
|
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
|
-
|
|
22141
|
-
|
|
22142
|
-
|
|
22143
|
-
"
|
|
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
|
-
|
|
22147
|
-
|
|
22148
|
-
|
|
22149
|
-
|
|
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
|
-
|
|
22153
|
-
|
|
22154
|
-
|
|
22155
|
-
"
|
|
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
|
-
|
|
22159
|
-
url2.pathname = "/";
|
|
22160
|
-
url2.search = "";
|
|
22161
|
-
return url2;
|
|
22686
|
+
return executable;
|
|
22162
22687
|
}
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
|
|
22166
|
-
|
|
22167
|
-
|
|
22168
|
-
|
|
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
|
|
22797
|
+
return join4(homedir(), ".mobius");
|
|
22172
22798
|
}
|
|
22173
|
-
|
|
22174
|
-
|
|
22175
|
-
|
|
22176
|
-
|
|
22177
|
-
|
|
22178
|
-
|
|
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
|
-
|
|
22182
|
-
|
|
22183
|
-
|
|
22184
|
-
|
|
22185
|
-
"Usage: mobius start --channel <channel-id>"
|
|
22186
|
-
);
|
|
22865
|
+
var AgentDiscovery = class {
|
|
22866
|
+
specification;
|
|
22867
|
+
constructor(specification) {
|
|
22868
|
+
this.specification = specification;
|
|
22187
22869
|
}
|
|
22188
|
-
|
|
22189
|
-
|
|
22190
|
-
|
|
22191
|
-
|
|
22192
|
-
|
|
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
|
-
|
|
22196
|
-
|
|
22197
|
-
|
|
22198
|
-
|
|
22199
|
-
|
|
22200
|
-
|
|
22201
|
-
|
|
22202
|
-
|
|
22203
|
-
|
|
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
|
-
|
|
22221
|
-
|
|
22222
|
-
|
|
22223
|
-
|
|
22224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
|
|
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
|
|
22239
|
-
|
|
22240
|
-
|
|
22241
|
-
if (
|
|
22242
|
-
|
|
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
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
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
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
|
|
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
|
|
23068
|
+
return version2;
|
|
22270
23069
|
}
|
|
22271
|
-
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
|
|
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
|
-
|
|
22299
|
-
|
|
22300
|
-
|
|
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
|
|
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
|
-
|
|
22305
|
-
|
|
22306
|
-
|
|
22307
|
-
|
|
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
|
-
|
|
22310
|
-
|
|
22311
|
-
|
|
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
|
|
22338
|
-
|
|
22339
|
-
|
|
22340
|
-
|
|
22341
|
-
|
|
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
|
-
|
|
22355
|
-
|
|
22356
|
-
|
|
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
|
-
|
|
22368
|
-
|
|
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
|
|
22371
|
-
if (!
|
|
22372
|
-
throw new
|
|
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
|
-
|
|
22378
|
-
}
|
|
22379
|
-
|
|
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
|
-
|
|
24817
|
+
}
|
|
24818
|
+
function resolveUserInstallationDirectory(path) {
|
|
22382
24819
|
try {
|
|
22383
|
-
|
|
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
|
|
22386
|
-
"
|
|
22387
|
-
"The
|
|
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
|
|
22400
|
-
|
|
22401
|
-
|
|
22402
|
-
|
|
22403
|
-
)
|
|
22404
|
-
|
|
22405
|
-
|
|
22406
|
-
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
|
|
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
|
-
|
|
22426
|
-
|
|
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
|
-
|
|
22446
|
-
|
|
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/
|
|
22459
|
-
import {
|
|
22460
|
-
|
|
22461
|
-
|
|
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
|
|
25052
|
+
import { readdir, realpath as realpath6, stat as stat5 } from "node:fs/promises";
|
|
22465
25053
|
import {
|
|
22466
25054
|
basename as basename2,
|
|
22467
|
-
dirname as
|
|
22468
|
-
isAbsolute,
|
|
22469
|
-
join as
|
|
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") ||
|
|
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 =
|
|
22517
|
-
return remainder.length === 0 || !
|
|
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
|
|
22536
|
-
const metadata = await
|
|
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 :
|
|
25152
|
+
const logicalChild = logicalRelativePath.length === 0 ? child.name : join8(logicalRelativePath, child.name);
|
|
22565
25153
|
try {
|
|
22566
|
-
const resolvedChild = await
|
|
22567
|
-
const metadata = await
|
|
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 ||
|
|
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
|
|
22614
|
-
const metadata = await
|
|
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((
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
23171
|
-
const processAttemptId =
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23538
|
-
|
|
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 {
|