@moxt-ai/mobius 0.0.3 → 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 +4284 -1178
- 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 +3476 -458
- 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/relay-connection/liveness.d.ts +14 -0
- package/dist/relay-connection/liveness.d.ts.map +1 -0
- package/dist/relay-connection/liveness.js +102 -0
- package/dist/relay-connection/liveness.js.map +1 -0
- package/dist/relay-connection/timer.d.ts +8 -0
- package/dist/relay-connection/timer.d.ts.map +1 -0
- package/dist/relay-connection/timer.js +8 -0
- package/dist/relay-connection/timer.js.map +1 -0
- package/dist/run-daemon.d.ts +3 -2
- package/dist/run-daemon.d.ts.map +1 -1
- package/dist/run-daemon.js +272 -22
- 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,10 +19787,21 @@ 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";
|
|
19331
19801
|
var SESSION_CANCEL_KIND = "session.cancel";
|
|
19802
|
+
var RUNTIME_LIVENESS_READY_KIND = "runtime.liveness.ready";
|
|
19803
|
+
var RUNTIME_HEARTBEAT_KIND = "runtime.heartbeat";
|
|
19804
|
+
var RUNTIME_HEARTBEAT_ACK_KIND = "runtime.heartbeat.ack";
|
|
19332
19805
|
var SESSION_STARTED_KIND = "session.started";
|
|
19333
19806
|
var AGENT_MESSAGE_DELTA_KIND = "agent.message.delta";
|
|
19334
19807
|
var AGENT_ACTIVITY_KIND = "agent.activity";
|
|
@@ -19551,6 +20024,38 @@ function decodeSessionCancelCommand(encoded) {
|
|
|
19551
20024
|
sessionId: readIdentifier(value, "sessionId")
|
|
19552
20025
|
};
|
|
19553
20026
|
}
|
|
20027
|
+
function decodeRuntimeLivenessReadyEvent(encoded) {
|
|
20028
|
+
const value = parseJson2(encoded);
|
|
20029
|
+
assertExactKeys(value, ["kind", "protocolVersion"]);
|
|
20030
|
+
assertProtocolVersion(value);
|
|
20031
|
+
assertKind(value, RUNTIME_LIVENESS_READY_KIND);
|
|
20032
|
+
return {
|
|
20033
|
+
kind: RUNTIME_LIVENESS_READY_KIND,
|
|
20034
|
+
protocolVersion: MOBIUS_PROTOCOL_VERSION
|
|
20035
|
+
};
|
|
20036
|
+
}
|
|
20037
|
+
function decodeRuntimeHeartbeatEvent(encoded) {
|
|
20038
|
+
const value = parseJson2(encoded);
|
|
20039
|
+
assertExactKeys(value, ["heartbeatId", "kind", "protocolVersion"]);
|
|
20040
|
+
assertProtocolVersion(value);
|
|
20041
|
+
assertKind(value, RUNTIME_HEARTBEAT_KIND);
|
|
20042
|
+
return {
|
|
20043
|
+
heartbeatId: readIdentifier(value, "heartbeatId"),
|
|
20044
|
+
kind: RUNTIME_HEARTBEAT_KIND,
|
|
20045
|
+
protocolVersion: MOBIUS_PROTOCOL_VERSION
|
|
20046
|
+
};
|
|
20047
|
+
}
|
|
20048
|
+
function decodeRuntimeHeartbeatAckEvent(encoded) {
|
|
20049
|
+
const value = parseJson2(encoded);
|
|
20050
|
+
assertExactKeys(value, ["heartbeatId", "kind", "protocolVersion"]);
|
|
20051
|
+
assertProtocolVersion(value);
|
|
20052
|
+
assertKind(value, RUNTIME_HEARTBEAT_ACK_KIND);
|
|
20053
|
+
return {
|
|
20054
|
+
heartbeatId: readIdentifier(value, "heartbeatId"),
|
|
20055
|
+
kind: RUNTIME_HEARTBEAT_ACK_KIND,
|
|
20056
|
+
protocolVersion: MOBIUS_PROTOCOL_VERSION
|
|
20057
|
+
};
|
|
20058
|
+
}
|
|
19554
20059
|
function decodeSessionStartedEvent(encoded) {
|
|
19555
20060
|
const value = parseJson2(encoded);
|
|
19556
20061
|
assertExactKeys(value, [
|
|
@@ -19668,6 +20173,9 @@ function decodeSessionFailedEvent(encoded) {
|
|
|
19668
20173
|
sessionId: readIdentifier(value, "sessionId")
|
|
19669
20174
|
};
|
|
19670
20175
|
}
|
|
20176
|
+
function encodeRuntimeHeartbeatEvent(message) {
|
|
20177
|
+
return JSON.stringify(decodeRuntimeHeartbeatEvent(JSON.stringify(message)));
|
|
20178
|
+
}
|
|
19671
20179
|
function encodeSessionStartedEvent(message) {
|
|
19672
20180
|
return JSON.stringify(decodeSessionStartedEvent(JSON.stringify(message)));
|
|
19673
20181
|
}
|
|
@@ -20360,7 +20868,7 @@ function createWritableAcpStream(output2) {
|
|
|
20360
20868
|
if (outputFailure !== void 0) {
|
|
20361
20869
|
return Promise.reject(outputFailure);
|
|
20362
20870
|
}
|
|
20363
|
-
return new Promise((
|
|
20871
|
+
return new Promise((resolve6, reject) => {
|
|
20364
20872
|
pendingWriteFailures.add(reject);
|
|
20365
20873
|
output2.write(chunk, (error61) => {
|
|
20366
20874
|
pendingWriteFailures.delete(reject);
|
|
@@ -20373,34 +20881,12 @@ function createWritableAcpStream(output2) {
|
|
|
20373
20881
|
reject(outputFailure);
|
|
20374
20882
|
return;
|
|
20375
20883
|
}
|
|
20376
|
-
|
|
20884
|
+
resolve6();
|
|
20377
20885
|
});
|
|
20378
20886
|
});
|
|
20379
20887
|
}
|
|
20380
20888
|
});
|
|
20381
20889
|
}
|
|
20382
|
-
async function stopAgentProcess(child) {
|
|
20383
|
-
child.stdin?.end();
|
|
20384
|
-
if (child.exitCode !== null) {
|
|
20385
|
-
return;
|
|
20386
|
-
}
|
|
20387
|
-
child.kill("SIGTERM");
|
|
20388
|
-
try {
|
|
20389
|
-
await once(child, "exit", {
|
|
20390
|
-
signal: AbortSignal.timeout(PROCESS_STOP_TIMEOUT_MILLISECONDS)
|
|
20391
|
-
});
|
|
20392
|
-
} catch {
|
|
20393
|
-
if (child.exitCode === null) {
|
|
20394
|
-
child.kill("SIGKILL");
|
|
20395
|
-
try {
|
|
20396
|
-
await once(child, "exit", {
|
|
20397
|
-
signal: AbortSignal.timeout(PROCESS_STOP_TIMEOUT_MILLISECONDS)
|
|
20398
|
-
});
|
|
20399
|
-
} catch {
|
|
20400
|
-
}
|
|
20401
|
-
}
|
|
20402
|
-
}
|
|
20403
|
-
}
|
|
20404
20890
|
function truncate2(value, maximumLength) {
|
|
20405
20891
|
return value.length <= maximumLength ? value : `${value.slice(0, Math.max(0, maximumLength - 1))}\u2026`;
|
|
20406
20892
|
}
|
|
@@ -20923,8 +21409,11 @@ var AcpProcessSession = class _AcpProcessSession {
|
|
|
20923
21409
|
this.#getObserver = getObserver;
|
|
20924
21410
|
this.#mobiusSessionId = mobiusSessionId;
|
|
20925
21411
|
this.#store = store;
|
|
20926
|
-
this.#child =
|
|
21412
|
+
this.#child = spawn2(agent.adapterPath, [...agent.arguments], {
|
|
20927
21413
|
cwd,
|
|
21414
|
+
detached: process.platform !== "win32",
|
|
21415
|
+
env: { ...process.env, ...agent.environment },
|
|
21416
|
+
shell: false,
|
|
20928
21417
|
stdio: ["pipe", "pipe", "pipe"]
|
|
20929
21418
|
});
|
|
20930
21419
|
const stdin = this.#child.stdin;
|
|
@@ -20938,9 +21427,9 @@ var AcpProcessSession = class _AcpProcessSession {
|
|
|
20938
21427
|
stderr.on("data", (chunk) => {
|
|
20939
21428
|
process.stderr.write(`[${agent.id}-acp] ${chunk}`);
|
|
20940
21429
|
});
|
|
20941
|
-
this.#processExit = new Promise((
|
|
20942
|
-
this.#child.once("error",
|
|
20943
|
-
this.#child.once("exit",
|
|
21430
|
+
this.#processExit = new Promise((resolve6) => {
|
|
21431
|
+
this.#child.once("error", resolve6);
|
|
21432
|
+
this.#child.once("exit", resolve6);
|
|
20944
21433
|
});
|
|
20945
21434
|
const stream = ndJsonStream(
|
|
20946
21435
|
createWritableAcpStream(stdin),
|
|
@@ -20950,6 +21439,18 @@ var AcpProcessSession = class _AcpProcessSession {
|
|
|
20950
21439
|
}
|
|
20951
21440
|
static start = async (options, getObserver, store) => {
|
|
20952
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
|
+
}
|
|
20953
21454
|
const runtime = new _AcpProcessSession(
|
|
20954
21455
|
options.agent,
|
|
20955
21456
|
options.cwd,
|
|
@@ -20991,7 +21492,7 @@ var AcpProcessSession = class _AcpProcessSession {
|
|
|
20991
21492
|
plan: {},
|
|
20992
21493
|
session: { configOptions: { boolean: {} } }
|
|
20993
21494
|
},
|
|
20994
|
-
clientInfo: { name: "mobius", version: "0.0.
|
|
21495
|
+
clientInfo: { name: "mobius", version: "0.0.3" },
|
|
20995
21496
|
protocolVersion: PROTOCOL_VERSION2
|
|
20996
21497
|
},
|
|
20997
21498
|
{ cancellationSignal: signal }
|
|
@@ -21629,7 +22130,7 @@ var AcpProcessSession = class _AcpProcessSession {
|
|
|
21629
22130
|
};
|
|
21630
22131
|
closeProcess = async () => {
|
|
21631
22132
|
this.#connection.close();
|
|
21632
|
-
await
|
|
22133
|
+
await stopProcessTree(this.#child);
|
|
21633
22134
|
};
|
|
21634
22135
|
race = async (operation, exitMessage, signal) => {
|
|
21635
22136
|
signal.throwIfAborted();
|
|
@@ -21951,9 +22452,16 @@ var AuditStore = class {
|
|
|
21951
22452
|
recorded_at TEXT NOT NULL,
|
|
21952
22453
|
event TEXT NOT NULL,
|
|
21953
22454
|
command_id TEXT NOT NULL,
|
|
21954
|
-
session_id TEXT NOT NULL
|
|
22455
|
+
session_id TEXT NOT NULL,
|
|
22456
|
+
details_json TEXT NOT NULL DEFAULT '{}'
|
|
21955
22457
|
) STRICT
|
|
21956
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
|
+
}
|
|
21957
22465
|
}
|
|
21958
22466
|
record(record2) {
|
|
21959
22467
|
this.database.prepare(
|
|
@@ -21962,14 +22470,16 @@ var AuditStore = class {
|
|
|
21962
22470
|
recorded_at,
|
|
21963
22471
|
event,
|
|
21964
22472
|
command_id,
|
|
21965
|
-
session_id
|
|
21966
|
-
|
|
22473
|
+
session_id,
|
|
22474
|
+
details_json
|
|
22475
|
+
) VALUES (?, ?, ?, ?, ?)
|
|
21967
22476
|
`
|
|
21968
22477
|
).run(
|
|
21969
22478
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
21970
22479
|
record2.event,
|
|
21971
22480
|
record2.commandId,
|
|
21972
|
-
record2.sessionId
|
|
22481
|
+
record2.sessionId,
|
|
22482
|
+
JSON.stringify(record2.details ?? {})
|
|
21973
22483
|
);
|
|
21974
22484
|
}
|
|
21975
22485
|
close() {
|
|
@@ -21978,7 +22488,7 @@ var AuditStore = class {
|
|
|
21978
22488
|
};
|
|
21979
22489
|
|
|
21980
22490
|
// src/authorization/authorize-binding.ts
|
|
21981
|
-
import { realpath, stat } from "node:fs/promises";
|
|
22491
|
+
import { realpath as realpath2, stat as stat2 } from "node:fs/promises";
|
|
21982
22492
|
var LocalAuthorizationError = class extends Error {
|
|
21983
22493
|
code = "local_authorization_failed";
|
|
21984
22494
|
constructor(message, options = {}) {
|
|
@@ -21991,8 +22501,8 @@ async function authorizeBinding(requestedBindingId, approvedBindingId, approvedR
|
|
|
21991
22501
|
throw new LocalAuthorizationError("The runtime binding is not authorized");
|
|
21992
22502
|
}
|
|
21993
22503
|
try {
|
|
21994
|
-
const resolvedRoot = await
|
|
21995
|
-
const metadata = await
|
|
22504
|
+
const resolvedRoot = await realpath2(approvedRoot);
|
|
22505
|
+
const metadata = await stat2(resolvedRoot);
|
|
21996
22506
|
if (!metadata.isDirectory()) {
|
|
21997
22507
|
throw new LocalAuthorizationError("The approved root is not a directory");
|
|
21998
22508
|
}
|
|
@@ -22010,324 +22520,1155 @@ async function authorizeBinding(requestedBindingId, approvedBindingId, approvedR
|
|
|
22010
22520
|
// src/pairing/pair-local-machine.ts
|
|
22011
22521
|
import { randomUUID } from "node:crypto";
|
|
22012
22522
|
import {
|
|
22013
|
-
mkdir,
|
|
22014
|
-
open as
|
|
22015
|
-
realpath as
|
|
22016
|
-
rename,
|
|
22017
|
-
stat as
|
|
22523
|
+
mkdir as mkdir2,
|
|
22524
|
+
open as open4,
|
|
22525
|
+
realpath as realpath5,
|
|
22526
|
+
rename as rename2,
|
|
22527
|
+
stat as stat4,
|
|
22018
22528
|
writeFile
|
|
22019
22529
|
} from "node:fs/promises";
|
|
22020
|
-
import {
|
|
22021
|
-
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";
|
|
22022
22532
|
import { fetch } from "undici";
|
|
22023
22533
|
|
|
22024
|
-
// src/
|
|
22025
|
-
import {
|
|
22026
|
-
|
|
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";
|
|
22027
22543
|
|
|
22028
|
-
// src/
|
|
22029
|
-
|
|
22030
|
-
|
|
22031
|
-
var
|
|
22032
|
-
var
|
|
22033
|
-
var MAX_CONNECTION_FILE_LENGTH = 16 * 1024;
|
|
22034
|
-
var CLAIM_RESPONSE_KEYS = ["channelId", "daemonToken"];
|
|
22035
|
-
var SAVED_CONNECTION_KEYS = [
|
|
22036
|
-
"approvedRoot",
|
|
22037
|
-
"channelId",
|
|
22038
|
-
"daemonToken",
|
|
22039
|
-
"relayOrigin"
|
|
22040
|
-
];
|
|
22041
|
-
var PairingClientError = class extends Error {
|
|
22042
|
-
code;
|
|
22043
|
-
constructor(code, message, options = {}) {
|
|
22044
|
-
super(message, options);
|
|
22045
|
-
this.code = code;
|
|
22046
|
-
this.name = "PairingClientError";
|
|
22047
|
-
}
|
|
22048
|
-
};
|
|
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}$/;
|
|
22049
22549
|
function isRecord4(value) {
|
|
22050
22550
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22051
22551
|
}
|
|
22052
|
-
function
|
|
22053
|
-
const
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
continue;
|
|
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();
|
|
22061
22560
|
}
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
);
|
|
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");
|
|
22068
22567
|
}
|
|
22069
|
-
|
|
22070
|
-
|
|
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();
|
|
22071
22583
|
}
|
|
22072
|
-
|
|
22073
|
-
|
|
22074
|
-
|
|
22075
|
-
|
|
22076
|
-
|
|
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 {
|
|
22600
|
+
code;
|
|
22601
|
+
constructor(code, message, options = {}) {
|
|
22602
|
+
super(message, options);
|
|
22603
|
+
this.code = code;
|
|
22604
|
+
this.name = "ManagedAdapterInstallError";
|
|
22077
22605
|
}
|
|
22078
|
-
|
|
22606
|
+
};
|
|
22607
|
+
function isRecord5(value) {
|
|
22608
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22079
22609
|
}
|
|
22080
|
-
function
|
|
22081
|
-
|
|
22082
|
-
|
|
22083
|
-
|
|
22084
|
-
|
|
22085
|
-
|
|
22086
|
-
|
|
22610
|
+
function packagePath(root, packageName) {
|
|
22611
|
+
return join3(root, "node_modules", ...packageName.split("/"));
|
|
22612
|
+
}
|
|
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"
|
|
22087
22621
|
);
|
|
22088
22622
|
}
|
|
22623
|
+
const value = JSON.parse(
|
|
22624
|
+
await handle.readFile({ encoding: "utf8" })
|
|
22625
|
+
);
|
|
22626
|
+
if (!isRecord5(value)) {
|
|
22627
|
+
throw new ManagedAdapterInstallError(
|
|
22628
|
+
"adapter_verification_failed",
|
|
22629
|
+
"The managed adapter package metadata is invalid"
|
|
22630
|
+
);
|
|
22631
|
+
}
|
|
22632
|
+
return value;
|
|
22633
|
+
} finally {
|
|
22634
|
+
await handle.close();
|
|
22089
22635
|
}
|
|
22090
22636
|
}
|
|
22091
|
-
function
|
|
22092
|
-
|
|
22093
|
-
|
|
22094
|
-
|
|
22095
|
-
}
|
|
22096
|
-
|
|
22097
|
-
|
|
22098
|
-
|
|
22099
|
-
|
|
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"
|
|
22100
22660
|
);
|
|
22101
22661
|
}
|
|
22102
|
-
|
|
22103
|
-
|
|
22104
|
-
|
|
22105
|
-
"
|
|
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"
|
|
22106
22667
|
);
|
|
22107
22668
|
}
|
|
22108
|
-
|
|
22109
|
-
|
|
22110
|
-
|
|
22111
|
-
|
|
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"
|
|
22112
22677
|
);
|
|
22113
22678
|
}
|
|
22114
|
-
|
|
22115
|
-
|
|
22116
|
-
|
|
22117
|
-
"
|
|
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"
|
|
22118
22684
|
);
|
|
22119
22685
|
}
|
|
22120
|
-
|
|
22121
|
-
url2.pathname = "/";
|
|
22122
|
-
url2.search = "";
|
|
22123
|
-
return url2;
|
|
22686
|
+
return executable;
|
|
22124
22687
|
}
|
|
22125
|
-
|
|
22126
|
-
|
|
22127
|
-
|
|
22128
|
-
|
|
22129
|
-
|
|
22130
|
-
|
|
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-`)
|
|
22131
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);
|
|
22132
22796
|
}
|
|
22133
|
-
return
|
|
22797
|
+
return join4(homedir(), ".mobius");
|
|
22134
22798
|
}
|
|
22135
|
-
|
|
22136
|
-
|
|
22137
|
-
|
|
22138
|
-
|
|
22139
|
-
|
|
22140
|
-
|
|
22141
|
-
}
|
|
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
|
+
]);
|
|
22142
22864
|
}
|
|
22143
|
-
|
|
22144
|
-
|
|
22145
|
-
|
|
22146
|
-
|
|
22147
|
-
"Usage: mobius start --channel <channel-id>"
|
|
22148
|
-
);
|
|
22865
|
+
var AgentDiscovery = class {
|
|
22866
|
+
specification;
|
|
22867
|
+
constructor(specification) {
|
|
22868
|
+
this.specification = specification;
|
|
22149
22869
|
}
|
|
22150
|
-
|
|
22151
|
-
|
|
22152
|
-
|
|
22153
|
-
|
|
22154
|
-
|
|
22155
|
-
);
|
|
22870
|
+
};
|
|
22871
|
+
var AvailableAgent = class extends AgentDiscovery {
|
|
22872
|
+
agent;
|
|
22873
|
+
status = "ready";
|
|
22874
|
+
constructor(specification, agent) {
|
|
22875
|
+
super(specification);
|
|
22876
|
+
this.agent = agent;
|
|
22156
22877
|
}
|
|
22157
|
-
|
|
22158
|
-
|
|
22159
|
-
|
|
22160
|
-
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
|
|
22166
|
-
"invalid_workspace",
|
|
22167
|
-
"The approved workspace root is not a directory"
|
|
22168
|
-
);
|
|
22169
|
-
}
|
|
22170
|
-
return approvedRoot;
|
|
22171
|
-
} catch (error61) {
|
|
22172
|
-
if (error61 instanceof PairingClientError) {
|
|
22173
|
-
throw error61;
|
|
22174
|
-
}
|
|
22175
|
-
throw new PairingClientError(
|
|
22176
|
-
"invalid_workspace",
|
|
22177
|
-
"The approved workspace root cannot be resolved",
|
|
22178
|
-
{ cause: error61 }
|
|
22179
|
-
);
|
|
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);
|
|
22180
22887
|
}
|
|
22181
|
-
}
|
|
22182
|
-
|
|
22183
|
-
|
|
22184
|
-
|
|
22185
|
-
|
|
22186
|
-
|
|
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
|
+
)
|
|
22187
22944
|
);
|
|
22945
|
+
this.unavailable = Object.freeze(unavailable);
|
|
22188
22946
|
}
|
|
22189
|
-
|
|
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;
|
|
22190
22983
|
try {
|
|
22191
|
-
|
|
22192
|
-
|
|
22193
|
-
|
|
22194
|
-
|
|
22195
|
-
|
|
22196
|
-
|
|
22984
|
+
managed = await validateManagedAdapter(
|
|
22985
|
+
join5(
|
|
22986
|
+
readStateDirectory(options.environment),
|
|
22987
|
+
"adapters",
|
|
22988
|
+
specification.id,
|
|
22989
|
+
specification.adapterVersion
|
|
22990
|
+
),
|
|
22991
|
+
specification
|
|
22197
22992
|
);
|
|
22993
|
+
} catch {
|
|
22994
|
+
return null;
|
|
22198
22995
|
}
|
|
22996
|
+
return await authorizeManagedExecutable(managed, options);
|
|
22199
22997
|
}
|
|
22200
|
-
function
|
|
22201
|
-
|
|
22202
|
-
|
|
22203
|
-
if (
|
|
22204
|
-
|
|
22205
|
-
const message = error61["message"];
|
|
22206
|
-
if (typeof code === "string" && typeof message === "string") {
|
|
22207
|
-
return new PairingClientError(code, message);
|
|
22208
|
-
}
|
|
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 [];
|
|
22209
23003
|
}
|
|
23004
|
+
const executable = await resolveExecutablePath(override, options.platform);
|
|
23005
|
+
return executable === null ? [] : [executable];
|
|
22210
23006
|
}
|
|
22211
|
-
|
|
22212
|
-
|
|
22213
|
-
|
|
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
|
|
22214
23031
|
);
|
|
22215
23032
|
}
|
|
22216
|
-
function
|
|
22217
|
-
|
|
22218
|
-
|
|
22219
|
-
|
|
22220
|
-
|
|
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"
|
|
22221
23066
|
);
|
|
22222
23067
|
}
|
|
22223
|
-
|
|
22224
|
-
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
23068
|
+
return version2;
|
|
23069
|
+
}
|
|
23070
|
+
var VerifiedAgentExecutable = class {
|
|
23071
|
+
executable;
|
|
23072
|
+
version;
|
|
23073
|
+
constructor(executable, version2) {
|
|
23074
|
+
this.executable = executable;
|
|
23075
|
+
this.version = version2;
|
|
22230
23076
|
}
|
|
22231
|
-
|
|
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
|
+
}
|
|
23094
|
+
}
|
|
23095
|
+
return null;
|
|
22232
23096
|
}
|
|
22233
|
-
|
|
22234
|
-
|
|
22235
|
-
|
|
22236
|
-
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
|
|
22240
|
-
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
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
|
+
}
|
|
22253
23219
|
} catch (error61) {
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
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",
|
|
22257
23236
|
{ cause: error61 }
|
|
22258
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
|
+
);
|
|
22259
23258
|
}
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
23259
|
+
return new ProbeFailed(
|
|
23260
|
+
specification,
|
|
23261
|
+
`${executableKind}_probe_failed`,
|
|
23262
|
+
`${executableKind === "agent" ? "Agent" : "Adapter"} probe failed`
|
|
23263
|
+
);
|
|
22265
23264
|
}
|
|
22266
|
-
function
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
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
|
+
}
|
|
22270
23339
|
}
|
|
22271
|
-
return
|
|
23340
|
+
return lastFailure ?? new IncompatibleAdapter(specification);
|
|
22272
23341
|
}
|
|
22273
|
-
async function
|
|
22274
|
-
const
|
|
22275
|
-
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
22279
|
-
`.${channelId}.${randomUUID()}.tmp`
|
|
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
|
+
)
|
|
22280
23348
|
);
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
22284
|
-
|
|
22285
|
-
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22290
|
-
|
|
22291
|
-
|
|
22292
|
-
|
|
22293
|
-
`,
|
|
22294
|
-
{ flag: "wx", mode: 384 }
|
|
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
|
|
22295
23361
|
);
|
|
22296
|
-
await rename(temporaryPath, connectionPath);
|
|
22297
|
-
return connectionPath;
|
|
22298
23362
|
}
|
|
22299
|
-
|
|
22300
|
-
|
|
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) {
|
|
22301
23413
|
throw new PairingClientError(
|
|
22302
|
-
"
|
|
22303
|
-
|
|
23414
|
+
"invalid_arguments",
|
|
23415
|
+
`${option} must be provided exactly once`
|
|
22304
23416
|
);
|
|
22305
23417
|
}
|
|
22306
|
-
|
|
22307
|
-
|
|
22308
|
-
|
|
22309
|
-
|
|
22310
|
-
|
|
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) {
|
|
22311
23436
|
throw new PairingClientError(
|
|
22312
|
-
"
|
|
22313
|
-
"The
|
|
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"
|
|
22314
23471
|
);
|
|
22315
23472
|
}
|
|
23473
|
+
return code;
|
|
23474
|
+
}
|
|
23475
|
+
function parsePairCommand(arguments_) {
|
|
23476
|
+
assertKnownArguments(arguments_);
|
|
22316
23477
|
return {
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
relayOrigin: parseRelayOrigin(relayOrigin)
|
|
23478
|
+
code: parsePairingCode(readSingleOption(arguments_, "--code")),
|
|
23479
|
+
relayOrigin: parseRelayOrigin(readSingleOption(arguments_, "--url")),
|
|
23480
|
+
root: readSingleOption(arguments_, "--root")
|
|
22321
23481
|
};
|
|
22322
23482
|
}
|
|
22323
|
-
|
|
22324
|
-
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
|
|
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
|
+
);
|
|
22329
23670
|
let encoded = "";
|
|
22330
|
-
const handle = await
|
|
23671
|
+
const handle = await open4(connectionPath, "r");
|
|
22331
23672
|
try {
|
|
22332
23673
|
const metadata = await handle.stat();
|
|
22333
23674
|
if (!metadata.isFile() || metadata.size > MAX_CONNECTION_FILE_LENGTH) {
|
|
@@ -22336,101 +23677,1386 @@ async function readSavedConnection(channelId) {
|
|
|
22336
23677
|
"The saved connection is invalid"
|
|
22337
23678
|
);
|
|
22338
23679
|
}
|
|
22339
|
-
encoded = await handle.readFile({ encoding: "utf8" });
|
|
22340
|
-
} finally {
|
|
22341
|
-
await handle.close();
|
|
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
|
+
]);
|
|
24774
|
+
}
|
|
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
|
+
}
|
|
24782
|
+
}
|
|
24783
|
+
throw new InstallationManagerError(
|
|
24784
|
+
"unknown_agent",
|
|
24785
|
+
"The requested Agent is not supported"
|
|
24786
|
+
);
|
|
24787
|
+
}
|
|
24788
|
+
function findRecipe(recipes, agentId) {
|
|
24789
|
+
for (const recipe of recipes) {
|
|
24790
|
+
if (recipe.id === agentId) {
|
|
24791
|
+
return recipe;
|
|
24792
|
+
}
|
|
24793
|
+
}
|
|
24794
|
+
throw new InstallationManagerError(
|
|
24795
|
+
"agent_not_installable",
|
|
24796
|
+
"The requested Agent does not have an approved installation recipe"
|
|
24797
|
+
);
|
|
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) {
|
|
24804
|
+
try {
|
|
24805
|
+
const directory = realpathSync2(resolve5(path));
|
|
24806
|
+
if (!statSync2(directory).isDirectory()) {
|
|
24807
|
+
throw new Error("Approved root is not a directory");
|
|
24808
|
+
}
|
|
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
|
+
);
|
|
22342
24816
|
}
|
|
22343
|
-
|
|
24817
|
+
}
|
|
24818
|
+
function resolveUserInstallationDirectory(path) {
|
|
22344
24819
|
try {
|
|
22345
|
-
|
|
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;
|
|
22346
24826
|
} catch (error61) {
|
|
22347
|
-
throw new
|
|
22348
|
-
"
|
|
22349
|
-
"The
|
|
24827
|
+
throw new InstallationManagerError(
|
|
24828
|
+
"unsafe_installation_root",
|
|
24829
|
+
"The local Agent installation root is unavailable or unsafe",
|
|
22350
24830
|
{ cause: error61 }
|
|
22351
24831
|
);
|
|
22352
24832
|
}
|
|
22353
|
-
const connection = decodeSavedConnection(value, channelId);
|
|
22354
|
-
return {
|
|
22355
|
-
approvedRoot: await resolveApprovedRoot(connection.approvedRoot),
|
|
22356
|
-
channelId: connection.channelId,
|
|
22357
|
-
daemonToken: connection.daemonToken,
|
|
22358
|
-
relayOrigin: connection.relayOrigin
|
|
22359
|
-
};
|
|
22360
24833
|
}
|
|
22361
|
-
function
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
)
|
|
22366
|
-
|
|
22367
|
-
|
|
22368
|
-
|
|
22369
|
-
|
|
22370
|
-
|
|
22371
|
-
|
|
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 }
|
|
22372
24847
|
);
|
|
22373
|
-
return {
|
|
22374
|
-
channelUrl: new URL(
|
|
22375
|
-
`/channels/${encodeURIComponent(connection.channelId)}`,
|
|
22376
|
-
connection.relayOrigin
|
|
22377
|
-
).toString(),
|
|
22378
|
-
config: createDaemonConfig({
|
|
22379
|
-
approvedRoot: connection.approvedRoot,
|
|
22380
|
-
auditDatabasePath,
|
|
22381
|
-
channelId: connection.channelId,
|
|
22382
|
-
relayUrl
|
|
22383
|
-
}),
|
|
22384
|
-
connectionPath
|
|
22385
|
-
};
|
|
22386
24848
|
}
|
|
22387
|
-
|
|
22388
|
-
|
|
22389
|
-
const approvedRoot = await resolveApprovedRoot(options.root);
|
|
22390
|
-
const claim2 = await claimPairing(options, approvedRoot);
|
|
22391
|
-
const connectionPath = await persistConnection(
|
|
22392
|
-
claim2.channelId,
|
|
22393
|
-
options.relayOrigin,
|
|
22394
|
-
claim2.daemonToken,
|
|
22395
|
-
approvedRoot
|
|
22396
|
-
);
|
|
22397
|
-
return buildPairedDaemon(
|
|
22398
|
-
{
|
|
22399
|
-
approvedRoot,
|
|
22400
|
-
channelId: claim2.channelId,
|
|
22401
|
-
daemonToken: claim2.daemonToken,
|
|
22402
|
-
relayOrigin: options.relayOrigin
|
|
22403
|
-
},
|
|
22404
|
-
connectionPath
|
|
22405
|
-
);
|
|
24849
|
+
function requiresAgentReplacement(code) {
|
|
24850
|
+
return code === "agent_not_found" || code === "agent_probe_failed" || code === "agent_probe_timeout";
|
|
22406
24851
|
}
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
const connectionPath = join(
|
|
22410
|
-
readStateDirectory(),
|
|
22411
|
-
"connections",
|
|
22412
|
-
`${channelId}.json`
|
|
22413
|
-
);
|
|
22414
|
-
return buildPairedDaemon(
|
|
22415
|
-
await readSavedConnection(channelId),
|
|
22416
|
-
connectionPath
|
|
22417
|
-
);
|
|
24852
|
+
function requiresAdapterReplacement(code) {
|
|
24853
|
+
return code === "adapter_not_found" || code === "adapter_probe_failed" || code === "adapter_probe_timeout" || code === "acp_incompatible";
|
|
22418
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
|
+
};
|
|
22419
25015
|
|
|
22420
|
-
// src/
|
|
22421
|
-
import {
|
|
22422
|
-
|
|
22423
|
-
|
|
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
|
+
};
|
|
22424
25050
|
|
|
22425
25051
|
// src/authorization/workspace-directory.ts
|
|
22426
|
-
import { readdir, realpath as
|
|
25052
|
+
import { readdir, realpath as realpath6, stat as stat5 } from "node:fs/promises";
|
|
22427
25053
|
import {
|
|
22428
25054
|
basename as basename2,
|
|
22429
|
-
dirname as
|
|
22430
|
-
isAbsolute,
|
|
22431
|
-
join as
|
|
25055
|
+
dirname as dirname4,
|
|
25056
|
+
isAbsolute as isAbsolute4,
|
|
25057
|
+
join as join8,
|
|
22432
25058
|
normalize,
|
|
22433
|
-
relative
|
|
25059
|
+
relative as relative5
|
|
22434
25060
|
} from "node:path";
|
|
22435
25061
|
var ROOT_DIRECTORY_ID = "root";
|
|
22436
25062
|
var DIRECTORY_ID_PREFIX = "dir-";
|
|
@@ -22469,14 +25095,14 @@ function decodeDirectoryId(value) {
|
|
|
22469
25095
|
throw new LocalAuthorizationError("The workspace directory is invalid");
|
|
22470
25096
|
}
|
|
22471
25097
|
const normalized = normalize(decoded);
|
|
22472
|
-
if (decoded.includes("\0") ||
|
|
25098
|
+
if (decoded.includes("\0") || isAbsolute4(decoded) || normalized === ".." || normalized.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || normalized !== decoded) {
|
|
22473
25099
|
throw new LocalAuthorizationError("The workspace directory is invalid");
|
|
22474
25100
|
}
|
|
22475
25101
|
return normalized;
|
|
22476
25102
|
}
|
|
22477
25103
|
function isContained(root, target) {
|
|
22478
|
-
const remainder =
|
|
22479
|
-
return remainder.length === 0 || !
|
|
25104
|
+
const remainder = relative5(root, target);
|
|
25105
|
+
return remainder.length === 0 || !isAbsolute4(remainder) && remainder !== ".." && !remainder.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
|
|
22480
25106
|
}
|
|
22481
25107
|
var WorkspaceDirectoryService = class {
|
|
22482
25108
|
#approvedBindingId;
|
|
@@ -22494,8 +25120,8 @@ var WorkspaceDirectoryService = class {
|
|
|
22494
25120
|
const root = await this.resolveRoot();
|
|
22495
25121
|
const relativePath = decodeDirectoryId(requestedDirectoryId);
|
|
22496
25122
|
try {
|
|
22497
|
-
const target = await
|
|
22498
|
-
const metadata = await
|
|
25123
|
+
const target = await realpath6(join8(root, relativePath));
|
|
25124
|
+
const metadata = await stat5(target);
|
|
22499
25125
|
if (!metadata.isDirectory() || !isContained(root, target)) {
|
|
22500
25126
|
throw new LocalAuthorizationError(
|
|
22501
25127
|
"The workspace directory is not authorized"
|
|
@@ -22523,10 +25149,10 @@ var WorkspaceDirectoryService = class {
|
|
|
22523
25149
|
if (entries.length === 500) {
|
|
22524
25150
|
break;
|
|
22525
25151
|
}
|
|
22526
|
-
const logicalChild = logicalRelativePath.length === 0 ? child.name :
|
|
25152
|
+
const logicalChild = logicalRelativePath.length === 0 ? child.name : join8(logicalRelativePath, child.name);
|
|
22527
25153
|
try {
|
|
22528
|
-
const resolvedChild = await
|
|
22529
|
-
const metadata = await
|
|
25154
|
+
const resolvedChild = await realpath6(join8(target, child.name));
|
|
25155
|
+
const metadata = await stat5(resolvedChild);
|
|
22530
25156
|
if (metadata.isDirectory() && isContained(root, resolvedChild)) {
|
|
22531
25157
|
entries.push({
|
|
22532
25158
|
directoryId: directoryId(logicalChild),
|
|
@@ -22538,7 +25164,7 @@ var WorkspaceDirectoryService = class {
|
|
|
22538
25164
|
}
|
|
22539
25165
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
22540
25166
|
const labelSuffix = logicalRelativePath.replaceAll("\\", "/");
|
|
22541
|
-
const parentRelative = logicalRelativePath.length === 0 ||
|
|
25167
|
+
const parentRelative = logicalRelativePath.length === 0 || dirname4(logicalRelativePath) === "." ? "" : dirname4(logicalRelativePath);
|
|
22542
25168
|
return new WorkspaceDirectoryListing(
|
|
22543
25169
|
requestedDirectoryId,
|
|
22544
25170
|
labelSuffix.length === 0 ? basename2(root) : `${basename2(root)}/${labelSuffix}`,
|
|
@@ -22572,8 +25198,8 @@ var WorkspaceDirectoryService = class {
|
|
|
22572
25198
|
};
|
|
22573
25199
|
resolveRoot = async () => {
|
|
22574
25200
|
try {
|
|
22575
|
-
const root = await
|
|
22576
|
-
const metadata = await
|
|
25201
|
+
const root = await realpath6(this.#approvedRoot);
|
|
25202
|
+
const metadata = await stat5(root);
|
|
22577
25203
|
if (!metadata.isDirectory()) {
|
|
22578
25204
|
throw new LocalAuthorizationError(
|
|
22579
25205
|
"The approved root is not a directory"
|
|
@@ -22607,7 +25233,7 @@ var RelayConnectionError = class extends Error {
|
|
|
22607
25233
|
};
|
|
22608
25234
|
function connectRelay(url2, signal) {
|
|
22609
25235
|
signal.throwIfAborted();
|
|
22610
|
-
return new Promise((
|
|
25236
|
+
return new Promise((resolve6, reject) => {
|
|
22611
25237
|
const socket = new WebSocket(url2, {
|
|
22612
25238
|
dispatcher: environmentProxyDispatcher
|
|
22613
25239
|
});
|
|
@@ -22660,7 +25286,7 @@ function connectRelay(url2, signal) {
|
|
|
22660
25286
|
}
|
|
22661
25287
|
finished = true;
|
|
22662
25288
|
cleanup();
|
|
22663
|
-
|
|
25289
|
+
resolve6(socket);
|
|
22664
25290
|
};
|
|
22665
25291
|
function onTimeout() {
|
|
22666
25292
|
fail(
|
|
@@ -22680,8 +25306,179 @@ function connectRelay(url2, signal) {
|
|
|
22680
25306
|
});
|
|
22681
25307
|
}
|
|
22682
25308
|
|
|
25309
|
+
// src/relay-connection/liveness.ts
|
|
25310
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
25311
|
+
var HEARTBEAT_ACK_TIMEOUT_MILLISECONDS = 1e4;
|
|
25312
|
+
var HEARTBEAT_INTERVAL_MILLISECONDS = 2e4;
|
|
25313
|
+
var RelayLivenessState = class {
|
|
25314
|
+
};
|
|
25315
|
+
var UnsupportedRelayLivenessState = class extends RelayLivenessState {
|
|
25316
|
+
};
|
|
25317
|
+
var AwaitingHeartbeatAckState = class extends RelayLivenessState {
|
|
25318
|
+
heartbeatId;
|
|
25319
|
+
timeout;
|
|
25320
|
+
constructor(heartbeatId, timeout) {
|
|
25321
|
+
super();
|
|
25322
|
+
this.heartbeatId = heartbeatId;
|
|
25323
|
+
this.timeout = timeout;
|
|
25324
|
+
}
|
|
25325
|
+
};
|
|
25326
|
+
var WaitingForHeartbeatState = class extends RelayLivenessState {
|
|
25327
|
+
schedule;
|
|
25328
|
+
constructor(schedule) {
|
|
25329
|
+
super();
|
|
25330
|
+
this.schedule = schedule;
|
|
25331
|
+
}
|
|
25332
|
+
};
|
|
25333
|
+
var FinishedRelayLivenessState = class extends RelayLivenessState {
|
|
25334
|
+
};
|
|
25335
|
+
var RelayConnectionLiveness = class {
|
|
25336
|
+
#timer;
|
|
25337
|
+
#transport;
|
|
25338
|
+
#state = new UnsupportedRelayLivenessState();
|
|
25339
|
+
constructor(timer, transport) {
|
|
25340
|
+
this.#timer = timer;
|
|
25341
|
+
this.#transport = transport;
|
|
25342
|
+
}
|
|
25343
|
+
acknowledge = (event) => {
|
|
25344
|
+
if (!(this.#state instanceof AwaitingHeartbeatAckState)) {
|
|
25345
|
+
throw new Error("Unexpected relay heartbeat acknowledgement");
|
|
25346
|
+
}
|
|
25347
|
+
if (event.heartbeatId !== this.#state.heartbeatId) {
|
|
25348
|
+
throw new Error("Relay acknowledged the wrong heartbeat");
|
|
25349
|
+
}
|
|
25350
|
+
this.#state.timeout.cancel();
|
|
25351
|
+
this.#state = new WaitingForHeartbeatState(
|
|
25352
|
+
this.#timer.schedule(
|
|
25353
|
+
this.#sendHeartbeat,
|
|
25354
|
+
HEARTBEAT_INTERVAL_MILLISECONDS
|
|
25355
|
+
)
|
|
25356
|
+
);
|
|
25357
|
+
};
|
|
25358
|
+
close = () => {
|
|
25359
|
+
if (this.#state instanceof AwaitingHeartbeatAckState) {
|
|
25360
|
+
this.#state.timeout.cancel();
|
|
25361
|
+
} else if (this.#state instanceof WaitingForHeartbeatState) {
|
|
25362
|
+
this.#state.schedule.cancel();
|
|
25363
|
+
} else if (!(this.#state instanceof UnsupportedRelayLivenessState) && !(this.#state instanceof FinishedRelayLivenessState)) {
|
|
25364
|
+
throw new Error("Unknown relay liveness state");
|
|
25365
|
+
}
|
|
25366
|
+
this.#state = new FinishedRelayLivenessState();
|
|
25367
|
+
};
|
|
25368
|
+
start = () => {
|
|
25369
|
+
if (this.#state instanceof UnsupportedRelayLivenessState) {
|
|
25370
|
+
this.#sendHeartbeat();
|
|
25371
|
+
return;
|
|
25372
|
+
}
|
|
25373
|
+
if (this.#state instanceof AwaitingHeartbeatAckState || this.#state instanceof WaitingForHeartbeatState) {
|
|
25374
|
+
return;
|
|
25375
|
+
}
|
|
25376
|
+
if (this.#state instanceof FinishedRelayLivenessState) {
|
|
25377
|
+
return;
|
|
25378
|
+
}
|
|
25379
|
+
throw new Error("Unknown relay liveness state");
|
|
25380
|
+
};
|
|
25381
|
+
#handleTimeout = () => {
|
|
25382
|
+
if (this.#state instanceof FinishedRelayLivenessState) {
|
|
25383
|
+
return;
|
|
25384
|
+
}
|
|
25385
|
+
if (!(this.#state instanceof AwaitingHeartbeatAckState)) {
|
|
25386
|
+
throw new Error("Heartbeat timeout occurred in an invalid state");
|
|
25387
|
+
}
|
|
25388
|
+
this.#state.timeout.cancel();
|
|
25389
|
+
this.#state = new FinishedRelayLivenessState();
|
|
25390
|
+
this.#transport.unresponsive();
|
|
25391
|
+
};
|
|
25392
|
+
#sendHeartbeat = () => {
|
|
25393
|
+
if (this.#state instanceof WaitingForHeartbeatState) {
|
|
25394
|
+
this.#state.schedule.cancel();
|
|
25395
|
+
} else if (!(this.#state instanceof UnsupportedRelayLivenessState)) {
|
|
25396
|
+
if (this.#state instanceof FinishedRelayLivenessState) {
|
|
25397
|
+
return;
|
|
25398
|
+
}
|
|
25399
|
+
throw new Error("Heartbeat was scheduled in an invalid state");
|
|
25400
|
+
}
|
|
25401
|
+
const heartbeatId = randomUUID3();
|
|
25402
|
+
this.#state = new AwaitingHeartbeatAckState(
|
|
25403
|
+
heartbeatId,
|
|
25404
|
+
this.#timer.schedule(
|
|
25405
|
+
this.#handleTimeout,
|
|
25406
|
+
HEARTBEAT_ACK_TIMEOUT_MILLISECONDS
|
|
25407
|
+
)
|
|
25408
|
+
);
|
|
25409
|
+
this.#transport.send(
|
|
25410
|
+
encodeRuntimeHeartbeatEvent({
|
|
25411
|
+
heartbeatId,
|
|
25412
|
+
kind: RUNTIME_HEARTBEAT_KIND,
|
|
25413
|
+
protocolVersion: MOBIUS_PROTOCOL_VERSION
|
|
25414
|
+
})
|
|
25415
|
+
);
|
|
25416
|
+
};
|
|
25417
|
+
};
|
|
25418
|
+
|
|
25419
|
+
// src/relay-connection/timer.ts
|
|
25420
|
+
var SystemDaemonTimer = class {
|
|
25421
|
+
schedule = (callback, milliseconds) => {
|
|
25422
|
+
const timeout = setTimeout(callback, milliseconds);
|
|
25423
|
+
return { cancel: () => clearTimeout(timeout) };
|
|
25424
|
+
};
|
|
25425
|
+
};
|
|
25426
|
+
var systemDaemonTimer = new SystemDaemonTimer();
|
|
25427
|
+
|
|
22683
25428
|
// src/run-daemon.ts
|
|
22684
25429
|
var RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
|
|
25430
|
+
var RELAY_CONNECTION_REFRESH_MILLISECONDS = 20 * 60 * 1e3;
|
|
25431
|
+
var RelayConnectionRefreshState = class {
|
|
25432
|
+
};
|
|
25433
|
+
var ScheduledRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
|
|
25434
|
+
};
|
|
25435
|
+
var DueRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
|
|
25436
|
+
};
|
|
25437
|
+
var FinishedRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
|
|
25438
|
+
};
|
|
25439
|
+
var RelayConnectionRefresh = class {
|
|
25440
|
+
#isSafe;
|
|
25441
|
+
#refresh;
|
|
25442
|
+
#scheduledTask;
|
|
25443
|
+
#state = new ScheduledRelayConnectionRefreshState();
|
|
25444
|
+
constructor(timer, isSafe, refresh) {
|
|
25445
|
+
this.#isSafe = isSafe;
|
|
25446
|
+
this.#refresh = refresh;
|
|
25447
|
+
this.#scheduledTask = timer.schedule(
|
|
25448
|
+
this.#request,
|
|
25449
|
+
RELAY_CONNECTION_REFRESH_MILLISECONDS
|
|
25450
|
+
);
|
|
25451
|
+
}
|
|
25452
|
+
afterTurnFinished = () => {
|
|
25453
|
+
if (this.#state instanceof DueRelayConnectionRefreshState) {
|
|
25454
|
+
this.#request();
|
|
25455
|
+
return;
|
|
25456
|
+
}
|
|
25457
|
+
if (this.#state instanceof ScheduledRelayConnectionRefreshState || this.#state instanceof FinishedRelayConnectionRefreshState) {
|
|
25458
|
+
return;
|
|
25459
|
+
}
|
|
25460
|
+
throw new Error("Unknown relay connection refresh state");
|
|
25461
|
+
};
|
|
25462
|
+
close = () => {
|
|
25463
|
+
this.#scheduledTask.cancel();
|
|
25464
|
+
this.#state = new FinishedRelayConnectionRefreshState();
|
|
25465
|
+
};
|
|
25466
|
+
#request = () => {
|
|
25467
|
+
if (this.#state instanceof FinishedRelayConnectionRefreshState) {
|
|
25468
|
+
return;
|
|
25469
|
+
}
|
|
25470
|
+
if (!(this.#state instanceof ScheduledRelayConnectionRefreshState) && !(this.#state instanceof DueRelayConnectionRefreshState)) {
|
|
25471
|
+
throw new Error("Unknown relay connection refresh state");
|
|
25472
|
+
}
|
|
25473
|
+
if (!this.#isSafe()) {
|
|
25474
|
+
this.#state = new DueRelayConnectionRefreshState();
|
|
25475
|
+
return;
|
|
25476
|
+
}
|
|
25477
|
+
this.#state = new FinishedRelayConnectionRefreshState();
|
|
25478
|
+
this.#scheduledTask.cancel();
|
|
25479
|
+
this.#refresh();
|
|
25480
|
+
};
|
|
25481
|
+
};
|
|
22685
25482
|
var DaemonAcpObserver = class {
|
|
22686
25483
|
#socket;
|
|
22687
25484
|
constructor(socket) {
|
|
@@ -22724,6 +25521,127 @@ var DaemonAcpObserver = class {
|
|
|
22724
25521
|
return Promise.resolve();
|
|
22725
25522
|
};
|
|
22726
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
|
+
};
|
|
22727
25645
|
var ResolvedSessionDirectories = class {
|
|
22728
25646
|
additionalDirectories;
|
|
22729
25647
|
additionalDirectoryEntries;
|
|
@@ -22780,13 +25698,13 @@ function reportFailure(socket, command, code, message) {
|
|
|
22780
25698
|
);
|
|
22781
25699
|
}
|
|
22782
25700
|
function operationErrorCode(error61) {
|
|
22783
|
-
if (error61 instanceof AcpExecutionError || error61 instanceof LocalAuthorizationError) {
|
|
25701
|
+
if (error61 instanceof AcpExecutionError || error61 instanceof LocalAuthorizationError || error61 instanceof LocalAgentCatalogError) {
|
|
22784
25702
|
return error61.code;
|
|
22785
25703
|
}
|
|
22786
25704
|
return "session_operation_failed";
|
|
22787
25705
|
}
|
|
22788
25706
|
function operationErrorMessage(error61) {
|
|
22789
|
-
if (error61 instanceof AcpExecutionError || error61 instanceof LocalAuthorizationError) {
|
|
25707
|
+
if (error61 instanceof AcpExecutionError || error61 instanceof LocalAuthorizationError || error61 instanceof LocalAgentCatalogError) {
|
|
22790
25708
|
return error61.message;
|
|
22791
25709
|
}
|
|
22792
25710
|
return "The local agent operation could not be completed";
|
|
@@ -22871,7 +25789,7 @@ async function executeSessionPrepare(socket, command, config2, directories, audi
|
|
|
22871
25789
|
command.directoryId,
|
|
22872
25790
|
command.additionalDirectoryIds
|
|
22873
25791
|
);
|
|
22874
|
-
const agent =
|
|
25792
|
+
const agent = config2.catalog.find(command.agentId);
|
|
22875
25793
|
const prepared = await sessions.prepareSession({
|
|
22876
25794
|
additionalDirectories: resolved.additionalDirectories,
|
|
22877
25795
|
agent,
|
|
@@ -22921,7 +25839,7 @@ async function executeSessionConfigure(socket, command, config2, directories, au
|
|
|
22921
25839
|
);
|
|
22922
25840
|
const configOptions = await sessions.configureSession({
|
|
22923
25841
|
additionalDirectories: resolved.additionalDirectories,
|
|
22924
|
-
agent:
|
|
25842
|
+
agent: config2.catalog.find(command.agentId),
|
|
22925
25843
|
booleanValue: command.booleanValue,
|
|
22926
25844
|
configId: command.configId,
|
|
22927
25845
|
cwd: resolved.cwd,
|
|
@@ -22958,8 +25876,8 @@ async function executePrompt(socket, command, config2, directories, audit, sessi
|
|
|
22958
25876
|
command.directoryId,
|
|
22959
25877
|
command.additionalDirectoryIds
|
|
22960
25878
|
);
|
|
22961
|
-
const agent =
|
|
22962
|
-
const processAttemptId =
|
|
25879
|
+
const agent = config2.catalog.find(command.agentId);
|
|
25880
|
+
const processAttemptId = randomUUID4();
|
|
22963
25881
|
audit.record({
|
|
22964
25882
|
commandId: command.commandId,
|
|
22965
25883
|
event: "session_authorized",
|
|
@@ -23043,41 +25961,48 @@ async function executePrompt(socket, command, config2, directories, audit, sessi
|
|
|
23043
25961
|
event: "session_failed",
|
|
23044
25962
|
sessionId: command.sessionId
|
|
23045
25963
|
});
|
|
25964
|
+
const code = operationErrorCode(error61);
|
|
23046
25965
|
reportFailure(
|
|
23047
25966
|
socket,
|
|
23048
25967
|
command,
|
|
23049
|
-
"agent_execution_failed",
|
|
23050
|
-
"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)
|
|
23051
25970
|
);
|
|
23052
25971
|
console.error("Mobius daemon command failed", error61);
|
|
23053
25972
|
}
|
|
23054
25973
|
}
|
|
23055
|
-
function waitForRelayRetry(signal) {
|
|
25974
|
+
function waitForRelayRetry(signal, timer) {
|
|
23056
25975
|
if (signal.aborted) {
|
|
23057
25976
|
return Promise.resolve();
|
|
23058
25977
|
}
|
|
23059
|
-
return new Promise((
|
|
23060
|
-
const
|
|
25978
|
+
return new Promise((resolve6) => {
|
|
25979
|
+
const retry = timer.schedule(finish, RELAY_RECONNECT_DELAY_MILLISECONDS);
|
|
23061
25980
|
function finish() {
|
|
23062
|
-
|
|
25981
|
+
retry.cancel();
|
|
23063
25982
|
signal.removeEventListener("abort", finish);
|
|
23064
|
-
|
|
25983
|
+
resolve6();
|
|
23065
25984
|
}
|
|
23066
25985
|
signal.addEventListener("abort", finish, { once: true });
|
|
23067
25986
|
});
|
|
23068
25987
|
}
|
|
23069
|
-
async function runRelayConnection(config2, directories, audit, sessions, signal) {
|
|
23070
|
-
const
|
|
25988
|
+
async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, signal, timer) {
|
|
25989
|
+
const relayUrl = new URL(config2.relayUrl);
|
|
25990
|
+
relayUrl.searchParams.set("liveness", "1");
|
|
25991
|
+
const socket = await connectRelay(relayUrl, signal);
|
|
23071
25992
|
const activeTurns = /* @__PURE__ */ new Map();
|
|
23072
25993
|
const connectionController = new AbortController();
|
|
23073
25994
|
sessions.setObserver(new DaemonAcpObserver(socket));
|
|
23074
|
-
|
|
25995
|
+
installationObserver.connect(socket);
|
|
25996
|
+
await new Promise((resolve6, reject) => {
|
|
23075
25997
|
let finished = false;
|
|
23076
25998
|
const cleanup = () => {
|
|
25999
|
+
connectionRefresh.close();
|
|
26000
|
+
connectionLiveness.close();
|
|
23077
26001
|
signal.removeEventListener("abort", stop);
|
|
23078
26002
|
socket.removeEventListener("close", handleClose);
|
|
23079
26003
|
socket.removeEventListener("error", handleError);
|
|
23080
26004
|
socket.removeEventListener("message", handleMessage);
|
|
26005
|
+
installationObserver.disconnect(socket);
|
|
23081
26006
|
connectionController.abort();
|
|
23082
26007
|
for (const turn of activeTurns.values()) {
|
|
23083
26008
|
turn.abortConnection();
|
|
@@ -23090,7 +26015,7 @@ async function runRelayConnection(config2, directories, audit, sessions, signal)
|
|
|
23090
26015
|
}
|
|
23091
26016
|
finished = true;
|
|
23092
26017
|
cleanup();
|
|
23093
|
-
|
|
26018
|
+
resolve6();
|
|
23094
26019
|
};
|
|
23095
26020
|
const rejectConnection = () => {
|
|
23096
26021
|
if (finished) {
|
|
@@ -23104,6 +26029,22 @@ async function runRelayConnection(config2, directories, audit, sessions, signal)
|
|
|
23104
26029
|
socket.close(1e3, "Daemon stopped");
|
|
23105
26030
|
resolveConnection();
|
|
23106
26031
|
};
|
|
26032
|
+
const refreshConnection = () => {
|
|
26033
|
+
socket.close(1e3, "Refreshing idle relay connection");
|
|
26034
|
+
resolveConnection();
|
|
26035
|
+
};
|
|
26036
|
+
const connectionLiveness = new RelayConnectionLiveness(timer, {
|
|
26037
|
+
send: (message) => send(socket, message),
|
|
26038
|
+
unresponsive: () => {
|
|
26039
|
+
socket.close(4001, "Relay liveness check timed out");
|
|
26040
|
+
resolveConnection();
|
|
26041
|
+
}
|
|
26042
|
+
});
|
|
26043
|
+
const connectionRefresh = new RelayConnectionRefresh(
|
|
26044
|
+
timer,
|
|
26045
|
+
() => activeTurns.size === 0,
|
|
26046
|
+
refreshConnection
|
|
26047
|
+
);
|
|
23107
26048
|
signal.addEventListener("abort", stop, { once: true });
|
|
23108
26049
|
const handleClose = () => {
|
|
23109
26050
|
resolveConnection();
|
|
@@ -23118,11 +26059,23 @@ async function runRelayConnection(config2, directories, audit, sessions, signal)
|
|
|
23118
26059
|
const handleMessage = (event) => {
|
|
23119
26060
|
const encoded = event.data;
|
|
23120
26061
|
if (typeof encoded !== "string") {
|
|
23121
|
-
socket.close(
|
|
26062
|
+
socket.close(4003, "Text message required");
|
|
26063
|
+
rejectConnection();
|
|
23122
26064
|
return;
|
|
23123
26065
|
}
|
|
23124
26066
|
try {
|
|
23125
26067
|
const kind = decodeMessageKind(encoded);
|
|
26068
|
+
if (kind === RUNTIME_LIVENESS_READY_KIND) {
|
|
26069
|
+
decodeRuntimeLivenessReadyEvent(encoded);
|
|
26070
|
+
connectionLiveness.start();
|
|
26071
|
+
return;
|
|
26072
|
+
}
|
|
26073
|
+
if (kind === RUNTIME_HEARTBEAT_ACK_KIND) {
|
|
26074
|
+
connectionLiveness.acknowledge(
|
|
26075
|
+
decodeRuntimeHeartbeatAckEvent(encoded)
|
|
26076
|
+
);
|
|
26077
|
+
return;
|
|
26078
|
+
}
|
|
23126
26079
|
if (kind === SESSION_CANCEL_KIND) {
|
|
23127
26080
|
const cancellation = decodeSessionCancelCommand(encoded);
|
|
23128
26081
|
const turn2 = activeTurns.get(cancellation.commandId);
|
|
@@ -23215,10 +26168,23 @@ async function runRelayConnection(config2, directories, audit, sessions, signal)
|
|
|
23215
26168
|
});
|
|
23216
26169
|
return;
|
|
23217
26170
|
}
|
|
23218
|
-
if (kind
|
|
23219
|
-
|
|
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
|
+
});
|
|
23220
26183
|
return;
|
|
23221
26184
|
}
|
|
26185
|
+
if (kind !== SESSION_PROMPT_KIND) {
|
|
26186
|
+
throw new Error("Unsupported protocol message");
|
|
26187
|
+
}
|
|
23222
26188
|
const command = decodeSessionPromptCommand(encoded);
|
|
23223
26189
|
if (activeTurns.has(command.commandId)) {
|
|
23224
26190
|
reportFailure(
|
|
@@ -23243,10 +26209,12 @@ async function runRelayConnection(config2, directories, audit, sessions, signal)
|
|
|
23243
26209
|
console.error("Mobius daemon turn failed", error61);
|
|
23244
26210
|
}).finally(() => {
|
|
23245
26211
|
activeTurns.delete(command.commandId);
|
|
26212
|
+
connectionRefresh.afterTurnFinished();
|
|
23246
26213
|
});
|
|
23247
26214
|
} catch (error61) {
|
|
23248
26215
|
console.error("Mobius daemon rejected a message", error61);
|
|
23249
|
-
socket.close(
|
|
26216
|
+
socket.close(4008, "Invalid protocol message");
|
|
26217
|
+
rejectConnection();
|
|
23250
26218
|
}
|
|
23251
26219
|
};
|
|
23252
26220
|
socket.addEventListener("close", handleClose);
|
|
@@ -23257,7 +26225,32 @@ async function runRelayConnection(config2, directories, audit, sessions, signal)
|
|
|
23257
26225
|
}
|
|
23258
26226
|
});
|
|
23259
26227
|
}
|
|
23260
|
-
async function runDaemon(config2, audit, signal) {
|
|
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);
|
|
23261
26254
|
const sessionStore = new SqliteAcpSessionStore(config2.auditDatabasePath);
|
|
23262
26255
|
const sessions = new AcpSessionManager(
|
|
23263
26256
|
new NullAcpSessionObserver(),
|
|
@@ -23267,11 +26260,29 @@ async function runDaemon(config2, audit, signal) {
|
|
|
23267
26260
|
config2.bindingId,
|
|
23268
26261
|
config2.approvedRoot
|
|
23269
26262
|
);
|
|
26263
|
+
const installationObserver = new DaemonInstallationObserver();
|
|
26264
|
+
const installations = new AgentInstallationManager(
|
|
26265
|
+
specifications,
|
|
26266
|
+
new ConsoleInstallationAuthorizer(stateDirectory),
|
|
26267
|
+
installationExecutor,
|
|
26268
|
+
installationObserver,
|
|
26269
|
+
installationAudit
|
|
26270
|
+
);
|
|
23270
26271
|
let retryAnnounced = false;
|
|
23271
26272
|
try {
|
|
23272
26273
|
while (!signal.aborted) {
|
|
23273
26274
|
try {
|
|
23274
|
-
await
|
|
26275
|
+
await publishAgentCatalog(config2, signal);
|
|
26276
|
+
await runRelayConnection(
|
|
26277
|
+
config2,
|
|
26278
|
+
directories,
|
|
26279
|
+
audit,
|
|
26280
|
+
sessions,
|
|
26281
|
+
installations,
|
|
26282
|
+
installationObserver,
|
|
26283
|
+
signal,
|
|
26284
|
+
timer
|
|
26285
|
+
);
|
|
23275
26286
|
retryAnnounced = false;
|
|
23276
26287
|
} catch {
|
|
23277
26288
|
if (signal.aborted) {
|
|
@@ -23282,11 +26293,18 @@ async function runDaemon(config2, audit, signal) {
|
|
|
23282
26293
|
retryAnnounced = true;
|
|
23283
26294
|
}
|
|
23284
26295
|
}
|
|
23285
|
-
await waitForRelayRetry(signal);
|
|
26296
|
+
await waitForRelayRetry(signal, timer);
|
|
23286
26297
|
}
|
|
23287
26298
|
} finally {
|
|
23288
|
-
|
|
23289
|
-
|
|
26299
|
+
try {
|
|
26300
|
+
await installations.close();
|
|
26301
|
+
} finally {
|
|
26302
|
+
try {
|
|
26303
|
+
await sessions.close();
|
|
26304
|
+
} finally {
|
|
26305
|
+
sessionStore.close();
|
|
26306
|
+
}
|
|
26307
|
+
}
|
|
23290
26308
|
}
|
|
23291
26309
|
}
|
|
23292
26310
|
export {
|