@moxt-ai/mobius 0.0.24 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/agent-catalog/publish-agent-catalog.d.ts.map +1 -1
  2. package/dist/agent-catalog/publish-agent-catalog.js +12 -7
  3. package/dist/agent-catalog/publish-agent-catalog.js.map +1 -1
  4. package/dist/cli.js +685 -290
  5. package/dist/cli.js.map +4 -4
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +3 -3
  9. package/dist/config.js.map +1 -1
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +746 -254
  13. package/dist/index.js.map +4 -4
  14. package/dist/network/environment-proxy.d.ts +1 -0
  15. package/dist/network/environment-proxy.d.ts.map +1 -1
  16. package/dist/network/environment-proxy.js +47 -0
  17. package/dist/network/environment-proxy.js.map +1 -1
  18. package/dist/pairing/pair-local-machine.d.ts +8 -0
  19. package/dist/pairing/pair-local-machine.d.ts.map +1 -1
  20. package/dist/pairing/pair-local-machine.js +64 -37
  21. package/dist/pairing/pair-local-machine.js.map +1 -1
  22. package/dist/prompt-artifact-resolver.d.ts.map +1 -1
  23. package/dist/prompt-artifact-resolver.js +11 -3
  24. package/dist/prompt-artifact-resolver.js.map +1 -1
  25. package/dist/relay-connection/connect.d.ts.map +1 -1
  26. package/dist/relay-connection/connect.js +25 -9
  27. package/dist/relay-connection/connect.js.map +1 -1
  28. package/dist/relay-connection/diagnostics.d.ts +11 -0
  29. package/dist/relay-connection/diagnostics.d.ts.map +1 -0
  30. package/dist/relay-connection/diagnostics.js +122 -0
  31. package/dist/relay-connection/diagnostics.js.map +1 -0
  32. package/dist/relay-connection/liveness.d.ts +1 -1
  33. package/dist/relay-connection/liveness.d.ts.map +1 -1
  34. package/dist/relay-connection/liveness.js +2 -1
  35. package/dist/relay-connection/liveness.js.map +1 -1
  36. package/dist/relay-connection/session-event-outbox.d.ts +2 -1
  37. package/dist/relay-connection/session-event-outbox.d.ts.map +1 -1
  38. package/dist/relay-connection/session-event-outbox.js +48 -8
  39. package/dist/relay-connection/session-event-outbox.js.map +1 -1
  40. package/dist/run-daemon.d.ts.map +1 -1
  41. package/dist/run-daemon.js +193 -149
  42. package/dist/run-daemon.js.map +1 -1
  43. package/dist/runtime.d.ts +23 -0
  44. package/dist/runtime.d.ts.map +1 -0
  45. package/dist/runtime.js +31339 -0
  46. package/dist/runtime.js.map +7 -0
  47. package/package.json +6 -2
package/dist/index.js CHANGED
@@ -20497,6 +20497,7 @@ var encode4 = (message, schema) => {
20497
20497
  }
20498
20498
  return JSON.stringify(decoded.data);
20499
20499
  };
20500
+ var decodeSessionEventEnvelope = (encoded) => decode5(encoded, eventEnvelopeSchema);
20500
20501
  var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
20501
20502
  var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
20502
20503
  var decodeSessionEventRejection = (encoded) => decode5(encoded, eventRejectionSchema);
@@ -21107,9 +21108,9 @@ function readStateDirectory(environment = process.env) {
21107
21108
  }
21108
21109
 
21109
21110
  // src/config.ts
21110
- function loadDefaultWorkspace() {
21111
- const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = process.env;
21112
- return configuredWorkspace ?? join3(readStateDirectory(), "workspace");
21111
+ function loadDefaultWorkspace(environment = process.env) {
21112
+ const { MOBIUS_DEFAULT_WORKSPACE: configuredWorkspace } = environment;
21113
+ return configuredWorkspace ?? join3(readStateDirectory(environment), "workspace");
21113
21114
  }
21114
21115
  function createDaemonConfig(connection, agents = [], installableAgents = []) {
21115
21116
  return {
@@ -22462,7 +22463,7 @@ function createWritableAcpStream(output2) {
22462
22463
  if (outputFailure !== void 0) {
22463
22464
  return Promise.reject(outputFailure);
22464
22465
  }
22465
- return new Promise((resolve5, reject) => {
22466
+ return new Promise((resolve6, reject) => {
22466
22467
  pendingWriteFailures.add(reject);
22467
22468
  output2.write(chunk, (error61) => {
22468
22469
  pendingWriteFailures.delete(reject);
@@ -22475,7 +22476,7 @@ function createWritableAcpStream(output2) {
22475
22476
  reject(outputFailure);
22476
22477
  return;
22477
22478
  }
22478
- resolve5();
22479
+ resolve6();
22479
22480
  });
22480
22481
  });
22481
22482
  }
@@ -22882,9 +22883,9 @@ var AcpProcessSession = class _AcpProcessSession {
22882
22883
  stderr.on("data", (chunk) => {
22883
22884
  process.stderr.write(`[${agent.id}-acp] ${chunk}`);
22884
22885
  });
22885
- this.#processExit = new Promise((resolve5) => {
22886
- this.#child.once("error", resolve5);
22887
- this.#child.once("exit", resolve5);
22886
+ this.#processExit = new Promise((resolve6) => {
22887
+ this.#child.once("error", resolve6);
22888
+ this.#child.once("exit", resolve6);
22888
22889
  });
22889
22890
  const stream = ndJsonStream(createWritableAcpStream(stdin), readableWebStream(stdout));
22890
22891
  this.#connection = createAcpClient(this).connect(stream);
@@ -25123,7 +25124,7 @@ function decodeResponse(encoded) {
25123
25124
  }
25124
25125
  async function requestLocalBotService(socketPath, payload, signal) {
25125
25126
  signal.throwIfAborted();
25126
- return await new Promise((resolve5, reject) => {
25127
+ return await new Promise((resolve6, reject) => {
25127
25128
  const socket = createConnection(socketPath);
25128
25129
  const chunks = [];
25129
25130
  let length = 0;
@@ -25172,7 +25173,7 @@ async function requestLocalBotService(socketPath, payload, signal) {
25172
25173
  finished = true;
25173
25174
  signal.removeEventListener("abort", abort);
25174
25175
  try {
25175
- resolve5(decodeResponse(Buffer.concat(chunks).toString("utf8")));
25176
+ resolve6(decodeResponse(Buffer.concat(chunks).toString("utf8")));
25176
25177
  } catch (error61) {
25177
25178
  reject(error61);
25178
25179
  } finally {
@@ -25326,12 +25327,12 @@ var BotOutboundIpcServer = class {
25326
25327
  }
25327
25328
  });
25328
25329
  }
25329
- await new Promise((resolve5, reject) => {
25330
+ await new Promise((resolve6, reject) => {
25330
25331
  const fail = (error61) => reject(error61);
25331
25332
  this.#server.once("error", fail);
25332
25333
  this.#server.listen(this.#socketPath, () => {
25333
25334
  this.#server.removeListener("error", fail);
25334
- resolve5();
25335
+ resolve6();
25335
25336
  });
25336
25337
  });
25337
25338
  if (this.#usesFilesystemSocket) {
@@ -25344,10 +25345,10 @@ var BotOutboundIpcServer = class {
25344
25345
  };
25345
25346
  close = async () => {
25346
25347
  if (this.#server.listening) {
25347
- await new Promise((resolve5, reject) => {
25348
+ await new Promise((resolve6, reject) => {
25348
25349
  this.#server.close((error61) => {
25349
25350
  if (error61 === void 0) {
25350
- resolve5();
25351
+ resolve6();
25351
25352
  return;
25352
25353
  }
25353
25354
  reject(error61);
@@ -26286,7 +26287,53 @@ import { fetch as fetch2 } from "undici";
26286
26287
 
26287
26288
  // src/network/environment-proxy.ts
26288
26289
  import { EnvHttpProxyAgent } from "undici";
26290
+ var configuredHttpProxy = process.env["http_proxy"] ?? process.env["HTTP_PROXY"] ?? "";
26291
+ var configuredHttpsProxy = process.env["https_proxy"] ?? process.env["HTTPS_PROXY"] ?? configuredHttpProxy;
26289
26292
  var environmentProxyDispatcher = new EnvHttpProxyAgent();
26293
+ function bypassesProxy(target) {
26294
+ const noProxy = process.env["no_proxy"] ?? process.env["NO_PROXY"] ?? "";
26295
+ if (noProxy === "*") {
26296
+ return true;
26297
+ }
26298
+ const hostname4 = target.hostname.replace(/^\[|\]$/gu, "").toLowerCase();
26299
+ const defaultPort = target.protocol === "wss:" || target.protocol === "https:" ? 443 : 80;
26300
+ const port = Number.parseInt(target.port, 10) || defaultPort;
26301
+ for (const rawEntry of noProxy.split(/[,\s]/u)) {
26302
+ if (rawEntry.length === 0) {
26303
+ continue;
26304
+ }
26305
+ const parsed = rawEntry.match(/^(.+):(\d+)$/u);
26306
+ const entryHostname = (parsed?.[1] ?? rawEntry).replace(/^\*?\./u, "").toLowerCase();
26307
+ const entryPort = parsed?.[2] === void 0 ? 0 : Number.parseInt(parsed[2], 10);
26308
+ if (entryPort !== 0 && entryPort !== port) {
26309
+ continue;
26310
+ }
26311
+ if (hostname4 === entryHostname || hostname4.endsWith(`.${entryHostname}`)) {
26312
+ return true;
26313
+ }
26314
+ }
26315
+ return false;
26316
+ }
26317
+ function sanitizedProxyOrigin(proxyUrl) {
26318
+ try {
26319
+ return new URL(proxyUrl).origin;
26320
+ } catch {
26321
+ return "configured-invalid-url";
26322
+ }
26323
+ }
26324
+ function describeEnvironmentProxyRoute(target) {
26325
+ if (bypassesProxy(target)) {
26326
+ return { proxyRoute: "direct" };
26327
+ }
26328
+ const proxyUrl = target.protocol === "wss:" || target.protocol === "https:" ? configuredHttpsProxy : configuredHttpProxy;
26329
+ if (proxyUrl.length === 0) {
26330
+ return { proxyRoute: "direct" };
26331
+ }
26332
+ return {
26333
+ proxyOrigin: sanitizedProxyOrigin(proxyUrl),
26334
+ proxyRoute: "proxy"
26335
+ };
26336
+ }
26290
26337
 
26291
26338
  // src/agent-installation/managed-agent-archive.ts
26292
26339
  var ARCHIVE_DOWNLOAD_LIMIT_BYTES = 128 * 1024 * 1024;
@@ -27270,7 +27317,9 @@ var IDENTIFIER_PATTERN6 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
27270
27317
  var TOKEN_PATTERN2 = /^[a-zA-Z0-9_-]{32,256}$/;
27271
27318
  var MAX_CONNECTION_FILE_LENGTH = 16 * 1024;
27272
27319
  var CLAIM_RESPONSE_KEYS = ["channelId", "daemonToken", "runtimeId"];
27273
- var SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "daemonToken", "relayOrigin"];
27320
+ var REDEEM_RESPONSE_KEYS = ["channelId", "runtimeCredential", "runtimeId"];
27321
+ var LEGACY_SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "daemonToken", "relayOrigin"];
27322
+ var SAVED_CONNECTION_KEYS = ["defaultWorkspace", "channelId", "relayOrigin", "runtimeCredential"];
27274
27323
  var PairingClientError = class extends Error {
27275
27324
  code;
27276
27325
  constructor(code, message, options = {}) {
@@ -27307,7 +27356,7 @@ function readSingleOption(arguments_, option2) {
27307
27356
  function assertKnownArguments(arguments_) {
27308
27357
  for (let index = 0; index < arguments_.length; index += 2) {
27309
27358
  const option2 = arguments_[index];
27310
- if (option2 !== "--url" && option2 !== "--code" && option2 !== "--workspace") {
27359
+ if (option2 !== "--url" && option2 !== "--code" && option2 !== "--pairing" && option2 !== "--workspace") {
27311
27360
  throw new PairingClientError("invalid_arguments", `Unknown pairing option: ${option2 ?? "missing option"}`);
27312
27361
  }
27313
27362
  }
@@ -27347,10 +27396,17 @@ function parsePairCommand(arguments_) {
27347
27396
  assertKnownArguments(arguments_);
27348
27397
  return {
27349
27398
  code: parsePairingCode(readSingleOption(arguments_, "--code")),
27399
+ pairingId: arguments_.includes("--pairing") ? validatePairingId(readSingleOption(arguments_, "--pairing")) : "",
27350
27400
  relayOrigin: parseRelayOrigin(readSingleOption(arguments_, "--url")),
27351
27401
  workspace: arguments_.includes("--workspace") ? readSingleOption(arguments_, "--workspace") : loadDefaultWorkspace()
27352
27402
  };
27353
27403
  }
27404
+ function validatePairingId(value) {
27405
+ if (!IDENTIFIER_PATTERN6.test(value)) {
27406
+ throw new PairingClientError("invalid_pairing_id", "The Runtime pairing ID is invalid");
27407
+ }
27408
+ return value;
27409
+ }
27354
27410
  function parseChannelArgument(arguments_) {
27355
27411
  if (arguments_.length !== 2 || arguments_[0] !== "--channel") {
27356
27412
  throw new PairingClientError("invalid_arguments", "Usage: mobius start --channel <channel-id>");
@@ -27407,17 +27463,17 @@ function readRelayError(value) {
27407
27463
  }
27408
27464
  return new PairingClientError("pairing_failed", "The relay could not pair this machine");
27409
27465
  }
27410
- function decodeClaimResponse(value) {
27411
- if (!isRecord11(value) || !hasExactKeys(value, CLAIM_RESPONSE_KEYS)) {
27466
+ function decodePairingResponse(value, credentialField, expectedKeys) {
27467
+ if (!isRecord11(value) || !hasExactKeys(value, expectedKeys)) {
27412
27468
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
27413
27469
  }
27414
27470
  const channelId = value["channelId"];
27415
- const daemonToken2 = value["daemonToken"];
27471
+ const runtimeCredential = value[credentialField];
27416
27472
  const runtimeId = value["runtimeId"];
27417
- if (typeof channelId !== "string" || !IDENTIFIER_PATTERN6.test(channelId) || typeof daemonToken2 !== "string" || !TOKEN_PATTERN2.test(daemonToken2) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN6.test(runtimeId)) {
27473
+ if (typeof channelId !== "string" || !IDENTIFIER_PATTERN6.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN6.test(runtimeId)) {
27418
27474
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
27419
27475
  }
27420
- return { channelId, daemonToken: daemonToken2, runtimeId };
27476
+ return { channelId, runtimeCredential, runtimeId };
27421
27477
  }
27422
27478
  async function claimPairing(options, defaultWorkspace, localAgents, installableAgents, signal) {
27423
27479
  const agents = localAgents.map((agent) => ({
@@ -27427,7 +27483,7 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
27427
27483
  id: agent.id,
27428
27484
  label: agent.label
27429
27485
  }));
27430
- const endpoint = mobiusApiUrl(options.relayOrigin, "/pairings/claim");
27486
+ const endpoint = options.pairingId.length > 0 ? mobiusApiUrl(options.relayOrigin, `/v1/runtime-pairings/${encodeURIComponent(options.pairingId)}/redeem`) : mobiusApiUrl(options.relayOrigin, "/pairings/claim");
27431
27487
  let response;
27432
27488
  try {
27433
27489
  response = await fetch3(endpoint, {
@@ -27450,10 +27506,10 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
27450
27506
  if (!response.ok) {
27451
27507
  throw readRelayError(value);
27452
27508
  }
27453
- return decodeClaimResponse(value);
27509
+ return options.pairingId.length > 0 ? decodePairingResponse(value, "runtimeCredential", REDEEM_RESPONSE_KEYS) : decodePairingResponse(value, "daemonToken", CLAIM_RESPONSE_KEYS);
27454
27510
  }
27455
- async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWorkspace) {
27456
- const connectionsDirectory = join14(readStateDirectory(), "connections");
27511
+ async function persistConnection(channelId, relayOrigin, runtimeCredential, defaultWorkspace, environment) {
27512
+ const connectionsDirectory = join14(readStateDirectory(environment), "connections");
27457
27513
  await mkdir4(connectionsDirectory, { mode: 448, recursive: true });
27458
27514
  const connectionPath = join14(connectionsDirectory, `${channelId}.json`);
27459
27515
  const temporaryPath = join14(connectionsDirectory, `.${channelId}.${randomUUID3()}.tmp`);
@@ -27463,8 +27519,8 @@ async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWo
27463
27519
  {
27464
27520
  defaultWorkspace,
27465
27521
  channelId,
27466
- daemonToken: daemonToken2,
27467
- relayOrigin: relayOrigin.toString()
27522
+ relayOrigin: relayOrigin.toString(),
27523
+ runtimeCredential
27468
27524
  },
27469
27525
  null,
27470
27526
  2
@@ -27476,25 +27532,25 @@ async function persistConnection(channelId, relayOrigin, daemonToken2, defaultWo
27476
27532
  return connectionPath;
27477
27533
  }
27478
27534
  function decodeSavedConnection(value, expectedChannelId) {
27479
- if (!isRecord11(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS)) {
27535
+ if (!isRecord11(value) || !hasExactKeys(value, SAVED_CONNECTION_KEYS) && !hasExactKeys(value, LEGACY_SAVED_CONNECTION_KEYS)) {
27480
27536
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
27481
27537
  }
27482
27538
  const defaultWorkspace = value["defaultWorkspace"];
27483
27539
  const channelId = value["channelId"];
27484
- const daemonToken2 = value["daemonToken"];
27540
+ const runtimeCredential = value["runtimeCredential"] ?? value["daemonToken"];
27485
27541
  const relayOrigin = value["relayOrigin"];
27486
- if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN6.test(channelId) || typeof daemonToken2 !== "string" || !TOKEN_PATTERN2.test(daemonToken2) || typeof relayOrigin !== "string") {
27542
+ if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN6.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof relayOrigin !== "string") {
27487
27543
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
27488
27544
  }
27489
27545
  return {
27490
27546
  defaultWorkspace,
27491
27547
  channelId,
27492
- daemonToken: daemonToken2,
27493
- relayOrigin: parseRelayOrigin(relayOrigin)
27548
+ relayOrigin: parseRelayOrigin(relayOrigin),
27549
+ runtimeCredential
27494
27550
  };
27495
27551
  }
27496
- async function readSavedConnection(channelId) {
27497
- const connectionPath = join14(readStateDirectory(), "connections", `${channelId}.json`);
27552
+ async function readSavedConnection(channelId, environment) {
27553
+ const connectionPath = join14(readStateDirectory(environment), "connections", `${channelId}.json`);
27498
27554
  let encoded = "";
27499
27555
  const handle = await open4(connectionPath, "r");
27500
27556
  try {
@@ -27516,8 +27572,8 @@ async function readSavedConnection(channelId) {
27516
27572
  return {
27517
27573
  defaultWorkspace: await resolveDefaultWorkspace(connection.defaultWorkspace),
27518
27574
  channelId: connection.channelId,
27519
- daemonToken: connection.daemonToken,
27520
- relayOrigin: connection.relayOrigin
27575
+ relayOrigin: connection.relayOrigin,
27576
+ runtimeCredential: connection.runtimeCredential
27521
27577
  };
27522
27578
  }
27523
27579
  function assertInventoryUsable(inventory) {
@@ -27525,15 +27581,12 @@ function assertInventoryUsable(inventory) {
27525
27581
  throw new PairingClientError("no_supported_agent", "No supported Agent is available for local discovery");
27526
27582
  }
27527
27583
  }
27528
- function buildPairedDaemon(connection, connectionPath, inventory) {
27584
+ function buildPairedDaemon(connection, connectionPath, inventory, environment) {
27529
27585
  assertInventoryUsable(inventory);
27530
- const relayUrl = mobiusApiUrl(
27531
- connection.relayOrigin,
27532
- `/channels/${encodeURIComponent(connection.channelId)}/daemon`
27533
- );
27586
+ const relayUrl = mobiusApiUrl(connection.relayOrigin, "/v1/runtimes/connect");
27534
27587
  relayUrl.protocol = relayUrl.protocol === "https:" ? "wss:" : "ws:";
27535
- relayUrl.searchParams.set("token", connection.daemonToken);
27536
- const auditDatabasePath = join14(readStateDirectory(), "audit", `${connection.channelId}.sqlite`);
27588
+ relayUrl.searchParams.set("token", connection.runtimeCredential);
27589
+ const auditDatabasePath = join14(readStateDirectory(environment), "audit", `${connection.channelId}.sqlite`);
27537
27590
  return {
27538
27591
  channelUrl: new URL(`/channels/${encodeURIComponent(connection.channelId)}`, connection.relayOrigin).toString(),
27539
27592
  config: createDaemonConfig(
@@ -27549,12 +27602,11 @@ function buildPairedDaemon(connection, connectionPath, inventory) {
27549
27602
  connectionPath
27550
27603
  };
27551
27604
  }
27552
- async function pairLocalMachine(arguments_, signal = new AbortController().signal) {
27553
- const options = parsePairCommand(arguments_);
27605
+ async function pairRuntime(options, signal, environment) {
27554
27606
  const defaultWorkspace = await resolveDefaultWorkspace(options.workspace);
27555
27607
  const inventory = await discoverLocalAgentInventory({
27556
27608
  defaultWorkspace,
27557
- environment: process.env,
27609
+ environment,
27558
27610
  signal
27559
27611
  });
27560
27612
  assertInventoryUsable(inventory);
@@ -27563,30 +27615,50 @@ async function pairLocalMachine(arguments_, signal = new AbortController().signa
27563
27615
  const connectionPath = await persistConnection(
27564
27616
  claim2.channelId,
27565
27617
  options.relayOrigin,
27566
- claim2.daemonToken,
27567
- defaultWorkspace
27618
+ claim2.runtimeCredential,
27619
+ defaultWorkspace,
27620
+ environment
27568
27621
  );
27569
27622
  return buildPairedDaemon(
27570
27623
  {
27571
27624
  defaultWorkspace,
27572
27625
  channelId: claim2.channelId,
27573
- daemonToken: claim2.daemonToken,
27574
- relayOrigin: options.relayOrigin
27626
+ relayOrigin: options.relayOrigin,
27627
+ runtimeCredential: claim2.runtimeCredential
27575
27628
  },
27576
27629
  connectionPath,
27577
- inventory
27630
+ inventory,
27631
+ environment
27578
27632
  );
27579
27633
  }
27580
- async function resumeLocalMachine(arguments_, signal = new AbortController().signal) {
27581
- const channelId = parseChannelArgument(arguments_);
27582
- const connectionPath = join14(readStateDirectory(), "connections", `${channelId}.json`);
27583
- const connection = await readSavedConnection(channelId);
27634
+ async function pairMobiusRuntime(options, signal, environment = process.env) {
27635
+ return await pairRuntime(
27636
+ {
27637
+ code: parsePairingCode(options.pairingCode),
27638
+ pairingId: validatePairingId(options.pairingId),
27639
+ relayOrigin: parseRelayOrigin(options.relayOrigin),
27640
+ workspace: options.defaultWorkspace
27641
+ },
27642
+ signal,
27643
+ environment
27644
+ );
27645
+ }
27646
+ async function pairLocalMachine(arguments_, signal = new AbortController().signal) {
27647
+ return await pairRuntime(parsePairCommand(arguments_), signal, process.env);
27648
+ }
27649
+ async function resumeMobiusRuntime(channelId, signal, environment = process.env) {
27650
+ const normalizedChannelId = validatePairingId(channelId);
27651
+ const connectionPath = join14(readStateDirectory(environment), "connections", `${normalizedChannelId}.json`);
27652
+ const connection = await readSavedConnection(normalizedChannelId, environment);
27584
27653
  const inventory = await discoverLocalAgentInventory({
27585
27654
  defaultWorkspace: connection.defaultWorkspace,
27586
- environment: process.env,
27655
+ environment,
27587
27656
  signal
27588
27657
  });
27589
- return buildPairedDaemon(connection, connectionPath, inventory);
27658
+ return buildPairedDaemon(connection, connectionPath, inventory, environment);
27659
+ }
27660
+ async function resumeLocalMachine(arguments_, signal = new AbortController().signal) {
27661
+ return await resumeMobiusRuntime(parseChannelArgument(arguments_), signal, process.env);
27590
27662
  }
27591
27663
 
27592
27664
  // src/run-daemon.ts
@@ -27605,14 +27677,19 @@ var AgentCatalogPublishError = class extends Error {
27605
27677
  this.name = "AgentCatalogPublishError";
27606
27678
  }
27607
27679
  };
27608
- function catalogEndpoint(relayUrl) {
27609
- const endpoint = new URL(relayUrl);
27680
+ function catalogEndpoint(config2) {
27681
+ const endpoint = new URL(config2.relayUrl);
27610
27682
  endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
27611
- if (!endpoint.pathname.endsWith("/daemon")) {
27612
- throw new AgentCatalogPublishError("The Relay URL is invalid");
27683
+ if (endpoint.pathname.endsWith("/daemon")) {
27684
+ endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
27685
+ return endpoint;
27613
27686
  }
27614
- endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
27615
- return endpoint;
27687
+ const runtimePath = "/v1/runtimes/connect";
27688
+ if (endpoint.pathname.endsWith(runtimePath)) {
27689
+ endpoint.pathname = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}/catalog`;
27690
+ return endpoint;
27691
+ }
27692
+ throw new AgentCatalogPublishError("The Relay URL is invalid");
27616
27693
  }
27617
27694
  async function publishAgentCatalog(config2, signal) {
27618
27695
  signal.throwIfAborted();
@@ -27621,7 +27698,7 @@ async function publishAgentCatalog(config2, signal) {
27621
27698
  let response;
27622
27699
  const catalog = config2.catalog.read();
27623
27700
  try {
27624
- response = await fetch4(catalogEndpoint(config2.relayUrl), {
27701
+ response = await fetch4(catalogEndpoint(config2), {
27625
27702
  body: JSON.stringify({
27626
27703
  agents: catalog.agents.map((agent) => ({
27627
27704
  agentPath: agent.agentIdentity.requestedPath,
@@ -28991,10 +29068,17 @@ function parseReference(resource) {
28991
29068
  function downloadUrl(config2, command, reference) {
28992
29069
  const url2 = new URL(config2.relayUrl);
28993
29070
  url2.protocol = url2.protocol === "wss:" ? "https:" : "http:";
28994
- if (!url2.pathname.endsWith("/daemon")) {
28995
- throw new PromptArtifactError("artifact_endpoint_invalid", "The Relay artifact endpoint is invalid");
29071
+ let channelPath = "";
29072
+ if (url2.pathname.endsWith("/daemon")) {
29073
+ channelPath = url2.pathname.slice(0, -"/daemon".length);
29074
+ } else {
29075
+ const runtimePath = "/v1/runtimes/connect";
29076
+ if (!url2.pathname.endsWith(runtimePath)) {
29077
+ throw new PromptArtifactError("artifact_endpoint_invalid", "The Relay artifact endpoint is invalid");
29078
+ }
29079
+ channelPath = `${url2.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
28996
29080
  }
28997
- url2.pathname = `${url2.pathname.slice(0, -"/daemon".length)}/sessions/${encodeURIComponent(command.sessionId)}/commands/${encodeURIComponent(command.commandId)}/artifacts/${encodeURIComponent(reference.artifactId)}/content`;
29081
+ url2.pathname = `${channelPath}/sessions/${encodeURIComponent(command.sessionId)}/commands/${encodeURIComponent(command.commandId)}/artifacts/${encodeURIComponent(reference.artifactId)}/content`;
28998
29082
  url2.searchParams.delete("liveness");
28999
29083
  return url2;
29000
29084
  }
@@ -29144,17 +29228,27 @@ import { WebSocket } from "undici";
29144
29228
  var CONNECTION_TIMEOUT_MILLISECONDS = 15e3;
29145
29229
  var RelayConnectionError = class extends Error {
29146
29230
  code;
29147
- constructor(code, message, options = {}) {
29231
+ diagnosticDetails;
29232
+ constructor(code, message, diagnosticDetails = {}, options = {}) {
29148
29233
  super(message, options);
29149
29234
  this.code = code;
29235
+ this.diagnosticDetails = diagnosticDetails;
29150
29236
  this.name = "RelayConnectionError";
29151
29237
  }
29152
29238
  };
29153
29239
  function connectRelay(url2, signal) {
29154
29240
  signal.throwIfAborted();
29155
- return new Promise((resolve5, reject) => {
29156
- const socket = new WebSocket(url2, {
29157
- dispatcher: environmentProxyDispatcher
29241
+ return new Promise((resolve6, reject) => {
29242
+ const endpoint = new URL(url2);
29243
+ const headers = {};
29244
+ if (endpoint.pathname.endsWith("/v1/runtimes/connect")) {
29245
+ const runtimeCredential = endpoint.searchParams.get("token") ?? "";
29246
+ endpoint.searchParams.delete("token");
29247
+ headers["authorization"] = `Bearer ${runtimeCredential}`;
29248
+ }
29249
+ const socket = new WebSocket(endpoint, {
29250
+ dispatcher: environmentProxyDispatcher,
29251
+ headers
29158
29252
  });
29159
29253
  let finished = false;
29160
29254
  const timeout = setTimeout(onTimeout, CONNECTION_TIMEOUT_MILLISECONDS);
@@ -29176,16 +29270,34 @@ function connectRelay(url2, signal) {
29176
29270
  };
29177
29271
  const onAbort = () => {
29178
29272
  fail(
29179
- new RelayConnectionError("relay_connection_cancelled", "Relay connection was cancelled", {
29180
- cause: signal.reason
29181
- })
29273
+ new RelayConnectionError(
29274
+ "relay_connection_cancelled",
29275
+ "Relay connection was cancelled",
29276
+ {
29277
+ cancellation: "requested"
29278
+ },
29279
+ { cause: signal.reason }
29280
+ )
29182
29281
  );
29183
29282
  };
29184
- const onClose = () => {
29185
- fail(new RelayConnectionError("relay_connection_closed", "Relay connection closed before it was ready"));
29283
+ const onClose = (event) => {
29284
+ fail(
29285
+ new RelayConnectionError("relay_connection_closed", "Relay connection closed before it was ready", {
29286
+ closeCode: event.code.toString(),
29287
+ closeReason: event.reason
29288
+ })
29289
+ );
29186
29290
  };
29187
- const onError = () => {
29188
- fail(new RelayConnectionError("relay_connection_failed", "Relay connection failed"));
29291
+ const onError = (event) => {
29292
+ const cause = Reflect.get(event, "error");
29293
+ fail(
29294
+ new RelayConnectionError(
29295
+ "relay_connection_failed",
29296
+ "Relay connection failed",
29297
+ {},
29298
+ { cause: cause ?? event }
29299
+ )
29300
+ );
29189
29301
  };
29190
29302
  const onOpen = () => {
29191
29303
  if (finished) {
@@ -29193,10 +29305,14 @@ function connectRelay(url2, signal) {
29193
29305
  }
29194
29306
  finished = true;
29195
29307
  cleanup();
29196
- resolve5(socket);
29308
+ resolve6(socket);
29197
29309
  };
29198
29310
  function onTimeout() {
29199
- fail(new RelayConnectionError("relay_connection_timeout", "Relay connection timed out"));
29311
+ fail(
29312
+ new RelayConnectionError("relay_connection_timeout", "Relay connection timed out", {
29313
+ timeoutMilliseconds: CONNECTION_TIMEOUT_MILLISECONDS.toString()
29314
+ })
29315
+ );
29200
29316
  }
29201
29317
  signal.addEventListener("abort", onAbort, { once: true });
29202
29318
  socket.addEventListener("close", onClose, { once: true });
@@ -29208,6 +29324,142 @@ function connectRelay(url2, signal) {
29208
29324
  });
29209
29325
  }
29210
29326
 
29327
+ // src/relay-connection/diagnostics.ts
29328
+ var MAX_DIAGNOSTIC_TEXT_LENGTH = 256;
29329
+ function sanitizeDiagnosticText(value) {
29330
+ const printable = Array.from(value, (character) => {
29331
+ const code = character.charCodeAt(0);
29332
+ return code < 32 || code === 127 ? " " : character;
29333
+ }).join("");
29334
+ const withoutSecrets = printable.replace(/([?&](?:access_token|authorization|credential|key|secret|token)=)[^&\s]*/giu, "$1[redacted]").replace(/(?:https?|wss?):\/\/[^\s)]+/giu, (encodedUrl) => {
29335
+ try {
29336
+ return new URL(encodedUrl).origin;
29337
+ } catch {
29338
+ return "[redacted-url]";
29339
+ }
29340
+ });
29341
+ return withoutSecrets.slice(0, MAX_DIAGNOSTIC_TEXT_LENGTH);
29342
+ }
29343
+ function addFailureDetails(details, failure) {
29344
+ let current = failure;
29345
+ for (let depth = 0; depth < 3 && current instanceof Error; depth += 1) {
29346
+ const prefix = depth === 0 ? "failure" : `cause${depth.toString()}`;
29347
+ details[`${prefix}Name`] = current.name;
29348
+ details[`${prefix}Message`] = sanitizeDiagnosticText(current.message);
29349
+ const code = Reflect.get(current, "code");
29350
+ if (typeof code === "string" || typeof code === "number") {
29351
+ details[`${prefix}Code`] = String(code);
29352
+ }
29353
+ const statusCode = Reflect.get(current, "statusCode");
29354
+ if (typeof statusCode === "string" || typeof statusCode === "number") {
29355
+ details[`${prefix}StatusCode`] = String(statusCode);
29356
+ }
29357
+ const diagnosticDetails = Reflect.get(current, "diagnosticDetails");
29358
+ if (typeof diagnosticDetails === "object" && diagnosticDetails !== null) {
29359
+ for (const [key, value] of Object.entries(diagnosticDetails)) {
29360
+ if (typeof value === "string") {
29361
+ details[key] = sanitizeDiagnosticText(value);
29362
+ }
29363
+ }
29364
+ }
29365
+ current = current.cause;
29366
+ }
29367
+ }
29368
+ var RelayConnectionDiagnostics = class {
29369
+ #audit;
29370
+ #channelId;
29371
+ #lastClosedEpoch = 0;
29372
+ #lastFailedEpoch = 0;
29373
+ #relayOrigin;
29374
+ #route;
29375
+ constructor(audit, channelId, relayUrl) {
29376
+ this.#audit = audit;
29377
+ this.#channelId = channelId;
29378
+ this.#relayOrigin = relayUrl.origin;
29379
+ this.#route = describeEnvironmentProxyRoute(relayUrl);
29380
+ }
29381
+ catalogFailed = (failure) => {
29382
+ const details = { ...this.#route, relayOrigin: this.#relayOrigin };
29383
+ addFailureDetails(details, failure);
29384
+ this.#record("agent_catalog_publish_failed", "catalog", details, true);
29385
+ };
29386
+ connectionClosed = (connectionEpoch, code, reason, source) => {
29387
+ if (this.#lastClosedEpoch === connectionEpoch) {
29388
+ return;
29389
+ }
29390
+ this.#lastClosedEpoch = connectionEpoch;
29391
+ this.#record(
29392
+ "relay_connection_closed",
29393
+ connectionEpoch.toString(),
29394
+ {
29395
+ closeCode: code.toString(),
29396
+ closeReason: sanitizeDiagnosticText(reason),
29397
+ connectionEpoch: connectionEpoch.toString(),
29398
+ relayOrigin: this.#relayOrigin,
29399
+ source,
29400
+ ...this.#route
29401
+ },
29402
+ true
29403
+ );
29404
+ };
29405
+ connectionFailed = (connectionEpoch, failure, source) => {
29406
+ if (this.#lastFailedEpoch === connectionEpoch) {
29407
+ return;
29408
+ }
29409
+ this.#lastFailedEpoch = connectionEpoch;
29410
+ const details = {
29411
+ connectionEpoch: connectionEpoch.toString(),
29412
+ relayOrigin: this.#relayOrigin,
29413
+ source,
29414
+ ...this.#route
29415
+ };
29416
+ addFailureDetails(details, failure);
29417
+ this.#record("relay_connection_failed", connectionEpoch.toString(), details, true);
29418
+ };
29419
+ connectionOpened = (connectionEpoch) => {
29420
+ this.#record(
29421
+ "relay_connection_opened",
29422
+ connectionEpoch.toString(),
29423
+ {
29424
+ connectionEpoch: connectionEpoch.toString(),
29425
+ relayOrigin: this.#relayOrigin,
29426
+ ...this.#route
29427
+ },
29428
+ false
29429
+ );
29430
+ };
29431
+ heartbeatTimedOut = (connectionEpoch, heartbeatId, timeoutMilliseconds) => {
29432
+ this.#record(
29433
+ "relay_heartbeat_timed_out",
29434
+ connectionEpoch.toString(),
29435
+ {
29436
+ connectionEpoch: connectionEpoch.toString(),
29437
+ heartbeatId,
29438
+ relayOrigin: this.#relayOrigin,
29439
+ timeoutMilliseconds: timeoutMilliseconds.toString(),
29440
+ ...this.#route
29441
+ },
29442
+ true
29443
+ );
29444
+ };
29445
+ #record = (event, commandId, details, warning) => {
29446
+ this.#audit.record({
29447
+ commandId,
29448
+ details,
29449
+ event,
29450
+ sessionId: this.#channelId
29451
+ });
29452
+ const encoded = JSON.stringify({ channelId: this.#channelId, event, ...details });
29453
+ if (warning) {
29454
+ process.stderr.write(`Mobius relay diagnostic ${encoded}
29455
+ `);
29456
+ return;
29457
+ }
29458
+ process.stdout.write(`Mobius relay diagnostic ${encoded}
29459
+ `);
29460
+ };
29461
+ };
29462
+
29211
29463
  // src/relay-connection/liveness.ts
29212
29464
  import { randomUUID as randomUUID5 } from "node:crypto";
29213
29465
  var HEARTBEAT_ACK_TIMEOUT_MILLISECONDS = 1e4;
@@ -29284,9 +29536,10 @@ var RelayConnectionLiveness = class {
29284
29536
  if (!(this.#state instanceof AwaitingHeartbeatAckState)) {
29285
29537
  throw new Error("Heartbeat timeout occurred in an invalid state");
29286
29538
  }
29539
+ const heartbeatId = this.#state.heartbeatId;
29287
29540
  this.#state.timeout.cancel();
29288
29541
  this.#state = new FinishedRelayLivenessState();
29289
- this.#transport.unresponsive();
29542
+ this.#transport.unresponsive(heartbeatId, HEARTBEAT_ACK_TIMEOUT_MILLISECONDS);
29290
29543
  };
29291
29544
  #sendHeartbeat = () => {
29292
29545
  if (this.#state instanceof WaitingForHeartbeatState) {
@@ -29335,6 +29588,7 @@ var SessionEventOutbox = class {
29335
29588
  ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
29336
29589
  event_id TEXT NOT NULL UNIQUE,
29337
29590
  session_id TEXT NOT NULL,
29591
+ source_key TEXT NOT NULL,
29338
29592
  envelope_json TEXT NOT NULL
29339
29593
  ) STRICT;
29340
29594
  CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
@@ -29362,6 +29616,27 @@ var SessionEventOutbox = class {
29362
29616
  )
29363
29617
  ) STRICT;
29364
29618
  `);
29619
+ const outboxColumns = this.#database.prepare("PRAGMA table_info(relay_session_event_outbox)").all();
29620
+ const hasSourceKey = outboxColumns.some(
29621
+ (column) => typeof column === "object" && column !== null && Reflect.get(column, "name") === "source_key"
29622
+ );
29623
+ if (!hasSourceKey) {
29624
+ this.#database.exec(`
29625
+ ALTER TABLE relay_session_event_outbox
29626
+ ADD COLUMN source_key TEXT NOT NULL DEFAULT '';
29627
+ `);
29628
+ }
29629
+ this.#database.exec(`
29630
+ UPDATE relay_session_event_outbox
29631
+ SET source_key = CASE json_extract(envelope_json, '$.source.kind')
29632
+ WHEN 'attempt' THEN
29633
+ 'attempt:' || session_id || ':' || json_extract(envelope_json, '$.source.attemptId')
29634
+ ELSE 'runtime:' || session_id
29635
+ END
29636
+ WHERE source_key = '';
29637
+ CREATE INDEX IF NOT EXISTS relay_session_event_outbox_source_order
29638
+ ON relay_session_event_outbox(source_key, ordinal);
29639
+ `);
29365
29640
  }
29366
29641
  claimExecution = (commandId, sessionId, commandJson) => {
29367
29642
  this.#database.exec("BEGIN IMMEDIATE");
@@ -29419,12 +29694,10 @@ var SessionEventOutbox = class {
29419
29694
  const state = typeof row === "object" && row !== null ? Reflect.get(row, "state") : void 0;
29420
29695
  return state === "succeeded" || state === "failed" || state === "cancelled";
29421
29696
  };
29422
- acknowledge = (acknowledgement) => {
29423
- this.#database.prepare(
29424
- `DELETE FROM relay_session_event_outbox
29697
+ acknowledge = (acknowledgement) => this.#database.prepare(
29698
+ `DELETE FROM relay_session_event_outbox
29425
29699
  WHERE event_id = ? AND session_id = ?`
29426
- ).run(acknowledgement.eventId, acknowledgement.sessionId);
29427
- };
29700
+ ).run(acknowledgement.eventId, acknowledgement.sessionId).changes === 1;
29428
29701
  quarantine = (rejection) => {
29429
29702
  this.#database.exec("BEGIN IMMEDIATE");
29430
29703
  try {
@@ -29491,9 +29764,9 @@ var SessionEventOutbox = class {
29491
29764
  ).run(sourceKey, nextSourceSeq + 1);
29492
29765
  this.#database.prepare(
29493
29766
  `INSERT INTO relay_session_event_outbox (
29494
- event_id, session_id, envelope_json
29495
- ) VALUES (?, ?, ?)`
29496
- ).run(envelope2.eventId, envelope2.sessionId, encoded);
29767
+ event_id, session_id, source_key, envelope_json
29768
+ ) VALUES (?, ?, ?, ?)`
29769
+ ).run(envelope2.eventId, envelope2.sessionId, sourceKey, encoded);
29497
29770
  this.#database.exec("COMMIT");
29498
29771
  return encoded;
29499
29772
  } catch (error61) {
@@ -29506,6 +29779,27 @@ var SessionEventOutbox = class {
29506
29779
  FROM relay_session_event_outbox
29507
29780
  ORDER BY ordinal`
29508
29781
  ).all().map(readPendingEnvelope);
29782
+ pendingBatch = (limit) => {
29783
+ if (!Number.isSafeInteger(limit) || limit < 1) {
29784
+ throw new Error("Session event replay batch limit must be a positive integer");
29785
+ }
29786
+ return this.#database.prepare(
29787
+ `WITH ranked_events AS (
29788
+ SELECT ordinal,
29789
+ ROW_NUMBER() OVER (
29790
+ PARTITION BY source_key
29791
+ ORDER BY ordinal
29792
+ ) AS source_position
29793
+ FROM relay_session_event_outbox
29794
+ )
29795
+ SELECT outbox.envelope_json
29796
+ FROM ranked_events AS ranked
29797
+ INNER JOIN relay_session_event_outbox AS outbox
29798
+ ON outbox.ordinal = ranked.ordinal
29799
+ ORDER BY ranked.source_position, outbox.ordinal
29800
+ LIMIT ?`
29801
+ ).all(limit).map(readPendingEnvelope);
29802
+ };
29509
29803
  };
29510
29804
  function isDaemonExecutionState(value) {
29511
29805
  return value === "cancelled" || value === "dispatching" || value === "failed" || value === "running" || value === "succeeded";
@@ -29522,7 +29816,7 @@ var systemDaemonTimer = new SystemDaemonTimer();
29522
29816
 
29523
29817
  // src/run-daemon.ts
29524
29818
  var RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
29525
- var RELAY_CONNECTION_REFRESH_MILLISECONDS = 20 * 60 * 1e3;
29819
+ var AGENT_ACTIVITY_COALESCING_MILLISECONDS = 100;
29526
29820
  var ACTIVE_TURN_RECONCILIATION_TIMEOUT_MILLISECONDS = 2e4;
29527
29821
  var ACTIVE_TURN_RECONCILIATION_CLOSE_CODE = 4009;
29528
29822
  function botIpcSocketPath() {
@@ -29531,54 +29825,6 @@ function botIpcSocketPath() {
29531
29825
  }
29532
29826
  return join16("/tmp", `mobius-bot-${randomUUID6()}.sock`);
29533
29827
  }
29534
- var RelayConnectionRefreshState = class {
29535
- };
29536
- var ScheduledRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29537
- };
29538
- var DueRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29539
- };
29540
- var FinishedRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29541
- };
29542
- var RelayConnectionRefresh = class {
29543
- #isSafe;
29544
- #refresh;
29545
- #scheduledTask;
29546
- #state = new ScheduledRelayConnectionRefreshState();
29547
- constructor(timer, isSafe, refresh) {
29548
- this.#isSafe = isSafe;
29549
- this.#refresh = refresh;
29550
- this.#scheduledTask = timer.schedule(this.#request, RELAY_CONNECTION_REFRESH_MILLISECONDS);
29551
- }
29552
- afterTurnFinished = () => {
29553
- if (this.#state instanceof DueRelayConnectionRefreshState) {
29554
- this.#request();
29555
- return;
29556
- }
29557
- if (this.#state instanceof ScheduledRelayConnectionRefreshState || this.#state instanceof FinishedRelayConnectionRefreshState) {
29558
- return;
29559
- }
29560
- throw new Error("Unknown relay connection refresh state");
29561
- };
29562
- close = () => {
29563
- this.#scheduledTask.cancel();
29564
- this.#state = new FinishedRelayConnectionRefreshState();
29565
- };
29566
- #request = () => {
29567
- if (this.#state instanceof FinishedRelayConnectionRefreshState) {
29568
- return;
29569
- }
29570
- if (!(this.#state instanceof ScheduledRelayConnectionRefreshState) && !(this.#state instanceof DueRelayConnectionRefreshState)) {
29571
- throw new Error("Unknown relay connection refresh state");
29572
- }
29573
- if (!this.#isSafe()) {
29574
- this.#state = new DueRelayConnectionRefreshState();
29575
- return;
29576
- }
29577
- this.#state = new FinishedRelayConnectionRefreshState();
29578
- this.#scheduledTask.cancel();
29579
- this.#refresh();
29580
- };
29581
- };
29582
29828
  var DaemonAcpObserver = class {
29583
29829
  #record;
29584
29830
  constructor(record2) {
@@ -29775,10 +30021,17 @@ var ActiveTurnReconciliationError = class extends Error {
29775
30021
  function activeTurnReconciliationEndpoint(config2, turn) {
29776
30022
  const endpoint = new URL(config2.relayUrl);
29777
30023
  endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
29778
- if (!endpoint.pathname.endsWith("/daemon")) {
29779
- throw new ActiveTurnReconciliationError("The Relay URL is invalid");
30024
+ let channelPath = "";
30025
+ if (endpoint.pathname.endsWith("/daemon")) {
30026
+ channelPath = endpoint.pathname.slice(0, -"/daemon".length);
30027
+ } else {
30028
+ const runtimePath = "/v1/runtimes/connect";
30029
+ if (!endpoint.pathname.endsWith(runtimePath)) {
30030
+ throw new ActiveTurnReconciliationError("The Relay URL is invalid");
30031
+ }
30032
+ channelPath = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
29780
30033
  }
29781
- endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
30034
+ endpoint.pathname = `${channelPath}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
29782
30035
  endpoint.searchParams.delete("liveness");
29783
30036
  return endpoint;
29784
30037
  }
@@ -29836,7 +30089,8 @@ var DaemonSessionEventConnectionState = class {
29836
30089
  };
29837
30090
  var DisconnectedDaemonSessionEventState = class extends DaemonSessionEventConnectionState {
29838
30091
  disconnect = () => this;
29839
- send = () => void 0;
30092
+ matches = () => false;
30093
+ send = () => false;
29840
30094
  };
29841
30095
  var ConnectedDaemonSessionEventState = class extends DaemonSessionEventConnectionState {
29842
30096
  #socket;
@@ -29845,38 +30099,155 @@ var ConnectedDaemonSessionEventState = class extends DaemonSessionEventConnectio
29845
30099
  this.#socket = socket;
29846
30100
  }
29847
30101
  disconnect = (socket) => socket === this.#socket ? new DisconnectedDaemonSessionEventState() : this;
30102
+ matches = (socket) => socket === this.#socket;
29848
30103
  send = (message) => {
29849
- if (this.#socket.readyState === WebSocket2.OPEN) {
29850
- try {
29851
- this.#socket.send(message);
29852
- } catch {
29853
- }
30104
+ if (this.#socket.readyState !== WebSocket2.OPEN) {
30105
+ return false;
30106
+ }
30107
+ try {
30108
+ this.#socket.send(message);
30109
+ return true;
30110
+ } catch {
30111
+ return false;
29854
30112
  }
29855
30113
  };
29856
30114
  };
30115
+ var SESSION_EVENT_REPLAY_WINDOW = 4;
29857
30116
  var DaemonSessionEventPublisher = class {
29858
30117
  #outbox;
30118
+ #inFlightEventIds = /* @__PURE__ */ new Set();
29859
30119
  #connection = new DisconnectedDaemonSessionEventState();
29860
30120
  constructor(outbox) {
29861
30121
  this.#outbox = outbox;
29862
30122
  }
29863
30123
  connect = (socket) => {
29864
- const connection = new ConnectedDaemonSessionEventState(socket);
29865
- this.#connection = connection;
29866
- for (const pending of this.#outbox.pending()) {
29867
- connection.send(pending);
29868
- }
30124
+ this.#connection = new ConnectedDaemonSessionEventState(socket);
30125
+ this.#inFlightEventIds.clear();
30126
+ this.#publishNextBatch();
29869
30127
  };
29870
30128
  disconnect = (socket) => {
30129
+ if (!this.#connection.matches(socket)) {
30130
+ return;
30131
+ }
29871
30132
  this.#connection = this.#connection.disconnect(socket);
30133
+ this.#inFlightEventIds.clear();
29872
30134
  };
29873
30135
  recordAttempt = (sessionId, payload, attemptId) => {
29874
- this.#connection.send(this.#outbox.enqueue(sessionId, payload, attemptId));
30136
+ this.#outbox.enqueue(sessionId, payload, attemptId);
30137
+ this.#publishNextBatch();
29875
30138
  };
29876
30139
  recordRuntime = (sessionId, payload) => {
29877
- this.#connection.send(this.#outbox.enqueue(sessionId, payload));
30140
+ this.#outbox.enqueue(sessionId, payload);
30141
+ this.#publishNextBatch();
30142
+ };
30143
+ acknowledge = (acknowledgement) => {
30144
+ if (!this.#outbox.acknowledge(acknowledgement)) {
30145
+ return;
30146
+ }
30147
+ this.#inFlightEventIds.delete(acknowledgement.eventId);
30148
+ this.#publishNextBatch();
30149
+ };
30150
+ quarantine = (rejection) => {
30151
+ const quarantined = this.#outbox.quarantine(rejection);
30152
+ if (!quarantined) {
30153
+ return false;
30154
+ }
30155
+ this.#inFlightEventIds.delete(rejection.eventId);
30156
+ this.#publishNextBatch();
30157
+ return true;
30158
+ };
30159
+ #publishNextBatch = () => {
30160
+ if (this.#inFlightEventIds.size > 0) {
30161
+ return;
30162
+ }
30163
+ const batch = this.#outbox.pendingBatch(SESSION_EVENT_REPLAY_WINDOW);
30164
+ for (const encoded of batch) {
30165
+ const envelope2 = decodeSessionEventEnvelope(encoded);
30166
+ if (!this.#connection.send(encoded)) {
30167
+ this.#inFlightEventIds.clear();
30168
+ return;
30169
+ }
30170
+ this.#inFlightEventIds.add(envelope2.eventId);
30171
+ }
30172
+ };
30173
+ };
30174
+ var CoalescingDaemonActivityPublisher = class {
30175
+ #pending = /* @__PURE__ */ new Map();
30176
+ #publish;
30177
+ #publishedActivityIds = /* @__PURE__ */ new Set();
30178
+ #scheduled = /* @__PURE__ */ new Map();
30179
+ #timer;
30180
+ constructor(timer, publish) {
30181
+ this.#publish = publish;
30182
+ this.#timer = timer;
30183
+ }
30184
+ flush = () => {
30185
+ const activities = [...this.#pending.values()].sort((left, right) => left.sequence - right.sequence);
30186
+ for (const task of this.#scheduled.values()) {
30187
+ task.cancel();
30188
+ }
30189
+ this.#pending.clear();
30190
+ this.#scheduled.clear();
30191
+ for (const activity of activities) {
30192
+ this.#publish(activity);
30193
+ }
30194
+ };
30195
+ record = (activity) => {
30196
+ if (activity.status !== "pending" && activity.status !== "running") {
30197
+ this.#discardPending(activity.activityId);
30198
+ this.#publishedActivityIds.add(activity.activityId);
30199
+ this.#publish(activity);
30200
+ return Promise.resolve();
30201
+ }
30202
+ if (!this.#publishedActivityIds.has(activity.activityId)) {
30203
+ this.#publishedActivityIds.add(activity.activityId);
30204
+ this.#publish(activity);
30205
+ return Promise.resolve();
30206
+ }
30207
+ this.#pending.set(activity.activityId, activity);
30208
+ if (!this.#scheduled.has(activity.activityId)) {
30209
+ this.#scheduled.set(
30210
+ activity.activityId,
30211
+ this.#timer.schedule(
30212
+ () => this.#flushActivity(activity.activityId),
30213
+ AGENT_ACTIVITY_COALESCING_MILLISECONDS
30214
+ )
30215
+ );
30216
+ }
30217
+ return Promise.resolve();
30218
+ };
30219
+ #discardPending = (activityId) => {
30220
+ this.#scheduled.get(activityId)?.cancel();
30221
+ this.#scheduled.delete(activityId);
30222
+ this.#pending.delete(activityId);
30223
+ };
30224
+ #flushActivity = (activityId) => {
30225
+ const activity = this.#pending.get(activityId);
30226
+ this.#discardPending(activityId);
30227
+ if (activity !== void 0) {
30228
+ this.#publish(activity);
30229
+ }
29878
30230
  };
29879
30231
  };
30232
+ function encodeDaemonActivity(command, activity) {
30233
+ return encodeAgentActivityEvent({
30234
+ activityId: activity.activityId,
30235
+ activityType: activity.activityType,
30236
+ commandId: command.commandId,
30237
+ content: activity.content,
30238
+ detail: activity.detail,
30239
+ input: activity.input,
30240
+ kind: AGENT_ACTIVITY_KIND,
30241
+ locations: activity.locations,
30242
+ name: activity.name,
30243
+ output: activity.output,
30244
+ sequence: activity.sequence,
30245
+ sessionId: command.sessionId,
30246
+ status: activity.status,
30247
+ title: activity.title,
30248
+ toolKind: activity.toolKind
30249
+ });
30250
+ }
29880
30251
  function reportFailure(publisher, command, code, message, attemptId) {
29881
30252
  publisher.recordAttempt(
29882
30253
  command.sessionId,
@@ -30053,7 +30424,10 @@ async function executeSessionConfigure(publisher, socket, command, config2, dire
30053
30424
  reportOperationFailure(socket, command.requestId, command.sessionId, error61);
30054
30425
  }
30055
30426
  }
30056
- async function executePrompt(outbox, publisher, command, config2, directories, audit, sessions, attemptId, signal) {
30427
+ async function executePrompt(outbox, publisher, command, config2, directories, audit, sessions, attemptId, timer, signal) {
30428
+ const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
30429
+ publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
30430
+ });
30057
30431
  try {
30058
30432
  const resolved = await resolveSessionDirectories(
30059
30433
  directories,
@@ -30110,30 +30484,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30110
30484
  cwd: resolved.cwd,
30111
30485
  disposition: command.disposition,
30112
30486
  images: attachments.images,
30113
- onActivity: (activity) => {
30114
- publisher.recordAttempt(
30115
- command.sessionId,
30116
- encodeAgentActivityEvent({
30117
- activityId: activity.activityId,
30118
- activityType: activity.activityType,
30119
- commandId: command.commandId,
30120
- content: activity.content,
30121
- detail: activity.detail,
30122
- input: activity.input,
30123
- kind: AGENT_ACTIVITY_KIND,
30124
- locations: activity.locations,
30125
- name: activity.name,
30126
- output: activity.output,
30127
- sequence: activity.sequence,
30128
- sessionId: command.sessionId,
30129
- status: activity.status,
30130
- title: activity.title,
30131
- toolKind: activity.toolKind
30132
- }),
30133
- attemptId
30134
- );
30135
- return Promise.resolve();
30136
- },
30487
+ onActivity: activities.record,
30137
30488
  onText: (text, messageId) => {
30138
30489
  publisher.recordAttempt(
30139
30490
  command.sessionId,
@@ -30156,6 +30507,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30156
30507
  });
30157
30508
  stopReason = result.stopReason;
30158
30509
  }
30510
+ activities.flush();
30159
30511
  publisher.recordAttempt(
30160
30512
  command.sessionId,
30161
30513
  encodeSessionCompletedEvent({
@@ -30173,6 +30525,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30173
30525
  sessionId: command.sessionId
30174
30526
  });
30175
30527
  } catch (error61) {
30528
+ activities.flush();
30176
30529
  audit.record({
30177
30530
  commandId: command.commandId,
30178
30531
  event: "session_failed",
@@ -30211,7 +30564,10 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
30211
30564
  agent.capabilities
30212
30565
  );
30213
30566
  }
30214
- async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, signal) {
30567
+ async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, timer, signal) {
30568
+ const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
30569
+ publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
30570
+ });
30215
30571
  try {
30216
30572
  const deadline = Date.parse(command.executionDeadlineAt);
30217
30573
  const remaining = deadline - Date.now();
@@ -30295,30 +30651,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30295
30651
  commandId: command.commandId,
30296
30652
  cwd: resolved.cwd,
30297
30653
  images: attachments.images,
30298
- onActivity: (activity) => {
30299
- publisher.recordAttempt(
30300
- command.sessionId,
30301
- encodeAgentActivityEvent({
30302
- activityId: activity.activityId,
30303
- activityType: activity.activityType,
30304
- commandId: command.commandId,
30305
- content: activity.content,
30306
- detail: activity.detail,
30307
- input: activity.input,
30308
- kind: AGENT_ACTIVITY_KIND,
30309
- locations: activity.locations,
30310
- name: activity.name,
30311
- output: activity.output,
30312
- sequence: activity.sequence,
30313
- sessionId: command.sessionId,
30314
- status: activity.status,
30315
- title: activity.title,
30316
- toolKind: activity.toolKind
30317
- }),
30318
- attemptId
30319
- );
30320
- return Promise.resolve();
30321
- },
30654
+ onActivity: activities.record,
30322
30655
  onText: (text, messageId) => {
30323
30656
  publisher.recordAttempt(
30324
30657
  command.sessionId,
@@ -30338,6 +30671,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30338
30671
  sessionId: command.sessionId,
30339
30672
  signal: executionSignal
30340
30673
  });
30674
+ activities.flush();
30341
30675
  publisher.recordAttempt(
30342
30676
  command.sessionId,
30343
30677
  encodeSessionCompletedEvent({
@@ -30362,6 +30696,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30362
30696
  lease.release();
30363
30697
  }
30364
30698
  } catch (error61) {
30699
+ activities.flush();
30365
30700
  const failure = Date.parse(command.executionDeadlineAt) <= Date.now() ? new LocalBotAuthorizationError("turn_expired", "The Bot execution deadline has expired", {
30366
30701
  cause: error61
30367
30702
  }) : error61;
@@ -30391,25 +30726,25 @@ function waitForRelayRetry(signal, timer) {
30391
30726
  if (signal.aborted) {
30392
30727
  return Promise.resolve();
30393
30728
  }
30394
- return new Promise((resolve5) => {
30729
+ return new Promise((resolve6) => {
30395
30730
  const retry = timer.schedule(finish, RELAY_RECONNECT_DELAY_MILLISECONDS);
30396
30731
  function finish() {
30397
30732
  retry.cancel();
30398
30733
  signal.removeEventListener("abort", finish);
30399
- resolve5();
30734
+ resolve6();
30400
30735
  }
30401
30736
  signal.addEventListener("abort", finish, { once: true });
30402
30737
  });
30403
30738
  }
30404
- async function publishAgentCatalogUntilAvailable(config2, signal, timer) {
30739
+ async function publishAgentCatalogUntilAvailable(config2, diagnostics, signal, timer) {
30405
30740
  try {
30406
30741
  await publishAgentCatalog(config2, signal);
30407
30742
  return;
30408
- } catch {
30743
+ } catch (error61) {
30409
30744
  if (signal.aborted) {
30410
30745
  return;
30411
30746
  }
30412
- console.warn("Mobius daemon is waiting to publish its Agent catalog");
30747
+ diagnostics.catalogFailed(error61);
30413
30748
  }
30414
30749
  while (!signal.aborted) {
30415
30750
  await waitForRelayRetry(signal, timer);
@@ -30423,20 +30758,20 @@ async function publishAgentCatalogUntilAvailable(config2, signal, timer) {
30423
30758
  }
30424
30759
  }
30425
30760
  }
30426
- async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, onConnected, signal, timer) {
30761
+ async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, connectionEpoch, diagnostics, onConnected, signal, timer) {
30427
30762
  const relayUrl = new URL(config2.relayUrl);
30428
30763
  relayUrl.searchParams.set("liveness", "1");
30429
30764
  const socket = await connectRelay(relayUrl, signal);
30430
30765
  const reconnectingTurns = [...activeTurns.values()];
30766
+ diagnostics.connectionOpened(connectionEpoch);
30431
30767
  onConnected();
30432
30768
  const connectionController = new AbortController();
30433
30769
  const nativeRequests = /* @__PURE__ */ new Map();
30434
30770
  installationObserver.connect(socket);
30435
30771
  try {
30436
- await new Promise((resolve5, reject) => {
30772
+ await new Promise((resolve6, reject) => {
30437
30773
  let finished = false;
30438
30774
  const cleanup = () => {
30439
- connectionRefresh.close();
30440
30775
  connectionLiveness.close();
30441
30776
  signal.removeEventListener("abort", stop);
30442
30777
  socket.removeEventListener("close", handleClose);
@@ -30452,63 +30787,72 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30452
30787
  }
30453
30788
  finished = true;
30454
30789
  cleanup();
30455
- resolve5();
30790
+ resolve6();
30456
30791
  };
30457
- const rejectConnection = () => {
30792
+ const rejectConnection = (failure) => {
30458
30793
  if (finished) {
30459
30794
  return;
30460
30795
  }
30461
30796
  finished = true;
30462
30797
  cleanup();
30463
- reject(new Error("Relay WebSocket failed"));
30798
+ reject(failure);
30464
30799
  };
30465
30800
  const stop = () => {
30801
+ diagnostics.connectionClosed(connectionEpoch, 1e3, "Daemon stopped", "daemon_stop");
30466
30802
  closeRelaySocket(socket, 1e3, "Daemon stopped");
30467
30803
  resolveConnection();
30468
30804
  };
30469
- const refreshConnection = () => {
30470
- closeRelaySocket(socket, 1e3, "Refreshing idle relay connection");
30471
- resolveConnection();
30805
+ const failAndClose = (failure, code, reason, source) => {
30806
+ diagnostics.connectionFailed(connectionEpoch, failure, source);
30807
+ diagnostics.connectionClosed(connectionEpoch, code, reason, source);
30808
+ closeRelaySocket(socket, code, reason);
30809
+ rejectConnection(failure);
30472
30810
  };
30473
30811
  const connectionLiveness = new RelayConnectionLiveness(timer, {
30474
30812
  send: (message) => send(socket, message),
30475
- unresponsive: () => {
30813
+ unresponsive: (heartbeatId, timeoutMilliseconds) => {
30814
+ diagnostics.heartbeatTimedOut(connectionEpoch, heartbeatId, timeoutMilliseconds);
30815
+ diagnostics.connectionClosed(
30816
+ connectionEpoch,
30817
+ 4001,
30818
+ "Relay liveness check timed out",
30819
+ "heartbeat_timeout"
30820
+ );
30476
30821
  closeRelaySocket(socket, 4001, "Relay liveness check timed out");
30477
30822
  resolveConnection();
30478
30823
  }
30479
30824
  });
30480
- const connectionRefresh = new RelayConnectionRefresh(
30481
- timer,
30482
- () => activeTurns.size === 0,
30483
- refreshConnection
30484
- );
30485
30825
  signal.addEventListener("abort", stop, { once: true });
30486
- const handleClose = () => {
30826
+ const handleClose = (event) => {
30827
+ diagnostics.connectionClosed(connectionEpoch, event.code, event.reason, "relay");
30487
30828
  resolveConnection();
30488
30829
  };
30489
- const handleError = () => {
30830
+ const handleError = (event) => {
30490
30831
  if (signal.aborted) {
30491
30832
  resolveConnection();
30492
30833
  return;
30493
30834
  }
30494
- rejectConnection();
30835
+ const cause = Reflect.get(event, "error");
30836
+ const failure = new Error("Relay WebSocket emitted an error", { cause: cause ?? event });
30837
+ diagnostics.connectionFailed(connectionEpoch, failure, "socket_error");
30838
+ diagnostics.connectionClosed(connectionEpoch, 1006, "WebSocket error", "socket_error");
30839
+ rejectConnection(failure);
30495
30840
  };
30496
30841
  const handleMessage = (event) => {
30497
30842
  const encoded = event.data;
30498
30843
  if (typeof encoded !== "string") {
30499
- closeRelaySocket(socket, 4003, "Text message required");
30500
- rejectConnection();
30844
+ failAndClose(new Error("Relay sent a non-text message"), 4003, "Text message required", "protocol");
30501
30845
  return;
30502
30846
  }
30503
30847
  try {
30504
30848
  const kind = decodeMessageKind(encoded);
30505
30849
  if (kind === SESSION_EVENT_ACK_KIND) {
30506
- outbox.acknowledge(decodeSessionEventAcknowledgement(encoded));
30850
+ publisher.acknowledge(decodeSessionEventAcknowledgement(encoded));
30507
30851
  return;
30508
30852
  }
30509
30853
  if (kind === SESSION_EVENT_REJECTED_KIND) {
30510
30854
  const rejection = decodeSessionEventRejection(encoded);
30511
- if (outbox.quarantine(rejection)) {
30855
+ if (publisher.quarantine(rejection)) {
30512
30856
  audit.record({
30513
30857
  commandId: rejection.eventId,
30514
30858
  details: { code: rejection.code },
@@ -30632,7 +30976,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30632
30976
  try {
30633
30977
  operationSignal.throwIfAborted();
30634
30978
  if (previous.length > 0) {
30635
- await new Promise((resolve6, reject2) => {
30979
+ await new Promise((resolve7, reject2) => {
30636
30980
  const cancel = () => reject2(operationSignal.reason);
30637
30981
  operationSignal.addEventListener("abort", cancel, {
30638
30982
  once: true
@@ -30640,7 +30984,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30640
30984
  void Promise.all(previous.map((request) => request.completion)).then(
30641
30985
  () => {
30642
30986
  operationSignal.removeEventListener("abort", cancel);
30643
- resolve6();
30987
+ resolve7();
30644
30988
  },
30645
30989
  (error61) => {
30646
30990
  operationSignal.removeEventListener("abort", cancel);
@@ -30714,13 +31058,13 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30714
31058
  try {
30715
31059
  operationSignal.throwIfAborted();
30716
31060
  if (previous.length > 0) {
30717
- await new Promise((resolve6, reject2) => {
31061
+ await new Promise((resolve7, reject2) => {
30718
31062
  const cancel = () => reject2(operationSignal.reason);
30719
31063
  operationSignal.addEventListener("abort", cancel, { once: true });
30720
31064
  void Promise.all(previous.map((request) => request.completion)).then(
30721
31065
  () => {
30722
31066
  operationSignal.removeEventListener("abort", cancel);
30723
- resolve6();
31067
+ resolve7();
30724
31068
  },
30725
31069
  (error61) => {
30726
31070
  operationSignal.removeEventListener("abort", cancel);
@@ -30887,6 +31231,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30887
31231
  applicator,
30888
31232
  ipc,
30889
31233
  receipt2.attemptId,
31234
+ timer,
30890
31235
  turn2.signal
30891
31236
  ).catch((error61) => {
30892
31237
  console.error("Mobius daemon Bot turn failed", error61);
@@ -30896,7 +31241,6 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30896
31241
  activeAttemptIds.delete(command2.sessionId);
30897
31242
  }
30898
31243
  turn2.finish();
30899
- connectionRefresh.afterTurnFinished();
30900
31244
  });
30901
31245
  return;
30902
31246
  }
@@ -30925,6 +31269,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30925
31269
  audit,
30926
31270
  sessions,
30927
31271
  receipt.attemptId,
31272
+ timer,
30928
31273
  turn.signal
30929
31274
  ).catch((error61) => {
30930
31275
  console.error("Mobius daemon turn failed", error61);
@@ -30934,12 +31279,15 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30934
31279
  activeAttemptIds.delete(command.sessionId);
30935
31280
  }
30936
31281
  turn.finish();
30937
- connectionRefresh.afterTurnFinished();
30938
31282
  });
30939
31283
  } catch (error61) {
30940
31284
  console.error("Mobius daemon rejected a message", error61);
30941
- closeRelaySocket(socket, 4008, "Invalid protocol message");
30942
- rejectConnection();
31285
+ failAndClose(
31286
+ new Error("Relay sent an invalid protocol message", { cause: error61 }),
31287
+ 4008,
31288
+ "Invalid protocol message",
31289
+ "protocol"
31290
+ );
30943
31291
  }
30944
31292
  };
30945
31293
  socket.addEventListener("close", handleClose);
@@ -30951,21 +31299,18 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30951
31299
  return;
30952
31300
  }
30953
31301
  publisher.connect(socket);
30954
- void Promise.all(reconnectingTurns.map((turn) => turn.completion)).then(
30955
- connectionRefresh.afterTurnFinished
30956
- );
30957
31302
  },
30958
31303
  (error61) => {
30959
31304
  if (finished) {
30960
31305
  return;
30961
31306
  }
30962
31307
  console.error("Mobius daemon could not reconcile active Agent turns", error61);
30963
- closeRelaySocket(
30964
- socket,
31308
+ failAndClose(
31309
+ new Error("Active Agent turn reconciliation failed", { cause: error61 }),
30965
31310
  ACTIVE_TURN_RECONCILIATION_CLOSE_CODE,
30966
- "Active Agent turn reconciliation failed"
31311
+ "Active Agent turn reconciliation failed",
31312
+ "reconciliation"
30967
31313
  );
30968
- rejectConnection();
30969
31314
  }
30970
31315
  );
30971
31316
  if (signal.aborted) {
@@ -31009,6 +31354,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31009
31354
  const sessions = new AcpSessionManager(new DaemonAcpObserver(recordSessionEvent), sessionStore);
31010
31355
  const directories = new WorkspaceDirectoryService(config2.bindingId, config2.defaultWorkspace);
31011
31356
  const installationObserver = new DaemonInstallationObserver();
31357
+ const diagnostics = new RelayConnectionDiagnostics(audit, config2.channelId, config2.relayUrl);
31012
31358
  const nativeSessions = new NativeSessionService(config2, directories, sessions, audit);
31013
31359
  const installations = new AgentInstallationManager(
31014
31360
  specifications,
@@ -31016,7 +31362,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31016
31362
  installationObserver,
31017
31363
  installationAudit
31018
31364
  );
31019
- let retryAnnounced = false;
31365
+ let connectionEpoch = 0;
31020
31366
  let connectionAnnounced = false;
31021
31367
  const announceConnection = () => {
31022
31368
  if (connectionAnnounced) {
@@ -31032,10 +31378,11 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31032
31378
  `
31033
31379
  );
31034
31380
  };
31035
- const catalogPublication = publishAgentCatalogUntilAvailable(config2, signal, timer);
31381
+ const catalogPublication = publishAgentCatalogUntilAvailable(config2, diagnostics, signal, timer);
31036
31382
  try {
31037
31383
  await ipc.listen(signal);
31038
31384
  while (!signal.aborted) {
31385
+ connectionEpoch += 1;
31039
31386
  try {
31040
31387
  await runRelayConnection(
31041
31388
  config2,
@@ -31053,19 +31400,17 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31053
31400
  ipc,
31054
31401
  activeTurns,
31055
31402
  activeAttemptIds,
31403
+ connectionEpoch,
31404
+ diagnostics,
31056
31405
  announceConnection,
31057
31406
  signal,
31058
31407
  timer
31059
31408
  );
31060
- retryAnnounced = false;
31061
- } catch {
31409
+ } catch (error61) {
31062
31410
  if (signal.aborted) {
31063
31411
  return;
31064
31412
  }
31065
- if (!retryAnnounced) {
31066
- console.warn("Mobius daemon is waiting for the relay");
31067
- retryAnnounced = true;
31068
- }
31413
+ diagnostics.connectionFailed(connectionEpoch, error61, "connection");
31069
31414
  }
31070
31415
  await waitForRelayRetry(signal, timer);
31071
31416
  }
@@ -31102,6 +31447,147 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31102
31447
  }
31103
31448
  }
31104
31449
  }
31450
+
31451
+ // src/runtime.ts
31452
+ import { mkdir as mkdir5, realpath as realpath8, stat as stat8 } from "node:fs/promises";
31453
+ import { dirname as dirname8, join as join17, resolve as resolve5 } from "node:path";
31454
+ var APP_CREDENTIAL_PATTERN = /^mapp_[a-zA-Z0-9_-]{22}_[a-zA-Z0-9_-]{43}$/;
31455
+ var CONVERSATION_CREDENTIAL_PATTERN = /^mconv_[a-zA-Z0-9_-]{22}_[a-zA-Z0-9_-]{43}$/;
31456
+ var IDENTIFIER_PATTERN8 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
31457
+ var RUNTIME_CREDENTIAL_PATTERN = /^[a-zA-Z0-9_-]{32,256}$/;
31458
+ var MobiusRuntimeConfigurationError = class extends Error {
31459
+ code = "runtime_configuration_invalid";
31460
+ constructor(message, options = {}) {
31461
+ super(message, options);
31462
+ this.name = "MobiusRuntimeConfigurationError";
31463
+ }
31464
+ };
31465
+ var MobiusRuntimeState = class {
31466
+ };
31467
+ var IdleMobiusRuntimeState = class extends MobiusRuntimeState {
31468
+ };
31469
+ var RunningMobiusRuntimeState = class extends MobiusRuntimeState {
31470
+ };
31471
+ var FinishedMobiusRuntimeState = class extends MobiusRuntimeState {
31472
+ };
31473
+ function validateRuntimeRelay(relayUrl) {
31474
+ if (relayUrl.protocol !== "ws:" && relayUrl.protocol !== "wss:") {
31475
+ throw new MobiusRuntimeConfigurationError("The Runtime relay URL must use WebSocket transport");
31476
+ }
31477
+ const credential = relayUrl.searchParams.get("token") ?? "";
31478
+ if (credential.length === 0) {
31479
+ throw new MobiusRuntimeConfigurationError("The Runtime relay credential is required");
31480
+ }
31481
+ if (APP_CREDENTIAL_PATTERN.test(credential)) {
31482
+ throw new MobiusRuntimeConfigurationError("An AppCredential cannot be held by a local Runtime");
31483
+ }
31484
+ if (CONVERSATION_CREDENTIAL_PATTERN.test(credential)) {
31485
+ throw new MobiusRuntimeConfigurationError("A ConversationAccessToken cannot be held by a local Runtime");
31486
+ }
31487
+ if (!RUNTIME_CREDENTIAL_PATTERN.test(credential)) {
31488
+ throw new MobiusRuntimeConfigurationError("The Runtime relay credential is invalid");
31489
+ }
31490
+ }
31491
+ function validateRuntimeConfiguration(config2) {
31492
+ validateRuntimeRelay(config2.relayUrl);
31493
+ }
31494
+ function requiredEnvironmentValue(environment, name) {
31495
+ const value = environment[name]?.trim();
31496
+ if (value === void 0 || value.length === 0) {
31497
+ throw new MobiusRuntimeConfigurationError(`${name} is required`);
31498
+ }
31499
+ return value;
31500
+ }
31501
+ function relayUrlFromConnection(connection) {
31502
+ let relayUrl;
31503
+ try {
31504
+ relayUrl = new URL(connection.relayUrl);
31505
+ } catch (error61) {
31506
+ throw new MobiusRuntimeConfigurationError("The Runtime relay URL is invalid", { cause: error61 });
31507
+ }
31508
+ relayUrl.searchParams.set("token", connection.runtimeCredential);
31509
+ validateRuntimeRelay(relayUrl);
31510
+ return relayUrl;
31511
+ }
31512
+ function loadMobiusRuntimeConnection(environment = process.env) {
31513
+ const channelId = requiredEnvironmentValue(environment, "MOBIUS_CHANNEL_ID");
31514
+ if (!IDENTIFIER_PATTERN8.test(channelId)) {
31515
+ throw new MobiusRuntimeConfigurationError("MOBIUS_CHANNEL_ID is invalid");
31516
+ }
31517
+ return Object.freeze({
31518
+ auditDatabasePath: environment["MOBIUS_AUDIT_DATABASE"]?.trim() || join17(readStateDirectory(environment), "audit", `${channelId}.sqlite`),
31519
+ channelId,
31520
+ defaultWorkspace: loadDefaultWorkspace(environment),
31521
+ relayUrl: requiredEnvironmentValue(environment, "MOBIUS_RELAY_URL"),
31522
+ runtimeCredential: requiredEnvironmentValue(environment, "MOBIUS_RELAY_TOKEN")
31523
+ });
31524
+ }
31525
+ async function resolveWorkspace(path2) {
31526
+ const requested = resolve5(path2);
31527
+ try {
31528
+ await mkdir5(requested, { recursive: true, mode: 448 });
31529
+ const resolved = await realpath8(requested);
31530
+ const metadata = await stat8(resolved);
31531
+ if (!metadata.isDirectory()) {
31532
+ throw new MobiusRuntimeConfigurationError("The Runtime workspace is not a directory");
31533
+ }
31534
+ return resolved;
31535
+ } catch (error61) {
31536
+ if (error61 instanceof MobiusRuntimeConfigurationError) {
31537
+ throw error61;
31538
+ }
31539
+ throw new MobiusRuntimeConfigurationError("The Runtime workspace cannot be resolved", { cause: error61 });
31540
+ }
31541
+ }
31542
+ async function createMobiusRuntimeEngine(connection, signal, environment = process.env) {
31543
+ signal.throwIfAborted();
31544
+ const relayUrl = relayUrlFromConnection(connection);
31545
+ const defaultWorkspace = await resolveWorkspace(connection.defaultWorkspace);
31546
+ const auditDatabasePath = resolve5(connection.auditDatabasePath);
31547
+ await mkdir5(dirname8(auditDatabasePath), { recursive: true, mode: 448 });
31548
+ const inventory = await discoverLocalAgentInventory({
31549
+ defaultWorkspace,
31550
+ environment,
31551
+ signal
31552
+ });
31553
+ if (inventory.available.length + inventory.installable.length === 0) {
31554
+ throw new MobiusRuntimeConfigurationError("No supported Agent is available for the local Runtime");
31555
+ }
31556
+ return new MobiusRuntimeEngine(
31557
+ createDaemonConfig(
31558
+ {
31559
+ auditDatabasePath,
31560
+ channelId: connection.channelId,
31561
+ defaultWorkspace,
31562
+ relayUrl
31563
+ },
31564
+ inventory.readyAgents(),
31565
+ inventory.installable
31566
+ )
31567
+ );
31568
+ }
31569
+ var MobiusRuntimeEngine = class {
31570
+ #config;
31571
+ #state = new IdleMobiusRuntimeState();
31572
+ constructor(config2) {
31573
+ validateRuntimeConfiguration(config2);
31574
+ this.#config = config2;
31575
+ }
31576
+ run = async (signal) => {
31577
+ if (!(this.#state instanceof IdleMobiusRuntimeState)) {
31578
+ throw new MobiusRuntimeConfigurationError("A Mobius Runtime engine can only be started once");
31579
+ }
31580
+ signal.throwIfAborted();
31581
+ this.#state = new RunningMobiusRuntimeState();
31582
+ const audit = new AuditStore(this.#config.auditDatabasePath);
31583
+ try {
31584
+ await runDaemon(this.#config, audit, signal);
31585
+ } finally {
31586
+ audit.close();
31587
+ this.#state = new FinishedMobiusRuntimeState();
31588
+ }
31589
+ };
31590
+ };
31105
31591
  export {
31106
31592
  AcpExecutionError,
31107
31593
  AuditStore,
@@ -31109,12 +31595,18 @@ export {
31109
31595
  BotMessageCommandError,
31110
31596
  BotOutboundIpcServer,
31111
31597
  LocalAuthorizationError,
31598
+ MobiusRuntimeConfigurationError,
31599
+ MobiusRuntimeEngine,
31112
31600
  PairingClientError,
31113
31601
  createAcpClient,
31602
+ createMobiusRuntimeEngine,
31114
31603
  executeBotMessageCommand,
31115
31604
  loadDaemonConfig,
31605
+ loadMobiusRuntimeConnection,
31116
31606
  pairLocalMachine,
31607
+ pairMobiusRuntime,
31117
31608
  resumeLocalMachine,
31609
+ resumeMobiusRuntime,
31118
31610
  runAcpPrompt,
31119
31611
  runDaemon
31120
31612
  };