@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/index.js CHANGED
@@ -19340,6 +19340,9 @@ var SESSION_CANCEL_KIND = "session.cancel";
19340
19340
  var RUNTIME_LIVENESS_READY_KIND = "runtime.liveness.ready";
19341
19341
  var RUNTIME_HEARTBEAT_KIND = "runtime.heartbeat";
19342
19342
  var RUNTIME_HEARTBEAT_ACK_KIND = "runtime.heartbeat.ack";
19343
+ var RUNTIME_DELIVERY_KIND = "runtime.delivery";
19344
+ var RUNTIME_DELIVERY_RECEIVED_KIND = "runtime.delivery.received";
19345
+ var RUNTIME_DELIVERY_COMMITTED_KIND = "runtime.delivery.committed";
19343
19346
  var SESSION_STARTED_KIND = "session.started";
19344
19347
  var AGENT_MESSAGE_DELTA_KIND = "agent.message.delta";
19345
19348
  var AGENT_ACTIVITY_KIND = "agent.activity";
@@ -19362,6 +19365,7 @@ var DIRECTORY_IDENTIFIER_PATTERN2 = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,2047}$/;
19362
19365
  var MAX_PROMPT_LENGTH = 1e4;
19363
19366
  var MAX_PROMPT_ATTACHMENT_LENGTH = 6e6;
19364
19367
  var MAX_EVENT_TEXT_LENGTH = 1e5;
19368
+ var MAX_RUNTIME_DELIVERY_PAYLOAD_LENGTH = 8 * 1024 * 1024;
19365
19369
  var ACTIVITY_TYPES = /* @__PURE__ */ new Set(["plan", "progress", "reasoning", "tool"]);
19366
19370
  var ACTIVITY_STATUSES = /* @__PURE__ */ new Set(["pending", "running", "completed", "failed"]);
19367
19371
  var TOOL_KINDS = /* @__PURE__ */ new Set([
@@ -19640,6 +19644,37 @@ function decodeRuntimeHeartbeatAckEvent(encoded) {
19640
19644
  kind: RUNTIME_HEARTBEAT_ACK_KIND
19641
19645
  };
19642
19646
  }
19647
+ function decodeRuntimeDelivery(encoded) {
19648
+ const value = parseJson2(encoded);
19649
+ assertExactKeys(value, ["deliveryId", "kind", "payload", "sessionId"]);
19650
+ assertKind(value, RUNTIME_DELIVERY_KIND);
19651
+ return {
19652
+ deliveryId: readIdentifier(value, "deliveryId"),
19653
+ kind: RUNTIME_DELIVERY_KIND,
19654
+ payload: readString(value, "payload", MAX_RUNTIME_DELIVERY_PAYLOAD_LENGTH),
19655
+ sessionId: readIdentifier(value, "sessionId")
19656
+ };
19657
+ }
19658
+ function decodeRuntimeDeliveryReceived(encoded) {
19659
+ const value = parseJson2(encoded);
19660
+ assertExactKeys(value, ["deliveryId", "kind", "sessionId"]);
19661
+ assertKind(value, RUNTIME_DELIVERY_RECEIVED_KIND);
19662
+ return {
19663
+ deliveryId: readIdentifier(value, "deliveryId"),
19664
+ kind: RUNTIME_DELIVERY_RECEIVED_KIND,
19665
+ sessionId: readIdentifier(value, "sessionId")
19666
+ };
19667
+ }
19668
+ function decodeRuntimeDeliveryCommitted(encoded) {
19669
+ const value = parseJson2(encoded);
19670
+ assertExactKeys(value, ["deliveryId", "kind", "sessionId"]);
19671
+ assertKind(value, RUNTIME_DELIVERY_COMMITTED_KIND);
19672
+ return {
19673
+ deliveryId: readIdentifier(value, "deliveryId"),
19674
+ kind: RUNTIME_DELIVERY_COMMITTED_KIND,
19675
+ sessionId: readIdentifier(value, "sessionId")
19676
+ };
19677
+ }
19643
19678
  function decodeSessionStartedEvent(encoded) {
19644
19679
  const value = parseJson2(encoded);
19645
19680
  assertExactKeys(value, ["commandId", "kind", "attemptId", "sessionId"]);
@@ -19727,6 +19762,9 @@ function decodeSessionFailedEvent(encoded) {
19727
19762
  function encodeRuntimeHeartbeatEvent(message) {
19728
19763
  return JSON.stringify(decodeRuntimeHeartbeatEvent(JSON.stringify(message)));
19729
19764
  }
19765
+ function encodeRuntimeDeliveryReceived(message) {
19766
+ return JSON.stringify(decodeRuntimeDeliveryReceived(JSON.stringify(message)));
19767
+ }
19730
19768
  function encodeSessionStartedEvent(message) {
19731
19769
  return JSON.stringify(decodeSessionStartedEvent(JSON.stringify(message)));
19732
19770
  }
@@ -20425,7 +20463,8 @@ var SESSION_EVENT_COMMITTED_KIND = "session.event.committed";
20425
20463
  var SESSION_EVENT_REJECTED_KIND = "session.event.rejected";
20426
20464
  var SessionEventRejectionCode = {
20427
20465
  SessionDeleted: "session_deleted",
20428
- SessionNotFound: "session_not_found"
20466
+ SessionNotFound: "session_not_found",
20467
+ SessionRejected: "session_rejected"
20429
20468
  };
20430
20469
  var IDENTIFIER_PATTERN4 = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
20431
20470
  var CURSOR_PATTERN = /^[A-Za-z0-9_-]{1,512}$/;
@@ -20497,6 +20536,7 @@ var encode4 = (message, schema) => {
20497
20536
  }
20498
20537
  return JSON.stringify(decoded.data);
20499
20538
  };
20539
+ var decodeSessionEventEnvelope = (encoded) => decode5(encoded, eventEnvelopeSchema);
20500
20540
  var encodeSessionEventEnvelope = (message) => encode4(message, eventEnvelopeSchema);
20501
20541
  var decodeSessionEventAcknowledgement = (encoded) => decode5(encoded, eventAcknowledgementSchema);
20502
20542
  var decodeSessionEventRejection = (encoded) => decode5(encoded, eventRejectionSchema);
@@ -26286,7 +26326,53 @@ import { fetch as fetch2 } from "undici";
26286
26326
 
26287
26327
  // src/network/environment-proxy.ts
26288
26328
  import { EnvHttpProxyAgent } from "undici";
26329
+ var configuredHttpProxy = process.env["http_proxy"] ?? process.env["HTTP_PROXY"] ?? "";
26330
+ var configuredHttpsProxy = process.env["https_proxy"] ?? process.env["HTTPS_PROXY"] ?? configuredHttpProxy;
26289
26331
  var environmentProxyDispatcher = new EnvHttpProxyAgent();
26332
+ function bypassesProxy(target) {
26333
+ const noProxy = process.env["no_proxy"] ?? process.env["NO_PROXY"] ?? "";
26334
+ if (noProxy === "*") {
26335
+ return true;
26336
+ }
26337
+ const hostname4 = target.hostname.replace(/^\[|\]$/gu, "").toLowerCase();
26338
+ const defaultPort = target.protocol === "wss:" || target.protocol === "https:" ? 443 : 80;
26339
+ const port = Number.parseInt(target.port, 10) || defaultPort;
26340
+ for (const rawEntry of noProxy.split(/[,\s]/u)) {
26341
+ if (rawEntry.length === 0) {
26342
+ continue;
26343
+ }
26344
+ const parsed = rawEntry.match(/^(.+):(\d+)$/u);
26345
+ const entryHostname = (parsed?.[1] ?? rawEntry).replace(/^\*?\./u, "").toLowerCase();
26346
+ const entryPort = parsed?.[2] === void 0 ? 0 : Number.parseInt(parsed[2], 10);
26347
+ if (entryPort !== 0 && entryPort !== port) {
26348
+ continue;
26349
+ }
26350
+ if (hostname4 === entryHostname || hostname4.endsWith(`.${entryHostname}`)) {
26351
+ return true;
26352
+ }
26353
+ }
26354
+ return false;
26355
+ }
26356
+ function sanitizedProxyOrigin(proxyUrl) {
26357
+ try {
26358
+ return new URL(proxyUrl).origin;
26359
+ } catch {
26360
+ return "configured-invalid-url";
26361
+ }
26362
+ }
26363
+ function describeEnvironmentProxyRoute(target) {
26364
+ if (bypassesProxy(target)) {
26365
+ return { proxyRoute: "direct" };
26366
+ }
26367
+ const proxyUrl = target.protocol === "wss:" || target.protocol === "https:" ? configuredHttpsProxy : configuredHttpProxy;
26368
+ if (proxyUrl.length === 0) {
26369
+ return { proxyRoute: "direct" };
26370
+ }
26371
+ return {
26372
+ proxyOrigin: sanitizedProxyOrigin(proxyUrl),
26373
+ proxyRoute: "proxy"
26374
+ };
26375
+ }
26290
26376
 
26291
26377
  // src/agent-installation/managed-agent-archive.ts
26292
26378
  var ARCHIVE_DOWNLOAD_LIMIT_BYTES = 128 * 1024 * 1024;
@@ -27421,12 +27507,12 @@ function decodePairingResponse(value, credentialField, expectedKeys) {
27421
27507
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
27422
27508
  }
27423
27509
  const channelId = value["channelId"];
27424
- const runtimeCredential = value[credentialField];
27510
+ const runtimeCredential2 = value[credentialField];
27425
27511
  const runtimeId = value["runtimeId"];
27426
- if (typeof channelId !== "string" || !IDENTIFIER_PATTERN6.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN6.test(runtimeId)) {
27512
+ if (typeof channelId !== "string" || !IDENTIFIER_PATTERN6.test(channelId) || typeof runtimeCredential2 !== "string" || !TOKEN_PATTERN2.test(runtimeCredential2) || typeof runtimeId !== "string" || !IDENTIFIER_PATTERN6.test(runtimeId)) {
27427
27513
  throw new PairingClientError("invalid_relay_response", "The relay returned an invalid pairing response");
27428
27514
  }
27429
- return { channelId, runtimeCredential, runtimeId };
27515
+ return { channelId, runtimeCredential: runtimeCredential2, runtimeId };
27430
27516
  }
27431
27517
  async function claimPairing(options, defaultWorkspace, localAgents, installableAgents, signal) {
27432
27518
  const agents = localAgents.map((agent) => ({
@@ -27461,7 +27547,7 @@ async function claimPairing(options, defaultWorkspace, localAgents, installableA
27461
27547
  }
27462
27548
  return options.pairingId.length > 0 ? decodePairingResponse(value, "runtimeCredential", REDEEM_RESPONSE_KEYS) : decodePairingResponse(value, "daemonToken", CLAIM_RESPONSE_KEYS);
27463
27549
  }
27464
- async function persistConnection(channelId, relayOrigin, runtimeCredential, defaultWorkspace, environment) {
27550
+ async function persistConnection(channelId, relayOrigin, runtimeCredential2, defaultWorkspace, environment) {
27465
27551
  const connectionsDirectory = join14(readStateDirectory(environment), "connections");
27466
27552
  await mkdir4(connectionsDirectory, { mode: 448, recursive: true });
27467
27553
  const connectionPath = join14(connectionsDirectory, `${channelId}.json`);
@@ -27473,7 +27559,7 @@ async function persistConnection(channelId, relayOrigin, runtimeCredential, defa
27473
27559
  defaultWorkspace,
27474
27560
  channelId,
27475
27561
  relayOrigin: relayOrigin.toString(),
27476
- runtimeCredential
27562
+ runtimeCredential: runtimeCredential2
27477
27563
  },
27478
27564
  null,
27479
27565
  2
@@ -27490,16 +27576,16 @@ function decodeSavedConnection(value, expectedChannelId) {
27490
27576
  }
27491
27577
  const defaultWorkspace = value["defaultWorkspace"];
27492
27578
  const channelId = value["channelId"];
27493
- const runtimeCredential = value["runtimeCredential"] ?? value["daemonToken"];
27579
+ const runtimeCredential2 = value["runtimeCredential"] ?? value["daemonToken"];
27494
27580
  const relayOrigin = value["relayOrigin"];
27495
- if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN6.test(channelId) || typeof runtimeCredential !== "string" || !TOKEN_PATTERN2.test(runtimeCredential) || typeof relayOrigin !== "string") {
27581
+ if (typeof defaultWorkspace !== "string" || typeof channelId !== "string" || channelId !== expectedChannelId || !IDENTIFIER_PATTERN6.test(channelId) || typeof runtimeCredential2 !== "string" || !TOKEN_PATTERN2.test(runtimeCredential2) || typeof relayOrigin !== "string") {
27496
27582
  throw new PairingClientError("invalid_connection", "The saved connection is invalid");
27497
27583
  }
27498
27584
  return {
27499
27585
  defaultWorkspace,
27500
27586
  channelId,
27501
27587
  relayOrigin: parseRelayOrigin(relayOrigin),
27502
- runtimeCredential
27588
+ runtimeCredential: runtimeCredential2
27503
27589
  };
27504
27590
  }
27505
27591
  async function readSavedConnection(channelId, environment) {
@@ -27622,6 +27708,23 @@ import { fetch as fetch6, WebSocket as WebSocket2 } from "undici";
27622
27708
 
27623
27709
  // src/agent-catalog/publish-agent-catalog.ts
27624
27710
  import { fetch as fetch4 } from "undici";
27711
+
27712
+ // src/relay-connection/runtime-authentication.ts
27713
+ function runtimeCredential(relayUrl) {
27714
+ return relayUrl.searchParams.get("token") ?? "";
27715
+ }
27716
+ function runtimeAuthorization(relayUrl) {
27717
+ const credential = runtimeCredential(relayUrl);
27718
+ return credential.length === 0 ? {} : { authorization: `Bearer ${credential}` };
27719
+ }
27720
+ function relayHttpEndpoint(relayUrl) {
27721
+ const endpoint = new URL(relayUrl);
27722
+ endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
27723
+ endpoint.search = "";
27724
+ return endpoint;
27725
+ }
27726
+
27727
+ // src/agent-catalog/publish-agent-catalog.ts
27625
27728
  var CATALOG_UPDATE_TIMEOUT_MILLISECONDS = 2e4;
27626
27729
  var AgentCatalogPublishError = class extends Error {
27627
27730
  code = "agent_catalog_update_failed";
@@ -27631,8 +27734,7 @@ var AgentCatalogPublishError = class extends Error {
27631
27734
  }
27632
27735
  };
27633
27736
  function catalogEndpoint(config2) {
27634
- const endpoint = new URL(config2.relayUrl);
27635
- endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
27737
+ const endpoint = relayHttpEndpoint(config2.relayUrl);
27636
27738
  if (endpoint.pathname.endsWith("/daemon")) {
27637
27739
  endpoint.pathname = `${endpoint.pathname.slice(0, -"/daemon".length)}/catalog`;
27638
27740
  return endpoint;
@@ -27663,7 +27765,7 @@ async function publishAgentCatalog(config2, signal) {
27663
27765
  installableAgents: catalog.installableAgents
27664
27766
  }),
27665
27767
  dispatcher: environmentProxyDispatcher,
27666
- headers: { "content-type": "application/json" },
27768
+ headers: { ...runtimeAuthorization(config2.relayUrl), "content-type": "application/json" },
27667
27769
  method: "PUT",
27668
27770
  signal: requestSignal
27669
27771
  });
@@ -29019,8 +29121,7 @@ function parseReference(resource) {
29019
29121
  return new PromptArtifactReference(artifactId, byteLength, sha256, resource);
29020
29122
  }
29021
29123
  function downloadUrl(config2, command, reference) {
29022
- const url2 = new URL(config2.relayUrl);
29023
- url2.protocol = url2.protocol === "wss:" ? "https:" : "http:";
29124
+ const url2 = relayHttpEndpoint(config2.relayUrl);
29024
29125
  let channelPath = "";
29025
29126
  if (url2.pathname.endsWith("/daemon")) {
29026
29127
  channelPath = url2.pathname.slice(0, -"/daemon".length);
@@ -29032,7 +29133,6 @@ function downloadUrl(config2, command, reference) {
29032
29133
  channelPath = `${url2.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
29033
29134
  }
29034
29135
  url2.pathname = `${channelPath}/sessions/${encodeURIComponent(command.sessionId)}/commands/${encodeURIComponent(command.commandId)}/artifacts/${encodeURIComponent(reference.artifactId)}/content`;
29035
- url2.searchParams.delete("liveness");
29036
29136
  return url2;
29037
29137
  }
29038
29138
  async function responseBytes(response, expectedByteLength, signal) {
@@ -29083,7 +29183,11 @@ async function download(config2, command, reference, signal) {
29083
29183
  try {
29084
29184
  response = await fetch5(downloadUrl(config2, command, reference), {
29085
29185
  dispatcher: environmentProxyDispatcher,
29086
- headers: { accept: reference.resource.mimeType },
29186
+ headers: {
29187
+ accept: reference.resource.mimeType,
29188
+ "accept-encoding": "identity",
29189
+ ...runtimeAuthorization(config2.relayUrl)
29190
+ },
29087
29191
  signal: requestSignal
29088
29192
  });
29089
29193
  if (!response.ok) {
@@ -29181,9 +29285,11 @@ import { WebSocket } from "undici";
29181
29285
  var CONNECTION_TIMEOUT_MILLISECONDS = 15e3;
29182
29286
  var RelayConnectionError = class extends Error {
29183
29287
  code;
29184
- constructor(code, message, options = {}) {
29288
+ diagnosticDetails;
29289
+ constructor(code, message, diagnosticDetails = {}, options = {}) {
29185
29290
  super(message, options);
29186
29291
  this.code = code;
29292
+ this.diagnosticDetails = diagnosticDetails;
29187
29293
  this.name = "RelayConnectionError";
29188
29294
  }
29189
29295
  };
@@ -29191,12 +29297,8 @@ function connectRelay(url2, signal) {
29191
29297
  signal.throwIfAborted();
29192
29298
  return new Promise((resolve6, reject) => {
29193
29299
  const endpoint = new URL(url2);
29194
- const headers = {};
29195
- if (endpoint.pathname.endsWith("/v1/runtimes/connect")) {
29196
- const runtimeCredential = endpoint.searchParams.get("token") ?? "";
29197
- endpoint.searchParams.delete("token");
29198
- headers["authorization"] = `Bearer ${runtimeCredential}`;
29199
- }
29300
+ const headers = runtimeAuthorization(endpoint);
29301
+ endpoint.searchParams.delete("token");
29200
29302
  const socket = new WebSocket(endpoint, {
29201
29303
  dispatcher: environmentProxyDispatcher,
29202
29304
  headers
@@ -29221,16 +29323,34 @@ function connectRelay(url2, signal) {
29221
29323
  };
29222
29324
  const onAbort = () => {
29223
29325
  fail(
29224
- new RelayConnectionError("relay_connection_cancelled", "Relay connection was cancelled", {
29225
- cause: signal.reason
29226
- })
29326
+ new RelayConnectionError(
29327
+ "relay_connection_cancelled",
29328
+ "Relay connection was cancelled",
29329
+ {
29330
+ cancellation: "requested"
29331
+ },
29332
+ { cause: signal.reason }
29333
+ )
29227
29334
  );
29228
29335
  };
29229
- const onClose = () => {
29230
- fail(new RelayConnectionError("relay_connection_closed", "Relay connection closed before it was ready"));
29336
+ const onClose = (event) => {
29337
+ fail(
29338
+ new RelayConnectionError("relay_connection_closed", "Relay connection closed before it was ready", {
29339
+ closeCode: event.code.toString(),
29340
+ closeReason: event.reason
29341
+ })
29342
+ );
29231
29343
  };
29232
- const onError = () => {
29233
- fail(new RelayConnectionError("relay_connection_failed", "Relay connection failed"));
29344
+ const onError = (event) => {
29345
+ const cause = Reflect.get(event, "error");
29346
+ fail(
29347
+ new RelayConnectionError(
29348
+ "relay_connection_failed",
29349
+ "Relay connection failed",
29350
+ {},
29351
+ { cause: cause ?? event }
29352
+ )
29353
+ );
29234
29354
  };
29235
29355
  const onOpen = () => {
29236
29356
  if (finished) {
@@ -29241,7 +29361,11 @@ function connectRelay(url2, signal) {
29241
29361
  resolve6(socket);
29242
29362
  };
29243
29363
  function onTimeout() {
29244
- fail(new RelayConnectionError("relay_connection_timeout", "Relay connection timed out"));
29364
+ fail(
29365
+ new RelayConnectionError("relay_connection_timeout", "Relay connection timed out", {
29366
+ timeoutMilliseconds: CONNECTION_TIMEOUT_MILLISECONDS.toString()
29367
+ })
29368
+ );
29245
29369
  }
29246
29370
  signal.addEventListener("abort", onAbort, { once: true });
29247
29371
  socket.addEventListener("close", onClose, { once: true });
@@ -29253,6 +29377,142 @@ function connectRelay(url2, signal) {
29253
29377
  });
29254
29378
  }
29255
29379
 
29380
+ // src/relay-connection/diagnostics.ts
29381
+ var MAX_DIAGNOSTIC_TEXT_LENGTH = 256;
29382
+ function sanitizeDiagnosticText(value) {
29383
+ const printable = Array.from(value, (character) => {
29384
+ const code = character.charCodeAt(0);
29385
+ return code < 32 || code === 127 ? " " : character;
29386
+ }).join("");
29387
+ const withoutSecrets = printable.replace(/([?&](?:access_token|authorization|credential|key|secret|token)=)[^&\s]*/giu, "$1[redacted]").replace(/(?:https?|wss?):\/\/[^\s)]+/giu, (encodedUrl) => {
29388
+ try {
29389
+ return new URL(encodedUrl).origin;
29390
+ } catch {
29391
+ return "[redacted-url]";
29392
+ }
29393
+ });
29394
+ return withoutSecrets.slice(0, MAX_DIAGNOSTIC_TEXT_LENGTH);
29395
+ }
29396
+ function addFailureDetails(details, failure) {
29397
+ let current = failure;
29398
+ for (let depth = 0; depth < 3 && current instanceof Error; depth += 1) {
29399
+ const prefix = depth === 0 ? "failure" : `cause${depth.toString()}`;
29400
+ details[`${prefix}Name`] = current.name;
29401
+ details[`${prefix}Message`] = sanitizeDiagnosticText(current.message);
29402
+ const code = Reflect.get(current, "code");
29403
+ if (typeof code === "string" || typeof code === "number") {
29404
+ details[`${prefix}Code`] = String(code);
29405
+ }
29406
+ const statusCode = Reflect.get(current, "statusCode");
29407
+ if (typeof statusCode === "string" || typeof statusCode === "number") {
29408
+ details[`${prefix}StatusCode`] = String(statusCode);
29409
+ }
29410
+ const diagnosticDetails = Reflect.get(current, "diagnosticDetails");
29411
+ if (typeof diagnosticDetails === "object" && diagnosticDetails !== null) {
29412
+ for (const [key, value] of Object.entries(diagnosticDetails)) {
29413
+ if (typeof value === "string") {
29414
+ details[key] = sanitizeDiagnosticText(value);
29415
+ }
29416
+ }
29417
+ }
29418
+ current = current.cause;
29419
+ }
29420
+ }
29421
+ var RelayConnectionDiagnostics = class {
29422
+ #audit;
29423
+ #channelId;
29424
+ #lastClosedEpoch = 0;
29425
+ #lastFailedEpoch = 0;
29426
+ #relayOrigin;
29427
+ #route;
29428
+ constructor(audit, channelId, relayUrl) {
29429
+ this.#audit = audit;
29430
+ this.#channelId = channelId;
29431
+ this.#relayOrigin = relayUrl.origin;
29432
+ this.#route = describeEnvironmentProxyRoute(relayUrl);
29433
+ }
29434
+ catalogFailed = (failure) => {
29435
+ const details = { ...this.#route, relayOrigin: this.#relayOrigin };
29436
+ addFailureDetails(details, failure);
29437
+ this.#record("agent_catalog_publish_failed", "catalog", details, true);
29438
+ };
29439
+ connectionClosed = (connectionEpoch, code, reason, source) => {
29440
+ if (this.#lastClosedEpoch === connectionEpoch) {
29441
+ return;
29442
+ }
29443
+ this.#lastClosedEpoch = connectionEpoch;
29444
+ this.#record(
29445
+ "relay_connection_closed",
29446
+ connectionEpoch.toString(),
29447
+ {
29448
+ closeCode: code.toString(),
29449
+ closeReason: sanitizeDiagnosticText(reason),
29450
+ connectionEpoch: connectionEpoch.toString(),
29451
+ relayOrigin: this.#relayOrigin,
29452
+ source,
29453
+ ...this.#route
29454
+ },
29455
+ true
29456
+ );
29457
+ };
29458
+ connectionFailed = (connectionEpoch, failure, source) => {
29459
+ if (this.#lastFailedEpoch === connectionEpoch) {
29460
+ return;
29461
+ }
29462
+ this.#lastFailedEpoch = connectionEpoch;
29463
+ const details = {
29464
+ connectionEpoch: connectionEpoch.toString(),
29465
+ relayOrigin: this.#relayOrigin,
29466
+ source,
29467
+ ...this.#route
29468
+ };
29469
+ addFailureDetails(details, failure);
29470
+ this.#record("relay_connection_failed", connectionEpoch.toString(), details, true);
29471
+ };
29472
+ connectionOpened = (connectionEpoch) => {
29473
+ this.#record(
29474
+ "relay_connection_opened",
29475
+ connectionEpoch.toString(),
29476
+ {
29477
+ connectionEpoch: connectionEpoch.toString(),
29478
+ relayOrigin: this.#relayOrigin,
29479
+ ...this.#route
29480
+ },
29481
+ false
29482
+ );
29483
+ };
29484
+ heartbeatTimedOut = (connectionEpoch, heartbeatId, timeoutMilliseconds) => {
29485
+ this.#record(
29486
+ "relay_heartbeat_timed_out",
29487
+ connectionEpoch.toString(),
29488
+ {
29489
+ connectionEpoch: connectionEpoch.toString(),
29490
+ heartbeatId,
29491
+ relayOrigin: this.#relayOrigin,
29492
+ timeoutMilliseconds: timeoutMilliseconds.toString(),
29493
+ ...this.#route
29494
+ },
29495
+ true
29496
+ );
29497
+ };
29498
+ #record = (event, commandId, details, warning) => {
29499
+ this.#audit.record({
29500
+ commandId,
29501
+ details,
29502
+ event,
29503
+ sessionId: this.#channelId
29504
+ });
29505
+ const encoded = JSON.stringify({ channelId: this.#channelId, event, ...details });
29506
+ if (warning) {
29507
+ process.stderr.write(`Mobius relay diagnostic ${encoded}
29508
+ `);
29509
+ return;
29510
+ }
29511
+ process.stdout.write(`Mobius relay diagnostic ${encoded}
29512
+ `);
29513
+ };
29514
+ };
29515
+
29256
29516
  // src/relay-connection/liveness.ts
29257
29517
  import { randomUUID as randomUUID5 } from "node:crypto";
29258
29518
  var HEARTBEAT_ACK_TIMEOUT_MILLISECONDS = 1e4;
@@ -29329,9 +29589,10 @@ var RelayConnectionLiveness = class {
29329
29589
  if (!(this.#state instanceof AwaitingHeartbeatAckState)) {
29330
29590
  throw new Error("Heartbeat timeout occurred in an invalid state");
29331
29591
  }
29592
+ const heartbeatId = this.#state.heartbeatId;
29332
29593
  this.#state.timeout.cancel();
29333
29594
  this.#state = new FinishedRelayLivenessState();
29334
- this.#transport.unresponsive();
29595
+ this.#transport.unresponsive(heartbeatId, HEARTBEAT_ACK_TIMEOUT_MILLISECONDS);
29335
29596
  };
29336
29597
  #sendHeartbeat = () => {
29337
29598
  if (this.#state instanceof WaitingForHeartbeatState) {
@@ -29360,6 +29621,42 @@ var RelayConnectionLiveness = class {
29360
29621
  import { mkdirSync as mkdirSync3 } from "node:fs";
29361
29622
  import { dirname as dirname7 } from "node:path";
29362
29623
  import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
29624
+ var DEFAULT_OUTBOX_LIMITS = {
29625
+ criticalEventBytes: 136 * 1024 * 1024,
29626
+ criticalEventCount: 10128,
29627
+ runtimeDeliveryBytes: 64 * 1024 * 1024,
29628
+ runtimeDeliveryCount: 64,
29629
+ standardEventBytes: 128 * 1024 * 1024,
29630
+ standardEventCount: 1e4
29631
+ };
29632
+ var SessionEventOutboxCapacityError = class extends Error {
29633
+ code = "session_event_outbox_full";
29634
+ constructor() {
29635
+ super("The durable Session event outbox is full");
29636
+ this.name = "SessionEventOutboxCapacityError";
29637
+ }
29638
+ };
29639
+ var RuntimeDeliveryInboxCapacityError = class extends Error {
29640
+ code = "runtime_delivery_inbox_full";
29641
+ constructor() {
29642
+ super("The durable Runtime delivery inbox is full");
29643
+ this.name = "RuntimeDeliveryInboxCapacityError";
29644
+ }
29645
+ };
29646
+ var StoredRuntimeDelivery = class {
29647
+ deliveryId;
29648
+ duplicate;
29649
+ payload;
29650
+ sessionId;
29651
+ state;
29652
+ constructor(deliveryId, sessionId, payload, state, duplicate) {
29653
+ this.deliveryId = deliveryId;
29654
+ this.duplicate = duplicate;
29655
+ this.payload = payload;
29656
+ this.sessionId = sessionId;
29657
+ this.state = state;
29658
+ }
29659
+ };
29363
29660
  function readPendingEnvelope(row) {
29364
29661
  if (typeof row !== "object" || row === null || Array.isArray(row)) {
29365
29662
  throw new Error("SQLite returned an invalid session event outbox row");
@@ -29370,9 +29667,26 @@ function readPendingEnvelope(row) {
29370
29667
  }
29371
29668
  return envelope2;
29372
29669
  }
29670
+ function rowNumber(row, field) {
29671
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
29672
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
29673
+ throw new Error(`SQLite returned an invalid ${field} value`);
29674
+ }
29675
+ return value;
29676
+ }
29677
+ function rowString2(row, field) {
29678
+ const value = typeof row === "object" && row !== null ? Reflect.get(row, field) : void 0;
29679
+ if (typeof value !== "string" || value.length === 0) {
29680
+ throw new Error(`SQLite returned an invalid ${field} value`);
29681
+ }
29682
+ return value;
29683
+ }
29373
29684
  var SessionEventOutbox = class {
29374
29685
  #database;
29375
- constructor(path2) {
29686
+ #limits;
29687
+ constructor(path2, limits = DEFAULT_OUTBOX_LIMITS) {
29688
+ validateLimits(limits);
29689
+ this.#limits = limits;
29376
29690
  mkdirSync3(dirname7(path2), { recursive: true });
29377
29691
  this.#database = new DatabaseSync4(path2);
29378
29692
  this.#database.exec(`
@@ -29380,6 +29694,8 @@ var SessionEventOutbox = class {
29380
29694
  ordinal INTEGER PRIMARY KEY AUTOINCREMENT,
29381
29695
  event_id TEXT NOT NULL UNIQUE,
29382
29696
  session_id TEXT NOT NULL,
29697
+ source_key TEXT NOT NULL,
29698
+ byte_length INTEGER NOT NULL,
29383
29699
  envelope_json TEXT NOT NULL
29384
29700
  ) STRICT;
29385
29701
  CREATE INDEX IF NOT EXISTS relay_session_event_outbox_order
@@ -29390,7 +29706,7 @@ var SessionEventOutbox = class {
29390
29706
  session_id TEXT NOT NULL,
29391
29707
  envelope_json TEXT NOT NULL,
29392
29708
  rejection_code TEXT NOT NULL CHECK (
29393
- rejection_code IN ('session_deleted', 'session_not_found')
29709
+ rejection_code IN ('session_deleted', 'session_not_found', 'session_rejected')
29394
29710
  )
29395
29711
  ) STRICT;
29396
29712
  CREATE TABLE IF NOT EXISTS relay_session_event_sources (
@@ -29406,6 +29722,92 @@ var SessionEventOutbox = class {
29406
29722
  state IN ('dispatching', 'running', 'succeeded', 'failed', 'cancelled')
29407
29723
  )
29408
29724
  ) STRICT;
29725
+ CREATE TABLE IF NOT EXISTS relay_session_event_outbox_state (
29726
+ singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
29727
+ event_count INTEGER NOT NULL,
29728
+ total_bytes INTEGER NOT NULL
29729
+ ) STRICT;
29730
+ INSERT OR IGNORE INTO relay_session_event_outbox_state (
29731
+ singleton, event_count, total_bytes
29732
+ ) VALUES (1, 0, 0);
29733
+ CREATE TABLE IF NOT EXISTS relay_runtime_deliveries (
29734
+ delivery_id TEXT PRIMARY KEY,
29735
+ session_id TEXT NOT NULL,
29736
+ payload_json TEXT NOT NULL,
29737
+ payload_bytes INTEGER NOT NULL,
29738
+ state TEXT NOT NULL CHECK (
29739
+ state IN ('received', 'relay_committed', 'processed')
29740
+ ),
29741
+ received_at INTEGER NOT NULL
29742
+ ) STRICT;
29743
+ `);
29744
+ const deadLetterTable = this.#database.prepare(
29745
+ `SELECT sql FROM sqlite_master
29746
+ WHERE type = 'table' AND name = 'relay_session_event_dead_letters'`
29747
+ ).get();
29748
+ const deadLetterSql = typeof deadLetterTable === "object" && deadLetterTable !== null ? Reflect.get(deadLetterTable, "sql") : void 0;
29749
+ if (typeof deadLetterSql !== "string" || !deadLetterSql.includes("'session_rejected'")) {
29750
+ this.#database.exec(`
29751
+ BEGIN IMMEDIATE;
29752
+ ALTER TABLE relay_session_event_dead_letters
29753
+ RENAME TO relay_session_event_dead_letters_legacy;
29754
+ CREATE TABLE relay_session_event_dead_letters (
29755
+ ordinal INTEGER PRIMARY KEY,
29756
+ event_id TEXT NOT NULL UNIQUE,
29757
+ session_id TEXT NOT NULL,
29758
+ envelope_json TEXT NOT NULL,
29759
+ rejection_code TEXT NOT NULL CHECK (
29760
+ rejection_code IN ('session_deleted', 'session_not_found', 'session_rejected')
29761
+ )
29762
+ ) STRICT;
29763
+ INSERT INTO relay_session_event_dead_letters (
29764
+ ordinal, event_id, session_id, envelope_json, rejection_code
29765
+ )
29766
+ SELECT ordinal, event_id, session_id, envelope_json, rejection_code
29767
+ FROM relay_session_event_dead_letters_legacy;
29768
+ DROP TABLE relay_session_event_dead_letters_legacy;
29769
+ COMMIT;
29770
+ `);
29771
+ }
29772
+ const outboxColumns = this.#database.prepare("PRAGMA table_info(relay_session_event_outbox)").all();
29773
+ const hasSourceKey = outboxColumns.some(
29774
+ (column) => typeof column === "object" && column !== null && Reflect.get(column, "name") === "source_key"
29775
+ );
29776
+ if (!hasSourceKey) {
29777
+ this.#database.exec(`
29778
+ ALTER TABLE relay_session_event_outbox
29779
+ ADD COLUMN source_key TEXT NOT NULL DEFAULT '';
29780
+ `);
29781
+ }
29782
+ const hasByteLength = outboxColumns.some(
29783
+ (column) => typeof column === "object" && column !== null && Reflect.get(column, "name") === "byte_length"
29784
+ );
29785
+ if (!hasByteLength) {
29786
+ this.#database.exec(`
29787
+ ALTER TABLE relay_session_event_outbox
29788
+ ADD COLUMN byte_length INTEGER NOT NULL DEFAULT 0;
29789
+ `);
29790
+ }
29791
+ this.#database.exec(`
29792
+ UPDATE relay_session_event_outbox
29793
+ SET source_key = CASE json_extract(envelope_json, '$.source.kind')
29794
+ WHEN 'attempt' THEN
29795
+ 'attempt:' || session_id || ':' || json_extract(envelope_json, '$.source.attemptId')
29796
+ ELSE 'runtime:' || session_id
29797
+ END
29798
+ WHERE source_key = '';
29799
+ UPDATE relay_session_event_outbox
29800
+ SET byte_length = length(CAST(envelope_json AS BLOB))
29801
+ WHERE byte_length = 0;
29802
+ UPDATE relay_session_event_outbox_state
29803
+ SET event_count = (SELECT COUNT(*) FROM relay_session_event_outbox),
29804
+ total_bytes = (
29805
+ SELECT COALESCE(SUM(byte_length), 0)
29806
+ FROM relay_session_event_outbox
29807
+ )
29808
+ WHERE singleton = 1;
29809
+ CREATE INDEX IF NOT EXISTS relay_session_event_outbox_source_order
29810
+ ON relay_session_event_outbox(source_key, ordinal);
29409
29811
  `);
29410
29812
  }
29411
29813
  claimExecution = (commandId, sessionId, commandJson) => {
@@ -29464,15 +29866,19 @@ var SessionEventOutbox = class {
29464
29866
  const state = typeof row === "object" && row !== null ? Reflect.get(row, "state") : void 0;
29465
29867
  return state === "succeeded" || state === "failed" || state === "cancelled";
29466
29868
  };
29467
- acknowledge = (acknowledgement) => {
29468
- this.#database.prepare(
29469
- `DELETE FROM relay_session_event_outbox
29470
- WHERE event_id = ? AND session_id = ?`
29471
- ).run(acknowledgement.eventId, acknowledgement.sessionId);
29472
- };
29869
+ acknowledge = (acknowledgement) => this.#removeAcknowledgedEvent(acknowledgement.eventId, acknowledgement.sessionId);
29473
29870
  quarantine = (rejection) => {
29474
29871
  this.#database.exec("BEGIN IMMEDIATE");
29475
29872
  try {
29873
+ const stored = this.#database.prepare(
29874
+ `SELECT byte_length FROM relay_session_event_outbox
29875
+ WHERE event_id = ? AND session_id = ?`
29876
+ ).get(rejection.eventId, rejection.sessionId);
29877
+ if (stored === void 0) {
29878
+ this.#database.exec("COMMIT");
29879
+ return false;
29880
+ }
29881
+ const byteLength = rowNumber(stored, "byte_length");
29476
29882
  const insertion = this.#database.prepare(
29477
29883
  `INSERT INTO relay_session_event_dead_letters (
29478
29884
  ordinal, event_id, session_id, envelope_json, rejection_code
@@ -29492,6 +29898,7 @@ var SessionEventOutbox = class {
29492
29898
  if (deletion.changes !== 1) {
29493
29899
  throw new Error("The rejected session event could not be removed from the durable outbox");
29494
29900
  }
29901
+ this.#decrementEventState(byteLength);
29495
29902
  this.#database.exec("COMMIT");
29496
29903
  return true;
29497
29904
  } catch (error61) {
@@ -29503,7 +29910,129 @@ var SessionEventOutbox = class {
29503
29910
  this.#database.close();
29504
29911
  };
29505
29912
  enqueue = (sessionId, payload, attemptId = null) => {
29506
- const sourceKey = attemptId === null ? `runtime:${sessionId}` : `attempt:${sessionId}:${attemptId}`;
29913
+ return this.#enqueueBounded(
29914
+ sessionId,
29915
+ payload,
29916
+ attemptId ?? "",
29917
+ this.#limits.standardEventCount,
29918
+ this.#limits.standardEventBytes
29919
+ );
29920
+ };
29921
+ enqueueCritical = (sessionId, payload, attemptId = "") => this.#enqueueBounded(
29922
+ sessionId,
29923
+ payload,
29924
+ attemptId,
29925
+ this.#limits.criticalEventCount,
29926
+ this.#limits.criticalEventBytes
29927
+ );
29928
+ claimRuntimeDelivery = (delivery) => {
29929
+ const payloadBytes = new TextEncoder().encode(delivery.payload).byteLength;
29930
+ this.#database.exec("BEGIN IMMEDIATE");
29931
+ try {
29932
+ const existing = this.#database.prepare(
29933
+ `SELECT session_id, payload_json, state
29934
+ FROM relay_runtime_deliveries
29935
+ WHERE delivery_id = ?`
29936
+ ).get(delivery.deliveryId);
29937
+ if (existing !== void 0) {
29938
+ const sessionId = rowString2(existing, "session_id");
29939
+ const payload = rowString2(existing, "payload_json");
29940
+ const state = runtimeDeliveryState(existing);
29941
+ if (sessionId !== delivery.sessionId || payload !== delivery.payload) {
29942
+ throw new Error("The Runtime delivery identity conflicts with its durable receipt");
29943
+ }
29944
+ this.#database.exec("COMMIT");
29945
+ return new StoredRuntimeDelivery(delivery.deliveryId, sessionId, payload, state, true);
29946
+ }
29947
+ const usage = this.#database.prepare(
29948
+ `SELECT COUNT(*) AS delivery_count,
29949
+ COALESCE(SUM(payload_bytes), 0) AS delivery_bytes
29950
+ FROM relay_runtime_deliveries`
29951
+ ).get();
29952
+ if (rowNumber(usage, "delivery_count") + 1 > this.#limits.runtimeDeliveryCount || rowNumber(usage, "delivery_bytes") + payloadBytes > this.#limits.runtimeDeliveryBytes) {
29953
+ throw new RuntimeDeliveryInboxCapacityError();
29954
+ }
29955
+ this.#database.prepare(
29956
+ `INSERT INTO relay_runtime_deliveries (
29957
+ delivery_id, session_id, payload_json, payload_bytes, state, received_at
29958
+ ) VALUES (?, ?, ?, ?, 'received', ?)`
29959
+ ).run(delivery.deliveryId, delivery.sessionId, delivery.payload, payloadBytes, Date.now());
29960
+ this.#database.exec("COMMIT");
29961
+ return new StoredRuntimeDelivery(
29962
+ delivery.deliveryId,
29963
+ delivery.sessionId,
29964
+ delivery.payload,
29965
+ "received",
29966
+ false
29967
+ );
29968
+ } catch (error61) {
29969
+ this.#database.exec("ROLLBACK");
29970
+ throw error61;
29971
+ }
29972
+ };
29973
+ markRuntimeDeliveryCommitted = (deliveryId, sessionId) => {
29974
+ this.#database.exec("BEGIN IMMEDIATE");
29975
+ try {
29976
+ const row = this.#database.prepare(
29977
+ `SELECT state FROM relay_runtime_deliveries
29978
+ WHERE delivery_id = ? AND session_id = ?`
29979
+ ).get(deliveryId, sessionId);
29980
+ if (row === void 0) {
29981
+ this.#database.exec("COMMIT");
29982
+ return false;
29983
+ }
29984
+ const state = runtimeDeliveryState(row);
29985
+ if (state === "processed") {
29986
+ this.#database.prepare(`DELETE FROM relay_runtime_deliveries WHERE delivery_id = ?`).run(deliveryId);
29987
+ } else if (state === "received") {
29988
+ this.#database.prepare(`UPDATE relay_runtime_deliveries SET state = 'relay_committed' WHERE delivery_id = ?`).run(deliveryId);
29989
+ }
29990
+ this.#database.exec("COMMIT");
29991
+ return true;
29992
+ } catch (error61) {
29993
+ this.#database.exec("ROLLBACK");
29994
+ throw error61;
29995
+ }
29996
+ };
29997
+ markRuntimeDeliveryProcessed = (deliveryId, sessionId) => {
29998
+ this.#database.exec("BEGIN IMMEDIATE");
29999
+ try {
30000
+ const row = this.#database.prepare(
30001
+ `SELECT state FROM relay_runtime_deliveries
30002
+ WHERE delivery_id = ? AND session_id = ?`
30003
+ ).get(deliveryId, sessionId);
30004
+ if (row === void 0) {
30005
+ this.#database.exec("COMMIT");
30006
+ return false;
30007
+ }
30008
+ const state = runtimeDeliveryState(row);
30009
+ if (state === "relay_committed") {
30010
+ this.#database.prepare(`DELETE FROM relay_runtime_deliveries WHERE delivery_id = ?`).run(deliveryId);
30011
+ } else if (state === "received") {
30012
+ this.#database.prepare(`UPDATE relay_runtime_deliveries SET state = 'processed' WHERE delivery_id = ?`).run(deliveryId);
30013
+ }
30014
+ this.#database.exec("COMMIT");
30015
+ return true;
30016
+ } catch (error61) {
30017
+ this.#database.exec("ROLLBACK");
30018
+ throw error61;
30019
+ }
30020
+ };
30021
+ runtimeDeliveries = () => this.#database.prepare(
30022
+ `SELECT delivery_id, session_id, payload_json, state
30023
+ FROM relay_runtime_deliveries
30024
+ ORDER BY received_at, delivery_id`
30025
+ ).all().map(
30026
+ (row) => new StoredRuntimeDelivery(
30027
+ rowString2(row, "delivery_id"),
30028
+ rowString2(row, "session_id"),
30029
+ rowString2(row, "payload_json"),
30030
+ runtimeDeliveryState(row),
30031
+ true
30032
+ )
30033
+ );
30034
+ #enqueueBounded = (sessionId, payload, attemptId, maximumEventCount, maximumEventBytes) => {
30035
+ const sourceKey = attemptId.length === 0 ? `runtime:${sessionId}` : `attempt:${sessionId}:${attemptId}`;
29507
30036
  this.#database.exec("BEGIN IMMEDIATE");
29508
30037
  try {
29509
30038
  const row = this.#database.prepare(
@@ -29520,13 +30049,22 @@ var SessionEventOutbox = class {
29520
30049
  kind: SESSION_EVENT_KIND,
29521
30050
  payload,
29522
30051
  sessionId,
29523
- source: attemptId === null ? { kind: "runtime", sourceSeq: nextSourceSeq.toString() } : {
30052
+ source: attemptId.length === 0 ? { kind: "runtime", sourceSeq: nextSourceSeq.toString() } : {
29524
30053
  attemptId,
29525
30054
  kind: "attempt",
29526
30055
  sourceSeq: nextSourceSeq.toString()
29527
30056
  }
29528
30057
  };
29529
30058
  const encoded = encodeSessionEventEnvelope(envelope2);
30059
+ const byteLength = new TextEncoder().encode(encoded).byteLength;
30060
+ const state = this.#database.prepare(
30061
+ `SELECT event_count, total_bytes
30062
+ FROM relay_session_event_outbox_state
30063
+ WHERE singleton = 1`
30064
+ ).get();
30065
+ if (rowNumber(state, "event_count") + 1 > maximumEventCount || rowNumber(state, "total_bytes") + byteLength > maximumEventBytes) {
30066
+ throw new SessionEventOutboxCapacityError();
30067
+ }
29530
30068
  this.#database.prepare(
29531
30069
  `INSERT INTO relay_session_event_sources (
29532
30070
  source_key, next_source_seq
@@ -29536,9 +30074,15 @@ var SessionEventOutbox = class {
29536
30074
  ).run(sourceKey, nextSourceSeq + 1);
29537
30075
  this.#database.prepare(
29538
30076
  `INSERT INTO relay_session_event_outbox (
29539
- event_id, session_id, envelope_json
29540
- ) VALUES (?, ?, ?)`
29541
- ).run(envelope2.eventId, envelope2.sessionId, encoded);
30077
+ event_id, session_id, source_key, byte_length, envelope_json
30078
+ ) VALUES (?, ?, ?, ?, ?)`
30079
+ ).run(envelope2.eventId, envelope2.sessionId, sourceKey, byteLength, encoded);
30080
+ this.#database.prepare(
30081
+ `UPDATE relay_session_event_outbox_state
30082
+ SET event_count = event_count + 1,
30083
+ total_bytes = total_bytes + ?
30084
+ WHERE singleton = 1`
30085
+ ).run(byteLength);
29542
30086
  this.#database.exec("COMMIT");
29543
30087
  return encoded;
29544
30088
  } catch (error61) {
@@ -29546,12 +30090,132 @@ var SessionEventOutbox = class {
29546
30090
  throw error61;
29547
30091
  }
29548
30092
  };
30093
+ #removeAcknowledgedEvent = (eventId, sessionId) => {
30094
+ this.#database.exec("BEGIN IMMEDIATE");
30095
+ try {
30096
+ const stored = this.#database.prepare(
30097
+ `SELECT byte_length FROM relay_session_event_outbox
30098
+ WHERE event_id = ? AND session_id = ?`
30099
+ ).get(eventId, sessionId);
30100
+ if (stored === void 0) {
30101
+ this.#database.exec("COMMIT");
30102
+ return false;
30103
+ }
30104
+ const byteLength = rowNumber(stored, "byte_length");
30105
+ const deletion = this.#database.prepare(
30106
+ `DELETE FROM relay_session_event_outbox
30107
+ WHERE event_id = ? AND session_id = ?`
30108
+ ).run(eventId, sessionId);
30109
+ if (deletion.changes !== 1) {
30110
+ throw new Error("The acknowledged session event could not be removed from the durable outbox");
30111
+ }
30112
+ this.#decrementEventState(byteLength);
30113
+ this.#database.exec("COMMIT");
30114
+ return true;
30115
+ } catch (error61) {
30116
+ this.#database.exec("ROLLBACK");
30117
+ throw error61;
30118
+ }
30119
+ };
30120
+ #decrementEventState = (byteLength) => {
30121
+ const result = this.#database.prepare(
30122
+ `UPDATE relay_session_event_outbox_state
30123
+ SET event_count = event_count - 1,
30124
+ total_bytes = total_bytes - ?
30125
+ WHERE singleton = 1 AND event_count > 0 AND total_bytes >= ?`
30126
+ ).run(byteLength, byteLength);
30127
+ if (result.changes !== 1) {
30128
+ throw new Error("The Session event outbox capacity state is inconsistent");
30129
+ }
30130
+ };
29549
30131
  pending = () => this.#database.prepare(
29550
30132
  `SELECT envelope_json
29551
30133
  FROM relay_session_event_outbox
29552
30134
  ORDER BY ordinal`
29553
30135
  ).all().map(readPendingEnvelope);
29554
- };
30136
+ pendingBatch = (limit) => {
30137
+ if (!Number.isSafeInteger(limit) || limit < 1) {
30138
+ throw new Error("Session event replay batch limit must be a positive integer");
30139
+ }
30140
+ return this.#database.prepare(
30141
+ `WITH ranked_events AS (
30142
+ SELECT ordinal,
30143
+ ROW_NUMBER() OVER (
30144
+ PARTITION BY source_key
30145
+ ORDER BY ordinal
30146
+ ) AS source_position
30147
+ FROM relay_session_event_outbox
30148
+ )
30149
+ SELECT outbox.envelope_json
30150
+ FROM ranked_events AS ranked
30151
+ INNER JOIN relay_session_event_outbox AS outbox
30152
+ ON outbox.ordinal = ranked.ordinal
30153
+ ORDER BY ranked.source_position, outbox.ordinal
30154
+ LIMIT ?`
30155
+ ).all(limit).map(readPendingEnvelope);
30156
+ };
30157
+ pendingSourceHeads = (limit, excludedEventIds, excludedSourceKeys) => {
30158
+ if (!Number.isSafeInteger(limit) || limit < 1) {
30159
+ throw new Error("Session event replay batch limit must be a positive integer");
30160
+ }
30161
+ const exclusions = [];
30162
+ const parameters = [];
30163
+ if (excludedEventIds.length > 0) {
30164
+ exclusions.push(`event_id NOT IN (${excludedEventIds.map(() => "?").join(", ")})`);
30165
+ parameters.push(...excludedEventIds);
30166
+ }
30167
+ if (excludedSourceKeys.length > 0) {
30168
+ exclusions.push(`source_key NOT IN (${excludedSourceKeys.map(() => "?").join(", ")})`);
30169
+ parameters.push(...excludedSourceKeys);
30170
+ }
30171
+ parameters.push(limit.toString());
30172
+ const where = exclusions.length === 0 ? "" : `WHERE ${exclusions.join(" AND ")}`;
30173
+ return this.#database.prepare(
30174
+ `WITH available_events AS (
30175
+ SELECT ordinal, source_key
30176
+ FROM relay_session_event_outbox
30177
+ ${where}
30178
+ ), ranked_events AS (
30179
+ SELECT ordinal,
30180
+ ROW_NUMBER() OVER (
30181
+ PARTITION BY source_key
30182
+ ORDER BY ordinal
30183
+ ) AS source_position
30184
+ FROM available_events
30185
+ )
30186
+ SELECT outbox.envelope_json
30187
+ FROM ranked_events AS ranked
30188
+ INNER JOIN relay_session_event_outbox AS outbox
30189
+ ON outbox.ordinal = ranked.ordinal
30190
+ WHERE ranked.source_position = 1
30191
+ ORDER BY outbox.ordinal
30192
+ LIMIT ?`
30193
+ ).all(...parameters).map(readPendingEnvelope);
30194
+ };
30195
+ };
30196
+ function validateLimits(limits) {
30197
+ const values = [
30198
+ limits.criticalEventBytes,
30199
+ limits.criticalEventCount,
30200
+ limits.runtimeDeliveryBytes,
30201
+ limits.runtimeDeliveryCount,
30202
+ limits.standardEventBytes,
30203
+ limits.standardEventCount
30204
+ ];
30205
+ if (values.some((value) => !Number.isSafeInteger(value) || value < 1)) {
30206
+ throw new Error("Session event outbox limits must be positive integers");
30207
+ }
30208
+ if (limits.criticalEventBytes < limits.standardEventBytes || limits.criticalEventCount < limits.standardEventCount) {
30209
+ throw new Error("Critical Session event capacity must include the standard capacity");
30210
+ }
30211
+ }
30212
+ function runtimeDeliveryState(row) {
30213
+ const state = rowString2(row, "state");
30214
+ if (state !== "received" && state !== "relay_committed" && state !== "processed") {
30215
+ throw new Error("SQLite returned an invalid Runtime delivery state");
30216
+ }
30217
+ return state;
30218
+ }
29555
30219
  function isDaemonExecutionState(value) {
29556
30220
  return value === "cancelled" || value === "dispatching" || value === "failed" || value === "running" || value === "succeeded";
29557
30221
  }
@@ -29567,7 +30231,7 @@ var systemDaemonTimer = new SystemDaemonTimer();
29567
30231
 
29568
30232
  // src/run-daemon.ts
29569
30233
  var RELAY_RECONNECT_DELAY_MILLISECONDS = 500;
29570
- var RELAY_CONNECTION_REFRESH_MILLISECONDS = 20 * 60 * 1e3;
30234
+ var AGENT_ACTIVITY_COALESCING_MILLISECONDS = 100;
29571
30235
  var ACTIVE_TURN_RECONCILIATION_TIMEOUT_MILLISECONDS = 2e4;
29572
30236
  var ACTIVE_TURN_RECONCILIATION_CLOSE_CODE = 4009;
29573
30237
  function botIpcSocketPath() {
@@ -29576,54 +30240,6 @@ function botIpcSocketPath() {
29576
30240
  }
29577
30241
  return join16("/tmp", `mobius-bot-${randomUUID6()}.sock`);
29578
30242
  }
29579
- var RelayConnectionRefreshState = class {
29580
- };
29581
- var ScheduledRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29582
- };
29583
- var DueRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29584
- };
29585
- var FinishedRelayConnectionRefreshState = class extends RelayConnectionRefreshState {
29586
- };
29587
- var RelayConnectionRefresh = class {
29588
- #isSafe;
29589
- #refresh;
29590
- #scheduledTask;
29591
- #state = new ScheduledRelayConnectionRefreshState();
29592
- constructor(timer, isSafe, refresh) {
29593
- this.#isSafe = isSafe;
29594
- this.#refresh = refresh;
29595
- this.#scheduledTask = timer.schedule(this.#request, RELAY_CONNECTION_REFRESH_MILLISECONDS);
29596
- }
29597
- afterTurnFinished = () => {
29598
- if (this.#state instanceof DueRelayConnectionRefreshState) {
29599
- this.#request();
29600
- return;
29601
- }
29602
- if (this.#state instanceof ScheduledRelayConnectionRefreshState || this.#state instanceof FinishedRelayConnectionRefreshState) {
29603
- return;
29604
- }
29605
- throw new Error("Unknown relay connection refresh state");
29606
- };
29607
- close = () => {
29608
- this.#scheduledTask.cancel();
29609
- this.#state = new FinishedRelayConnectionRefreshState();
29610
- };
29611
- #request = () => {
29612
- if (this.#state instanceof FinishedRelayConnectionRefreshState) {
29613
- return;
29614
- }
29615
- if (!(this.#state instanceof ScheduledRelayConnectionRefreshState) && !(this.#state instanceof DueRelayConnectionRefreshState)) {
29616
- throw new Error("Unknown relay connection refresh state");
29617
- }
29618
- if (!this.#isSafe()) {
29619
- this.#state = new DueRelayConnectionRefreshState();
29620
- return;
29621
- }
29622
- this.#state = new FinishedRelayConnectionRefreshState();
29623
- this.#scheduledTask.cancel();
29624
- this.#refresh();
29625
- };
29626
- };
29627
30243
  var DaemonAcpObserver = class {
29628
30244
  #record;
29629
30245
  constructor(record2) {
@@ -29818,8 +30434,7 @@ var ActiveTurnReconciliationError = class extends Error {
29818
30434
  }
29819
30435
  };
29820
30436
  function activeTurnReconciliationEndpoint(config2, turn) {
29821
- const endpoint = new URL(config2.relayUrl);
29822
- endpoint.protocol = endpoint.protocol === "wss:" ? "https:" : "http:";
30437
+ const endpoint = relayHttpEndpoint(config2.relayUrl);
29823
30438
  let channelPath = "";
29824
30439
  if (endpoint.pathname.endsWith("/daemon")) {
29825
30440
  channelPath = endpoint.pathname.slice(0, -"/daemon".length);
@@ -29831,7 +30446,6 @@ function activeTurnReconciliationEndpoint(config2, turn) {
29831
30446
  channelPath = `${endpoint.pathname.slice(0, -runtimePath.length)}/channels/${encodeURIComponent(config2.channelId)}`;
29832
30447
  }
29833
30448
  endpoint.pathname = `${channelPath}/sessions/${encodeURIComponent(turn.command.sessionId)}/attempts/${encodeURIComponent(turn.attemptId)}/reconciliation`;
29834
- endpoint.searchParams.delete("liveness");
29835
30449
  return endpoint;
29836
30450
  }
29837
30451
  async function reconcileActiveTurn(config2, turn, signal) {
@@ -29843,7 +30457,7 @@ async function reconcileActiveTurn(config2, turn, signal) {
29843
30457
  response = await fetch6(activeTurnReconciliationEndpoint(config2, turn), {
29844
30458
  body: JSON.stringify({ outcome: { kind: "running" } }),
29845
30459
  dispatcher: environmentProxyDispatcher,
29846
- headers: { "content-type": "application/json" },
30460
+ headers: { ...runtimeAuthorization(config2.relayUrl), "content-type": "application/json" },
29847
30461
  method: "POST",
29848
30462
  signal: requestSignal
29849
30463
  });
@@ -29888,7 +30502,8 @@ var DaemonSessionEventConnectionState = class {
29888
30502
  };
29889
30503
  var DisconnectedDaemonSessionEventState = class extends DaemonSessionEventConnectionState {
29890
30504
  disconnect = () => this;
29891
- send = () => void 0;
30505
+ matches = () => false;
30506
+ send = () => false;
29892
30507
  };
29893
30508
  var ConnectedDaemonSessionEventState = class extends DaemonSessionEventConnectionState {
29894
30509
  #socket;
@@ -29897,38 +30512,210 @@ var ConnectedDaemonSessionEventState = class extends DaemonSessionEventConnectio
29897
30512
  this.#socket = socket;
29898
30513
  }
29899
30514
  disconnect = (socket) => socket === this.#socket ? new DisconnectedDaemonSessionEventState() : this;
30515
+ matches = (socket) => socket === this.#socket;
29900
30516
  send = (message) => {
29901
- if (this.#socket.readyState === WebSocket2.OPEN) {
29902
- try {
29903
- this.#socket.send(message);
29904
- } catch {
29905
- }
30517
+ if (this.#socket.readyState !== WebSocket2.OPEN) {
30518
+ return false;
30519
+ }
30520
+ try {
30521
+ this.#socket.send(message);
30522
+ return true;
30523
+ } catch {
30524
+ return false;
29906
30525
  }
29907
30526
  };
29908
30527
  };
30528
+ var SESSION_EVENT_REPLAY_WINDOW = 16;
30529
+ var SESSION_EVENT_SOURCE_REPLAY_WINDOW = 4;
30530
+ var SESSION_EVENT_REPLAY_BYTE_LIMIT = 32 * 1024 * 1024;
30531
+ var InFlightSessionEvent = class {
30532
+ byteLength;
30533
+ sourceKey;
30534
+ constructor(sourceKey, byteLength) {
30535
+ this.byteLength = byteLength;
30536
+ this.sourceKey = sourceKey;
30537
+ }
30538
+ };
30539
+ function sessionEventSourceKey(envelope2) {
30540
+ return envelope2.source.kind === "attempt" ? `attempt:${envelope2.sessionId}:${envelope2.source.attemptId}` : `runtime:${envelope2.sessionId}`;
30541
+ }
29909
30542
  var DaemonSessionEventPublisher = class {
29910
30543
  #outbox;
30544
+ #inFlightEvents = /* @__PURE__ */ new Map();
30545
+ #inFlightBytes = 0;
29911
30546
  #connection = new DisconnectedDaemonSessionEventState();
29912
30547
  constructor(outbox) {
29913
30548
  this.#outbox = outbox;
29914
30549
  }
29915
30550
  connect = (socket) => {
29916
- const connection = new ConnectedDaemonSessionEventState(socket);
29917
- this.#connection = connection;
29918
- for (const pending of this.#outbox.pending()) {
29919
- connection.send(pending);
29920
- }
30551
+ this.#connection = new ConnectedDaemonSessionEventState(socket);
30552
+ this.#clearInFlight();
30553
+ this.#publishNextBatch();
29921
30554
  };
29922
30555
  disconnect = (socket) => {
30556
+ if (!this.#connection.matches(socket)) {
30557
+ return;
30558
+ }
29923
30559
  this.#connection = this.#connection.disconnect(socket);
30560
+ this.#clearInFlight();
29924
30561
  };
29925
30562
  recordAttempt = (sessionId, payload, attemptId) => {
29926
- this.#connection.send(this.#outbox.enqueue(sessionId, payload, attemptId));
30563
+ const kind = decodeMessageKind(payload);
30564
+ if (kind === SESSION_COMPLETED_KIND || kind === SESSION_FAILED_KIND) {
30565
+ this.#outbox.enqueueCritical(sessionId, payload, attemptId);
30566
+ } else {
30567
+ this.#outbox.enqueue(sessionId, payload, attemptId);
30568
+ }
30569
+ this.#publishNextBatch();
29927
30570
  };
29928
30571
  recordRuntime = (sessionId, payload) => {
29929
- this.#connection.send(this.#outbox.enqueue(sessionId, payload));
30572
+ this.#outbox.enqueue(sessionId, payload);
30573
+ this.#publishNextBatch();
30574
+ };
30575
+ acknowledge = (acknowledgement) => {
30576
+ if (!this.#outbox.acknowledge(acknowledgement)) {
30577
+ return;
30578
+ }
30579
+ this.#removeInFlight(acknowledgement.eventId);
30580
+ this.#publishNextBatch();
30581
+ };
30582
+ quarantine = (rejection) => {
30583
+ const quarantined = this.#outbox.quarantine(rejection);
30584
+ if (!quarantined) {
30585
+ return false;
30586
+ }
30587
+ this.#removeInFlight(rejection.eventId);
30588
+ this.#publishNextBatch();
30589
+ return true;
30590
+ };
30591
+ #publishNextBatch = () => {
30592
+ while (this.#inFlightEvents.size < SESSION_EVENT_REPLAY_WINDOW) {
30593
+ const sourceCounts = /* @__PURE__ */ new Map();
30594
+ for (const inFlight of this.#inFlightEvents.values()) {
30595
+ sourceCounts.set(inFlight.sourceKey, (sourceCounts.get(inFlight.sourceKey) ?? 0) + 1);
30596
+ }
30597
+ const saturatedSources = [...sourceCounts.entries()].filter(([, count]) => count >= SESSION_EVENT_SOURCE_REPLAY_WINDOW).map(([sourceKey]) => sourceKey);
30598
+ const batch = this.#outbox.pendingSourceHeads(
30599
+ SESSION_EVENT_REPLAY_WINDOW - this.#inFlightEvents.size,
30600
+ [...this.#inFlightEvents.keys()],
30601
+ saturatedSources
30602
+ );
30603
+ let sent = 0;
30604
+ for (const encoded of batch) {
30605
+ const envelope2 = decodeSessionEventEnvelope(encoded);
30606
+ const sourceKey = sessionEventSourceKey(envelope2);
30607
+ const sourceCount = sourceCounts.get(sourceKey) ?? 0;
30608
+ if (sourceCount >= SESSION_EVENT_SOURCE_REPLAY_WINDOW) {
30609
+ continue;
30610
+ }
30611
+ const byteLength = new TextEncoder().encode(encoded).byteLength;
30612
+ if (this.#inFlightBytes + byteLength > SESSION_EVENT_REPLAY_BYTE_LIMIT) {
30613
+ continue;
30614
+ }
30615
+ if (!this.#connection.send(encoded)) {
30616
+ this.#clearInFlight();
30617
+ return;
30618
+ }
30619
+ this.#inFlightEvents.set(envelope2.eventId, new InFlightSessionEvent(sourceKey, byteLength));
30620
+ this.#inFlightBytes += byteLength;
30621
+ sourceCounts.set(sourceKey, sourceCount + 1);
30622
+ sent += 1;
30623
+ }
30624
+ if (sent === 0) {
30625
+ return;
30626
+ }
30627
+ }
30628
+ };
30629
+ #removeInFlight = (eventId) => {
30630
+ const inFlight = this.#inFlightEvents.get(eventId);
30631
+ if (inFlight === void 0) {
30632
+ return;
30633
+ }
30634
+ this.#inFlightEvents.delete(eventId);
30635
+ this.#inFlightBytes -= inFlight.byteLength;
30636
+ };
30637
+ #clearInFlight = () => {
30638
+ this.#inFlightEvents.clear();
30639
+ this.#inFlightBytes = 0;
29930
30640
  };
29931
30641
  };
30642
+ var CoalescingDaemonActivityPublisher = class {
30643
+ #pending = /* @__PURE__ */ new Map();
30644
+ #publish;
30645
+ #publishedActivityIds = /* @__PURE__ */ new Set();
30646
+ #scheduled = /* @__PURE__ */ new Map();
30647
+ #timer;
30648
+ constructor(timer, publish) {
30649
+ this.#publish = publish;
30650
+ this.#timer = timer;
30651
+ }
30652
+ flush = () => {
30653
+ const activities = [...this.#pending.values()].sort((left, right) => left.sequence - right.sequence);
30654
+ for (const task of this.#scheduled.values()) {
30655
+ task.cancel();
30656
+ }
30657
+ this.#pending.clear();
30658
+ this.#scheduled.clear();
30659
+ for (const activity of activities) {
30660
+ this.#publish(activity);
30661
+ }
30662
+ };
30663
+ record = (activity) => {
30664
+ if (activity.status !== "pending" && activity.status !== "running") {
30665
+ this.#discardPending(activity.activityId);
30666
+ this.#publishedActivityIds.add(activity.activityId);
30667
+ this.#publish(activity);
30668
+ return Promise.resolve();
30669
+ }
30670
+ if (!this.#publishedActivityIds.has(activity.activityId)) {
30671
+ this.#publishedActivityIds.add(activity.activityId);
30672
+ this.#publish(activity);
30673
+ return Promise.resolve();
30674
+ }
30675
+ this.#pending.set(activity.activityId, activity);
30676
+ if (!this.#scheduled.has(activity.activityId)) {
30677
+ this.#scheduled.set(
30678
+ activity.activityId,
30679
+ this.#timer.schedule(
30680
+ () => this.#flushActivity(activity.activityId),
30681
+ AGENT_ACTIVITY_COALESCING_MILLISECONDS
30682
+ )
30683
+ );
30684
+ }
30685
+ return Promise.resolve();
30686
+ };
30687
+ #discardPending = (activityId) => {
30688
+ this.#scheduled.get(activityId)?.cancel();
30689
+ this.#scheduled.delete(activityId);
30690
+ this.#pending.delete(activityId);
30691
+ };
30692
+ #flushActivity = (activityId) => {
30693
+ const activity = this.#pending.get(activityId);
30694
+ this.#discardPending(activityId);
30695
+ if (activity !== void 0) {
30696
+ this.#publish(activity);
30697
+ }
30698
+ };
30699
+ };
30700
+ function encodeDaemonActivity(command, activity) {
30701
+ return encodeAgentActivityEvent({
30702
+ activityId: activity.activityId,
30703
+ activityType: activity.activityType,
30704
+ commandId: command.commandId,
30705
+ content: activity.content,
30706
+ detail: activity.detail,
30707
+ input: activity.input,
30708
+ kind: AGENT_ACTIVITY_KIND,
30709
+ locations: activity.locations,
30710
+ name: activity.name,
30711
+ output: activity.output,
30712
+ sequence: activity.sequence,
30713
+ sessionId: command.sessionId,
30714
+ status: activity.status,
30715
+ title: activity.title,
30716
+ toolKind: activity.toolKind
30717
+ });
30718
+ }
29932
30719
  function reportFailure(publisher, command, code, message, attemptId) {
29933
30720
  publisher.recordAttempt(
29934
30721
  command.sessionId,
@@ -30102,10 +30889,16 @@ async function executeSessionConfigure(publisher, socket, command, config2, dire
30102
30889
  })
30103
30890
  );
30104
30891
  } catch (error61) {
30892
+ if (signal.aborted) {
30893
+ signal.throwIfAborted();
30894
+ }
30105
30895
  reportOperationFailure(socket, command.requestId, command.sessionId, error61);
30106
30896
  }
30107
30897
  }
30108
- async function executePrompt(outbox, publisher, command, config2, directories, audit, sessions, attemptId, signal) {
30898
+ async function executePrompt(outbox, publisher, command, config2, directories, audit, sessions, attemptId, timer, signal) {
30899
+ const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
30900
+ publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
30901
+ });
30109
30902
  try {
30110
30903
  const resolved = await resolveSessionDirectories(
30111
30904
  directories,
@@ -30162,30 +30955,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30162
30955
  cwd: resolved.cwd,
30163
30956
  disposition: command.disposition,
30164
30957
  images: attachments.images,
30165
- onActivity: (activity) => {
30166
- publisher.recordAttempt(
30167
- command.sessionId,
30168
- encodeAgentActivityEvent({
30169
- activityId: activity.activityId,
30170
- activityType: activity.activityType,
30171
- commandId: command.commandId,
30172
- content: activity.content,
30173
- detail: activity.detail,
30174
- input: activity.input,
30175
- kind: AGENT_ACTIVITY_KIND,
30176
- locations: activity.locations,
30177
- name: activity.name,
30178
- output: activity.output,
30179
- sequence: activity.sequence,
30180
- sessionId: command.sessionId,
30181
- status: activity.status,
30182
- title: activity.title,
30183
- toolKind: activity.toolKind
30184
- }),
30185
- attemptId
30186
- );
30187
- return Promise.resolve();
30188
- },
30958
+ onActivity: activities.record,
30189
30959
  onText: (text, messageId) => {
30190
30960
  publisher.recordAttempt(
30191
30961
  command.sessionId,
@@ -30208,6 +30978,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30208
30978
  });
30209
30979
  stopReason = result.stopReason;
30210
30980
  }
30981
+ activities.flush();
30211
30982
  publisher.recordAttempt(
30212
30983
  command.sessionId,
30213
30984
  encodeSessionCompletedEvent({
@@ -30225,6 +30996,7 @@ async function executePrompt(outbox, publisher, command, config2, directories, a
30225
30996
  sessionId: command.sessionId
30226
30997
  });
30227
30998
  } catch (error61) {
30999
+ activities.flush();
30228
31000
  audit.record({
30229
31001
  commandId: command.commandId,
30230
31002
  event: "session_failed",
@@ -30263,7 +31035,10 @@ function botExecutionAgent(agent, contextHandle, socketPath) {
30263
31035
  agent.capabilities
30264
31036
  );
30265
31037
  }
30266
- async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, signal) {
31038
+ async function executeBotPrompt(outbox, publisher, command, config2, directories, audit, sessions, grants, contexts, applicator, ipc, attemptId, timer, signal) {
31039
+ const activities = new CoalescingDaemonActivityPublisher(timer, (activity) => {
31040
+ publisher.recordAttempt(command.sessionId, encodeDaemonActivity(command, activity), attemptId);
31041
+ });
30267
31042
  try {
30268
31043
  const deadline = Date.parse(command.executionDeadlineAt);
30269
31044
  const remaining = deadline - Date.now();
@@ -30347,30 +31122,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30347
31122
  commandId: command.commandId,
30348
31123
  cwd: resolved.cwd,
30349
31124
  images: attachments.images,
30350
- onActivity: (activity) => {
30351
- publisher.recordAttempt(
30352
- command.sessionId,
30353
- encodeAgentActivityEvent({
30354
- activityId: activity.activityId,
30355
- activityType: activity.activityType,
30356
- commandId: command.commandId,
30357
- content: activity.content,
30358
- detail: activity.detail,
30359
- input: activity.input,
30360
- kind: AGENT_ACTIVITY_KIND,
30361
- locations: activity.locations,
30362
- name: activity.name,
30363
- output: activity.output,
30364
- sequence: activity.sequence,
30365
- sessionId: command.sessionId,
30366
- status: activity.status,
30367
- title: activity.title,
30368
- toolKind: activity.toolKind
30369
- }),
30370
- attemptId
30371
- );
30372
- return Promise.resolve();
30373
- },
31125
+ onActivity: activities.record,
30374
31126
  onText: (text, messageId) => {
30375
31127
  publisher.recordAttempt(
30376
31128
  command.sessionId,
@@ -30390,6 +31142,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30390
31142
  sessionId: command.sessionId,
30391
31143
  signal: executionSignal
30392
31144
  });
31145
+ activities.flush();
30393
31146
  publisher.recordAttempt(
30394
31147
  command.sessionId,
30395
31148
  encodeSessionCompletedEvent({
@@ -30414,6 +31167,7 @@ async function executeBotPrompt(outbox, publisher, command, config2, directories
30414
31167
  lease.release();
30415
31168
  }
30416
31169
  } catch (error61) {
31170
+ activities.flush();
30417
31171
  const failure = Date.parse(command.executionDeadlineAt) <= Date.now() ? new LocalBotAuthorizationError("turn_expired", "The Bot execution deadline has expired", {
30418
31172
  cause: error61
30419
31173
  }) : error61;
@@ -30453,15 +31207,15 @@ function waitForRelayRetry(signal, timer) {
30453
31207
  signal.addEventListener("abort", finish, { once: true });
30454
31208
  });
30455
31209
  }
30456
- async function publishAgentCatalogUntilAvailable(config2, signal, timer) {
31210
+ async function publishAgentCatalogUntilAvailable(config2, diagnostics, signal, timer) {
30457
31211
  try {
30458
31212
  await publishAgentCatalog(config2, signal);
30459
31213
  return;
30460
- } catch {
31214
+ } catch (error61) {
30461
31215
  if (signal.aborted) {
30462
31216
  return;
30463
31217
  }
30464
- console.warn("Mobius daemon is waiting to publish its Agent catalog");
31218
+ diagnostics.catalogFailed(error61);
30465
31219
  }
30466
31220
  while (!signal.aborted) {
30467
31221
  await waitForRelayRetry(signal, timer);
@@ -30475,11 +31229,13 @@ async function publishAgentCatalogUntilAvailable(config2, signal, timer) {
30475
31229
  }
30476
31230
  }
30477
31231
  }
30478
- async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, onConnected, signal, timer) {
31232
+ async function runRelayConnection(config2, directories, audit, sessions, installations, installationObserver, nativeSessions, outbox, publisher, grants, contexts, applicator, ipc, activeTurns, activeAttemptIds, connectionEpoch, diagnostics, onConnected, signal, timer) {
30479
31233
  const relayUrl = new URL(config2.relayUrl);
31234
+ relayUrl.searchParams.set("deliveryReceipts", "1");
30480
31235
  relayUrl.searchParams.set("liveness", "1");
30481
31236
  const socket = await connectRelay(relayUrl, signal);
30482
31237
  const reconnectingTurns = [...activeTurns.values()];
31238
+ diagnostics.connectionOpened(connectionEpoch);
30483
31239
  onConnected();
30484
31240
  const connectionController = new AbortController();
30485
31241
  const nativeRequests = /* @__PURE__ */ new Map();
@@ -30488,7 +31244,6 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30488
31244
  await new Promise((resolve6, reject) => {
30489
31245
  let finished = false;
30490
31246
  const cleanup = () => {
30491
- connectionRefresh.close();
30492
31247
  connectionLiveness.close();
30493
31248
  signal.removeEventListener("abort", stop);
30494
31249
  socket.removeEventListener("close", handleClose);
@@ -30506,61 +31261,282 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30506
31261
  cleanup();
30507
31262
  resolve6();
30508
31263
  };
30509
- const rejectConnection = () => {
31264
+ const rejectConnection = (failure) => {
30510
31265
  if (finished) {
30511
31266
  return;
30512
31267
  }
30513
31268
  finished = true;
30514
31269
  cleanup();
30515
- reject(new Error("Relay WebSocket failed"));
31270
+ reject(failure);
30516
31271
  };
30517
31272
  const stop = () => {
31273
+ diagnostics.connectionClosed(connectionEpoch, 1e3, "Daemon stopped", "daemon_stop");
30518
31274
  closeRelaySocket(socket, 1e3, "Daemon stopped");
30519
31275
  resolveConnection();
30520
31276
  };
30521
- const refreshConnection = () => {
30522
- closeRelaySocket(socket, 1e3, "Refreshing idle relay connection");
30523
- resolveConnection();
31277
+ const failAndClose = (failure, code, reason, source) => {
31278
+ diagnostics.connectionFailed(connectionEpoch, failure, source);
31279
+ diagnostics.connectionClosed(connectionEpoch, code, reason, source);
31280
+ closeRelaySocket(socket, code, reason);
31281
+ rejectConnection(failure);
31282
+ };
31283
+ const processingDeliveryIds = /* @__PURE__ */ new Set();
31284
+ const processDeliveredPrompt = async (delivery) => {
31285
+ const command = decodeSessionPromptCommand(delivery.payload);
31286
+ if (command.sessionId !== delivery.sessionId) {
31287
+ throw new Error("Runtime delivery Session identity does not match its payload");
31288
+ }
31289
+ const active = activeTurns.get(command.commandId);
31290
+ if (active !== void 0) {
31291
+ await active.completion;
31292
+ return;
31293
+ }
31294
+ const receipt = outbox.claimExecution(command.commandId, command.sessionId, delivery.payload);
31295
+ if (receipt.kind === "duplicate") {
31296
+ if (receipt.state === "running") {
31297
+ reportFailure(
31298
+ publisher,
31299
+ command,
31300
+ "agent_execution_interrupted",
31301
+ "The local Agent execution was interrupted before it completed",
31302
+ receipt.attemptId
31303
+ );
31304
+ outbox.transitionExecution(command.commandId, "failed");
31305
+ return;
31306
+ }
31307
+ if (receipt.state !== "dispatching") {
31308
+ return;
31309
+ }
31310
+ }
31311
+ const turn = new ActiveDaemonTurn(command, receipt.attemptId);
31312
+ activeTurns.set(command.commandId, turn);
31313
+ if (command.disposition !== SessionPromptDisposition.Steer) {
31314
+ activeAttemptIds.set(command.sessionId, receipt.attemptId);
31315
+ }
31316
+ try {
31317
+ await executePrompt(
31318
+ outbox,
31319
+ publisher,
31320
+ command,
31321
+ config2,
31322
+ directories,
31323
+ audit,
31324
+ sessions,
31325
+ receipt.attemptId,
31326
+ timer,
31327
+ turn.signal
31328
+ );
31329
+ } finally {
31330
+ activeTurns.delete(command.commandId);
31331
+ if (activeAttemptIds.get(command.sessionId) === receipt.attemptId) {
31332
+ activeAttemptIds.delete(command.sessionId);
31333
+ }
31334
+ turn.finish();
31335
+ }
31336
+ };
31337
+ const processDeliveredPayload = async (delivery) => {
31338
+ const kind = decodeMessageKind(delivery.payload);
31339
+ if (kind === SESSION_CANCEL_KIND) {
31340
+ const cancellation = decodeSessionCancelCommand(delivery.payload);
31341
+ if (cancellation.sessionId !== delivery.sessionId) {
31342
+ throw new Error("Runtime delivery Session identity does not match its payload");
31343
+ }
31344
+ const turn = activeTurns.get(cancellation.commandId);
31345
+ if (turn === void 0 || !turn.matches(cancellation) || !turn.requestCancellation()) {
31346
+ return;
31347
+ }
31348
+ audit.record({
31349
+ commandId: cancellation.commandId,
31350
+ event: "session_cancellation_requested",
31351
+ sessionId: cancellation.sessionId
31352
+ });
31353
+ await sessions.cancelPrompt(cancellation.sessionId);
31354
+ return;
31355
+ }
31356
+ if (kind === SESSION_CONFIGURE_KIND) {
31357
+ const command = decodeSessionConfigureCommand(delivery.payload);
31358
+ if (command.sessionId !== delivery.sessionId) {
31359
+ throw new Error("Runtime delivery Session identity does not match its payload");
31360
+ }
31361
+ await executeSessionConfigure(
31362
+ publisher,
31363
+ socket,
31364
+ command,
31365
+ config2,
31366
+ directories,
31367
+ audit,
31368
+ sessions,
31369
+ connectionController.signal
31370
+ );
31371
+ return;
31372
+ }
31373
+ if (kind === SESSION_PERMISSION_RESPOND_KIND) {
31374
+ const command = decodeSessionPermissionRespondCommand(delivery.payload);
31375
+ if (command.sessionId !== delivery.sessionId) {
31376
+ throw new Error("Runtime delivery Session identity does not match its payload");
31377
+ }
31378
+ try {
31379
+ await sessions.respondPermission(command);
31380
+ audit.record({
31381
+ commandId: command.interactionId,
31382
+ event: "session_permission_resolved",
31383
+ sessionId: command.sessionId
31384
+ });
31385
+ } catch (error61) {
31386
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error61);
31387
+ }
31388
+ return;
31389
+ }
31390
+ if (kind === SESSION_ELICITATION_RESPOND_KIND) {
31391
+ const command = decodeSessionElicitationRespondCommand(delivery.payload);
31392
+ if (command.sessionId !== delivery.sessionId) {
31393
+ throw new Error("Runtime delivery Session identity does not match its payload");
31394
+ }
31395
+ try {
31396
+ await sessions.respondElicitation(command);
31397
+ audit.record({
31398
+ commandId: command.interactionId,
31399
+ event: "session_elicitation_resolved",
31400
+ sessionId: command.sessionId
31401
+ });
31402
+ } catch (error61) {
31403
+ reportOperationFailure(socket, command.interactionId, command.sessionId, error61);
31404
+ }
31405
+ return;
31406
+ }
31407
+ if (kind === SESSION_PROMPT_KIND) {
31408
+ await processDeliveredPrompt(delivery);
31409
+ return;
31410
+ }
31411
+ throw new Error("Runtime delivery contains an unsupported protocol message");
31412
+ };
31413
+ const processRuntimeDelivery = (delivery, state) => {
31414
+ if (state === "processed" || processingDeliveryIds.has(delivery.deliveryId)) {
31415
+ return;
31416
+ }
31417
+ processingDeliveryIds.add(delivery.deliveryId);
31418
+ void processDeliveredPayload(delivery).then(() => {
31419
+ outbox.markRuntimeDeliveryProcessed(delivery.deliveryId, delivery.sessionId);
31420
+ }).catch((error61) => {
31421
+ if (connectionController.signal.aborted) {
31422
+ return;
31423
+ }
31424
+ console.error("Mobius daemon could not process a durable Runtime delivery", error61);
31425
+ failAndClose(
31426
+ new Error("Runtime delivery processing failed", { cause: error61 }),
31427
+ 4008,
31428
+ "Invalid Runtime delivery",
31429
+ "delivery"
31430
+ );
31431
+ }).finally(() => processingDeliveryIds.delete(delivery.deliveryId));
31432
+ };
31433
+ const receiveRuntimeDelivery = (delivery) => {
31434
+ const payloadKind = decodeMessageKind(delivery.payload);
31435
+ let payloadSessionId = "";
31436
+ if (payloadKind === SESSION_CANCEL_KIND) {
31437
+ payloadSessionId = decodeSessionCancelCommand(delivery.payload).sessionId;
31438
+ } else if (payloadKind === SESSION_CONFIGURE_KIND) {
31439
+ payloadSessionId = decodeSessionConfigureCommand(delivery.payload).sessionId;
31440
+ } else if (payloadKind === SESSION_PERMISSION_RESPOND_KIND) {
31441
+ payloadSessionId = decodeSessionPermissionRespondCommand(delivery.payload).sessionId;
31442
+ } else if (payloadKind === SESSION_ELICITATION_RESPOND_KIND) {
31443
+ payloadSessionId = decodeSessionElicitationRespondCommand(delivery.payload).sessionId;
31444
+ } else if (payloadKind === SESSION_PROMPT_KIND) {
31445
+ payloadSessionId = decodeSessionPromptCommand(delivery.payload).sessionId;
31446
+ } else {
31447
+ throw new Error("Runtime delivery contains an unsupported protocol message");
31448
+ }
31449
+ if (payloadSessionId !== delivery.sessionId) {
31450
+ throw new Error("Runtime delivery Session identity does not match its payload");
31451
+ }
31452
+ const stored = outbox.claimRuntimeDelivery(delivery);
31453
+ send(
31454
+ socket,
31455
+ encodeRuntimeDeliveryReceived({
31456
+ deliveryId: delivery.deliveryId,
31457
+ kind: RUNTIME_DELIVERY_RECEIVED_KIND,
31458
+ sessionId: delivery.sessionId
31459
+ })
31460
+ );
31461
+ processRuntimeDelivery(delivery, stored.state);
31462
+ };
31463
+ const resumeRuntimeDeliveries = () => {
31464
+ for (const stored of outbox.runtimeDeliveries()) {
31465
+ const delivery = {
31466
+ deliveryId: stored.deliveryId,
31467
+ kind: RUNTIME_DELIVERY_KIND,
31468
+ payload: stored.payload,
31469
+ sessionId: stored.sessionId
31470
+ };
31471
+ if (stored.state !== "relay_committed") {
31472
+ send(
31473
+ socket,
31474
+ encodeRuntimeDeliveryReceived({
31475
+ deliveryId: stored.deliveryId,
31476
+ kind: RUNTIME_DELIVERY_RECEIVED_KIND,
31477
+ sessionId: stored.sessionId
31478
+ })
31479
+ );
31480
+ }
31481
+ processRuntimeDelivery(delivery, stored.state);
31482
+ }
30524
31483
  };
30525
31484
  const connectionLiveness = new RelayConnectionLiveness(timer, {
30526
31485
  send: (message) => send(socket, message),
30527
- unresponsive: () => {
31486
+ unresponsive: (heartbeatId, timeoutMilliseconds) => {
31487
+ diagnostics.heartbeatTimedOut(connectionEpoch, heartbeatId, timeoutMilliseconds);
31488
+ diagnostics.connectionClosed(
31489
+ connectionEpoch,
31490
+ 4001,
31491
+ "Relay liveness check timed out",
31492
+ "heartbeat_timeout"
31493
+ );
30528
31494
  closeRelaySocket(socket, 4001, "Relay liveness check timed out");
30529
31495
  resolveConnection();
30530
31496
  }
30531
31497
  });
30532
- const connectionRefresh = new RelayConnectionRefresh(
30533
- timer,
30534
- () => activeTurns.size === 0,
30535
- refreshConnection
30536
- );
30537
31498
  signal.addEventListener("abort", stop, { once: true });
30538
- const handleClose = () => {
31499
+ const handleClose = (event) => {
31500
+ diagnostics.connectionClosed(connectionEpoch, event.code, event.reason, "relay");
30539
31501
  resolveConnection();
30540
31502
  };
30541
- const handleError = () => {
31503
+ const handleError = (event) => {
30542
31504
  if (signal.aborted) {
30543
31505
  resolveConnection();
30544
31506
  return;
30545
31507
  }
30546
- rejectConnection();
31508
+ const cause = Reflect.get(event, "error");
31509
+ const failure = new Error("Relay WebSocket emitted an error", { cause: cause ?? event });
31510
+ diagnostics.connectionFailed(connectionEpoch, failure, "socket_error");
31511
+ diagnostics.connectionClosed(connectionEpoch, 1006, "WebSocket error", "socket_error");
31512
+ rejectConnection(failure);
30547
31513
  };
30548
31514
  const handleMessage = (event) => {
30549
31515
  const encoded = event.data;
30550
31516
  if (typeof encoded !== "string") {
30551
- closeRelaySocket(socket, 4003, "Text message required");
30552
- rejectConnection();
31517
+ failAndClose(new Error("Relay sent a non-text message"), 4003, "Text message required", "protocol");
30553
31518
  return;
30554
31519
  }
30555
31520
  try {
30556
31521
  const kind = decodeMessageKind(encoded);
31522
+ if (kind === RUNTIME_DELIVERY_COMMITTED_KIND) {
31523
+ const committed = decodeRuntimeDeliveryCommitted(encoded);
31524
+ if (!outbox.markRuntimeDeliveryCommitted(committed.deliveryId, committed.sessionId)) {
31525
+ console.warn("Mobius daemon ignored confirmation for an unknown Runtime delivery");
31526
+ }
31527
+ return;
31528
+ }
31529
+ if (kind === RUNTIME_DELIVERY_KIND) {
31530
+ receiveRuntimeDelivery(decodeRuntimeDelivery(encoded));
31531
+ return;
31532
+ }
30557
31533
  if (kind === SESSION_EVENT_ACK_KIND) {
30558
- outbox.acknowledge(decodeSessionEventAcknowledgement(encoded));
31534
+ publisher.acknowledge(decodeSessionEventAcknowledgement(encoded));
30559
31535
  return;
30560
31536
  }
30561
31537
  if (kind === SESSION_EVENT_REJECTED_KIND) {
30562
31538
  const rejection = decodeSessionEventRejection(encoded);
30563
- if (outbox.quarantine(rejection)) {
31539
+ if (publisher.quarantine(rejection)) {
30564
31540
  audit.record({
30565
31541
  commandId: rejection.eventId,
30566
31542
  details: { code: rejection.code },
@@ -30939,6 +31915,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30939
31915
  applicator,
30940
31916
  ipc,
30941
31917
  receipt2.attemptId,
31918
+ timer,
30942
31919
  turn2.signal
30943
31920
  ).catch((error61) => {
30944
31921
  console.error("Mobius daemon Bot turn failed", error61);
@@ -30948,7 +31925,6 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30948
31925
  activeAttemptIds.delete(command2.sessionId);
30949
31926
  }
30950
31927
  turn2.finish();
30951
- connectionRefresh.afterTurnFinished();
30952
31928
  });
30953
31929
  return;
30954
31930
  }
@@ -30977,6 +31953,7 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30977
31953
  audit,
30978
31954
  sessions,
30979
31955
  receipt.attemptId,
31956
+ timer,
30980
31957
  turn.signal
30981
31958
  ).catch((error61) => {
30982
31959
  console.error("Mobius daemon turn failed", error61);
@@ -30986,12 +31963,19 @@ async function runRelayConnection(config2, directories, audit, sessions, install
30986
31963
  activeAttemptIds.delete(command.sessionId);
30987
31964
  }
30988
31965
  turn.finish();
30989
- connectionRefresh.afterTurnFinished();
30990
31966
  });
30991
31967
  } catch (error61) {
30992
31968
  console.error("Mobius daemon rejected a message", error61);
30993
- closeRelaySocket(socket, 4008, "Invalid protocol message");
30994
- rejectConnection();
31969
+ if (error61 instanceof RuntimeDeliveryInboxCapacityError) {
31970
+ failAndClose(error61, 4013, "Runtime delivery queue full", "delivery_backpressure");
31971
+ return;
31972
+ }
31973
+ failAndClose(
31974
+ new Error("Relay sent an invalid protocol message", { cause: error61 }),
31975
+ 4008,
31976
+ "Invalid protocol message",
31977
+ "protocol"
31978
+ );
30995
31979
  }
30996
31980
  };
30997
31981
  socket.addEventListener("close", handleClose);
@@ -31003,21 +31987,19 @@ async function runRelayConnection(config2, directories, audit, sessions, install
31003
31987
  return;
31004
31988
  }
31005
31989
  publisher.connect(socket);
31006
- void Promise.all(reconnectingTurns.map((turn) => turn.completion)).then(
31007
- connectionRefresh.afterTurnFinished
31008
- );
31990
+ resumeRuntimeDeliveries();
31009
31991
  },
31010
31992
  (error61) => {
31011
31993
  if (finished) {
31012
31994
  return;
31013
31995
  }
31014
31996
  console.error("Mobius daemon could not reconcile active Agent turns", error61);
31015
- closeRelaySocket(
31016
- socket,
31997
+ failAndClose(
31998
+ new Error("Active Agent turn reconciliation failed", { cause: error61 }),
31017
31999
  ACTIVE_TURN_RECONCILIATION_CLOSE_CODE,
31018
- "Active Agent turn reconciliation failed"
32000
+ "Active Agent turn reconciliation failed",
32001
+ "reconciliation"
31019
32002
  );
31020
- rejectConnection();
31021
32003
  }
31022
32004
  );
31023
32005
  if (signal.aborted) {
@@ -31061,6 +32043,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31061
32043
  const sessions = new AcpSessionManager(new DaemonAcpObserver(recordSessionEvent), sessionStore);
31062
32044
  const directories = new WorkspaceDirectoryService(config2.bindingId, config2.defaultWorkspace);
31063
32045
  const installationObserver = new DaemonInstallationObserver();
32046
+ const diagnostics = new RelayConnectionDiagnostics(audit, config2.channelId, config2.relayUrl);
31064
32047
  const nativeSessions = new NativeSessionService(config2, directories, sessions, audit);
31065
32048
  const installations = new AgentInstallationManager(
31066
32049
  specifications,
@@ -31068,7 +32051,7 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31068
32051
  installationObserver,
31069
32052
  installationAudit
31070
32053
  );
31071
- let retryAnnounced = false;
32054
+ let connectionEpoch = 0;
31072
32055
  let connectionAnnounced = false;
31073
32056
  const announceConnection = () => {
31074
32057
  if (connectionAnnounced) {
@@ -31084,10 +32067,11 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31084
32067
  `
31085
32068
  );
31086
32069
  };
31087
- const catalogPublication = publishAgentCatalogUntilAvailable(config2, signal, timer);
32070
+ const catalogPublication = publishAgentCatalogUntilAvailable(config2, diagnostics, signal, timer);
31088
32071
  try {
31089
32072
  await ipc.listen(signal);
31090
32073
  while (!signal.aborted) {
32074
+ connectionEpoch += 1;
31091
32075
  try {
31092
32076
  await runRelayConnection(
31093
32077
  config2,
@@ -31105,19 +32089,17 @@ async function runDaemon(config2, audit, signal, timer = systemDaemonTimer) {
31105
32089
  ipc,
31106
32090
  activeTurns,
31107
32091
  activeAttemptIds,
32092
+ connectionEpoch,
32093
+ diagnostics,
31108
32094
  announceConnection,
31109
32095
  signal,
31110
32096
  timer
31111
32097
  );
31112
- retryAnnounced = false;
31113
- } catch {
32098
+ } catch (error61) {
31114
32099
  if (signal.aborted) {
31115
32100
  return;
31116
32101
  }
31117
- if (!retryAnnounced) {
31118
- console.warn("Mobius daemon is waiting for the relay");
31119
- retryAnnounced = true;
31120
- }
32102
+ diagnostics.connectionFailed(connectionEpoch, error61, "connection");
31121
32103
  }
31122
32104
  await waitForRelayRetry(signal, timer);
31123
32105
  }