@moxt-ai/mobius 0.0.12 → 0.0.14
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/cli.js +566 -513
- package/dist/cli.js.map +4 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +719 -666
- package/dist/index.js.map +4 -4
- package/dist/pairing/pair-local-machine.d.ts.map +1 -1
- package/dist/pairing/pair-local-machine.js +3 -2
- package/dist/pairing/pair-local-machine.js.map +1 -1
- package/dist/run-daemon.d.ts.map +1 -1
- package/dist/run-daemon.js +34 -5
- package/dist/run-daemon.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,646 +25,6 @@ var createAcpClient = (handler) => client({ name: "mobius" }).onRequest(
|
|
|
25
25
|
import { join as join3 } from "node:path";
|
|
26
26
|
import { fileURLToPath } from "node:url";
|
|
27
27
|
|
|
28
|
-
// src/agent-catalog/local-agent.ts
|
|
29
|
-
import { realpathSync, statSync } from "node:fs";
|
|
30
|
-
|
|
31
|
-
// src/process/executable.ts
|
|
32
|
-
import { execFile, spawn } from "node:child_process";
|
|
33
|
-
import { constants } from "node:fs";
|
|
34
|
-
import { access, realpath, stat } from "node:fs/promises";
|
|
35
|
-
import { join, resolve } from "node:path";
|
|
36
|
-
var DEFAULT_OUTPUT_LIMIT_BYTES = 16 * 1024;
|
|
37
|
-
var PROCESS_GRACEFUL_STOP_TIMEOUT_MILLISECONDS = 1e3;
|
|
38
|
-
var PROCESS_FORCED_STOP_TIMEOUT_MILLISECONDS = 5e3;
|
|
39
|
-
var PROCESS_TABLE_TIMEOUT_MILLISECONDS = 1e3;
|
|
40
|
-
var PROCESS_TABLE_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
41
|
-
var PROCESS_EXIT_CHECK_ATTEMPTS = 8;
|
|
42
|
-
var GRACEFUL_STOP_SIGNAL = 15;
|
|
43
|
-
var FORCED_STOP_SIGNAL = 9;
|
|
44
|
-
var ExecutableIdentity = class {
|
|
45
|
-
changedAtMilliseconds;
|
|
46
|
-
device;
|
|
47
|
-
inode;
|
|
48
|
-
realPath;
|
|
49
|
-
requestedPath;
|
|
50
|
-
size;
|
|
51
|
-
constructor(requestedPath, realPath_, device, inode, size, changedAtMilliseconds) {
|
|
52
|
-
this.changedAtMilliseconds = changedAtMilliseconds;
|
|
53
|
-
this.device = device;
|
|
54
|
-
this.inode = inode;
|
|
55
|
-
this.realPath = realPath_;
|
|
56
|
-
this.requestedPath = requestedPath;
|
|
57
|
-
this.size = size;
|
|
58
|
-
}
|
|
59
|
-
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;
|
|
60
|
-
};
|
|
61
|
-
var ResolvedExecutable = class {
|
|
62
|
-
identity;
|
|
63
|
-
path;
|
|
64
|
-
constructor(identity2) {
|
|
65
|
-
this.identity = identity2;
|
|
66
|
-
this.path = identity2.realPath;
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
var ProcessExecutionError = class extends Error {
|
|
70
|
-
code;
|
|
71
|
-
constructor(code, message, options = {}) {
|
|
72
|
-
super(message, options);
|
|
73
|
-
this.code = code;
|
|
74
|
-
this.name = "ProcessExecutionError";
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
var ProcessResult = class {
|
|
78
|
-
stderr;
|
|
79
|
-
stdout;
|
|
80
|
-
constructor(stdout, stderr) {
|
|
81
|
-
this.stderr = stderr;
|
|
82
|
-
this.stdout = stdout;
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
function executableExtensions(executableName, environment, platform) {
|
|
86
|
-
if (platform !== "win32") {
|
|
87
|
-
return [""];
|
|
88
|
-
}
|
|
89
|
-
if (/\.[a-zA-Z0-9]+$/.test(executableName)) {
|
|
90
|
-
return [""];
|
|
91
|
-
}
|
|
92
|
-
const configured = environment["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD";
|
|
93
|
-
const extensions = configured.split(";").map((extension) => extension.trim()).filter((extension) => extension.length > 0).map(
|
|
94
|
-
(extension) => extension.startsWith(".") ? extension : `.${extension}`
|
|
95
|
-
);
|
|
96
|
-
return extensions.length > 0 ? extensions : [".EXE"];
|
|
97
|
-
}
|
|
98
|
-
async function resolveExecutablePath(requestedPath, platform = process.platform) {
|
|
99
|
-
const absoluteRequestedPath = resolve(requestedPath);
|
|
100
|
-
try {
|
|
101
|
-
const resolvedPath = await realpath(absoluteRequestedPath);
|
|
102
|
-
const metadata = await stat(resolvedPath);
|
|
103
|
-
if (!metadata.isFile()) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
if (platform !== "win32") {
|
|
107
|
-
await access(resolvedPath, constants.X_OK);
|
|
108
|
-
}
|
|
109
|
-
return new ResolvedExecutable(
|
|
110
|
-
new ExecutableIdentity(
|
|
111
|
-
absoluteRequestedPath,
|
|
112
|
-
resolvedPath,
|
|
113
|
-
metadata.dev,
|
|
114
|
-
metadata.ino,
|
|
115
|
-
metadata.size,
|
|
116
|
-
metadata.ctimeMs
|
|
117
|
-
)
|
|
118
|
-
);
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
async function findExecutableOnPath(executableName, environment, platform = process.platform) {
|
|
124
|
-
const encodedPath = environment["PATH"] ?? "";
|
|
125
|
-
const separator = platform === "win32" ? ";" : ":";
|
|
126
|
-
const extensions = executableExtensions(
|
|
127
|
-
executableName,
|
|
128
|
-
environment,
|
|
129
|
-
platform
|
|
130
|
-
);
|
|
131
|
-
for (const entry of encodedPath.split(separator)) {
|
|
132
|
-
if (entry.length === 0) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
for (const extension of extensions) {
|
|
136
|
-
const requestedPath = resolve(
|
|
137
|
-
join(entry, `${executableName}${extension}`)
|
|
138
|
-
);
|
|
139
|
-
const executable = await resolveExecutablePath(requestedPath, platform);
|
|
140
|
-
if (executable !== null) {
|
|
141
|
-
return executable;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
async function revalidateExecutable(identity2, platform = process.platform) {
|
|
148
|
-
const current = await resolveExecutablePath(identity2.requestedPath, platform);
|
|
149
|
-
return current !== null && identity2.matches(current.identity);
|
|
150
|
-
}
|
|
151
|
-
function appendBounded(chunks, chunk, currentBytes, limitBytes) {
|
|
152
|
-
const nextBytes = currentBytes + chunk.byteLength;
|
|
153
|
-
if (nextBytes > limitBytes) {
|
|
154
|
-
throw new ProcessExecutionError(
|
|
155
|
-
"process_output_limit",
|
|
156
|
-
"The executable produced too much output"
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
chunks.push(chunk);
|
|
160
|
-
return nextBytes;
|
|
161
|
-
}
|
|
162
|
-
function parseProcessTable(output2) {
|
|
163
|
-
const entries = [];
|
|
164
|
-
for (const line of output2.split("\n")) {
|
|
165
|
-
const groups = /^\s*(?<pid>\d+)\s+(?<ppid>\d+)\s+(?<pgid>\d+)\s*$/.exec(
|
|
166
|
-
line
|
|
167
|
-
)?.groups;
|
|
168
|
-
if (groups === void 0) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
const processId = Number(groups["pid"]);
|
|
172
|
-
const parentProcessId = Number(groups["ppid"]);
|
|
173
|
-
const processGroupId = Number(groups["pgid"]);
|
|
174
|
-
if (!Number.isSafeInteger(processId) || processId <= 0 || !Number.isSafeInteger(parentProcessId) || parentProcessId < 0 || !Number.isSafeInteger(processGroupId) || processGroupId <= 0) {
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
entries.push({ parentProcessId, processGroupId, processId });
|
|
178
|
-
}
|
|
179
|
-
return entries;
|
|
180
|
-
}
|
|
181
|
-
function readProcessTable() {
|
|
182
|
-
if (process.platform === "win32") {
|
|
183
|
-
return Promise.resolve([]);
|
|
184
|
-
}
|
|
185
|
-
const executable = process.platform === "darwin" ? "/bin/ps" : "/usr/bin/ps";
|
|
186
|
-
return new Promise((resolveTable) => {
|
|
187
|
-
execFile(
|
|
188
|
-
executable,
|
|
189
|
-
["-axo", "pid=,ppid=,pgid="],
|
|
190
|
-
{
|
|
191
|
-
encoding: "utf8",
|
|
192
|
-
maxBuffer: PROCESS_TABLE_OUTPUT_LIMIT_BYTES,
|
|
193
|
-
signal: AbortSignal.timeout(PROCESS_TABLE_TIMEOUT_MILLISECONDS),
|
|
194
|
-
timeout: PROCESS_TABLE_TIMEOUT_MILLISECONDS
|
|
195
|
-
},
|
|
196
|
-
(error61, stdout) => {
|
|
197
|
-
resolveTable(error61 === null ? parseProcessTable(stdout) : []);
|
|
198
|
-
}
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
function selectProcessTree(entries, rootProcessId) {
|
|
203
|
-
const processIds = /* @__PURE__ */ new Set([rootProcessId]);
|
|
204
|
-
let previousSize = 0;
|
|
205
|
-
while (previousSize !== processIds.size) {
|
|
206
|
-
previousSize = processIds.size;
|
|
207
|
-
for (const entry of entries) {
|
|
208
|
-
if (processIds.has(entry.parentProcessId)) {
|
|
209
|
-
processIds.add(entry.processId);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return entries.filter((entry) => processIds.has(entry.processId)).map((entry) => ({
|
|
214
|
-
processGroupId: entry.processGroupId,
|
|
215
|
-
processId: entry.processId
|
|
216
|
-
}));
|
|
217
|
-
}
|
|
218
|
-
function mergeProcessTrees(captured, current) {
|
|
219
|
-
const processes = /* @__PURE__ */ new Map();
|
|
220
|
-
for (const identity2 of captured) {
|
|
221
|
-
processes.set(identity2.processId, identity2);
|
|
222
|
-
}
|
|
223
|
-
for (const identity2 of current) {
|
|
224
|
-
processes.set(identity2.processId, identity2);
|
|
225
|
-
}
|
|
226
|
-
return [...processes.values()];
|
|
227
|
-
}
|
|
228
|
-
async function captureProcessTree(child) {
|
|
229
|
-
if (process.platform === "win32" || child.pid === void 0) {
|
|
230
|
-
return [];
|
|
231
|
-
}
|
|
232
|
-
return selectProcessTree(await readProcessTable(), child.pid);
|
|
233
|
-
}
|
|
234
|
-
function signalDetachedProcessGroups(processes, rootProcessId, signal) {
|
|
235
|
-
const processIds = new Set(processes.map((identity2) => identity2.processId));
|
|
236
|
-
const signalledGroups = /* @__PURE__ */ new Set();
|
|
237
|
-
for (const identity2 of processes) {
|
|
238
|
-
if (identity2.processGroupId === rootProcessId || !processIds.has(identity2.processGroupId) || signalledGroups.has(identity2.processGroupId)) {
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
try {
|
|
242
|
-
process.kill(-identity2.processGroupId, signal);
|
|
243
|
-
signalledGroups.add(identity2.processGroupId);
|
|
244
|
-
} catch {
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
for (const identity2 of processes) {
|
|
248
|
-
if (identity2.processId === rootProcessId || identity2.processGroupId === rootProcessId || signalledGroups.has(identity2.processGroupId)) {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
try {
|
|
252
|
-
process.kill(identity2.processId, signal);
|
|
253
|
-
} catch {
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function processExists(processId) {
|
|
258
|
-
try {
|
|
259
|
-
process.kill(processId, 0);
|
|
260
|
-
return true;
|
|
261
|
-
} catch {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
async function waitForCapturedProcessesExit(processes, rootProcessId) {
|
|
266
|
-
const processIds = processes.map((identity2) => identity2.processId).filter((processId) => processId !== rootProcessId);
|
|
267
|
-
for (let attempt = 0; attempt < PROCESS_EXIT_CHECK_ATTEMPTS; attempt += 1) {
|
|
268
|
-
if (processIds.every((processId) => !processExists(processId))) {
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
await readProcessTable();
|
|
272
|
-
}
|
|
273
|
-
return processIds.every((processId) => !processExists(processId));
|
|
274
|
-
}
|
|
275
|
-
function terminateProcess(child) {
|
|
276
|
-
if (child.exitCode !== null || child.signalCode !== null || child.pid === void 0) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (process.platform !== "win32") {
|
|
280
|
-
try {
|
|
281
|
-
process.kill(-child.pid, "SIGTERM");
|
|
282
|
-
return;
|
|
283
|
-
} catch {
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
child.kill("SIGTERM");
|
|
287
|
-
}
|
|
288
|
-
function forceTerminateProcess(child) {
|
|
289
|
-
if (child.exitCode !== null || child.signalCode !== null || child.pid === void 0) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
if (process.platform !== "win32") {
|
|
293
|
-
try {
|
|
294
|
-
process.kill(-child.pid, "SIGKILL");
|
|
295
|
-
return;
|
|
296
|
-
} catch {
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
child.kill("SIGKILL");
|
|
300
|
-
}
|
|
301
|
-
async function stopProcessTree(child, capturedProcesses = []) {
|
|
302
|
-
const childProcessId = child.pid;
|
|
303
|
-
if (childProcessId === void 0) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const childExited = child.exitCode !== null || child.signalCode !== null;
|
|
307
|
-
if (childExited && capturedProcesses.length === 0) {
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
const processes = mergeProcessTrees(
|
|
311
|
-
capturedProcesses,
|
|
312
|
-
childExited ? [] : await captureProcessTree(child)
|
|
313
|
-
);
|
|
314
|
-
signalDetachedProcessGroups(processes, childProcessId, GRACEFUL_STOP_SIGNAL);
|
|
315
|
-
terminateProcess(child);
|
|
316
|
-
const [exitedAfterTermination, descendantsExitedAfterTermination] = await Promise.all([
|
|
317
|
-
waitForProcessExit(child, PROCESS_GRACEFUL_STOP_TIMEOUT_MILLISECONDS),
|
|
318
|
-
waitForCapturedProcessesExit(processes, childProcessId)
|
|
319
|
-
]);
|
|
320
|
-
if (exitedAfterTermination && descendantsExitedAfterTermination) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
signalDetachedProcessGroups(processes, childProcessId, FORCED_STOP_SIGNAL);
|
|
324
|
-
forceTerminateProcess(child);
|
|
325
|
-
const [exitedAfterForce, descendantsExitedAfterForce] = await Promise.all([
|
|
326
|
-
waitForProcessExit(child, PROCESS_FORCED_STOP_TIMEOUT_MILLISECONDS),
|
|
327
|
-
waitForCapturedProcessesExit(processes, childProcessId)
|
|
328
|
-
]);
|
|
329
|
-
if (!exitedAfterForce || !descendantsExitedAfterForce) {
|
|
330
|
-
throw new ProcessExecutionError(
|
|
331
|
-
"process_termination_timeout",
|
|
332
|
-
"The executable process could not be stopped"
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
function waitForProcessExit(child, timeoutMilliseconds) {
|
|
337
|
-
if (child.exitCode !== null || child.signalCode !== null) {
|
|
338
|
-
return Promise.resolve(true);
|
|
339
|
-
}
|
|
340
|
-
return new Promise((resolveExit) => {
|
|
341
|
-
let settled = false;
|
|
342
|
-
const finish = (exited) => {
|
|
343
|
-
if (settled) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
settled = true;
|
|
347
|
-
clearTimeout(timeout);
|
|
348
|
-
child.removeListener("exit", handleExit);
|
|
349
|
-
resolveExit(exited);
|
|
350
|
-
};
|
|
351
|
-
const handleExit = () => finish(true);
|
|
352
|
-
const timeout = setTimeout(() => finish(false), timeoutMilliseconds);
|
|
353
|
-
child.once("exit", handleExit);
|
|
354
|
-
if (child.exitCode !== null || child.signalCode !== null) {
|
|
355
|
-
finish(true);
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
async function runBoundedProcess(options) {
|
|
360
|
-
options.signal.throwIfAborted();
|
|
361
|
-
const outputLimitBytes = options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES;
|
|
362
|
-
const child = spawn(options.command, [...options.arguments], {
|
|
363
|
-
cwd: options.cwd,
|
|
364
|
-
detached: process.platform !== "win32",
|
|
365
|
-
env: { ...options.environment },
|
|
366
|
-
shell: false,
|
|
367
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
368
|
-
});
|
|
369
|
-
const stdout = [];
|
|
370
|
-
const stderr = [];
|
|
371
|
-
let stdoutBytes = 0;
|
|
372
|
-
let stderrBytes = 0;
|
|
373
|
-
let interruptionFailure = null;
|
|
374
|
-
let interruptionCleanupFailure = null;
|
|
375
|
-
let stopping = false;
|
|
376
|
-
const interrupted = Promise.withResolvers();
|
|
377
|
-
const interrupt = (failure) => {
|
|
378
|
-
if (stopping) {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
stopping = true;
|
|
382
|
-
interruptionFailure = failure;
|
|
383
|
-
const stop = async () => {
|
|
384
|
-
try {
|
|
385
|
-
await stopProcessTree(child);
|
|
386
|
-
} catch (error61) {
|
|
387
|
-
interruptionCleanupFailure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
|
|
388
|
-
"process_termination_failed",
|
|
389
|
-
"The executable process could not be stopped",
|
|
390
|
-
{ cause: error61 }
|
|
391
|
-
);
|
|
392
|
-
} finally {
|
|
393
|
-
interrupted.resolve();
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
void stop();
|
|
397
|
-
};
|
|
398
|
-
const collectStdout = (chunk) => {
|
|
399
|
-
if (interruptionFailure !== null) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
try {
|
|
403
|
-
stdoutBytes = appendBounded(stdout, chunk, stdoutBytes, outputLimitBytes);
|
|
404
|
-
} catch (error61) {
|
|
405
|
-
const failure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
|
|
406
|
-
"process_output_limit",
|
|
407
|
-
"The executable produced too much output",
|
|
408
|
-
{ cause: error61 }
|
|
409
|
-
);
|
|
410
|
-
interrupt(failure);
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
const collectStderr = (chunk) => {
|
|
414
|
-
if (interruptionFailure !== null) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
try {
|
|
418
|
-
stderrBytes = appendBounded(stderr, chunk, stderrBytes, outputLimitBytes);
|
|
419
|
-
} catch (error61) {
|
|
420
|
-
const failure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
|
|
421
|
-
"process_output_limit",
|
|
422
|
-
"The executable produced too much output",
|
|
423
|
-
{ cause: error61 }
|
|
424
|
-
);
|
|
425
|
-
interrupt(failure);
|
|
426
|
-
}
|
|
427
|
-
};
|
|
428
|
-
child.stdout?.on("data", collectStdout);
|
|
429
|
-
child.stderr?.on("data", collectStderr);
|
|
430
|
-
const timeout = setTimeout(() => {
|
|
431
|
-
interrupt(
|
|
432
|
-
new ProcessExecutionError(
|
|
433
|
-
"process_timeout",
|
|
434
|
-
"The executable probe timed out"
|
|
435
|
-
)
|
|
436
|
-
);
|
|
437
|
-
}, options.timeoutMilliseconds);
|
|
438
|
-
const abort = () => interrupt(
|
|
439
|
-
new ProcessExecutionError(
|
|
440
|
-
"process_cancelled",
|
|
441
|
-
"The executable probe was cancelled",
|
|
442
|
-
{ cause: options.signal.reason }
|
|
443
|
-
)
|
|
444
|
-
);
|
|
445
|
-
options.signal.addEventListener("abort", abort, { once: true });
|
|
446
|
-
if (options.signal.aborted) {
|
|
447
|
-
abort();
|
|
448
|
-
}
|
|
449
|
-
try {
|
|
450
|
-
const exit = new Promise((resolveExit, rejectExit) => {
|
|
451
|
-
child.once("error", rejectExit);
|
|
452
|
-
child.once("exit", (code) => resolveExit(code ?? 1));
|
|
453
|
-
});
|
|
454
|
-
const exitCode = await Promise.race([
|
|
455
|
-
exit,
|
|
456
|
-
interrupted.promise.then(() => -1)
|
|
457
|
-
]);
|
|
458
|
-
if (interruptionCleanupFailure !== null) {
|
|
459
|
-
throw interruptionCleanupFailure;
|
|
460
|
-
}
|
|
461
|
-
if (interruptionFailure !== null) {
|
|
462
|
-
throw interruptionFailure;
|
|
463
|
-
}
|
|
464
|
-
if (exitCode !== 0) {
|
|
465
|
-
throw new ProcessExecutionError(
|
|
466
|
-
"process_exit_failed",
|
|
467
|
-
"The executable probe failed"
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
return new ProcessResult(
|
|
471
|
-
Buffer.concat(stdout).toString("utf8"),
|
|
472
|
-
Buffer.concat(stderr).toString("utf8")
|
|
473
|
-
);
|
|
474
|
-
} catch (error61) {
|
|
475
|
-
if (error61 instanceof ProcessExecutionError) {
|
|
476
|
-
throw error61;
|
|
477
|
-
}
|
|
478
|
-
throw new ProcessExecutionError(
|
|
479
|
-
"process_spawn_failed",
|
|
480
|
-
"The executable could not be started",
|
|
481
|
-
{ cause: error61 }
|
|
482
|
-
);
|
|
483
|
-
} finally {
|
|
484
|
-
clearTimeout(timeout);
|
|
485
|
-
options.signal.removeEventListener("abort", abort);
|
|
486
|
-
child.stdout?.removeListener("data", collectStdout);
|
|
487
|
-
child.stderr?.removeListener("data", collectStderr);
|
|
488
|
-
if (stopping) {
|
|
489
|
-
await interrupted.promise;
|
|
490
|
-
} else {
|
|
491
|
-
await stopProcessTree(child);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// src/agent-catalog/local-agent.ts
|
|
497
|
-
function readTrustedIdentity(path2) {
|
|
498
|
-
const resolvedPath = realpathSync(path2);
|
|
499
|
-
const metadata = statSync(resolvedPath);
|
|
500
|
-
return new ExecutableIdentity(
|
|
501
|
-
resolvedPath,
|
|
502
|
-
resolvedPath,
|
|
503
|
-
metadata.dev,
|
|
504
|
-
metadata.ino,
|
|
505
|
-
metadata.size,
|
|
506
|
-
metadata.ctimeMs
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
var LocalAgentDefinition = class {
|
|
510
|
-
adapterIdentity;
|
|
511
|
-
adapterPath;
|
|
512
|
-
adapterVersion;
|
|
513
|
-
agentIdentity;
|
|
514
|
-
agentVersion;
|
|
515
|
-
arguments;
|
|
516
|
-
command;
|
|
517
|
-
environment;
|
|
518
|
-
id;
|
|
519
|
-
label;
|
|
520
|
-
promptTimeoutMilliseconds;
|
|
521
|
-
constructor(id, label, command, arguments_, environment = {}, adapterVersion = "", agentVersion = "", adapterIdentity = readTrustedIdentity(command), agentIdentity = adapterIdentity, promptTimeoutMilliseconds = 30 * 60 * 1e3) {
|
|
522
|
-
if (!Number.isSafeInteger(promptTimeoutMilliseconds) || promptTimeoutMilliseconds < 1) {
|
|
523
|
-
throw new Error("The local Agent prompt deadline is invalid");
|
|
524
|
-
}
|
|
525
|
-
this.adapterIdentity = adapterIdentity;
|
|
526
|
-
this.adapterPath = adapterIdentity.realPath;
|
|
527
|
-
this.adapterVersion = adapterVersion;
|
|
528
|
-
this.agentIdentity = agentIdentity;
|
|
529
|
-
this.agentVersion = agentVersion;
|
|
530
|
-
this.id = id;
|
|
531
|
-
this.label = label;
|
|
532
|
-
this.promptTimeoutMilliseconds = promptTimeoutMilliseconds;
|
|
533
|
-
this.command = adapterIdentity.realPath;
|
|
534
|
-
this.arguments = Object.freeze([...arguments_]);
|
|
535
|
-
this.environment = Object.freeze({ ...environment });
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
var InstallableAgentDefinition = class {
|
|
539
|
-
id;
|
|
540
|
-
label;
|
|
541
|
-
requiresLocalApproval;
|
|
542
|
-
constructor(id, label, requiresLocalApproval = true) {
|
|
543
|
-
this.id = id;
|
|
544
|
-
this.label = label;
|
|
545
|
-
this.requiresLocalApproval = requiresLocalApproval;
|
|
546
|
-
}
|
|
547
|
-
};
|
|
548
|
-
var LocalAgentCatalogSnapshot = class {
|
|
549
|
-
agents;
|
|
550
|
-
installableAgents;
|
|
551
|
-
constructor(agents, installableAgents) {
|
|
552
|
-
this.agents = Object.freeze([...agents]);
|
|
553
|
-
this.installableAgents = Object.freeze([...installableAgents]);
|
|
554
|
-
}
|
|
555
|
-
};
|
|
556
|
-
var LocalAgentCatalogError = class extends Error {
|
|
557
|
-
code = "agent_not_ready";
|
|
558
|
-
constructor() {
|
|
559
|
-
super("The requested local Agent is not ready");
|
|
560
|
-
this.name = "LocalAgentCatalogError";
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
var LocalAgentCatalog = class {
|
|
564
|
-
#snapshot;
|
|
565
|
-
constructor(agents, installableAgents) {
|
|
566
|
-
this.#snapshot = this.validate(agents, installableAgents);
|
|
567
|
-
}
|
|
568
|
-
find = (agentId) => {
|
|
569
|
-
for (const agent of this.#snapshot.agents) {
|
|
570
|
-
if (agent.id === agentId) {
|
|
571
|
-
return agent;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
throw new LocalAgentCatalogError();
|
|
575
|
-
};
|
|
576
|
-
read = () => this.#snapshot;
|
|
577
|
-
transition = (agents, installableAgents) => {
|
|
578
|
-
const next = this.validate(agents, installableAgents);
|
|
579
|
-
this.#snapshot = next;
|
|
580
|
-
return next;
|
|
581
|
-
};
|
|
582
|
-
validate = (agents, installableAgents) => {
|
|
583
|
-
const identifiers = /* @__PURE__ */ new Set();
|
|
584
|
-
for (const agent of agents) {
|
|
585
|
-
if (identifiers.has(agent.id)) {
|
|
586
|
-
throw new Error("The local Agent catalog contains duplicate entries");
|
|
587
|
-
}
|
|
588
|
-
identifiers.add(agent.id);
|
|
589
|
-
}
|
|
590
|
-
for (const agent of installableAgents) {
|
|
591
|
-
if (identifiers.has(agent.id)) {
|
|
592
|
-
throw new Error("A local Agent cannot be ready and installable");
|
|
593
|
-
}
|
|
594
|
-
identifiers.add(agent.id);
|
|
595
|
-
}
|
|
596
|
-
return new LocalAgentCatalogSnapshot(agents, installableAgents);
|
|
597
|
-
};
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
// src/storage/state-directory.ts
|
|
601
|
-
import { homedir } from "node:os";
|
|
602
|
-
import { join as join2, resolve as resolve2 } from "node:path";
|
|
603
|
-
function readStateDirectory(environment = process.env) {
|
|
604
|
-
const configured = environment["MOBIUS_STATE_DIRECTORY"]?.trim();
|
|
605
|
-
if (configured !== void 0 && configured.length > 0) {
|
|
606
|
-
return resolve2(configured);
|
|
607
|
-
}
|
|
608
|
-
return join2(homedir(), ".mobius");
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// src/config.ts
|
|
612
|
-
function loadDefaultWorkspace() {
|
|
613
|
-
const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = process.env;
|
|
614
|
-
return configuredWorkspace ?? join3(readStateDirectory(), "workspace");
|
|
615
|
-
}
|
|
616
|
-
function createDaemonConfig(connection, agents = [], installableAgents = []) {
|
|
617
|
-
return {
|
|
618
|
-
agents,
|
|
619
|
-
defaultWorkspace: connection.defaultWorkspace,
|
|
620
|
-
auditDatabasePath: connection.auditDatabasePath,
|
|
621
|
-
bindingId: "workspace",
|
|
622
|
-
catalog: new LocalAgentCatalog(agents, installableAgents),
|
|
623
|
-
channelId: connection.channelId,
|
|
624
|
-
installableAgents,
|
|
625
|
-
relayUrl: connection.relayUrl
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
function loadDaemonConfig() {
|
|
629
|
-
const relayUrl = new URL(
|
|
630
|
-
// biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
|
|
631
|
-
process.env["MOBIUS_RELAY_URL"] ?? "ws://127.0.0.1:8787/mobius/api/channels/local/daemon"
|
|
632
|
-
);
|
|
633
|
-
relayUrl.searchParams.set(
|
|
634
|
-
"token",
|
|
635
|
-
// biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
|
|
636
|
-
process.env["MOBIUS_RELAY_TOKEN"] ?? "local-development-token"
|
|
637
|
-
);
|
|
638
|
-
if (relayUrl.protocol !== "ws:" && relayUrl.protocol !== "wss:") {
|
|
639
|
-
throw new Error("MOBIUS_RELAY_URL must use ws or wss");
|
|
640
|
-
}
|
|
641
|
-
return createDaemonConfig(
|
|
642
|
-
{
|
|
643
|
-
defaultWorkspace: loadDefaultWorkspace(),
|
|
644
|
-
auditDatabasePath: (
|
|
645
|
-
// biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
|
|
646
|
-
process.env["MOBIUS_AUDIT_DATABASE"] ?? fileURLToPath(
|
|
647
|
-
new URL("../../../.mobius/daemon.sqlite", import.meta.url)
|
|
648
|
-
)
|
|
649
|
-
),
|
|
650
|
-
// biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
|
|
651
|
-
channelId: process.env["MOBIUS_CHANNEL_ID"] ?? "local",
|
|
652
|
-
relayUrl
|
|
653
|
-
},
|
|
654
|
-
[],
|
|
655
|
-
[]
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// src/acp/session-manager.ts
|
|
660
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
661
|
-
import {
|
|
662
|
-
methods as methods2,
|
|
663
|
-
ndJsonStream,
|
|
664
|
-
PROTOCOL_VERSION,
|
|
665
|
-
RequestError
|
|
666
|
-
} from "@agentclientprotocol/sdk";
|
|
667
|
-
|
|
668
28
|
// ../../node_modules/.pnpm/zod@4.5.4/node_modules/zod/v4/classic/external.js
|
|
669
29
|
var external_exports = {};
|
|
670
30
|
__export(external_exports, {
|
|
@@ -20393,6 +19753,29 @@ function encodeSessionFailedEvent(message) {
|
|
|
20393
19753
|
return JSON.stringify(decodeSessionFailedEvent(JSON.stringify(message)));
|
|
20394
19754
|
}
|
|
20395
19755
|
|
|
19756
|
+
// ../protocol/src/mobius-api.ts
|
|
19757
|
+
var MOBIUS_API_PREFIX = "/mobius/api";
|
|
19758
|
+
var API_PREFIX_PATTERN = /^\/[a-zA-Z0-9._~-]+(?:\/[a-zA-Z0-9._~-]+)*$/;
|
|
19759
|
+
var MobiusApiConfigurationError = class extends Error {
|
|
19760
|
+
code = "invalid_api_prefix";
|
|
19761
|
+
constructor() {
|
|
19762
|
+
super("The Mobius API prefix must be an absolute path without parameters");
|
|
19763
|
+
this.name = "MobiusApiConfigurationError";
|
|
19764
|
+
}
|
|
19765
|
+
};
|
|
19766
|
+
function parseMobiusApiPrefix(value) {
|
|
19767
|
+
if (typeof value !== "string" || !API_PREFIX_PATTERN.test(value)) {
|
|
19768
|
+
throw new MobiusApiConfigurationError();
|
|
19769
|
+
}
|
|
19770
|
+
return value;
|
|
19771
|
+
}
|
|
19772
|
+
function mobiusApiPath(resourcePath, apiPrefix = MOBIUS_API_PREFIX) {
|
|
19773
|
+
return `${parseMobiusApiPrefix(apiPrefix)}${resourcePath}`;
|
|
19774
|
+
}
|
|
19775
|
+
function mobiusApiUrl(origin, resourcePath, apiPrefix = MOBIUS_API_PREFIX) {
|
|
19776
|
+
return new URL(mobiusApiPath(resourcePath, apiPrefix), origin);
|
|
19777
|
+
}
|
|
19778
|
+
|
|
20396
19779
|
// ../protocol/src/session-history.ts
|
|
20397
19780
|
var SESSION_HISTORY_CHARACTER_LIMIT = 256e3;
|
|
20398
19781
|
var SESSION_HISTORY_MESSAGE_LIMIT = 1e3;
|
|
@@ -21084,29 +20467,669 @@ var eventAcknowledgementSchema = external_exports.strictObject({
|
|
|
21084
20467
|
function decode5(encoded, schema) {
|
|
21085
20468
|
let value;
|
|
21086
20469
|
try {
|
|
21087
|
-
value = JSON.parse(encoded);
|
|
21088
|
-
} catch (cause) {
|
|
21089
|
-
throw new ProtocolMessageError("Message is not valid JSON", { cause });
|
|
20470
|
+
value = JSON.parse(encoded);
|
|
20471
|
+
} catch (cause) {
|
|
20472
|
+
throw new ProtocolMessageError("Message is not valid JSON", { cause });
|
|
20473
|
+
}
|
|
20474
|
+
const decoded = schema.safeParse(value);
|
|
20475
|
+
if (!decoded.success) {
|
|
20476
|
+
throw new ProtocolMessageError("Message does not match its schema", {
|
|
20477
|
+
cause: decoded.error
|
|
20478
|
+
});
|
|
20479
|
+
}
|
|
20480
|
+
return decoded.data;
|
|
20481
|
+
}
|
|
20482
|
+
var encode4 = (message, schema) => {
|
|
20483
|
+
const decoded = schema.safeParse(message);
|
|
20484
|
+
if (!decoded.success) {
|
|
20485
|
+
throw new ProtocolMessageError("Message does not match its schema", {
|
|
20486
|
+
cause: decoded.error
|
|
20487
|
+
});
|
|
20488
|
+
}
|
|
20489
|
+
return JSON.stringify(decoded.data);
|
|
20490
|
+
};
|
|
20491
|
+
var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
|
|
20492
|
+
var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
|
|
20493
|
+
|
|
20494
|
+
// src/agent-catalog/local-agent.ts
|
|
20495
|
+
import { realpathSync, statSync } from "node:fs";
|
|
20496
|
+
|
|
20497
|
+
// src/process/executable.ts
|
|
20498
|
+
import { execFile, spawn } from "node:child_process";
|
|
20499
|
+
import { constants } from "node:fs";
|
|
20500
|
+
import { access, realpath, stat } from "node:fs/promises";
|
|
20501
|
+
import { join, resolve } from "node:path";
|
|
20502
|
+
var DEFAULT_OUTPUT_LIMIT_BYTES = 16 * 1024;
|
|
20503
|
+
var PROCESS_GRACEFUL_STOP_TIMEOUT_MILLISECONDS = 1e3;
|
|
20504
|
+
var PROCESS_FORCED_STOP_TIMEOUT_MILLISECONDS = 5e3;
|
|
20505
|
+
var PROCESS_TABLE_TIMEOUT_MILLISECONDS = 1e3;
|
|
20506
|
+
var PROCESS_TABLE_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
20507
|
+
var PROCESS_EXIT_CHECK_ATTEMPTS = 8;
|
|
20508
|
+
var GRACEFUL_STOP_SIGNAL = 15;
|
|
20509
|
+
var FORCED_STOP_SIGNAL = 9;
|
|
20510
|
+
var ExecutableIdentity = class {
|
|
20511
|
+
changedAtMilliseconds;
|
|
20512
|
+
device;
|
|
20513
|
+
inode;
|
|
20514
|
+
realPath;
|
|
20515
|
+
requestedPath;
|
|
20516
|
+
size;
|
|
20517
|
+
constructor(requestedPath, realPath_, device, inode, size, changedAtMilliseconds) {
|
|
20518
|
+
this.changedAtMilliseconds = changedAtMilliseconds;
|
|
20519
|
+
this.device = device;
|
|
20520
|
+
this.inode = inode;
|
|
20521
|
+
this.realPath = realPath_;
|
|
20522
|
+
this.requestedPath = requestedPath;
|
|
20523
|
+
this.size = size;
|
|
20524
|
+
}
|
|
20525
|
+
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;
|
|
20526
|
+
};
|
|
20527
|
+
var ResolvedExecutable = class {
|
|
20528
|
+
identity;
|
|
20529
|
+
path;
|
|
20530
|
+
constructor(identity2) {
|
|
20531
|
+
this.identity = identity2;
|
|
20532
|
+
this.path = identity2.realPath;
|
|
20533
|
+
}
|
|
20534
|
+
};
|
|
20535
|
+
var ProcessExecutionError = class extends Error {
|
|
20536
|
+
code;
|
|
20537
|
+
constructor(code, message, options = {}) {
|
|
20538
|
+
super(message, options);
|
|
20539
|
+
this.code = code;
|
|
20540
|
+
this.name = "ProcessExecutionError";
|
|
20541
|
+
}
|
|
20542
|
+
};
|
|
20543
|
+
var ProcessResult = class {
|
|
20544
|
+
stderr;
|
|
20545
|
+
stdout;
|
|
20546
|
+
constructor(stdout, stderr) {
|
|
20547
|
+
this.stderr = stderr;
|
|
20548
|
+
this.stdout = stdout;
|
|
20549
|
+
}
|
|
20550
|
+
};
|
|
20551
|
+
function executableExtensions(executableName, environment, platform) {
|
|
20552
|
+
if (platform !== "win32") {
|
|
20553
|
+
return [""];
|
|
20554
|
+
}
|
|
20555
|
+
if (/\.[a-zA-Z0-9]+$/.test(executableName)) {
|
|
20556
|
+
return [""];
|
|
20557
|
+
}
|
|
20558
|
+
const configured = environment["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD";
|
|
20559
|
+
const extensions = configured.split(";").map((extension) => extension.trim()).filter((extension) => extension.length > 0).map(
|
|
20560
|
+
(extension) => extension.startsWith(".") ? extension : `.${extension}`
|
|
20561
|
+
);
|
|
20562
|
+
return extensions.length > 0 ? extensions : [".EXE"];
|
|
20563
|
+
}
|
|
20564
|
+
async function resolveExecutablePath(requestedPath, platform = process.platform) {
|
|
20565
|
+
const absoluteRequestedPath = resolve(requestedPath);
|
|
20566
|
+
try {
|
|
20567
|
+
const resolvedPath = await realpath(absoluteRequestedPath);
|
|
20568
|
+
const metadata = await stat(resolvedPath);
|
|
20569
|
+
if (!metadata.isFile()) {
|
|
20570
|
+
return null;
|
|
20571
|
+
}
|
|
20572
|
+
if (platform !== "win32") {
|
|
20573
|
+
await access(resolvedPath, constants.X_OK);
|
|
20574
|
+
}
|
|
20575
|
+
return new ResolvedExecutable(
|
|
20576
|
+
new ExecutableIdentity(
|
|
20577
|
+
absoluteRequestedPath,
|
|
20578
|
+
resolvedPath,
|
|
20579
|
+
metadata.dev,
|
|
20580
|
+
metadata.ino,
|
|
20581
|
+
metadata.size,
|
|
20582
|
+
metadata.ctimeMs
|
|
20583
|
+
)
|
|
20584
|
+
);
|
|
20585
|
+
} catch {
|
|
20586
|
+
return null;
|
|
20587
|
+
}
|
|
20588
|
+
}
|
|
20589
|
+
async function findExecutableOnPath(executableName, environment, platform = process.platform) {
|
|
20590
|
+
const encodedPath = environment["PATH"] ?? "";
|
|
20591
|
+
const separator = platform === "win32" ? ";" : ":";
|
|
20592
|
+
const extensions = executableExtensions(
|
|
20593
|
+
executableName,
|
|
20594
|
+
environment,
|
|
20595
|
+
platform
|
|
20596
|
+
);
|
|
20597
|
+
for (const entry of encodedPath.split(separator)) {
|
|
20598
|
+
if (entry.length === 0) {
|
|
20599
|
+
continue;
|
|
20600
|
+
}
|
|
20601
|
+
for (const extension of extensions) {
|
|
20602
|
+
const requestedPath = resolve(
|
|
20603
|
+
join(entry, `${executableName}${extension}`)
|
|
20604
|
+
);
|
|
20605
|
+
const executable = await resolveExecutablePath(requestedPath, platform);
|
|
20606
|
+
if (executable !== null) {
|
|
20607
|
+
return executable;
|
|
20608
|
+
}
|
|
20609
|
+
}
|
|
20610
|
+
}
|
|
20611
|
+
return null;
|
|
20612
|
+
}
|
|
20613
|
+
async function revalidateExecutable(identity2, platform = process.platform) {
|
|
20614
|
+
const current = await resolveExecutablePath(identity2.requestedPath, platform);
|
|
20615
|
+
return current !== null && identity2.matches(current.identity);
|
|
20616
|
+
}
|
|
20617
|
+
function appendBounded(chunks, chunk, currentBytes, limitBytes) {
|
|
20618
|
+
const nextBytes = currentBytes + chunk.byteLength;
|
|
20619
|
+
if (nextBytes > limitBytes) {
|
|
20620
|
+
throw new ProcessExecutionError(
|
|
20621
|
+
"process_output_limit",
|
|
20622
|
+
"The executable produced too much output"
|
|
20623
|
+
);
|
|
20624
|
+
}
|
|
20625
|
+
chunks.push(chunk);
|
|
20626
|
+
return nextBytes;
|
|
20627
|
+
}
|
|
20628
|
+
function parseProcessTable(output2) {
|
|
20629
|
+
const entries = [];
|
|
20630
|
+
for (const line of output2.split("\n")) {
|
|
20631
|
+
const groups = /^\s*(?<pid>\d+)\s+(?<ppid>\d+)\s+(?<pgid>\d+)\s*$/.exec(
|
|
20632
|
+
line
|
|
20633
|
+
)?.groups;
|
|
20634
|
+
if (groups === void 0) {
|
|
20635
|
+
continue;
|
|
20636
|
+
}
|
|
20637
|
+
const processId = Number(groups["pid"]);
|
|
20638
|
+
const parentProcessId = Number(groups["ppid"]);
|
|
20639
|
+
const processGroupId = Number(groups["pgid"]);
|
|
20640
|
+
if (!Number.isSafeInteger(processId) || processId <= 0 || !Number.isSafeInteger(parentProcessId) || parentProcessId < 0 || !Number.isSafeInteger(processGroupId) || processGroupId <= 0) {
|
|
20641
|
+
continue;
|
|
20642
|
+
}
|
|
20643
|
+
entries.push({ parentProcessId, processGroupId, processId });
|
|
20644
|
+
}
|
|
20645
|
+
return entries;
|
|
20646
|
+
}
|
|
20647
|
+
function readProcessTable() {
|
|
20648
|
+
if (process.platform === "win32") {
|
|
20649
|
+
return Promise.resolve([]);
|
|
20650
|
+
}
|
|
20651
|
+
const executable = process.platform === "darwin" ? "/bin/ps" : "/usr/bin/ps";
|
|
20652
|
+
return new Promise((resolveTable) => {
|
|
20653
|
+
execFile(
|
|
20654
|
+
executable,
|
|
20655
|
+
["-axo", "pid=,ppid=,pgid="],
|
|
20656
|
+
{
|
|
20657
|
+
encoding: "utf8",
|
|
20658
|
+
maxBuffer: PROCESS_TABLE_OUTPUT_LIMIT_BYTES,
|
|
20659
|
+
signal: AbortSignal.timeout(PROCESS_TABLE_TIMEOUT_MILLISECONDS),
|
|
20660
|
+
timeout: PROCESS_TABLE_TIMEOUT_MILLISECONDS
|
|
20661
|
+
},
|
|
20662
|
+
(error61, stdout) => {
|
|
20663
|
+
resolveTable(error61 === null ? parseProcessTable(stdout) : []);
|
|
20664
|
+
}
|
|
20665
|
+
);
|
|
20666
|
+
});
|
|
20667
|
+
}
|
|
20668
|
+
function selectProcessTree(entries, rootProcessId) {
|
|
20669
|
+
const processIds = /* @__PURE__ */ new Set([rootProcessId]);
|
|
20670
|
+
let previousSize = 0;
|
|
20671
|
+
while (previousSize !== processIds.size) {
|
|
20672
|
+
previousSize = processIds.size;
|
|
20673
|
+
for (const entry of entries) {
|
|
20674
|
+
if (processIds.has(entry.parentProcessId)) {
|
|
20675
|
+
processIds.add(entry.processId);
|
|
20676
|
+
}
|
|
20677
|
+
}
|
|
20678
|
+
}
|
|
20679
|
+
return entries.filter((entry) => processIds.has(entry.processId)).map((entry) => ({
|
|
20680
|
+
processGroupId: entry.processGroupId,
|
|
20681
|
+
processId: entry.processId
|
|
20682
|
+
}));
|
|
20683
|
+
}
|
|
20684
|
+
function mergeProcessTrees(captured, current) {
|
|
20685
|
+
const processes = /* @__PURE__ */ new Map();
|
|
20686
|
+
for (const identity2 of captured) {
|
|
20687
|
+
processes.set(identity2.processId, identity2);
|
|
20688
|
+
}
|
|
20689
|
+
for (const identity2 of current) {
|
|
20690
|
+
processes.set(identity2.processId, identity2);
|
|
20691
|
+
}
|
|
20692
|
+
return [...processes.values()];
|
|
20693
|
+
}
|
|
20694
|
+
async function captureProcessTree(child) {
|
|
20695
|
+
if (process.platform === "win32" || child.pid === void 0) {
|
|
20696
|
+
return [];
|
|
20697
|
+
}
|
|
20698
|
+
return selectProcessTree(await readProcessTable(), child.pid);
|
|
20699
|
+
}
|
|
20700
|
+
function signalDetachedProcessGroups(processes, rootProcessId, signal) {
|
|
20701
|
+
const processIds = new Set(processes.map((identity2) => identity2.processId));
|
|
20702
|
+
const signalledGroups = /* @__PURE__ */ new Set();
|
|
20703
|
+
for (const identity2 of processes) {
|
|
20704
|
+
if (identity2.processGroupId === rootProcessId || !processIds.has(identity2.processGroupId) || signalledGroups.has(identity2.processGroupId)) {
|
|
20705
|
+
continue;
|
|
20706
|
+
}
|
|
20707
|
+
try {
|
|
20708
|
+
process.kill(-identity2.processGroupId, signal);
|
|
20709
|
+
signalledGroups.add(identity2.processGroupId);
|
|
20710
|
+
} catch {
|
|
20711
|
+
}
|
|
20712
|
+
}
|
|
20713
|
+
for (const identity2 of processes) {
|
|
20714
|
+
if (identity2.processId === rootProcessId || identity2.processGroupId === rootProcessId || signalledGroups.has(identity2.processGroupId)) {
|
|
20715
|
+
continue;
|
|
20716
|
+
}
|
|
20717
|
+
try {
|
|
20718
|
+
process.kill(identity2.processId, signal);
|
|
20719
|
+
} catch {
|
|
20720
|
+
}
|
|
20721
|
+
}
|
|
20722
|
+
}
|
|
20723
|
+
function processExists(processId) {
|
|
20724
|
+
try {
|
|
20725
|
+
process.kill(processId, 0);
|
|
20726
|
+
return true;
|
|
20727
|
+
} catch {
|
|
20728
|
+
return false;
|
|
20729
|
+
}
|
|
20730
|
+
}
|
|
20731
|
+
async function waitForCapturedProcessesExit(processes, rootProcessId) {
|
|
20732
|
+
const processIds = processes.map((identity2) => identity2.processId).filter((processId) => processId !== rootProcessId);
|
|
20733
|
+
for (let attempt = 0; attempt < PROCESS_EXIT_CHECK_ATTEMPTS; attempt += 1) {
|
|
20734
|
+
if (processIds.every((processId) => !processExists(processId))) {
|
|
20735
|
+
return true;
|
|
20736
|
+
}
|
|
20737
|
+
await readProcessTable();
|
|
20738
|
+
}
|
|
20739
|
+
return processIds.every((processId) => !processExists(processId));
|
|
20740
|
+
}
|
|
20741
|
+
function terminateProcess(child) {
|
|
20742
|
+
if (child.exitCode !== null || child.signalCode !== null || child.pid === void 0) {
|
|
20743
|
+
return;
|
|
20744
|
+
}
|
|
20745
|
+
if (process.platform !== "win32") {
|
|
20746
|
+
try {
|
|
20747
|
+
process.kill(-child.pid, "SIGTERM");
|
|
20748
|
+
return;
|
|
20749
|
+
} catch {
|
|
20750
|
+
}
|
|
20751
|
+
}
|
|
20752
|
+
child.kill("SIGTERM");
|
|
20753
|
+
}
|
|
20754
|
+
function forceTerminateProcess(child) {
|
|
20755
|
+
if (child.exitCode !== null || child.signalCode !== null || child.pid === void 0) {
|
|
20756
|
+
return;
|
|
20757
|
+
}
|
|
20758
|
+
if (process.platform !== "win32") {
|
|
20759
|
+
try {
|
|
20760
|
+
process.kill(-child.pid, "SIGKILL");
|
|
20761
|
+
return;
|
|
20762
|
+
} catch {
|
|
20763
|
+
}
|
|
20764
|
+
}
|
|
20765
|
+
child.kill("SIGKILL");
|
|
20766
|
+
}
|
|
20767
|
+
async function stopProcessTree(child, capturedProcesses = []) {
|
|
20768
|
+
const childProcessId = child.pid;
|
|
20769
|
+
if (childProcessId === void 0) {
|
|
20770
|
+
return;
|
|
20771
|
+
}
|
|
20772
|
+
const childExited = child.exitCode !== null || child.signalCode !== null;
|
|
20773
|
+
if (childExited && capturedProcesses.length === 0) {
|
|
20774
|
+
return;
|
|
20775
|
+
}
|
|
20776
|
+
const processes = mergeProcessTrees(
|
|
20777
|
+
capturedProcesses,
|
|
20778
|
+
childExited ? [] : await captureProcessTree(child)
|
|
20779
|
+
);
|
|
20780
|
+
signalDetachedProcessGroups(processes, childProcessId, GRACEFUL_STOP_SIGNAL);
|
|
20781
|
+
terminateProcess(child);
|
|
20782
|
+
const [exitedAfterTermination, descendantsExitedAfterTermination] = await Promise.all([
|
|
20783
|
+
waitForProcessExit(child, PROCESS_GRACEFUL_STOP_TIMEOUT_MILLISECONDS),
|
|
20784
|
+
waitForCapturedProcessesExit(processes, childProcessId)
|
|
20785
|
+
]);
|
|
20786
|
+
if (exitedAfterTermination && descendantsExitedAfterTermination) {
|
|
20787
|
+
return;
|
|
20788
|
+
}
|
|
20789
|
+
signalDetachedProcessGroups(processes, childProcessId, FORCED_STOP_SIGNAL);
|
|
20790
|
+
forceTerminateProcess(child);
|
|
20791
|
+
const [exitedAfterForce, descendantsExitedAfterForce] = await Promise.all([
|
|
20792
|
+
waitForProcessExit(child, PROCESS_FORCED_STOP_TIMEOUT_MILLISECONDS),
|
|
20793
|
+
waitForCapturedProcessesExit(processes, childProcessId)
|
|
20794
|
+
]);
|
|
20795
|
+
if (!exitedAfterForce || !descendantsExitedAfterForce) {
|
|
20796
|
+
throw new ProcessExecutionError(
|
|
20797
|
+
"process_termination_timeout",
|
|
20798
|
+
"The executable process could not be stopped"
|
|
20799
|
+
);
|
|
20800
|
+
}
|
|
20801
|
+
}
|
|
20802
|
+
function waitForProcessExit(child, timeoutMilliseconds) {
|
|
20803
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
20804
|
+
return Promise.resolve(true);
|
|
20805
|
+
}
|
|
20806
|
+
return new Promise((resolveExit) => {
|
|
20807
|
+
let settled = false;
|
|
20808
|
+
const finish = (exited) => {
|
|
20809
|
+
if (settled) {
|
|
20810
|
+
return;
|
|
20811
|
+
}
|
|
20812
|
+
settled = true;
|
|
20813
|
+
clearTimeout(timeout);
|
|
20814
|
+
child.removeListener("exit", handleExit);
|
|
20815
|
+
resolveExit(exited);
|
|
20816
|
+
};
|
|
20817
|
+
const handleExit = () => finish(true);
|
|
20818
|
+
const timeout = setTimeout(() => finish(false), timeoutMilliseconds);
|
|
20819
|
+
child.once("exit", handleExit);
|
|
20820
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
20821
|
+
finish(true);
|
|
20822
|
+
}
|
|
20823
|
+
});
|
|
20824
|
+
}
|
|
20825
|
+
async function runBoundedProcess(options) {
|
|
20826
|
+
options.signal.throwIfAborted();
|
|
20827
|
+
const outputLimitBytes = options.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES;
|
|
20828
|
+
const child = spawn(options.command, [...options.arguments], {
|
|
20829
|
+
cwd: options.cwd,
|
|
20830
|
+
detached: process.platform !== "win32",
|
|
20831
|
+
env: { ...options.environment },
|
|
20832
|
+
shell: false,
|
|
20833
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
20834
|
+
});
|
|
20835
|
+
const stdout = [];
|
|
20836
|
+
const stderr = [];
|
|
20837
|
+
let stdoutBytes = 0;
|
|
20838
|
+
let stderrBytes = 0;
|
|
20839
|
+
let interruptionFailure = null;
|
|
20840
|
+
let interruptionCleanupFailure = null;
|
|
20841
|
+
let stopping = false;
|
|
20842
|
+
const interrupted = Promise.withResolvers();
|
|
20843
|
+
const interrupt = (failure) => {
|
|
20844
|
+
if (stopping) {
|
|
20845
|
+
return;
|
|
20846
|
+
}
|
|
20847
|
+
stopping = true;
|
|
20848
|
+
interruptionFailure = failure;
|
|
20849
|
+
const stop = async () => {
|
|
20850
|
+
try {
|
|
20851
|
+
await stopProcessTree(child);
|
|
20852
|
+
} catch (error61) {
|
|
20853
|
+
interruptionCleanupFailure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
|
|
20854
|
+
"process_termination_failed",
|
|
20855
|
+
"The executable process could not be stopped",
|
|
20856
|
+
{ cause: error61 }
|
|
20857
|
+
);
|
|
20858
|
+
} finally {
|
|
20859
|
+
interrupted.resolve();
|
|
20860
|
+
}
|
|
20861
|
+
};
|
|
20862
|
+
void stop();
|
|
20863
|
+
};
|
|
20864
|
+
const collectStdout = (chunk) => {
|
|
20865
|
+
if (interruptionFailure !== null) {
|
|
20866
|
+
return;
|
|
20867
|
+
}
|
|
20868
|
+
try {
|
|
20869
|
+
stdoutBytes = appendBounded(stdout, chunk, stdoutBytes, outputLimitBytes);
|
|
20870
|
+
} catch (error61) {
|
|
20871
|
+
const failure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
|
|
20872
|
+
"process_output_limit",
|
|
20873
|
+
"The executable produced too much output",
|
|
20874
|
+
{ cause: error61 }
|
|
20875
|
+
);
|
|
20876
|
+
interrupt(failure);
|
|
20877
|
+
}
|
|
20878
|
+
};
|
|
20879
|
+
const collectStderr = (chunk) => {
|
|
20880
|
+
if (interruptionFailure !== null) {
|
|
20881
|
+
return;
|
|
20882
|
+
}
|
|
20883
|
+
try {
|
|
20884
|
+
stderrBytes = appendBounded(stderr, chunk, stderrBytes, outputLimitBytes);
|
|
20885
|
+
} catch (error61) {
|
|
20886
|
+
const failure = error61 instanceof ProcessExecutionError ? error61 : new ProcessExecutionError(
|
|
20887
|
+
"process_output_limit",
|
|
20888
|
+
"The executable produced too much output",
|
|
20889
|
+
{ cause: error61 }
|
|
20890
|
+
);
|
|
20891
|
+
interrupt(failure);
|
|
20892
|
+
}
|
|
20893
|
+
};
|
|
20894
|
+
child.stdout?.on("data", collectStdout);
|
|
20895
|
+
child.stderr?.on("data", collectStderr);
|
|
20896
|
+
const timeout = setTimeout(() => {
|
|
20897
|
+
interrupt(
|
|
20898
|
+
new ProcessExecutionError(
|
|
20899
|
+
"process_timeout",
|
|
20900
|
+
"The executable probe timed out"
|
|
20901
|
+
)
|
|
20902
|
+
);
|
|
20903
|
+
}, options.timeoutMilliseconds);
|
|
20904
|
+
const abort = () => interrupt(
|
|
20905
|
+
new ProcessExecutionError(
|
|
20906
|
+
"process_cancelled",
|
|
20907
|
+
"The executable probe was cancelled",
|
|
20908
|
+
{ cause: options.signal.reason }
|
|
20909
|
+
)
|
|
20910
|
+
);
|
|
20911
|
+
options.signal.addEventListener("abort", abort, { once: true });
|
|
20912
|
+
if (options.signal.aborted) {
|
|
20913
|
+
abort();
|
|
21090
20914
|
}
|
|
21091
|
-
|
|
21092
|
-
|
|
21093
|
-
|
|
21094
|
-
|
|
20915
|
+
try {
|
|
20916
|
+
const exit = new Promise((resolveExit, rejectExit) => {
|
|
20917
|
+
child.once("error", rejectExit);
|
|
20918
|
+
child.once("exit", (code) => resolveExit(code ?? 1));
|
|
21095
20919
|
});
|
|
20920
|
+
const exitCode = await Promise.race([
|
|
20921
|
+
exit,
|
|
20922
|
+
interrupted.promise.then(() => -1)
|
|
20923
|
+
]);
|
|
20924
|
+
if (interruptionCleanupFailure !== null) {
|
|
20925
|
+
throw interruptionCleanupFailure;
|
|
20926
|
+
}
|
|
20927
|
+
if (interruptionFailure !== null) {
|
|
20928
|
+
throw interruptionFailure;
|
|
20929
|
+
}
|
|
20930
|
+
if (exitCode !== 0) {
|
|
20931
|
+
throw new ProcessExecutionError(
|
|
20932
|
+
"process_exit_failed",
|
|
20933
|
+
"The executable probe failed"
|
|
20934
|
+
);
|
|
20935
|
+
}
|
|
20936
|
+
return new ProcessResult(
|
|
20937
|
+
Buffer.concat(stdout).toString("utf8"),
|
|
20938
|
+
Buffer.concat(stderr).toString("utf8")
|
|
20939
|
+
);
|
|
20940
|
+
} catch (error61) {
|
|
20941
|
+
if (error61 instanceof ProcessExecutionError) {
|
|
20942
|
+
throw error61;
|
|
20943
|
+
}
|
|
20944
|
+
throw new ProcessExecutionError(
|
|
20945
|
+
"process_spawn_failed",
|
|
20946
|
+
"The executable could not be started",
|
|
20947
|
+
{ cause: error61 }
|
|
20948
|
+
);
|
|
20949
|
+
} finally {
|
|
20950
|
+
clearTimeout(timeout);
|
|
20951
|
+
options.signal.removeEventListener("abort", abort);
|
|
20952
|
+
child.stdout?.removeListener("data", collectStdout);
|
|
20953
|
+
child.stderr?.removeListener("data", collectStderr);
|
|
20954
|
+
if (stopping) {
|
|
20955
|
+
await interrupted.promise;
|
|
20956
|
+
} else {
|
|
20957
|
+
await stopProcessTree(child);
|
|
20958
|
+
}
|
|
21096
20959
|
}
|
|
21097
|
-
return decoded.data;
|
|
21098
20960
|
}
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
|
|
21102
|
-
|
|
21103
|
-
|
|
21104
|
-
|
|
20961
|
+
|
|
20962
|
+
// src/agent-catalog/local-agent.ts
|
|
20963
|
+
function readTrustedIdentity(path2) {
|
|
20964
|
+
const resolvedPath = realpathSync(path2);
|
|
20965
|
+
const metadata = statSync(resolvedPath);
|
|
20966
|
+
return new ExecutableIdentity(
|
|
20967
|
+
resolvedPath,
|
|
20968
|
+
resolvedPath,
|
|
20969
|
+
metadata.dev,
|
|
20970
|
+
metadata.ino,
|
|
20971
|
+
metadata.size,
|
|
20972
|
+
metadata.ctimeMs
|
|
20973
|
+
);
|
|
20974
|
+
}
|
|
20975
|
+
var LocalAgentDefinition = class {
|
|
20976
|
+
adapterIdentity;
|
|
20977
|
+
adapterPath;
|
|
20978
|
+
adapterVersion;
|
|
20979
|
+
agentIdentity;
|
|
20980
|
+
agentVersion;
|
|
20981
|
+
arguments;
|
|
20982
|
+
command;
|
|
20983
|
+
environment;
|
|
20984
|
+
id;
|
|
20985
|
+
label;
|
|
20986
|
+
promptTimeoutMilliseconds;
|
|
20987
|
+
constructor(id, label, command, arguments_, environment = {}, adapterVersion = "", agentVersion = "", adapterIdentity = readTrustedIdentity(command), agentIdentity = adapterIdentity, promptTimeoutMilliseconds = 30 * 60 * 1e3) {
|
|
20988
|
+
if (!Number.isSafeInteger(promptTimeoutMilliseconds) || promptTimeoutMilliseconds < 1) {
|
|
20989
|
+
throw new Error("The local Agent prompt deadline is invalid");
|
|
20990
|
+
}
|
|
20991
|
+
this.adapterIdentity = adapterIdentity;
|
|
20992
|
+
this.adapterPath = adapterIdentity.realPath;
|
|
20993
|
+
this.adapterVersion = adapterVersion;
|
|
20994
|
+
this.agentIdentity = agentIdentity;
|
|
20995
|
+
this.agentVersion = agentVersion;
|
|
20996
|
+
this.id = id;
|
|
20997
|
+
this.label = label;
|
|
20998
|
+
this.promptTimeoutMilliseconds = promptTimeoutMilliseconds;
|
|
20999
|
+
this.command = adapterIdentity.realPath;
|
|
21000
|
+
this.arguments = Object.freeze([...arguments_]);
|
|
21001
|
+
this.environment = Object.freeze({ ...environment });
|
|
21105
21002
|
}
|
|
21106
|
-
return JSON.stringify(decoded.data);
|
|
21107
21003
|
};
|
|
21108
|
-
var
|
|
21109
|
-
|
|
21004
|
+
var InstallableAgentDefinition = class {
|
|
21005
|
+
id;
|
|
21006
|
+
label;
|
|
21007
|
+
requiresLocalApproval;
|
|
21008
|
+
constructor(id, label, requiresLocalApproval = true) {
|
|
21009
|
+
this.id = id;
|
|
21010
|
+
this.label = label;
|
|
21011
|
+
this.requiresLocalApproval = requiresLocalApproval;
|
|
21012
|
+
}
|
|
21013
|
+
};
|
|
21014
|
+
var LocalAgentCatalogSnapshot = class {
|
|
21015
|
+
agents;
|
|
21016
|
+
installableAgents;
|
|
21017
|
+
constructor(agents, installableAgents) {
|
|
21018
|
+
this.agents = Object.freeze([...agents]);
|
|
21019
|
+
this.installableAgents = Object.freeze([...installableAgents]);
|
|
21020
|
+
}
|
|
21021
|
+
};
|
|
21022
|
+
var LocalAgentCatalogError = class extends Error {
|
|
21023
|
+
code = "agent_not_ready";
|
|
21024
|
+
constructor() {
|
|
21025
|
+
super("The requested local Agent is not ready");
|
|
21026
|
+
this.name = "LocalAgentCatalogError";
|
|
21027
|
+
}
|
|
21028
|
+
};
|
|
21029
|
+
var LocalAgentCatalog = class {
|
|
21030
|
+
#snapshot;
|
|
21031
|
+
constructor(agents, installableAgents) {
|
|
21032
|
+
this.#snapshot = this.validate(agents, installableAgents);
|
|
21033
|
+
}
|
|
21034
|
+
find = (agentId) => {
|
|
21035
|
+
for (const agent of this.#snapshot.agents) {
|
|
21036
|
+
if (agent.id === agentId) {
|
|
21037
|
+
return agent;
|
|
21038
|
+
}
|
|
21039
|
+
}
|
|
21040
|
+
throw new LocalAgentCatalogError();
|
|
21041
|
+
};
|
|
21042
|
+
read = () => this.#snapshot;
|
|
21043
|
+
transition = (agents, installableAgents) => {
|
|
21044
|
+
const next = this.validate(agents, installableAgents);
|
|
21045
|
+
this.#snapshot = next;
|
|
21046
|
+
return next;
|
|
21047
|
+
};
|
|
21048
|
+
validate = (agents, installableAgents) => {
|
|
21049
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
21050
|
+
for (const agent of agents) {
|
|
21051
|
+
if (identifiers.has(agent.id)) {
|
|
21052
|
+
throw new Error("The local Agent catalog contains duplicate entries");
|
|
21053
|
+
}
|
|
21054
|
+
identifiers.add(agent.id);
|
|
21055
|
+
}
|
|
21056
|
+
for (const agent of installableAgents) {
|
|
21057
|
+
if (identifiers.has(agent.id)) {
|
|
21058
|
+
throw new Error("A local Agent cannot be ready and installable");
|
|
21059
|
+
}
|
|
21060
|
+
identifiers.add(agent.id);
|
|
21061
|
+
}
|
|
21062
|
+
return new LocalAgentCatalogSnapshot(agents, installableAgents);
|
|
21063
|
+
};
|
|
21064
|
+
};
|
|
21065
|
+
|
|
21066
|
+
// src/storage/state-directory.ts
|
|
21067
|
+
import { homedir } from "node:os";
|
|
21068
|
+
import { join as join2, resolve as resolve2 } from "node:path";
|
|
21069
|
+
function readStateDirectory(environment = process.env) {
|
|
21070
|
+
const configured = environment["MOBIUS_STATE_DIRECTORY"]?.trim();
|
|
21071
|
+
if (configured !== void 0 && configured.length > 0) {
|
|
21072
|
+
return resolve2(configured);
|
|
21073
|
+
}
|
|
21074
|
+
return join2(homedir(), ".mobius");
|
|
21075
|
+
}
|
|
21076
|
+
|
|
21077
|
+
// src/config.ts
|
|
21078
|
+
function loadDefaultWorkspace() {
|
|
21079
|
+
const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = process.env;
|
|
21080
|
+
return configuredWorkspace ?? join3(readStateDirectory(), "workspace");
|
|
21081
|
+
}
|
|
21082
|
+
function createDaemonConfig(connection, agents = [], installableAgents = []) {
|
|
21083
|
+
return {
|
|
21084
|
+
agents,
|
|
21085
|
+
defaultWorkspace: connection.defaultWorkspace,
|
|
21086
|
+
auditDatabasePath: connection.auditDatabasePath,
|
|
21087
|
+
bindingId: "workspace",
|
|
21088
|
+
catalog: new LocalAgentCatalog(agents, installableAgents),
|
|
21089
|
+
channelId: connection.channelId,
|
|
21090
|
+
installableAgents,
|
|
21091
|
+
relayUrl: connection.relayUrl
|
|
21092
|
+
};
|
|
21093
|
+
}
|
|
21094
|
+
function loadDaemonConfig() {
|
|
21095
|
+
const relayUrl = new URL(
|
|
21096
|
+
// biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
|
|
21097
|
+
process.env["MOBIUS_RELAY_URL"] ?? `ws://127.0.0.1:8787${mobiusApiPath("/channels/local/daemon")}`
|
|
21098
|
+
);
|
|
21099
|
+
relayUrl.searchParams.set(
|
|
21100
|
+
"token",
|
|
21101
|
+
// biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
|
|
21102
|
+
process.env["MOBIUS_RELAY_TOKEN"] ?? "local-development-token"
|
|
21103
|
+
);
|
|
21104
|
+
if (relayUrl.protocol !== "ws:" && relayUrl.protocol !== "wss:") {
|
|
21105
|
+
throw new Error("MOBIUS_RELAY_URL must use ws or wss");
|
|
21106
|
+
}
|
|
21107
|
+
return createDaemonConfig(
|
|
21108
|
+
{
|
|
21109
|
+
defaultWorkspace: loadDefaultWorkspace(),
|
|
21110
|
+
auditDatabasePath: (
|
|
21111
|
+
// biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
|
|
21112
|
+
process.env["MOBIUS_AUDIT_DATABASE"] ?? fileURLToPath(
|
|
21113
|
+
new URL("../../../.mobius/daemon.sqlite", import.meta.url)
|
|
21114
|
+
)
|
|
21115
|
+
),
|
|
21116
|
+
// biome-ignore lint/complexity/useLiteralKeys: Environment variables use an index signature.
|
|
21117
|
+
channelId: process.env["MOBIUS_CHANNEL_ID"] ?? "local",
|
|
21118
|
+
relayUrl
|
|
21119
|
+
},
|
|
21120
|
+
[],
|
|
21121
|
+
[]
|
|
21122
|
+
);
|
|
21123
|
+
}
|
|
21124
|
+
|
|
21125
|
+
// src/acp/session-manager.ts
|
|
21126
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
21127
|
+
import {
|
|
21128
|
+
methods as methods2,
|
|
21129
|
+
ndJsonStream,
|
|
21130
|
+
PROTOCOL_VERSION,
|
|
21131
|
+
RequestError
|
|
21132
|
+
} from "@agentclientprotocol/sdk";
|
|
21110
21133
|
|
|
21111
21134
|
// src/acp/activity.ts
|
|
21112
21135
|
var TRUNCATION_MARKER = "\n\u2026 output truncated by Mobius";
|
|
@@ -26370,7 +26393,7 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
|
|
|
26370
26393
|
id: agent.id,
|
|
26371
26394
|
label: agent.label
|
|
26372
26395
|
}));
|
|
26373
|
-
const endpoint =
|
|
26396
|
+
const endpoint = mobiusApiUrl(options.relayOrigin, "/pairings/claim");
|
|
26374
26397
|
let response;
|
|
26375
26398
|
try {
|
|
26376
26399
|
response = await fetch2(endpoint, {
|
|
@@ -26506,9 +26529,9 @@ function assertInventoryUsable(inventory) {
|
|
|
26506
26529
|
}
|
|
26507
26530
|
function buildPairedDaemon(connection, connectionPath, inventory) {
|
|
26508
26531
|
assertInventoryUsable(inventory);
|
|
26509
|
-
const relayUrl =
|
|
26510
|
-
|
|
26511
|
-
connection.
|
|
26532
|
+
const relayUrl = mobiusApiUrl(
|
|
26533
|
+
connection.relayOrigin,
|
|
26534
|
+
`/channels/${encodeURIComponent(connection.channelId)}/daemon`
|
|
26512
26535
|
);
|
|
26513
26536
|
relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
26514
26537
|
relayUrl.searchParams.set("token", connection.daemonToken);
|
|
@@ -29551,6 +29574,28 @@ function waitForRelayRetry(signal, timer) {
|
|
|
29551
29574
|
signal.addEventListener("abort", finish, { once: true });
|
|
29552
29575
|
});
|
|
29553
29576
|
}
|
|
29577
|
+
async function publishAgentCatalogUntilAvailable(config2, signal, timer) {
|
|
29578
|
+
try {
|
|
29579
|
+
await publishAgentCatalog(config2, signal);
|
|
29580
|
+
return;
|
|
29581
|
+
} catch {
|
|
29582
|
+
if (signal.aborted) {
|
|
29583
|
+
return;
|
|
29584
|
+
}
|
|
29585
|
+
console.warn("Mobius daemon is waiting to publish its Agent catalog");
|
|
29586
|
+
}
|
|
29587
|
+
while (!signal.aborted) {
|
|
29588
|
+
await waitForRelayRetry(signal, timer);
|
|
29589
|
+
try {
|
|
29590
|
+
await publishAgentCatalog(config2, signal);
|
|
29591
|
+
return;
|
|
29592
|
+
} catch {
|
|
29593
|
+
if (signal.aborted) {
|
|
29594
|
+
return;
|
|
29595
|
+
}
|
|
29596
|
+
}
|
|
29597
|
+
}
|
|
29598
|
+
}
|
|
29554
29599
|
async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, onConnected, signal, timer) {
|
|
29555
29600
|
const relayUrl = new URL(config2.relayUrl);
|
|
29556
29601
|
relayUrl.searchParams.set("liveness", "1");
|
|
@@ -30008,10 +30053,14 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
|
|
|
30008
30053
|
`
|
|
30009
30054
|
);
|
|
30010
30055
|
};
|
|
30056
|
+
const catalogPublication = publishAgentCatalogUntilAvailable(
|
|
30057
|
+
config2,
|
|
30058
|
+
signal,
|
|
30059
|
+
timer
|
|
30060
|
+
);
|
|
30011
30061
|
try {
|
|
30012
30062
|
while (!signal.aborted) {
|
|
30013
30063
|
try {
|
|
30014
|
-
await publishAgentCatalog(config2, signal);
|
|
30015
30064
|
await runRelayConnection(
|
|
30016
30065
|
config2,
|
|
30017
30066
|
directories,
|
|
@@ -30039,15 +30088,19 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
|
|
|
30039
30088
|
}
|
|
30040
30089
|
} finally {
|
|
30041
30090
|
try {
|
|
30042
|
-
await
|
|
30091
|
+
await catalogPublication;
|
|
30043
30092
|
} finally {
|
|
30044
30093
|
try {
|
|
30045
|
-
await
|
|
30094
|
+
await installations.close();
|
|
30046
30095
|
} finally {
|
|
30047
30096
|
try {
|
|
30048
|
-
|
|
30097
|
+
await sessions.close();
|
|
30049
30098
|
} finally {
|
|
30050
|
-
|
|
30099
|
+
try {
|
|
30100
|
+
sessionStore.close();
|
|
30101
|
+
} finally {
|
|
30102
|
+
outbox.close();
|
|
30103
|
+
}
|
|
30051
30104
|
}
|
|
30052
30105
|
}
|
|
30053
30106
|
}
|