@moxt-ai/mobius 0.0.13 → 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/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
- const decoded = schema.safeParse(value);
21092
- if (!decoded.success) {
21093
- throw new ProtocolMessageError("Message does not match its schema", {
21094
- cause: decoded.error
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
- var encode4 = (message, schema) => {
21100
- const decoded = schema.safeParse(message);
21101
- if (!decoded.success) {
21102
- throw new ProtocolMessageError("Message does not match its schema", {
21103
- cause: decoded.error
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 encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
21109
- var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
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 = new URL("/mobius/api/pairings/claim", options.relayOrigin);
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 = new URL(
26510
- `/mobius/api/channels/${encodeURIComponent(connection.channelId)}/daemon`,
26511
- connection.relayOrigin
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);