@moxt-ai/mobius 0.0.25 → 0.0.27

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 (38) hide show
  1. package/dist/agent-catalog/publish-agent-catalog.js +3 -3
  2. package/dist/agent-catalog/publish-agent-catalog.js.map +1 -1
  3. package/dist/cli.js +1194 -205
  4. package/dist/cli.js.map +4 -4
  5. package/dist/index.js +1186 -204
  6. package/dist/index.js.map +4 -4
  7. package/dist/network/environment-proxy.d.ts +1 -0
  8. package/dist/network/environment-proxy.d.ts.map +1 -1
  9. package/dist/network/environment-proxy.js +47 -0
  10. package/dist/network/environment-proxy.js.map +1 -1
  11. package/dist/prompt-artifact-resolver.d.ts.map +1 -1
  12. package/dist/prompt-artifact-resolver.js +7 -4
  13. package/dist/prompt-artifact-resolver.js.map +1 -1
  14. package/dist/relay-connection/connect.d.ts.map +1 -1
  15. package/dist/relay-connection/connect.js +19 -14
  16. package/dist/relay-connection/connect.js.map +1 -1
  17. package/dist/relay-connection/diagnostics.d.ts +11 -0
  18. package/dist/relay-connection/diagnostics.d.ts.map +1 -0
  19. package/dist/relay-connection/diagnostics.js +122 -0
  20. package/dist/relay-connection/diagnostics.js.map +1 -0
  21. package/dist/relay-connection/liveness.d.ts +1 -1
  22. package/dist/relay-connection/liveness.d.ts.map +1 -1
  23. package/dist/relay-connection/liveness.js +2 -1
  24. package/dist/relay-connection/liveness.js.map +1 -1
  25. package/dist/relay-connection/runtime-authentication.d.ts +3 -0
  26. package/dist/relay-connection/runtime-authentication.d.ts.map +1 -0
  27. package/dist/relay-connection/runtime-authentication.js +14 -0
  28. package/dist/relay-connection/runtime-authentication.js.map +1 -0
  29. package/dist/relay-connection/session-event-outbox.d.ts +34 -3
  30. package/dist/relay-connection/session-event-outbox.d.ts.map +1 -1
  31. package/dist/relay-connection/session-event-outbox.js +404 -13
  32. package/dist/relay-connection/session-event-outbox.js.map +1 -1
  33. package/dist/run-daemon.d.ts.map +1 -1
  34. package/dist/run-daemon.js +437 -151
  35. package/dist/run-daemon.js.map +1 -1
  36. package/dist/runtime.js +1186 -204
  37. package/dist/runtime.js.map +4 -4
  38. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import { mkdir as mkdir5 } from "node:fs/promises";
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@moxt-ai/mobius",
14
- version: "0.0.25",
14
+ version: "0.0.27",
15
15
  description: "Mobius CLI for pairing and running local AI agent services.",
16
16
  type: "module",
17
17
  bin: {
@@ -1578,7 +1578,53 @@ import { fetch } from "undici";
1578
1578
 
1579
1579
  // src/network/environment-proxy.ts
1580
1580
  import { EnvHttpProxyAgent } from "undici";
1581
+ var configuredHttpProxy = process.env["http_proxy"] ?? process.env["HTTP_PROXY"] ?? "";
1582
+ var configuredHttpsProxy = process.env["https_proxy"] ?? process.env["HTTPS_PROXY"] ?? configuredHttpProxy;
1581
1583
  var environmentProxyDispatcher = new EnvHttpProxyAgent();
1584
+ function bypassesProxy(target) {
1585
+ const noProxy = process.env["no_proxy"] ?? process.env["NO_PROXY"] ?? "";
1586
+ if (noProxy === "*") {
1587
+ return true;
1588
+ }
1589
+ const hostname4 = target.hostname.replace(/^\[|\]$/gu, "").toLowerCase();
1590
+ const defaultPort = target.protocol === "wss:" || target.protocol === "https:" ? 443 : 80;
1591
+ const port = Number.parseInt(target.port, 10) || defaultPort;
1592
+ for (const rawEntry of noProxy.split(/[,\s]/u)) {
1593
+ if (rawEntry.length === 0) {
1594
+ continue;
1595
+ }
1596
+ const parsed = rawEntry.match(/^(.+):(\d+)$/u);
1597
+ const entryHostname = (parsed?.[1] ?? rawEntry).replace(/^\*?\./u, "").toLowerCase();
1598
+ const entryPort = parsed?.[2] === void 0 ? 0 : Number.parseInt(parsed[2], 10);
1599
+ if (entryPort !== 0 && entryPort !== port) {
1600
+ continue;
1601
+ }
1602
+ if (hostname4 === entryHostname || hostname4.endsWith(`.${entryHostname}`)) {
1603
+ return true;
1604
+ }
1605
+ }
1606
+ return false;
1607
+ }
1608
+ function sanitizedProxyOrigin(proxyUrl) {
1609
+ try {
1610
+ return new URL(proxyUrl).origin;
1611
+ } catch {
1612
+ return "configured-invalid-url";
1613
+ }
1614
+ }
1615
+ function describeEnvironmentProxyRoute(target) {
1616
+ if (bypassesProxy(target)) {
1617
+ return { proxyRoute: "direct" };
1618
+ }
1619
+ const proxyUrl = target.protocol === "wss:" || target.protocol === "https:" ? configuredHttpsProxy : configuredHttpProxy;
1620
+ if (proxyUrl.length === 0) {
1621
+ return { proxyRoute: "direct" };
1622
+ }
1623
+ return {
1624
+ proxyOrigin: sanitizedProxyOrigin(proxyUrl),
1625
+ proxyRoute: "proxy"
1626
+ };
1627
+ }
1582
1628
 
1583
1629
  // src/agent-installation/managed-agent-archive.ts
1584
1630
  var ARCHIVE_DOWNLOAD_LIMIT_BYTES = 128 * 1024 * 1024;
@@ -22048,6 +22094,9 @@ var SESSION_CANCEL_KIND = "session.cancel";
22048
22094
  var RUNTIME_LIVENESS_READY_KIND = "runtime.liveness.ready";
22049
22095
  var RUNTIME_HEARTBEAT_KIND = "runtime.heartbeat";
22050
22096
  var RUNTIME_HEARTBEAT_ACK_KIND = "runtime.heartbeat.ack";
22097
+ var RUNTIME_DELIVERY_KIND = "runtime.delivery";
22098
+ var RUNTIME_DELIVERY_RECEIVED_KIND = "runtime.delivery.received";
22099
+ var RUNTIME_DELIVERY_COMMITTED_KIND = "runtime.delivery.committed";
22051
22100
  var SESSION_STARTED_KIND = "session.started";
22052
22101
  var AGENT_MESSAGE_DELTA_KIND = "agent.message.delta";
22053
22102
  var AGENT_ACTIVITY_KIND = "agent.activity";
@@ -22070,6 +22119,7 @@ var DIRECTORY_IDENTIFIER_PATTERN2 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,2047}$/;
22070
22119
  var MAX_PROMPT_LENGTH = 1e4;
22071
22120
  var MAX_PROMPT_ATTACHMENT_LENGTH = 6e6;
22072
22121
  var MAX_EVENT_TEXT_LENGTH = 1e5;
22122
+ var MAX_RUNTIME_DELIVERY_PAYLOAD_LENGTH = 8 * 1024 * 1024;
22073
22123
  var ACTIVITY_TYPES = /* @__PURE__ */ new Set(["plan", "progress", "reasoning", "tool"]);
22074
22124
  var ACTIVITY_STATUSES = /* @__PURE__ */ new Set(["pending", "running", "completed", "failed"]);
22075
22125
  var TOOL_KINDS = /* @__PURE__ */ new Set([
@@ -22348,6 +22398,37 @@ function decodeRuntimeHeartbeatAckEvent(encoded) {
22348
22398
  kind: RUNTIME_HEARTBEAT_ACK_KIND
22349
22399
  };
22350
22400
  }
22401
+ function decodeRuntimeDelivery(encoded) {
22402
+ const value = parseJson2(encoded);
22403
+ assertExactKeys(value, ["deliveryId", "kind", "payload", "sessionId"]);
22404
+ assertKind(value, RUNTIME_DELIVERY_KIND);
22405
+ return {
22406
+ deliveryId: readIdentifier(value, "deliveryId"),
22407
+ kind: RUNTIME_DELIVERY_KIND,
22408
+ payload: readString(value, "payload", MAX_RUNTIME_DELIVERY_PAYLOAD_LENGTH),
22409
+ sessionId: readIdentifier(value, "sessionId")
22410
+ };
22411
+ }
22412
+ function decodeRuntimeDeliveryReceived(encoded) {
22413
+ const value = parseJson2(encoded);
22414
+ assertExactKeys(value, ["deliveryId", "kind", "sessionId"]);
22415
+ assertKind(value, RUNTIME_DELIVERY_RECEIVED_KIND);
22416
+ return {
22417
+ deliveryId: readIdentifier(value, "deliveryId"),
22418
+ kind: RUNTIME_DELIVERY_RECEIVED_KIND,
22419
+ sessionId: readIdentifier(value, "sessionId")
22420
+ };
22421
+ }
22422
+ function decodeRuntimeDeliveryCommitted(encoded) {
22423
+ const value = parseJson2(encoded);
22424
+ assertExactKeys(value, ["deliveryId", "kind", "sessionId"]);
22425
+ assertKind(value, RUNTIME_DELIVERY_COMMITTED_KIND);
22426
+ return {
22427
+ deliveryId: readIdentifier(value, "deliveryId"),
22428
+ kind: RUNTIME_DELIVERY_COMMITTED_KIND,
22429
+ sessionId: readIdentifier(value, "sessionId")
22430
+ };
22431
+ }
22351
22432
  function decodeSessionStartedEvent(encoded) {
22352
22433
  const value = parseJson2(encoded);
22353
22434
  assertExactKeys(value, ["commandId", "kind", "attemptId", "sessionId"]);
@@ -22435,6 +22516,9 @@ function decodeSessionFailedEvent(encoded) {
22435
22516
  function encodeRuntimeHeartbeatEvent(message) {
22436
22517
  return JSON.stringify(decodeRuntimeHeartbeatEvent(JSON.stringify(message)));
22437
22518
  }
22519
+ function encodeRuntimeDeliveryReceived(message) {
22520
+ return JSON.stringify(decodeRuntimeDeliveryReceived(JSON.stringify(message)));
22521
+ }
22438
22522
  function encodeSessionStartedEvent(message) {
22439
22523
  return JSON.stringify(decodeSessionStartedEvent(JSON.stringify(message)));
22440
22524
  }
@@ -23133,7 +23217,8 @@ var SESSION_EVENT_COMMITTED_KIND = "session.event.committed";
23133
23217
  var SESSION_EVENT_REJECTED_KIND = "session.event.rejected";
23134
23218
  var SessionEventRejectionCode = {
23135
23219
  SessionDeleted: "session_deleted",
23136
- SessionNotFound: "session_not_found"
23220
+ SessionNotFound: "session_not_found",
23221
+ SessionRejected: "session_rejected"
23137
23222
  };
23138
23223
  var IDENTIFIER_PATTERN4 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
23139
23224
  var CURSOR_PATTERN = /^[A-Za-z0-9_-]{1,512}$/;
@@ -23205,6 +23290,7 @@ var encode4 = (message, schema) => {
23205
23290
  }
23206
23291
  return JSON.stringify(decoded.data);
23207
23292
  };
23293
+ var decodeSessionEventEnvelope = (encoded) => decode5(encoded, eventEnvelopeSchema);
23208
23294
  var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
23209
23295
  var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
23210
23296
  var decodeSessionEventRejection = (encoded) => decode5(encoded, eventRejectionSchema);
@@ -23390,12 +23476,12 @@ function decodePairingResponse(value, credentialField, expectedKeys) {
23390
23476
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
23391
23477
  }
23392
23478
  const channelId = value["channelId"];
23393
- const runtimeCredential = value[credentialField];
23479
+ const runtimeCredential2 = value[credentialField];
23394
23480
  const runtimeId = value["runtimeId"];
23395
- if (typeof channelId !== "string" || !IDENTIFIER_PATTERN5.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN5.test(runtimeId)) {
23481
+ if (typeof channelId !== "string" || !IDENTIFIER_PATTERN5.test(channelId) || typeof runtimeCredential2 !== "string" || !TOKEN_PATTERN2.test(runtimeCredential2) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN5.test(runtimeId)) {
23396
23482
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
23397
23483
  }
23398
- return { channelId, runtimeCredential, runtimeId };
23484
+ return { channelId, runtimeCredential: runtimeCredential2, runtimeId };
23399
23485
  }
23400
23486
  async function claimPairing(options, defaultWorkspace, localAgents, installableAgents, signal) {
23401
23487
  const agents = localAgents.map((agent) => ({
@@ -23430,7 +23516,7 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
23430
23516
  }
23431
23517
  return options.pairingId.length > 0 ? decodePairingResponse(value, "runtimeCredential", REDEEM_RESPONSE_KEYS) : decodePairingResponse(value, "daemonToken", CLAIM_RESPONSE_KEYS);
23432
23518
  }
23433
- async function persistConnection(channelId, relayOrigin, runtimeCredential, defaultWorkspace, environment) {
23519
+ async function persistConnection(channelId, relayOrigin, runtimeCredential2, defaultWorkspace, environment) {
23434
23520
  const connectionsDirectory = join14(readStateDirectory(environment), "connections");
23435
23521
  await mkdir3(connectionsDirectory, { mode: 448, recursive: true });
23436
23522
  const connectionPath = join14(connectionsDirectory, `${channelId}.json`);
@@ -23442,7 +23528,7 @@ async function persistConnection(channelId, relayOrigin, runtimeCredential, defa
23442
23528
  defaultWorkspace,
23443
23529
  channelId,
23444
23530
  relayOrigin: relayOrigin.toString(),
23445
- runtimeCredential
23531
+ runtimeCredential: runtimeCredential2
23446
23532
  },
23447
23533
  null,
23448
23534
  2
@@ -23459,16 +23545,16 @@ function decodeSavedConnection(value, expectedChannelId) {
23459
23545
  }
23460
23546
  const defaultWorkspace = value["defaultWorkspace"];
23461
23547
  const channelId = value["channelId"];
23462
- const runtimeCredential = value["runtimeCredential"] ?? value["daemonToken"];
23548
+ const runtimeCredential2 = value["runtimeCredential"] ?? value["daemonToken"];
23463
23549
  const relayOrigin = value["relayOrigin"];
23464
- if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN5.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof relayOrigin !== "string") {
23550
+ if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN5.test(channelId) || typeof runtimeCredential2 !== "string" || !TOKEN_PATTERN2.test(runtimeCredential2) || typeof relayOrigin !== "string") {
23465
23551
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
23466
23552
  }
23467
23553
  return {
23468
23554
  defaultWorkspace,
23469
23555
  channelId,
23470
23556
  relayOrigin: parseRelayOrigin(relayOrigin),
23471
- runtimeCredential
23557
+ runtimeCredential: runtimeCredential2
23472
23558
  };
23473
23559
  }
23474
23560
  async function readSavedConnection(channelId, environment) {
@@ -27001,6 +27087,23 @@ var AcpSessionManager = class {
27001
27087
 
27002
27088
  // src/agent-catalog/publish-agent-catalog.ts
27003
27089
  import { fetch as fetch3 } from "undici";
27090
+
27091
+ // src/relay-connection/runtime-authentication.ts
27092
+ function runtimeCredential(relayUrl) {
27093
+ return relayUrl.searchParams.get("token") ?? "";
27094
+ }
27095
+ function runtimeAuthorization(relayUrl) {
27096
+ const credential = runtimeCredential(relayUrl);
27097
+ return credential.length === 0 ? {} : { authorization: `Bearer ${credential}` };
27098
+ }
27099
+ function relayHttpEndpoint(relayUrl) {
27100
+ const endpoint = new URL(relayUrl);
27101
+ endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
27102
+ endpoint.search = "";
27103
+ return endpoint;
27104
+ }
27105
+
27106
+ // src/agent-catalog/publish-agent-catalog.ts
27004
27107
  var CATALOG_UPDATE_TIMEOUT_MILLISECONDS = 2e4;
27005
27108
  var AgentCatalogPublishError = class extends Error {
27006
27109
  code = "agent_catalog_update_failed";
@@ -27010,8 +27113,7 @@ var AgentCatalogPublishError = class extends Error {
27010
27113
  }
27011
27114
  };
27012
27115
  function catalogEndpoint(config2) {
27013
- const endpoint = new URL(config2.relayUrl);
27014
- endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
27116
+ const endpoint = relayHttpEndpoint(config2.relayUrl);
27015
27117
  if (endpoint.pathname.endsWith("/daemon")) {
27016
27118
  endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
27017
27119
  return endpoint;
@@ -27042,7 +27144,7 @@ async function publishAgentCatalog(config2, signal) {
27042
27144
  installableAgents: catalog.installableAgents
27043
27145
  }),
27044
27146
  dispatcher: environmentProxyDispatcher,
27045
- headers: { "content-type": "application/json" },
27147
+ headers: { ...runtimeAuthorization(config2.relayUrl), "content-type": "application/json" },
27046
27148
  method: "PUT",
27047
27149
  signal: requestSignal
27048
27150
  });
@@ -29084,8 +29186,7 @@ function parseReference(resource) {
29084
29186
  return new PromptArtifactReference(artifactId, byteLength, sha256, resource);
29085
29187
  }
29086
29188
  function downloadUrl(config2, command, reference) {
29087
- const url2 = new URL(config2.relayUrl);
29088
- url2.protocol = url2.protocol === "wss:" ? "https:" : "http:";
29189
+ const url2 = relayHttpEndpoint(config2.relayUrl);
29089
29190
  let channelPath = "";
29090
29191
  if (url2.pathname.endsWith("/daemon")) {
29091
29192
  channelPath = url2.pathname.slice(0, -"/daemon".length);
@@ -29097,7 +29198,6 @@ function downloadUrl(config2, command, reference) {
29097
29198
  channelPath = `${url2.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
29098
29199
  }
29099
29200
  url2.pathname = `${channelPath}/sessions/${encodeURIComponent(command.sessionId)}/commands/${encodeURIComponent(command.commandId)}/artifacts/${encodeURIComponent(reference.artifactId)}/content`;
29100
- url2.searchParams.delete("liveness");
29101
29201
  return url2;
29102
29202
  }
29103
29203
  async function responseBytes(response, expectedByteLength, signal) {
@@ -29148,7 +29248,11 @@ async function download(config2, command, reference, signal) {
29148
29248
  try {
29149
29249
  response = await fetch5(downloadUrl(config2, command, reference), {
29150
29250
  dispatcher: environmentProxyDispatcher,
29151
- headers: { accept: reference.resource.mimeType },
29251
+ headers: {
29252
+ accept: reference.resource.mimeType,
29253
+ "accept-encoding": "identity",
29254
+ ...runtimeAuthorization(config2.relayUrl)
29255
+ },
29152
29256
  signal: requestSignal
29153
29257
  });
29154
29258
  if (!response.ok) {
@@ -29246,9 +29350,11 @@ import { WebSocket } from "undici";
29246
29350
  var CONNECTION_TIMEOUT_MILLISECONDS = 15e3;
29247
29351
  var RelayConnectionError = class extends Error {
29248
29352
  code;
29249
- constructor(code, message, options = {}) {
29353
+ diagnosticDetails;
29354
+ constructor(code, message, diagnosticDetails = {}, options = {}) {
29250
29355
  super(message, options);
29251
29356
  this.code = code;
29357
+ this.diagnosticDetails = diagnosticDetails;
29252
29358
  this.name = "RelayConnectionError";
29253
29359
  }
29254
29360
  };
@@ -29256,12 +29362,8 @@ function connectRelay(url2, signal) {
29256
29362
  signal.throwIfAborted();
29257
29363
  return new Promise((resolve5, reject) => {
29258
29364
  const endpoint = new URL(url2);
29259
- const headers = {};
29260
- if (endpoint.pathname.endsWith("/v1/runtimes/connect")) {
29261
- const runtimeCredential = endpoint.searchParams.get("token") ?? "";
29262
- endpoint.searchParams.delete("token");
29263
- headers["authorization"] = `Bearer ${runtimeCredential}`;
29264
- }
29365
+ const headers = runtimeAuthorization(endpoint);
29366
+ endpoint.searchParams.delete("token");
29265
29367
  const socket = new WebSocket(endpoint, {
29266
29368
  dispatcher: environmentProxyDispatcher,
29267
29369
  headers
@@ -29286,16 +29388,34 @@ function connectRelay(url2, signal) {
29286
29388
  };
29287
29389
  const onAbort = () => {
29288
29390
  fail(
29289
- new RelayConnectionError("relay_connection_cancelled", "Relay connection was cancelled", {
29290
- cause: signal.reason
29291
- })
29391
+ new RelayConnectionError(
29392
+ "relay_connection_cancelled",
29393
+ "Relay connection was cancelled",
29394
+ {
29395
+ cancellation: "requested"
29396
+ },
29397
+ { cause: signal.reason }
29398
+ )
29292
29399
  );
29293
29400
  };
29294
- const onClose = () => {
29295
- fail(new RelayConnectionError("relay_connection_closed", "Relay connection closed before it was ready"));
29401
+ const onClose = (event) => {
29402
+ fail(
29403
+ new RelayConnectionError("relay_connection_closed", "Relay connection closed before it was ready", {
29404
+ closeCode: event.code.toString(),
29405
+ closeReason: event.reason
29406
+ })
29407
+ );
29296
29408
  };
29297
- const onError = () => {
29298
- fail(new RelayConnectionError("relay_connection_failed", "Relay connection failed"));
29409
+ const onError = (event) => {
29410
+ const cause = Reflect.get(event, "error");
29411
+ fail(
29412
+ new RelayConnectionError(
29413
+ "relay_connection_failed",
29414
+ "Relay connection failed",
29415
+ {},
29416
+ { cause: cause ?? event }
29417
+ )
29418
+ );
29299
29419
  };
29300
29420
  const onOpen = () => {
29301
29421
  if (finished) {
@@ -29306,7 +29426,11 @@ function connectRelay(url2, signal) {
29306
29426
  resolve5(socket);
29307
29427
  };
29308
29428
  function onTimeout() {
29309
- fail(new RelayConnectionError("relay_connection_timeout", "Relay connection timed out"));
29429
+ fail(
29430
+ new RelayConnectionError("relay_connection_timeout", "Relay connection timed out", {
29431
+ timeoutMilliseconds: CONNECTION_TIMEOUT_MILLISECONDS.toString()
29432
+ })
29433
+ );
29310
29434
  }
29311
29435
  signal.addEventListener("abort", onAbort, { once: true });
29312
29436
  socket.addEventListener("close", onClose, { once: true });
@@ -29318,6 +29442,142 @@ function connectRelay(url2, signal) {
29318
29442
  });
29319
29443
  }
29320
29444
 
29445
+ // src/relay-connection/diagnostics.ts
29446
+ var MAX_DIAGNOSTIC_TEXT_LENGTH = 256;
29447
+ function sanitizeDiagnosticText(value) {
29448
+ const printable = Array.from(value, (character) => {
29449
+ const code = character.charCodeAt(0);
29450
+ return code < 32 || code === 127 ? " " : character;
29451
+ }).join("");
29452
+ const withoutSecrets = printable.replace(/([?&](?:access_token|authorization|credential|key|secret|token)=)[^&\s]*/giu, "$1[redacted]").replace(/(?:https?|wss?):\/\/[^\s)]+/giu, (encodedUrl) => {
29453
+ try {
29454
+ return new URL(encodedUrl).origin;
29455
+ } catch {
29456
+ return "[redacted-url]";
29457
+ }
29458
+ });
29459
+ return withoutSecrets.slice(0, MAX_DIAGNOSTIC_TEXT_LENGTH);
29460
+ }
29461
+ function addFailureDetails(details, failure) {
29462
+ let current = failure;
29463
+ for (let depth = 0; depth < 3 && current instanceof Error; depth += 1) {
29464
+ const prefix = depth === 0 ? "failure" : `cause${depth.toString()}`;
29465
+ details[`${prefix}Name`] = current.name;
29466
+ details[`${prefix}Message`] = sanitizeDiagnosticText(current.message);
29467
+ const code = Reflect.get(current, "code");
29468
+ if (typeof code === "string" || typeof code === "number") {
29469
+ details[`${prefix}Code`] = String(code);
29470
+ }
29471
+ const statusCode = Reflect.get(current, "statusCode");
29472
+ if (typeof statusCode === "string" || typeof statusCode === "number") {
29473
+ details[`${prefix}StatusCode`] = String(statusCode);
29474
+ }
29475
+ const diagnosticDetails = Reflect.get(current, "diagnosticDetails");
29476
+ if (typeof diagnosticDetails === "object" && diagnosticDetails !== null) {
29477
+ for (const [key, value] of Object.entries(diagnosticDetails)) {
29478
+ if (typeof value === "string") {
29479
+ details[key] = sanitizeDiagnosticText(value);
29480
+ }
29481
+ }
29482
+ }
29483
+ current = current.cause;
29484
+ }
29485
+ }
29486
+ var RelayConnectionDiagnostics = class {
29487
+ #audit;
29488
+ #channelId;
29489
+ #lastClosedEpoch = 0;
29490
+ #lastFailedEpoch = 0;
29491
+ #relayOrigin;
29492
+ #route;
29493
+ constructor(audit, channelId, relayUrl) {
29494
+ this.#audit = audit;
29495
+ this.#channelId = channelId;
29496
+ this.#relayOrigin = relayUrl.origin;
29497
+ this.#route = describeEnvironmentProxyRoute(relayUrl);
29498
+ }
29499
+ catalogFailed = (failure) => {
29500
+ const details = { ...this.#route, relayOrigin: this.#relayOrigin };
29501
+ addFailureDetails(details, failure);
29502
+ this.#record("agent_catalog_publish_failed", "catalog", details, true);
29503
+ };
29504
+ connectionClosed = (connectionEpoch, code, reason, source) => {
29505
+ if (this.#lastClosedEpoch === connectionEpoch) {
29506
+ return;
29507
+ }
29508
+ this.#lastClosedEpoch = connectionEpoch;
29509
+ this.#record(
29510
+ "relay_connection_closed",
29511
+ connectionEpoch.toString(),
29512
+ {
29513
+ closeCode: code.toString(),
29514
+ closeReason: sanitizeDiagnosticText(reason),
29515
+ connectionEpoch: connectionEpoch.toString(),
29516
+ relayOrigin: this.#relayOrigin,
29517
+ source,
29518
+ ...this.#route
29519
+ },
29520
+ true
29521
+ );
29522
+ };
29523
+ connectionFailed = (connectionEpoch, failure, source) => {
29524
+ if (this.#lastFailedEpoch === connectionEpoch) {
29525
+ return;
29526
+ }
29527
+ this.#lastFailedEpoch = connectionEpoch;
29528
+ const details = {
29529
+ connectionEpoch: connectionEpoch.toString(),
29530
+ relayOrigin: this.#relayOrigin,
29531
+ source,
29532
+ ...this.#route
29533
+ };
29534
+ addFailureDetails(details, failure);
29535
+ this.#record("relay_connection_failed", connectionEpoch.toString(), details, true);
29536
+ };
29537
+ connectionOpened = (connectionEpoch) => {
29538
+ this.#record(
29539
+ "relay_connection_opened",
29540
+ connectionEpoch.toString(),
29541
+ {
29542
+ connectionEpoch: connectionEpoch.toString(),
29543
+ relayOrigin: this.#relayOrigin,
29544
+ ...this.#route
29545
+ },
29546
+ false
29547
+ );
29548
+ };
29549
+ heartbeatTimedOut = (connectionEpoch, heartbeatId, timeoutMilliseconds) => {
29550
+ this.#record(
29551
+ "relay_heartbeat_timed_out",
29552
+ connectionEpoch.toString(),
29553
+ {
29554
+ connectionEpoch: connectionEpoch.toString(),
29555
+ heartbeatId,
29556
+ relayOrigin: this.#relayOrigin,
29557
+ timeoutMilliseconds: timeoutMilliseconds.toString(),
29558
+ ...this.#route
29559
+ },
29560
+ true
29561
+ );
29562
+ };
29563
+ #record = (event, commandId, details, warning) => {
29564
+ this.#audit.record({
29565
+ commandId,
29566
+ details,
29567
+ event,
29568
+ sessionId: this.#channelId
29569
+ });
29570
+ const encoded = JSON.stringify({ channelId: this.#channelId, event, ...details });
29571
+ if (warning) {
29572
+ process.stderr.write(`Mobius relay diagnostic ${encoded}
29573
+ `);
29574
+ return;
29575
+ }
29576
+ process.stdout.write(`Mobius relay diagnostic ${encoded}
29577
+ `);
29578
+ };
29579
+ };
29580
+
29321
29581
  // src/relay-connection/liveness.ts
29322
29582
  import { randomUUID as randomUUID5 } from "node:crypto";
29323
29583
  var HEARTBEAT_ACK_TIMEOUT_MILLISECONDS = 1e4;
@@ -29394,9 +29654,10 @@ var RelayConnectionLiveness = class {
29394
29654
  if (!(this.#state instanceof AwaitingHeartbeatAckState)) {
29395
29655
  throw new Error("Heartbeat timeout occurred in an invalid state");
29396
29656
  }
29657
+ const heartbeatId = this.#state.heartbeatId;
29397
29658
  this.#state.timeout.cancel();
29398
29659
  this.#state = new FinishedRelayLivenessState();
29399
- this.#transport.unresponsive();
29660
+ this.#transport.unresponsive(heartbeatId, HEARTBEAT_ACK_TIMEOUT_MILLISECONDS);
29400
29661
  };
29401
29662
  #sendHeartbeat = () => {
29402
29663
  if (this.#state instanceof WaitingForHeartbeatState) {
@@ -29425,6 +29686,42 @@ var RelayConnectionLiveness = class {
29425
29686
  import { mkdirSync as mkdirSync3 } from "node:fs";
29426
29687
  import { dirname as dirname7 } from "node:path";
29427
29688
  import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
29689
+ var DEFAULT_OUTBOX_LIMITS = {
29690
+ criticalEventBytes: 136 * 1024 * 1024,
29691
+ criticalEventCount: 10128,
29692
+ runtimeDeliveryBytes: 64 * 1024 * 1024,
29693
+ runtimeDeliveryCount: 64,
29694
+ standardEventBytes: 128 * 1024 * 1024,
29695
+ standardEventCount: 1e4
29696
+ };
29697
+ var SessionEventOutboxCapacityError = class extends Error {
29698
+ code = "session_event_outbox_full";
29699
+ constructor() {
29700
+ super("The durable Session event outbox is full");
29701
+ this.name = "SessionEventOutboxCapacityError";
29702
+ }
29703
+ };
29704
+ var RuntimeDeliveryInboxCapacityError = class extends Error {
29705
+ code = "runtime_delivery_inbox_full";
29706
+ constructor() {
29707
+ super("The durable Runtime delivery inbox is full");
29708
+ this.name = "RuntimeDeliveryInboxCapacityError";
29709
+ }
29710
+ };
29711
+ var StoredRuntimeDelivery = class {
29712
+ deliveryId;
29713
+ duplicate;
29714
+ payload;
29715
+ sessionId;
29716
+ state;
29717
+ constructor(deliveryId, sessionId, payload, state, duplicate) {
29718
+ this.deliveryId = deliveryId;
29719
+ this.duplicate = duplicate;
29720
+ this.payload = payload;
29721
+ this.sessionId = sessionId;
29722
+ this.state = state;
29723
+ }
29724
+ };
29428
29725
  function readPendingEnvelope(row) {
29429
29726
  if (typeof row !== "object" || row === null || Array.isArray(row)) {
29430
29727
  throw new Error("SQLite returned an invalid session event outbox row");
@@ -29435,9 +29732,26 @@ function readPendingEnvelope(row) {
29435
29732
  }
29436
29733
  return envelope2;
29437
29734
  }
29735
+ function rowNumber(row, field) {
29736
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
29737
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
29738
+ throw new Error(`SQLite returned an invalid ${field} value`);
29739
+ }
29740
+ return value;
29741
+ }
29742
+ function rowString2(row, field) {
29743
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
29744
+ if (typeof value !== "string" || value.length === 0) {
29745
+ throw new Error(`SQLite returned an invalid ${field} value`);
29746
+ }
29747
+ return value;
29748
+ }
29438
29749
  var SessionEventOutbox = class {
29439
29750
  #database;
29440
- constructor(path2) {
29751
+ #limits;
29752
+ constructor(path2, limits = DEFAULT_OUTBOX_LIMITS) {
29753
+ validateLimits(limits);
29754
+ this.#limits = limits;
29441
29755
  mkdirSync3(dirname7(path2), { recursive: true });
29442
29756
  this.#database = new DatabaseSync4(path2);
29443
29757
  this.#database.exec(`
@@ -29445,6 +29759,8 @@ var SessionEventOutbox = class {
29445
29759
  ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
29446
29760
  event_id TEXT NOT NULL UNIQUE,
29447
29761
  session_id TEXT NOT NULL,
29762
+ source_key TEXT NOT NULL,
29763
+ byte_length INTEGER NOT NULL,
29448
29764
  envelope_json TEXT NOT NULL
29449
29765
  ) STRICT;
29450
29766
  CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
@@ -29455,7 +29771,7 @@ var SessionEventOutbox = class {
29455
29771
  session_id TEXT NOT NULL,
29456
29772
  envelope_json TEXT NOT NULL,
29457
29773
  rejection_code TEXT NOT NULL CHECK (
29458
- rejection_code IN ('session_deleted', 'session_not_found')
29774
+ rejection_code IN ('session_deleted', 'session_not_found', 'session_rejected')
29459
29775
  )
29460
29776
  ) STRICT;
29461
29777
  CREATE TABLE IF NOT EXISTS relay_session_event_sources (
@@ -29471,6 +29787,92 @@ var SessionEventOutbox = class {
29471
29787
  state IN ('dispatching', 'running', 'succeeded', 'failed', 'cancelled')
29472
29788
  )
29473
29789
  ) STRICT;
29790
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
29791
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
29792
+ event_count INTEGER NOT NULL,
29793
+ total_bytes INTEGER NOT NULL
29794
+ ) STRICT;
29795
+ INSERT OR IGNORE INTO relay_session_event_outbox_state (
29796
+ singleton, event_count, total_bytes
29797
+ ) VALUES (1, 0, 0);
29798
+ CREATE TABLE IF NOT EXISTS relay_runtime_deliveries (
29799
+ delivery_id TEXT PRIMARY KEY,
29800
+ session_id TEXT NOT NULL,
29801
+ payload_json TEXT NOT NULL,
29802
+ payload_bytes INTEGER NOT NULL,
29803
+ state TEXT NOT NULL CHECK (
29804
+ state IN ('received', 'relay_committed', 'processed')
29805
+ ),
29806
+ received_at INTEGER NOT NULL
29807
+ ) STRICT;
29808
+ `);
29809
+ const deadLetterTable = this.#database.prepare(
29810
+ `SELECT sql FROM sqlite_master
29811
+ WHERE type = 'table' AND name = 'relay_session_event_dead_letters'`
29812
+ ).get();
29813
+ const deadLetterSql = typeof deadLetterTable === "object" && deadLetterTable !== null ? Reflect.get(deadLetterTable, "sql") : void 0;
29814
+ if (typeof deadLetterSql !== "string" || !deadLetterSql.includes("'session_rejected'")) {
29815
+ this.#database.exec(`
29816
+ BEGIN IMMEDIATE;
29817
+ ALTER TABLE relay_session_event_dead_letters
29818
+ RENAME TO relay_session_event_dead_letters_legacy;
29819
+ CREATE TABLE relay_session_event_dead_letters (
29820
+ ordinal INTEGER PRIMARY KEY,
29821
+ event_id TEXT NOT NULL UNIQUE,
29822
+ session_id TEXT NOT NULL,
29823
+ envelope_json TEXT NOT NULL,
29824
+ rejection_code TEXT NOT NULL CHECK (
29825
+ rejection_code IN ('session_deleted', 'session_not_found', 'session_rejected')
29826
+ )
29827
+ ) STRICT;
29828
+ INSERT INTO relay_session_event_dead_letters (
29829
+ ordinal, event_id, session_id, envelope_json, rejection_code
29830
+ )
29831
+ SELECT ordinal, event_id, session_id, envelope_json, rejection_code
29832
+ FROM relay_session_event_dead_letters_legacy;
29833
+ DROP TABLE relay_session_event_dead_letters_legacy;
29834
+ COMMIT;
29835
+ `);
29836
+ }
29837
+ const outboxColumns = this.#database.prepare("PRAGMA table_info(relay_session_event_outbox)").all();
29838
+ const hasSourceKey = outboxColumns.some(
29839
+ (column) => typeof column === "object" && column !== null && Reflect.get(column, "name") === "source_key"
29840
+ );
29841
+ if (!hasSourceKey) {
29842
+ this.#database.exec(`
29843
+ ALTER TABLE relay_session_event_outbox
29844
+ ADD COLUMN source_key TEXT NOT NULL DEFAULT '';
29845
+ `);
29846
+ }
29847
+ const hasByteLength = outboxColumns.some(
29848
+ (column) => typeof column === "object" && column !== null && Reflect.get(column, "name") === "byte_length"
29849
+ );
29850
+ if (!hasByteLength) {
29851
+ this.#database.exec(`
29852
+ ALTER TABLE relay_session_event_outbox
29853
+ ADD COLUMN byte_length INTEGER NOT NULL DEFAULT 0;
29854
+ `);
29855
+ }
29856
+ this.#database.exec(`
29857
+ UPDATE relay_session_event_outbox
29858
+ SET source_key = CASE json_extract(envelope_json, '$.source.kind')
29859
+ WHEN 'attempt' THEN
29860
+ 'attempt:' || session_id || ':' || json_extract(envelope_json, '$.source.attemptId')
29861
+ ELSE 'runtime:' || session_id
29862
+ END
29863
+ WHERE source_key = '';
29864
+ UPDATE relay_session_event_outbox
29865
+ SET byte_length = length(CAST(envelope_json AS BLOB))
29866
+ WHERE byte_length = 0;
29867
+ UPDATE relay_session_event_outbox_state
29868
+ SET event_count = (SELECT COUNT(*) FROM relay_session_event_outbox),
29869
+ total_bytes = (
29870
+ SELECT COALESCE(SUM(byte_length), 0)
29871
+ FROM relay_session_event_outbox
29872
+ )
29873
+ WHERE singleton = 1;
29874
+ CREATE INDEX IF NOT EXISTS relay_session_event_outbox_source_order
29875
+ ON relay_session_event_outbox(source_key, ordinal);
29474
29876
  `);
29475
29877
  }
29476
29878
  claimExecution = (commandId, sessionId, commandJson) => {
@@ -29529,15 +29931,19 @@ var SessionEventOutbox = class {
29529
29931
  const state = typeof row === "object" && row !== null ? Reflect.get(row, "state") : void 0;
29530
29932
  return state === "succeeded" || state === "failed" || state === "cancelled";
29531
29933
  };
29532
- acknowledge = (acknowledgement) => {
29533
- this.#database.prepare(
29534
- `DELETE FROM relay_session_event_outbox
29535
- WHERE event_id = ? AND session_id = ?`
29536
- ).run(acknowledgement.eventId, acknowledgement.sessionId);
29537
- };
29934
+ acknowledge = (acknowledgement) => this.#removeAcknowledgedEvent(acknowledgement.eventId, acknowledgement.sessionId);
29538
29935
  quarantine = (rejection) => {
29539
29936
  this.#database.exec("BEGIN IMMEDIATE");
29540
29937
  try {
29938
+ const stored = this.#database.prepare(
29939
+ `SELECT byte_length FROM relay_session_event_outbox
29940
+ WHERE event_id = ? AND session_id = ?`
29941
+ ).get(rejection.eventId, rejection.sessionId);
29942
+ if (stored === void 0) {
29943
+ this.#database.exec("COMMIT");
29944
+ return false;
29945
+ }
29946
+ const byteLength = rowNumber(stored, "byte_length");
29541
29947
  const insertion = this.#database.prepare(
29542
29948
  `INSERT INTO relay_session_event_dead_letters (
29543
29949
  ordinal, event_id, session_id, envelope_json, rejection_code
@@ -29557,6 +29963,7 @@ var SessionEventOutbox = class {
29557
29963
  if (deletion.changes !== 1) {
29558
29964
  throw new Error("The rejected session event could not be removed from the durable outbox");
29559
29965
  }
29966
+ this.#decrementEventState(byteLength);
29560
29967
  this.#database.exec("COMMIT");
29561
29968
  return true;
29562
29969
  } catch (error61) {
@@ -29568,7 +29975,129 @@ var SessionEventOutbox = class {
29568
29975
  this.#database.close();
29569
29976
  };
29570
29977
  enqueue = (sessionId, payload, attemptId = null) => {
29571
- const sourceKey = attemptId === null ? `runtime:${sessionId}` : `attempt:${sessionId}:${attemptId}`;
29978
+ return this.#enqueueBounded(
29979
+ sessionId,
29980
+ payload,
29981
+ attemptId ?? "",
29982
+ this.#limits.standardEventCount,
29983
+ this.#limits.standardEventBytes
29984
+ );
29985
+ };
29986
+ enqueueCritical = (sessionId, payload, attemptId = "") => this.#enqueueBounded(
29987
+ sessionId,
29988
+ payload,
29989
+ attemptId,
29990
+ this.#limits.criticalEventCount,
29991
+ this.#limits.criticalEventBytes
29992
+ );
29993
+ claimRuntimeDelivery = (delivery) => {
29994
+ const payloadBytes = new TextEncoder().encode(delivery.payload).byteLength;
29995
+ this.#database.exec("BEGIN IMMEDIATE");
29996
+ try {
29997
+ const existing = this.#database.prepare(
29998
+ `SELECT session_id, payload_json, state
29999
+ FROM relay_runtime_deliveries
30000
+ WHERE delivery_id = ?`
30001
+ ).get(delivery.deliveryId);
30002
+ if (existing !== void 0) {
30003
+ const sessionId = rowString2(existing, "session_id");
30004
+ const payload = rowString2(existing, "payload_json");
30005
+ const state = runtimeDeliveryState(existing);
30006
+ if (sessionId !== delivery.sessionId || payload !== delivery.payload) {
30007
+ throw new Error("The Runtime delivery identity conflicts with its durable receipt");
30008
+ }
30009
+ this.#database.exec("COMMIT");
30010
+ return new StoredRuntimeDelivery(delivery.deliveryId, sessionId, payload, state, true);
30011
+ }
30012
+ const usage = this.#database.prepare(
30013
+ `SELECT COUNT(*) AS delivery_count,
30014
+ COALESCE(SUM(payload_bytes), 0) AS delivery_bytes
30015
+ FROM relay_runtime_deliveries`
30016
+ ).get();
30017
+ if (rowNumber(usage, "delivery_count") + 1 > this.#limits.runtimeDeliveryCount || rowNumber(usage, "delivery_bytes") + payloadBytes > this.#limits.runtimeDeliveryBytes) {
30018
+ throw new RuntimeDeliveryInboxCapacityError();
30019
+ }
30020
+ this.#database.prepare(
30021
+ `INSERT INTO relay_runtime_deliveries (
30022
+ delivery_id, session_id, payload_json, payload_bytes, state, received_at
30023
+ ) VALUES (?, ?, ?, ?, 'received', ?)`
30024
+ ).run(delivery.deliveryId, delivery.sessionId, delivery.payload, payloadBytes, Date.now());
30025
+ this.#database.exec("COMMIT");
30026
+ return new StoredRuntimeDelivery(
30027
+ delivery.deliveryId,
30028
+ delivery.sessionId,
30029
+ delivery.payload,
30030
+ "received",
30031
+ false
30032
+ );
30033
+ } catch (error61) {
30034
+ this.#database.exec("ROLLBACK");
30035
+ throw error61;
30036
+ }
30037
+ };
30038
+ markRuntimeDeliveryCommitted = (deliveryId, sessionId) => {
30039
+ this.#database.exec("BEGIN IMMEDIATE");
30040
+ try {
30041
+ const row = this.#database.prepare(
30042
+ `SELECT state FROM relay_runtime_deliveries
30043
+ WHERE delivery_id = ? AND session_id = ?`
30044
+ ).get(deliveryId, sessionId);
30045
+ if (row === void 0) {
30046
+ this.#database.exec("COMMIT");
30047
+ return false;
30048
+ }
30049
+ const state = runtimeDeliveryState(row);
30050
+ if (state === "processed") {
30051
+ this.#database.prepare(`DELETE FROM relay_runtime_deliveries WHERE delivery_id = ?`).run(deliveryId);
30052
+ } else if (state === "received") {
30053
+ this.#database.prepare(`UPDATE relay_runtime_deliveries SET state = 'relay_committed' WHERE delivery_id = ?`).run(deliveryId);
30054
+ }
30055
+ this.#database.exec("COMMIT");
30056
+ return true;
30057
+ } catch (error61) {
30058
+ this.#database.exec("ROLLBACK");
30059
+ throw error61;
30060
+ }
30061
+ };
30062
+ markRuntimeDeliveryProcessed = (deliveryId, sessionId) => {
30063
+ this.#database.exec("BEGIN IMMEDIATE");
30064
+ try {
30065
+ const row = this.#database.prepare(
30066
+ `SELECT state FROM relay_runtime_deliveries
30067
+ WHERE delivery_id = ? AND session_id = ?`
30068
+ ).get(deliveryId, sessionId);
30069
+ if (row === void 0) {
30070
+ this.#database.exec("COMMIT");
30071
+ return false;
30072
+ }
30073
+ const state = runtimeDeliveryState(row);
30074
+ if (state === "relay_committed") {
30075
+ this.#database.prepare(`DELETE FROM relay_runtime_deliveries WHERE delivery_id = ?`).run(deliveryId);
30076
+ } else if (state === "received") {
30077
+ this.#database.prepare(`UPDATE relay_runtime_deliveries SET state = 'processed' WHERE delivery_id = ?`).run(deliveryId);
30078
+ }
30079
+ this.#database.exec("COMMIT");
30080
+ return true;
30081
+ } catch (error61) {
30082
+ this.#database.exec("ROLLBACK");
30083
+ throw error61;
30084
+ }
30085
+ };
30086
+ runtimeDeliveries = () => this.#database.prepare(
30087
+ `SELECT delivery_id, session_id, payload_json, state
30088
+ FROM relay_runtime_deliveries
30089
+ ORDER BY received_at, delivery_id`
30090
+ ).all().map(
30091
+ (row) => new StoredRuntimeDelivery(
30092
+ rowString2(row, "delivery_id"),
30093
+ rowString2(row, "session_id"),
30094
+ rowString2(row, "payload_json"),
30095
+ runtimeDeliveryState(row),
30096
+ true
30097
+ )
30098
+ );
30099
+ #enqueueBounded = (sessionId, payload, attemptId, maximumEventCount, maximumEventBytes) => {
30100
+ const sourceKey = attemptId.length === 0 ? `runtime:${sessionId}` : `attempt:${sessionId}:${attemptId}`;
29572
30101
  this.#database.exec("BEGIN IMMEDIATE");
29573
30102
  try {
29574
30103
  const row = this.#database.prepare(
@@ -29585,13 +30114,22 @@ var SessionEventOutbox = class {
29585
30114
  kind: SESSION_EVENT_KIND,
29586
30115
  payload,
29587
30116
  sessionId,
29588
- source: attemptId === null ? { kind: "runtime", sourceSeq: nextSourceSeq.toString() } : {
30117
+ source: attemptId.length === 0 ? { kind: "runtime", sourceSeq: nextSourceSeq.toString() } : {
29589
30118
  attemptId,
29590
30119
  kind: "attempt",
29591
30120
  sourceSeq: nextSourceSeq.toString()
29592
30121
  }
29593
30122
  };
29594
30123
  const encoded = encodeSessionEventEnvelope(envelope2);
30124
+ const byteLength = new TextEncoder().encode(encoded).byteLength;
30125
+ const state = this.#database.prepare(
30126
+ `SELECT event_count, total_bytes
30127
+ FROM relay_session_event_outbox_state
30128
+ WHERE singleton = 1`
30129
+ ).get();
30130
+ if (rowNumber(state, "event_count") + 1 > maximumEventCount || rowNumber(state, "total_bytes") + byteLength > maximumEventBytes) {
30131
+ throw new SessionEventOutboxCapacityError();
30132
+ }
29595
30133
  this.#database.prepare(
29596
30134
  `INSERT INTO relay_session_event_sources (
29597
30135
  source_key, next_source_seq
@@ -29601,9 +30139,15 @@ var SessionEventOutbox = class {
29601
30139
  ).run(sourceKey, nextSourceSeq + 1);
29602
30140
  this.#database.prepare(
29603
30141
  `INSERT INTO relay_session_event_outbox (
29604
- event_id, session_id, envelope_json
29605
- ) VALUES (?, ?, ?)`
29606
- ).run(envelope2.eventId, envelope2.sessionId, encoded);
30142
+ event_id, session_id, source_key, byte_length, envelope_json
30143
+ ) VALUES (?, ?, ?, ?, ?)`
30144
+ ).run(envelope2.eventId, envelope2.sessionId, sourceKey, byteLength, encoded);
30145
+ this.#database.prepare(
30146
+ `UPDATE relay_session_event_outbox_state
30147
+ SET event_count = event_count + 1,
30148
+ total_bytes = total_bytes + ?
30149
+ WHERE singleton = 1`
30150
+ ).run(byteLength);
29607
30151
  this.#database.exec("COMMIT");
29608
30152
  return encoded;
29609
30153
  } catch (error61) {
@@ -29611,12 +30155,132 @@ var SessionEventOutbox = class {
29611
30155
  throw error61;
29612
30156
  }
29613
30157
  };
30158
+ #removeAcknowledgedEvent = (eventId, sessionId) => {
30159
+ this.#database.exec("BEGIN IMMEDIATE");
30160
+ try {
30161
+ const stored = this.#database.prepare(
30162
+ `SELECT byte_length FROM relay_session_event_outbox
30163
+ WHERE event_id = ? AND session_id = ?`
30164
+ ).get(eventId, sessionId);
30165
+ if (stored === void 0) {
30166
+ this.#database.exec("COMMIT");
30167
+ return false;
30168
+ }
30169
+ const byteLength = rowNumber(stored, "byte_length");
30170
+ const deletion = this.#database.prepare(
30171
+ `DELETE FROM relay_session_event_outbox
30172
+ WHERE event_id = ? AND session_id = ?`
30173
+ ).run(eventId, sessionId);
30174
+ if (deletion.changes !== 1) {
30175
+ throw new Error("The acknowledged session event could not be removed from the durable outbox");
30176
+ }
30177
+ this.#decrementEventState(byteLength);
30178
+ this.#database.exec("COMMIT");
30179
+ return true;
30180
+ } catch (error61) {
30181
+ this.#database.exec("ROLLBACK");
30182
+ throw error61;
30183
+ }
30184
+ };
30185
+ #decrementEventState = (byteLength) => {
30186
+ const result = this.#database.prepare(
30187
+ `UPDATE relay_session_event_outbox_state
30188
+ SET event_count = event_count - 1,
30189
+ total_bytes = total_bytes - ?
30190
+ WHERE singleton = 1 AND event_count > 0 AND total_bytes >= ?`
30191
+ ).run(byteLength, byteLength);
30192
+ if (result.changes !== 1) {
30193
+ throw new Error("The Session event outbox capacity state is inconsistent");
30194
+ }
30195
+ };
29614
30196
  pending = () => this.#database.prepare(
29615
30197
  `SELECT envelope_json
29616
30198
  FROM relay_session_event_outbox
29617
30199
  ORDER BY ordinal`
29618
30200
  ).all().map(readPendingEnvelope);
29619
- };
30201
+ pendingBatch = (limit) => {
30202
+ if (!Number.isSafeInteger(limit) || limit < 1) {
30203
+ throw new Error("Session event replay batch limit must be a positive integer");
30204
+ }
30205
+ return this.#database.prepare(
30206
+ `WITH ranked_events AS (
30207
+ SELECT ordinal,
30208
+ ROW_NUMBER() OVER (
30209
+ PARTITION BY source_key
30210
+ ORDER BY ordinal
30211
+ ) AS source_position
30212
+ FROM relay_session_event_outbox
30213
+ )
30214
+ SELECT outbox.envelope_json
30215
+ FROM ranked_events AS ranked
30216
+ INNER JOIN relay_session_event_outbox AS outbox
30217
+ ON outbox.ordinal = ranked.ordinal
30218
+ ORDER BY ranked.source_position, outbox.ordinal
30219
+ LIMIT ?`
30220
+ ).all(limit).map(readPendingEnvelope);
30221
+ };
30222
+ pendingSourceHeads = (limit, excludedEventIds, excludedSourceKeys) => {
30223
+ if (!Number.isSafeInteger(limit) || limit < 1) {
30224
+ throw new Error("Session event replay batch limit must be a positive integer");
30225
+ }
30226
+ const exclusions = [];
30227
+ const parameters = [];
30228
+ if (excludedEventIds.length > 0) {
30229
+ exclusions.push(`event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`);
30230
+ parameters.push(...excludedEventIds);
30231
+ }
30232
+ if (excludedSourceKeys.length > 0) {
30233
+ exclusions.push(`source_key NOT IN (${excludedSourceKeys.map(() => "?").join(", ")})`);
30234
+ parameters.push(...excludedSourceKeys);
30235
+ }
30236
+ parameters.push(limit.toString());
30237
+ const where = exclusions.length === 0 ? "" : `WHERE ${exclusions.join(" AND ")}`;
30238
+ return this.#database.prepare(
30239
+ `WITH available_events AS (
30240
+ SELECT ordinal, source_key
30241
+ FROM relay_session_event_outbox
30242
+ ${where}
30243
+ ), ranked_events AS (
30244
+ SELECT ordinal,
30245
+ ROW_NUMBER() OVER (
30246
+ PARTITION BY source_key
30247
+ ORDER BY ordinal
30248
+ ) AS source_position
30249
+ FROM available_events
30250
+ )
30251
+ SELECT outbox.envelope_json
30252
+ FROM ranked_events AS ranked
30253
+ INNER JOIN relay_session_event_outbox AS outbox
30254
+ ON outbox.ordinal = ranked.ordinal
30255
+ WHERE ranked.source_position = 1
30256
+ ORDER BY outbox.ordinal
30257
+ LIMIT ?`
30258
+ ).all(...parameters).map(readPendingEnvelope);
30259
+ };
30260
+ };
30261
+ function validateLimits(limits) {
30262
+ const values = [
30263
+ limits.criticalEventBytes,
30264
+ limits.criticalEventCount,
30265
+ limits.runtimeDeliveryBytes,
30266
+ limits.runtimeDeliveryCount,
30267
+ limits.standardEventBytes,
30268
+ limits.standardEventCount
30269
+ ];
30270
+ if (values.some((value) => !Number.isSafeInteger(value) || value < 1)) {
30271
+ throw new Error("Session event outbox limits must be positive integers");
30272
+ }
30273
+ if (limits.criticalEventBytes < limits.standardEventBytes || limits.criticalEventCount < limits.standardEventCount) {
30274
+ throw new Error("Critical Session event capacity must include the standard capacity");
30275
+ }
30276
+ }
30277
+ function runtimeDeliveryState(row) {
30278
+ const state = rowString2(row, "state");
30279
+ if (state !== "received" && state !== "relay_committed" && state !== "processed") {
30280
+ throw new Error("SQLite returned an invalid Runtime delivery state");
30281
+ }
30282
+ return state;
30283
+ }
29620
30284
  function isDaemonExecutionState(value) {
29621
30285
  return value === "cancelled" || value === "dispatching" || value === "failed" || value === "running" || value === "succeeded";
29622
30286
  }
@@ -29632,7 +30296,7 @@ var systemDaemonTimer = new SystemDaemonTimer();
29632
30296
 
29633
30297
  // src/run-daemon.ts
29634
30298
  var RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
29635
- var RELAY_CONNECTION_REFRESH_MILLISECONDS = 20 * 60 * 1e3;
30299
+ var AGENT_ACTIVITY_COALESCING_MILLISECONDS = 100;
29636
30300
  var ACTIVE_TURN_RECONCILIATION_TIMEOUT_MILLISECONDS = 2e4;
29637
30301
  var ACTIVE_TURN_RECONCILIATION_CLOSE_CODE = 4009;
29638
30302
  function botIpcSocketPath() {
@@ -29641,54 +30305,6 @@ function botIpcSocketPath() {
29641
30305
  }
29642
30306
  return join16("/tmp", `mobius-bot-${randomUUID6()}.sock`);
29643
30307
  }
29644
- var RelayConnectionRefreshState = class {
29645
- };
29646
- var ScheduledRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29647
- };
29648
- var DueRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29649
- };
29650
- var FinishedRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29651
- };
29652
- var RelayConnectionRefresh = class {
29653
- #isSafe;
29654
- #refresh;
29655
- #scheduledTask;
29656
- #state = new ScheduledRelayConnectionRefreshState();
29657
- constructor(timer, isSafe, refresh) {
29658
- this.#isSafe = isSafe;
29659
- this.#refresh = refresh;
29660
- this.#scheduledTask = timer.schedule(this.#request, RELAY_CONNECTION_REFRESH_MILLISECONDS);
29661
- }
29662
- afterTurnFinished = () => {
29663
- if (this.#state instanceof DueRelayConnectionRefreshState) {
29664
- this.#request();
29665
- return;
29666
- }
29667
- if (this.#state instanceof ScheduledRelayConnectionRefreshState || this.#state instanceof FinishedRelayConnectionRefreshState) {
29668
- return;
29669
- }
29670
- throw new Error("Unknown relay connection refresh state");
29671
- };
29672
- close = () => {
29673
- this.#scheduledTask.cancel();
29674
- this.#state = new FinishedRelayConnectionRefreshState();
29675
- };
29676
- #request = () => {
29677
- if (this.#state instanceof FinishedRelayConnectionRefreshState) {
29678
- return;
29679
- }
29680
- if (!(this.#state instanceof ScheduledRelayConnectionRefreshState) && !(this.#state instanceof DueRelayConnectionRefreshState)) {
29681
- throw new Error("Unknown relay connection refresh state");
29682
- }
29683
- if (!this.#isSafe()) {
29684
- this.#state = new DueRelayConnectionRefreshState();
29685
- return;
29686
- }
29687
- this.#state = new FinishedRelayConnectionRefreshState();
29688
- this.#scheduledTask.cancel();
29689
- this.#refresh();
29690
- };
29691
- };
29692
30308
  var DaemonAcpObserver = class {
29693
30309
  #record;
29694
30310
  constructor(record2) {
@@ -29883,8 +30499,7 @@ var ActiveTurnReconciliationError = class extends Error {
29883
30499
  }
29884
30500
  };
29885
30501
  function activeTurnReconciliationEndpoint(config2, turn) {
29886
- const endpoint = new URL(config2.relayUrl);
29887
- endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
30502
+ const endpoint = relayHttpEndpoint(config2.relayUrl);
29888
30503
  let channelPath = "";
29889
30504
  if (endpoint.pathname.endsWith("/daemon")) {
29890
30505
  channelPath = endpoint.pathname.slice(0, -"/daemon".length);
@@ -29896,7 +30511,6 @@ function activeTurnReconciliationEndpoint(config2, turn) {
29896
30511
  channelPath = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
29897
30512
  }
29898
30513
  endpoint.pathname = `${channelPath}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
29899
- endpoint.searchParams.delete("liveness");
29900
30514
  return endpoint;
29901
30515
  }
29902
30516
  async function reconcileActiveTurn(config2, turn, signal) {
@@ -29908,7 +30522,7 @@ async function reconcileActiveTurn(config2, turn, signal) {
29908
30522
  response = await fetch6(activeTurnReconciliationEndpoint(config2, turn), {
29909
30523
  body: JSON.stringify({ outcome: { kind: "running" } }),
29910
30524
  dispatcher: environmentProxyDispatcher,
29911
- headers: { "content-type": "application/json" },
30525
+ headers: { ...runtimeAuthorization(config2.relayUrl), "content-type": "application/json" },
29912
30526
  method: "POST",
29913
30527
  signal: requestSignal
29914
30528
  });
@@ -29953,7 +30567,8 @@ var DaemonSessionEventConnectionState = class {
29953
30567
  };
29954
30568
  var DisconnectedDaemonSessionEventState = class extends DaemonSessionEventConnectionState {
29955
30569
  disconnect = () => this;
29956
- send = () => void 0;
30570
+ matches = () => false;
30571
+ send = () => false;
29957
30572
  };
29958
30573
  var ConnectedDaemonSessionEventState = class extends DaemonSessionEventConnectionState {
29959
30574
  #socket;
@@ -29962,38 +30577,210 @@ var ConnectedDaemonSessionEventState = class extends DaemonSessionEventConnectio
29962
30577
  this.#socket = socket;
29963
30578
  }
29964
30579
  disconnect = (socket) => socket === this.#socket ? new DisconnectedDaemonSessionEventState() : this;
30580
+ matches = (socket) => socket === this.#socket;
29965
30581
  send = (message) => {
29966
- if (this.#socket.readyState === WebSocket2.OPEN) {
29967
- try {
29968
- this.#socket.send(message);
29969
- } catch {
29970
- }
30582
+ if (this.#socket.readyState !== WebSocket2.OPEN) {
30583
+ return false;
30584
+ }
30585
+ try {
30586
+ this.#socket.send(message);
30587
+ return true;
30588
+ } catch {
30589
+ return false;
29971
30590
  }
29972
30591
  };
29973
30592
  };
30593
+ var SESSION_EVENT_REPLAY_WINDOW = 16;
30594
+ var SESSION_EVENT_SOURCE_REPLAY_WINDOW = 4;
30595
+ var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
30596
+ var InFlightSessionEvent = class {
30597
+ byteLength;
30598
+ sourceKey;
30599
+ constructor(sourceKey, byteLength) {
30600
+ this.byteLength = byteLength;
30601
+ this.sourceKey = sourceKey;
30602
+ }
30603
+ };
30604
+ function sessionEventSourceKey(envelope2) {
30605
+ return envelope2.source.kind === "attempt" ? `attempt:${envelope2.sessionId}:${envelope2.source.attemptId}` : `runtime:${envelope2.sessionId}`;
30606
+ }
29974
30607
  var DaemonSessionEventPublisher = class {
29975
30608
  #outbox;
30609
+ #inFlightEvents = /* @__PURE__ */ new Map();
30610
+ #inFlightBytes = 0;
29976
30611
  #connection = new DisconnectedDaemonSessionEventState();
29977
30612
  constructor(outbox) {
29978
30613
  this.#outbox = outbox;
29979
30614
  }
29980
30615
  connect = (socket) => {
29981
- const connection = new ConnectedDaemonSessionEventState(socket);
29982
- this.#connection = connection;
29983
- for (const pending of this.#outbox.pending()) {
29984
- connection.send(pending);
29985
- }
30616
+ this.#connection = new ConnectedDaemonSessionEventState(socket);
30617
+ this.#clearInFlight();
30618
+ this.#publishNextBatch();
29986
30619
  };
29987
30620
  disconnect = (socket) => {
30621
+ if (!this.#connection.matches(socket)) {
30622
+ return;
30623
+ }
29988
30624
  this.#connection = this.#connection.disconnect(socket);
30625
+ this.#clearInFlight();
29989
30626
  };
29990
30627
  recordAttempt = (sessionId, payload, attemptId) => {
29991
- this.#connection.send(this.#outbox.enqueue(sessionId, payload, attemptId));
30628
+ const kind = decodeMessageKind(payload);
30629
+ if (kind === SESSION_COMPLETED_KIND || kind === SESSION_FAILED_KIND) {
30630
+ this.#outbox.enqueueCritical(sessionId, payload, attemptId);
30631
+ } else {
30632
+ this.#outbox.enqueue(sessionId, payload, attemptId);
30633
+ }
30634
+ this.#publishNextBatch();
29992
30635
  };
29993
30636
  recordRuntime = (sessionId, payload) => {
29994
- this.#connection.send(this.#outbox.enqueue(sessionId, payload));
30637
+ this.#outbox.enqueue(sessionId, payload);
30638
+ this.#publishNextBatch();
30639
+ };
30640
+ acknowledge = (acknowledgement) => {
30641
+ if (!this.#outbox.acknowledge(acknowledgement)) {
30642
+ return;
30643
+ }
30644
+ this.#removeInFlight(acknowledgement.eventId);
30645
+ this.#publishNextBatch();
30646
+ };
30647
+ quarantine = (rejection) => {
30648
+ const quarantined = this.#outbox.quarantine(rejection);
30649
+ if (!quarantined) {
30650
+ return false;
30651
+ }
30652
+ this.#removeInFlight(rejection.eventId);
30653
+ this.#publishNextBatch();
30654
+ return true;
30655
+ };
30656
+ #publishNextBatch = () => {
30657
+ while (this.#inFlightEvents.size < SESSION_EVENT_REPLAY_WINDOW) {
30658
+ const sourceCounts = /* @__PURE__ */ new Map();
30659
+ for (const inFlight of this.#inFlightEvents.values()) {
30660
+ sourceCounts.set(inFlight.sourceKey, (sourceCounts.get(inFlight.sourceKey) ?? 0) + 1);
30661
+ }
30662
+ const saturatedSources = [...sourceCounts.entries()].filter(([, count]) => count >= SESSION_EVENT_SOURCE_REPLAY_WINDOW).map(([sourceKey]) => sourceKey);
30663
+ const batch = this.#outbox.pendingSourceHeads(
30664
+ SESSION_EVENT_REPLAY_WINDOW - this.#inFlightEvents.size,
30665
+ [...this.#inFlightEvents.keys()],
30666
+ saturatedSources
30667
+ );
30668
+ let sent = 0;
30669
+ for (const encoded of batch) {
30670
+ const envelope2 = decodeSessionEventEnvelope(encoded);
30671
+ const sourceKey = sessionEventSourceKey(envelope2);
30672
+ const sourceCount = sourceCounts.get(sourceKey) ?? 0;
30673
+ if (sourceCount >= SESSION_EVENT_SOURCE_REPLAY_WINDOW) {
30674
+ continue;
30675
+ }
30676
+ const byteLength = new TextEncoder().encode(encoded).byteLength;
30677
+ if (this.#inFlightBytes + byteLength > SESSION_EVENT_REPLAY_BYTE_LIMIT) {
30678
+ continue;
30679
+ }
30680
+ if (!this.#connection.send(encoded)) {
30681
+ this.#clearInFlight();
30682
+ return;
30683
+ }
30684
+ this.#inFlightEvents.set(envelope2.eventId, new InFlightSessionEvent(sourceKey, byteLength));
30685
+ this.#inFlightBytes += byteLength;
30686
+ sourceCounts.set(sourceKey, sourceCount + 1);
30687
+ sent += 1;
30688
+ }
30689
+ if (sent === 0) {
30690
+ return;
30691
+ }
30692
+ }
30693
+ };
30694
+ #removeInFlight = (eventId) => {
30695
+ const inFlight = this.#inFlightEvents.get(eventId);
30696
+ if (inFlight === void 0) {
30697
+ return;
30698
+ }
30699
+ this.#inFlightEvents.delete(eventId);
30700
+ this.#inFlightBytes -= inFlight.byteLength;
30701
+ };
30702
+ #clearInFlight = () => {
30703
+ this.#inFlightEvents.clear();
30704
+ this.#inFlightBytes = 0;
29995
30705
  };
29996
30706
  };
30707
+ var CoalescingDaemonActivityPublisher = class {
30708
+ #pending = /* @__PURE__ */ new Map();
30709
+ #publish;
30710
+ #publishedActivityIds = /* @__PURE__ */ new Set();
30711
+ #scheduled = /* @__PURE__ */ new Map();
30712
+ #timer;
30713
+ constructor(timer, publish) {
30714
+ this.#publish = publish;
30715
+ this.#timer = timer;
30716
+ }
30717
+ flush = () => {
30718
+ const activities = [...this.#pending.values()].sort((left, right) => left.sequence - right.sequence);
30719
+ for (const task of this.#scheduled.values()) {
30720
+ task.cancel();
30721
+ }
30722
+ this.#pending.clear();
30723
+ this.#scheduled.clear();
30724
+ for (const activity of activities) {
30725
+ this.#publish(activity);
30726
+ }
30727
+ };
30728
+ record = (activity) => {
30729
+ if (activity.status !== "pending" && activity.status !== "running") {
30730
+ this.#discardPending(activity.activityId);
30731
+ this.#publishedActivityIds.add(activity.activityId);
30732
+ this.#publish(activity);
30733
+ return Promise.resolve();
30734
+ }
30735
+ if (!this.#publishedActivityIds.has(activity.activityId)) {
30736
+ this.#publishedActivityIds.add(activity.activityId);
30737
+ this.#publish(activity);
30738
+ return Promise.resolve();
30739
+ }
30740
+ this.#pending.set(activity.activityId, activity);
30741
+ if (!this.#scheduled.has(activity.activityId)) {
30742
+ this.#scheduled.set(
30743
+ activity.activityId,
30744
+ this.#timer.schedule(
30745
+ () => this.#flushActivity(activity.activityId),
30746
+ AGENT_ACTIVITY_COALESCING_MILLISECONDS
30747
+ )
30748
+ );
30749
+ }
30750
+ return Promise.resolve();
30751
+ };
30752
+ #discardPending = (activityId) => {
30753
+ this.#scheduled.get(activityId)?.cancel();
30754
+ this.#scheduled.delete(activityId);
30755
+ this.#pending.delete(activityId);
30756
+ };
30757
+ #flushActivity = (activityId) => {
30758
+ const activity = this.#pending.get(activityId);
30759
+ this.#discardPending(activityId);
30760
+ if (activity !== void 0) {
30761
+ this.#publish(activity);
30762
+ }
30763
+ };
30764
+ };
30765
+ function encodeDaemonActivity(command, activity) {
30766
+ return encodeAgentActivityEvent({
30767
+ activityId: activity.activityId,
30768
+ activityType: activity.activityType,
30769
+ commandId: command.commandId,
30770
+ content: activity.content,
30771
+ detail: activity.detail,
30772
+ input: activity.input,
30773
+ kind: AGENT_ACTIVITY_KIND,
30774
+ locations: activity.locations,
30775
+ name: activity.name,
30776
+ output: activity.output,
30777
+ sequence: activity.sequence,
30778
+ sessionId: command.sessionId,
30779
+ status: activity.status,
30780
+ title: activity.title,
30781
+ toolKind: activity.toolKind
30782
+ });
30783
+ }
29997
30784
  function reportFailure(publisher, command, code, message, attemptId) {
29998
30785
  publisher.recordAttempt(
29999
30786
  command.sessionId,
@@ -30167,10 +30954,16 @@ async function executeSessionConfigure(publisher, socket, command, config2, dire
30167
30954
  })
30168
30955
  );
30169
30956
  } catch (error61) {
30957
+ if (signal.aborted) {
30958
+ signal.throwIfAborted();
30959
+ }
30170
30960
  reportOperationFailure(socket, command.requestId, command.sessionId, error61);
30171
30961
  }
30172
30962
  }
30173
- async function executePrompt(outbox, publisher, command, config2, directories, audit, sessions, attemptId, signal) {
30963
+ async function executePrompt(outbox, publisher, command, config2, directories, audit, sessions, attemptId, timer, signal) {
30964
+ const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
30965
+ publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
30966
+ });
30174
30967
  try {
30175
30968
  const resolved = await resolveSessionDirectories(
30176
30969
  directories,
@@ -30227,30 +31020,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30227
31020
  cwd: resolved.cwd,
30228
31021
  disposition: command.disposition,
30229
31022
  images: attachments.images,
30230
- onActivity: (activity) => {
30231
- publisher.recordAttempt(
30232
- command.sessionId,
30233
- 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
- attemptId
30251
- );
30252
- return Promise.resolve();
30253
- },
31023
+ onActivity: activities.record,
30254
31024
  onText: (text, messageId) => {
30255
31025
  publisher.recordAttempt(
30256
31026
  command.sessionId,
@@ -30273,6 +31043,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30273
31043
  });
30274
31044
  stopReason = result.stopReason;
30275
31045
  }
31046
+ activities.flush();
30276
31047
  publisher.recordAttempt(
30277
31048
  command.sessionId,
30278
31049
  encodeSessionCompletedEvent({
@@ -30290,6 +31061,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30290
31061
  sessionId: command.sessionId
30291
31062
  });
30292
31063
  } catch (error61) {
31064
+ activities.flush();
30293
31065
  audit.record({
30294
31066
  commandId: command.commandId,
30295
31067
  event: "session_failed",
@@ -30328,7 +31100,10 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
30328
31100
  agent.capabilities
30329
31101
  );
30330
31102
  }
30331
- async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, signal) {
31103
+ async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, timer, signal) {
31104
+ const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
31105
+ publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
31106
+ });
30332
31107
  try {
30333
31108
  const deadline = Date.parse(command.executionDeadlineAt);
30334
31109
  const remaining = deadline - Date.now();
@@ -30412,30 +31187,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30412
31187
  commandId: command.commandId,
30413
31188
  cwd: resolved.cwd,
30414
31189
  images: attachments.images,
30415
- onActivity: (activity) => {
30416
- publisher.recordAttempt(
30417
- command.sessionId,
30418
- encodeAgentActivityEvent({
30419
- activityId: activity.activityId,
30420
- activityType: activity.activityType,
30421
- commandId: command.commandId,
30422
- content: activity.content,
30423
- detail: activity.detail,
30424
- input: activity.input,
30425
- kind: AGENT_ACTIVITY_KIND,
30426
- locations: activity.locations,
30427
- name: activity.name,
30428
- output: activity.output,
30429
- sequence: activity.sequence,
30430
- sessionId: command.sessionId,
30431
- status: activity.status,
30432
- title: activity.title,
30433
- toolKind: activity.toolKind
30434
- }),
30435
- attemptId
30436
- );
30437
- return Promise.resolve();
30438
- },
31190
+ onActivity: activities.record,
30439
31191
  onText: (text, messageId) => {
30440
31192
  publisher.recordAttempt(
30441
31193
  command.sessionId,
@@ -30455,6 +31207,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30455
31207
  sessionId: command.sessionId,
30456
31208
  signal: executionSignal
30457
31209
  });
31210
+ activities.flush();
30458
31211
  publisher.recordAttempt(
30459
31212
  command.sessionId,
30460
31213
  encodeSessionCompletedEvent({
@@ -30479,6 +31232,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30479
31232
  lease.release();
30480
31233
  }
30481
31234
  } catch (error61) {
31235
+ activities.flush();
30482
31236
  const failure = Date.parse(command.executionDeadlineAt) <= Date.now() ? new LocalBotAuthorizationError("turn_expired", "The Bot execution deadline has expired", {
30483
31237
  cause: error61
30484
31238
  }) : error61;
@@ -30518,15 +31272,15 @@ function waitForRelayRetry(signal, timer) {
30518
31272
  signal.addEventListener("abort", finish, { once: true });
30519
31273
  });
30520
31274
  }
30521
- async function publishAgentCatalogUntilAvailable(config2, signal, timer) {
31275
+ async function publishAgentCatalogUntilAvailable(config2, diagnostics, signal, timer) {
30522
31276
  try {
30523
31277
  await publishAgentCatalog(config2, signal);
30524
31278
  return;
30525
- } catch {
31279
+ } catch (error61) {
30526
31280
  if (signal.aborted) {
30527
31281
  return;
30528
31282
  }
30529
- console.warn("Mobius daemon is waiting to publish its Agent catalog");
31283
+ diagnostics.catalogFailed(error61);
30530
31284
  }
30531
31285
  while (!signal.aborted) {
30532
31286
  await waitForRelayRetry(signal, timer);
@@ -30540,11 +31294,13 @@ async function publishAgentCatalogUntilAvailable(config2, signal, timer) {
30540
31294
  }
30541
31295
  }
30542
31296
  }
30543
- async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, onConnected, signal, timer) {
31297
+ async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, connectionEpoch, diagnostics, onConnected, signal, timer) {
30544
31298
  const relayUrl = new URL(config2.relayUrl);
31299
+ relayUrl.searchParams.set("deliveryReceipts", "1");
30545
31300
  relayUrl.searchParams.set("liveness", "1");
30546
31301
  const socket = await connectRelay(relayUrl, signal);
30547
31302
  const reconnectingTurns = [...activeTurns.values()];
31303
+ diagnostics.connectionOpened(connectionEpoch);
30548
31304
  onConnected();
30549
31305
  const connectionController = new AbortController();
30550
31306
  const nativeRequests = /* @__PURE__ */ new Map();
@@ -30553,7 +31309,6 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30553
31309
  await new Promise((resolve5, reject) => {
30554
31310
  let finished = false;
30555
31311
  const cleanup = () => {
30556
- connectionRefresh.close();
30557
31312
  connectionLiveness.close();
30558
31313
  signal.removeEventListener("abort", stop);
30559
31314
  socket.removeEventListener("close", handleClose);
@@ -30571,61 +31326,282 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30571
31326
  cleanup();
30572
31327
  resolve5();
30573
31328
  };
30574
- const rejectConnection = () => {
31329
+ const rejectConnection = (failure) => {
30575
31330
  if (finished) {
30576
31331
  return;
30577
31332
  }
30578
31333
  finished = true;
30579
31334
  cleanup();
30580
- reject(new Error("Relay WebSocket failed"));
31335
+ reject(failure);
30581
31336
  };
30582
31337
  const stop = () => {
31338
+ diagnostics.connectionClosed(connectionEpoch, 1e3, "Daemon stopped", "daemon_stop");
30583
31339
  closeRelaySocket(socket, 1e3, "Daemon stopped");
30584
31340
  resolveConnection();
30585
31341
  };
30586
- const refreshConnection = () => {
30587
- closeRelaySocket(socket, 1e3, "Refreshing idle relay connection");
30588
- resolveConnection();
31342
+ const failAndClose = (failure, code, reason, source) => {
31343
+ diagnostics.connectionFailed(connectionEpoch, failure, source);
31344
+ diagnostics.connectionClosed(connectionEpoch, code, reason, source);
31345
+ closeRelaySocket(socket, code, reason);
31346
+ rejectConnection(failure);
31347
+ };
31348
+ const processingDeliveryIds = /* @__PURE__ */ new Set();
31349
+ const processDeliveredPrompt = async (delivery) => {
31350
+ const command = decodeSessionPromptCommand(delivery.payload);
31351
+ if (command.sessionId !== delivery.sessionId) {
31352
+ throw new Error("Runtime delivery Session identity does not match its payload");
31353
+ }
31354
+ const active = activeTurns.get(command.commandId);
31355
+ if (active !== void 0) {
31356
+ await active.completion;
31357
+ return;
31358
+ }
31359
+ const receipt = outbox.claimExecution(command.commandId, command.sessionId, delivery.payload);
31360
+ if (receipt.kind === "duplicate") {
31361
+ if (receipt.state === "running") {
31362
+ reportFailure(
31363
+ publisher,
31364
+ command,
31365
+ "agent_execution_interrupted",
31366
+ "The local Agent execution was interrupted before it completed",
31367
+ receipt.attemptId
31368
+ );
31369
+ outbox.transitionExecution(command.commandId, "failed");
31370
+ return;
31371
+ }
31372
+ if (receipt.state !== "dispatching") {
31373
+ return;
31374
+ }
31375
+ }
31376
+ const turn = new ActiveDaemonTurn(command, receipt.attemptId);
31377
+ activeTurns.set(command.commandId, turn);
31378
+ if (command.disposition !== SessionPromptDisposition.Steer) {
31379
+ activeAttemptIds.set(command.sessionId, receipt.attemptId);
31380
+ }
31381
+ try {
31382
+ await executePrompt(
31383
+ outbox,
31384
+ publisher,
31385
+ command,
31386
+ config2,
31387
+ directories,
31388
+ audit,
31389
+ sessions,
31390
+ receipt.attemptId,
31391
+ timer,
31392
+ turn.signal
31393
+ );
31394
+ } finally {
31395
+ activeTurns.delete(command.commandId);
31396
+ if (activeAttemptIds.get(command.sessionId) === receipt.attemptId) {
31397
+ activeAttemptIds.delete(command.sessionId);
31398
+ }
31399
+ turn.finish();
31400
+ }
31401
+ };
31402
+ const processDeliveredPayload = async (delivery) => {
31403
+ const kind = decodeMessageKind(delivery.payload);
31404
+ if (kind === SESSION_CANCEL_KIND) {
31405
+ const cancellation = decodeSessionCancelCommand(delivery.payload);
31406
+ if (cancellation.sessionId !== delivery.sessionId) {
31407
+ throw new Error("Runtime delivery Session identity does not match its payload");
31408
+ }
31409
+ const turn = activeTurns.get(cancellation.commandId);
31410
+ if (turn === void 0 || !turn.matches(cancellation) || !turn.requestCancellation()) {
31411
+ return;
31412
+ }
31413
+ audit.record({
31414
+ commandId: cancellation.commandId,
31415
+ event: "session_cancellation_requested",
31416
+ sessionId: cancellation.sessionId
31417
+ });
31418
+ await sessions.cancelPrompt(cancellation.sessionId);
31419
+ return;
31420
+ }
31421
+ if (kind === SESSION_CONFIGURE_KIND) {
31422
+ const command = decodeSessionConfigureCommand(delivery.payload);
31423
+ if (command.sessionId !== delivery.sessionId) {
31424
+ throw new Error("Runtime delivery Session identity does not match its payload");
31425
+ }
31426
+ await executeSessionConfigure(
31427
+ publisher,
31428
+ socket,
31429
+ command,
31430
+ config2,
31431
+ directories,
31432
+ audit,
31433
+ sessions,
31434
+ connectionController.signal
31435
+ );
31436
+ return;
31437
+ }
31438
+ if (kind === SESSION_PERMISSION_RESPOND_KIND) {
31439
+ const command = decodeSessionPermissionRespondCommand(delivery.payload);
31440
+ if (command.sessionId !== delivery.sessionId) {
31441
+ throw new Error("Runtime delivery Session identity does not match its payload");
31442
+ }
31443
+ try {
31444
+ await sessions.respondPermission(command);
31445
+ audit.record({
31446
+ commandId: command.interactionId,
31447
+ event: "session_permission_resolved",
31448
+ sessionId: command.sessionId
31449
+ });
31450
+ } catch (error61) {
31451
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error61);
31452
+ }
31453
+ return;
31454
+ }
31455
+ if (kind === SESSION_ELICITATION_RESPOND_KIND) {
31456
+ const command = decodeSessionElicitationRespondCommand(delivery.payload);
31457
+ if (command.sessionId !== delivery.sessionId) {
31458
+ throw new Error("Runtime delivery Session identity does not match its payload");
31459
+ }
31460
+ try {
31461
+ await sessions.respondElicitation(command);
31462
+ audit.record({
31463
+ commandId: command.interactionId,
31464
+ event: "session_elicitation_resolved",
31465
+ sessionId: command.sessionId
31466
+ });
31467
+ } catch (error61) {
31468
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error61);
31469
+ }
31470
+ return;
31471
+ }
31472
+ if (kind === SESSION_PROMPT_KIND) {
31473
+ await processDeliveredPrompt(delivery);
31474
+ return;
31475
+ }
31476
+ throw new Error("Runtime delivery contains an unsupported protocol message");
31477
+ };
31478
+ const processRuntimeDelivery = (delivery, state) => {
31479
+ if (state === "processed" || processingDeliveryIds.has(delivery.deliveryId)) {
31480
+ return;
31481
+ }
31482
+ processingDeliveryIds.add(delivery.deliveryId);
31483
+ void processDeliveredPayload(delivery).then(() => {
31484
+ outbox.markRuntimeDeliveryProcessed(delivery.deliveryId, delivery.sessionId);
31485
+ }).catch((error61) => {
31486
+ if (connectionController.signal.aborted) {
31487
+ return;
31488
+ }
31489
+ console.error("Mobius daemon could not process a durable Runtime delivery", error61);
31490
+ failAndClose(
31491
+ new Error("Runtime delivery processing failed", { cause: error61 }),
31492
+ 4008,
31493
+ "Invalid Runtime delivery",
31494
+ "delivery"
31495
+ );
31496
+ }).finally(() => processingDeliveryIds.delete(delivery.deliveryId));
31497
+ };
31498
+ const receiveRuntimeDelivery = (delivery) => {
31499
+ const payloadKind = decodeMessageKind(delivery.payload);
31500
+ let payloadSessionId = "";
31501
+ if (payloadKind === SESSION_CANCEL_KIND) {
31502
+ payloadSessionId = decodeSessionCancelCommand(delivery.payload).sessionId;
31503
+ } else if (payloadKind === SESSION_CONFIGURE_KIND) {
31504
+ payloadSessionId = decodeSessionConfigureCommand(delivery.payload).sessionId;
31505
+ } else if (payloadKind === SESSION_PERMISSION_RESPOND_KIND) {
31506
+ payloadSessionId = decodeSessionPermissionRespondCommand(delivery.payload).sessionId;
31507
+ } else if (payloadKind === SESSION_ELICITATION_RESPOND_KIND) {
31508
+ payloadSessionId = decodeSessionElicitationRespondCommand(delivery.payload).sessionId;
31509
+ } else if (payloadKind === SESSION_PROMPT_KIND) {
31510
+ payloadSessionId = decodeSessionPromptCommand(delivery.payload).sessionId;
31511
+ } else {
31512
+ throw new Error("Runtime delivery contains an unsupported protocol message");
31513
+ }
31514
+ if (payloadSessionId !== delivery.sessionId) {
31515
+ throw new Error("Runtime delivery Session identity does not match its payload");
31516
+ }
31517
+ const stored = outbox.claimRuntimeDelivery(delivery);
31518
+ send(
31519
+ socket,
31520
+ encodeRuntimeDeliveryReceived({
31521
+ deliveryId: delivery.deliveryId,
31522
+ kind: RUNTIME_DELIVERY_RECEIVED_KIND,
31523
+ sessionId: delivery.sessionId
31524
+ })
31525
+ );
31526
+ processRuntimeDelivery(delivery, stored.state);
31527
+ };
31528
+ const resumeRuntimeDeliveries = () => {
31529
+ for (const stored of outbox.runtimeDeliveries()) {
31530
+ const delivery = {
31531
+ deliveryId: stored.deliveryId,
31532
+ kind: RUNTIME_DELIVERY_KIND,
31533
+ payload: stored.payload,
31534
+ sessionId: stored.sessionId
31535
+ };
31536
+ if (stored.state !== "relay_committed") {
31537
+ send(
31538
+ socket,
31539
+ encodeRuntimeDeliveryReceived({
31540
+ deliveryId: stored.deliveryId,
31541
+ kind: RUNTIME_DELIVERY_RECEIVED_KIND,
31542
+ sessionId: stored.sessionId
31543
+ })
31544
+ );
31545
+ }
31546
+ processRuntimeDelivery(delivery, stored.state);
31547
+ }
30589
31548
  };
30590
31549
  const connectionLiveness = new RelayConnectionLiveness(timer, {
30591
31550
  send: (message) => send(socket, message),
30592
- unresponsive: () => {
31551
+ unresponsive: (heartbeatId, timeoutMilliseconds) => {
31552
+ diagnostics.heartbeatTimedOut(connectionEpoch, heartbeatId, timeoutMilliseconds);
31553
+ diagnostics.connectionClosed(
31554
+ connectionEpoch,
31555
+ 4001,
31556
+ "Relay liveness check timed out",
31557
+ "heartbeat_timeout"
31558
+ );
30593
31559
  closeRelaySocket(socket, 4001, "Relay liveness check timed out");
30594
31560
  resolveConnection();
30595
31561
  }
30596
31562
  });
30597
- const connectionRefresh = new RelayConnectionRefresh(
30598
- timer,
30599
- () => activeTurns.size === 0,
30600
- refreshConnection
30601
- );
30602
31563
  signal.addEventListener("abort", stop, { once: true });
30603
- const handleClose = () => {
31564
+ const handleClose = (event) => {
31565
+ diagnostics.connectionClosed(connectionEpoch, event.code, event.reason, "relay");
30604
31566
  resolveConnection();
30605
31567
  };
30606
- const handleError = () => {
31568
+ const handleError = (event) => {
30607
31569
  if (signal.aborted) {
30608
31570
  resolveConnection();
30609
31571
  return;
30610
31572
  }
30611
- rejectConnection();
31573
+ const cause = Reflect.get(event, "error");
31574
+ const failure = new Error("Relay WebSocket emitted an error", { cause: cause ?? event });
31575
+ diagnostics.connectionFailed(connectionEpoch, failure, "socket_error");
31576
+ diagnostics.connectionClosed(connectionEpoch, 1006, "WebSocket error", "socket_error");
31577
+ rejectConnection(failure);
30612
31578
  };
30613
31579
  const handleMessage = (event) => {
30614
31580
  const encoded = event.data;
30615
31581
  if (typeof encoded !== "string") {
30616
- closeRelaySocket(socket, 4003, "Text message required");
30617
- rejectConnection();
31582
+ failAndClose(new Error("Relay sent a non-text message"), 4003, "Text message required", "protocol");
30618
31583
  return;
30619
31584
  }
30620
31585
  try {
30621
31586
  const kind = decodeMessageKind(encoded);
31587
+ if (kind === RUNTIME_DELIVERY_COMMITTED_KIND) {
31588
+ const committed = decodeRuntimeDeliveryCommitted(encoded);
31589
+ if (!outbox.markRuntimeDeliveryCommitted(committed.deliveryId, committed.sessionId)) {
31590
+ console.warn("Mobius daemon ignored confirmation for an unknown Runtime delivery");
31591
+ }
31592
+ return;
31593
+ }
31594
+ if (kind === RUNTIME_DELIVERY_KIND) {
31595
+ receiveRuntimeDelivery(decodeRuntimeDelivery(encoded));
31596
+ return;
31597
+ }
30622
31598
  if (kind === SESSION_EVENT_ACK_KIND) {
30623
- outbox.acknowledge(decodeSessionEventAcknowledgement(encoded));
31599
+ publisher.acknowledge(decodeSessionEventAcknowledgement(encoded));
30624
31600
  return;
30625
31601
  }
30626
31602
  if (kind === SESSION_EVENT_REJECTED_KIND) {
30627
31603
  const rejection = decodeSessionEventRejection(encoded);
30628
- if (outbox.quarantine(rejection)) {
31604
+ if (publisher.quarantine(rejection)) {
30629
31605
  audit.record({
30630
31606
  commandId: rejection.eventId,
30631
31607
  details: { code: rejection.code },
@@ -31004,6 +31980,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
31004
31980
  applicator,
31005
31981
  ipc,
31006
31982
  receipt2.attemptId,
31983
+ timer,
31007
31984
  turn2.signal
31008
31985
  ).catch((error61) => {
31009
31986
  console.error("Mobius daemon Bot turn failed", error61);
@@ -31013,7 +31990,6 @@ async function runRelayConnection(config2, directories, audit, sessions, install
31013
31990
  activeAttemptIds.delete(command2.sessionId);
31014
31991
  }
31015
31992
  turn2.finish();
31016
- connectionRefresh.afterTurnFinished();
31017
31993
  });
31018
31994
  return;
31019
31995
  }
@@ -31042,6 +32018,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
31042
32018
  audit,
31043
32019
  sessions,
31044
32020
  receipt.attemptId,
32021
+ timer,
31045
32022
  turn.signal
31046
32023
  ).catch((error61) => {
31047
32024
  console.error("Mobius daemon turn failed", error61);
@@ -31051,12 +32028,19 @@ async function runRelayConnection(config2, directories, audit, sessions, install
31051
32028
  activeAttemptIds.delete(command.sessionId);
31052
32029
  }
31053
32030
  turn.finish();
31054
- connectionRefresh.afterTurnFinished();
31055
32031
  });
31056
32032
  } catch (error61) {
31057
32033
  console.error("Mobius daemon rejected a message", error61);
31058
- closeRelaySocket(socket, 4008, "Invalid protocol message");
31059
- rejectConnection();
32034
+ if (error61 instanceof RuntimeDeliveryInboxCapacityError) {
32035
+ failAndClose(error61, 4013, "Runtime delivery queue full", "delivery_backpressure");
32036
+ return;
32037
+ }
32038
+ failAndClose(
32039
+ new Error("Relay sent an invalid protocol message", { cause: error61 }),
32040
+ 4008,
32041
+ "Invalid protocol message",
32042
+ "protocol"
32043
+ );
31060
32044
  }
31061
32045
  };
31062
32046
  socket.addEventListener("close", handleClose);
@@ -31068,21 +32052,19 @@ async function runRelayConnection(config2, directories, audit, sessions, install
31068
32052
  return;
31069
32053
  }
31070
32054
  publisher.connect(socket);
31071
- void Promise.all(reconnectingTurns.map((turn) => turn.completion)).then(
31072
- connectionRefresh.afterTurnFinished
31073
- );
32055
+ resumeRuntimeDeliveries();
31074
32056
  },
31075
32057
  (error61) => {
31076
32058
  if (finished) {
31077
32059
  return;
31078
32060
  }
31079
32061
  console.error("Mobius daemon could not reconcile active Agent turns", error61);
31080
- closeRelaySocket(
31081
- socket,
32062
+ failAndClose(
32063
+ new Error("Active Agent turn reconciliation failed", { cause: error61 }),
31082
32064
  ACTIVE_TURN_RECONCILIATION_CLOSE_CODE,
31083
- "Active Agent turn reconciliation failed"
32065
+ "Active Agent turn reconciliation failed",
32066
+ "reconciliation"
31084
32067
  );
31085
- rejectConnection();
31086
32068
  }
31087
32069
  );
31088
32070
  if (signal.aborted) {
@@ -31126,6 +32108,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31126
32108
  const sessions = new AcpSessionManager(new DaemonAcpObserver(recordSessionEvent), sessionStore);
31127
32109
  const directories = new WorkspaceDirectoryService(config2.bindingId, config2.defaultWorkspace);
31128
32110
  const installationObserver = new DaemonInstallationObserver();
32111
+ const diagnostics = new RelayConnectionDiagnostics(audit, config2.channelId, config2.relayUrl);
31129
32112
  const nativeSessions = new NativeSessionService(config2, directories, sessions, audit);
31130
32113
  const installations = new AgentInstallationManager(
31131
32114
  specifications,
@@ -31133,7 +32116,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31133
32116
  installationObserver,
31134
32117
  installationAudit
31135
32118
  );
31136
- let retryAnnounced = false;
32119
+ let connectionEpoch = 0;
31137
32120
  let connectionAnnounced = false;
31138
32121
  const announceConnection = () => {
31139
32122
  if (connectionAnnounced) {
@@ -31149,10 +32132,11 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31149
32132
  `
31150
32133
  );
31151
32134
  };
31152
- const catalogPublication = publishAgentCatalogUntilAvailable(config2, signal, timer);
32135
+ const catalogPublication = publishAgentCatalogUntilAvailable(config2, diagnostics, signal, timer);
31153
32136
  try {
31154
32137
  await ipc.listen(signal);
31155
32138
  while (!signal.aborted) {
32139
+ connectionEpoch += 1;
31156
32140
  try {
31157
32141
  await runRelayConnection(
31158
32142
  config2,
@@ -31170,19 +32154,17 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31170
32154
  ipc,
31171
32155
  activeTurns,
31172
32156
  activeAttemptIds,
32157
+ connectionEpoch,
32158
+ diagnostics,
31173
32159
  announceConnection,
31174
32160
  signal,
31175
32161
  timer
31176
32162
  );
31177
- retryAnnounced = false;
31178
- } catch {
32163
+ } catch (error61) {
31179
32164
  if (signal.aborted) {
31180
32165
  return;
31181
32166
  }
31182
- if (!retryAnnounced) {
31183
- console.warn("Mobius daemon is waiting for the relay");
31184
- retryAnnounced = true;
31185
- }
32167
+ diagnostics.connectionFailed(connectionEpoch, error61, "connection");
31186
32168
  }
31187
32169
  await waitForRelayRetry(signal, timer);
31188
32170
  }
@@ -31290,12 +32272,14 @@ process.once("SIGTERM", () => shutdown.abort());
31290
32272
  var USAGE = `Mobius CLI
31291
32273
 
31292
32274
  Usage:
32275
+ mobius --version
31293
32276
  mobius pair --url <relay-url> --pairing <runtime-pairing-id> --code <pairing-code> [--workspace <directory>]
31294
32277
  mobius start --channel <channel-id>
31295
32278
  mobius agents [--json]
31296
32279
  mobius bot message send --target <current|alias> --text <message>
31297
32280
 
31298
32281
  Commands:
32282
+ --version Print the installed Mobius CLI version
31299
32283
  pair Pair this machine and start its local daemon
31300
32284
  start Start the local daemon for a saved channel
31301
32285
  agents Show detected, missing, and integration-ready Agents
@@ -31310,6 +32294,11 @@ async function main() {
31310
32294
  process.stdout.write(USAGE);
31311
32295
  return;
31312
32296
  }
32297
+ if (command === "--version" || command === "-v" || command === "version") {
32298
+ process.stdout.write(`${package_default.version}
32299
+ `);
32300
+ return;
32301
+ }
31313
32302
  if (command === "agents") {
31314
32303
  const arguments_ = process.argv.slice(3);
31315
32304
  if (arguments_.length > 1 || arguments_.length === 1 && arguments_[0] !== "--json") {