@okxweb3/a2a-node 0.2.0 → 0.2.1-beta-8ae7a0d300-260807181806

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 (3) hide show
  1. package/dist/cli.js +905 -69
  2. package/dist/index.js +893 -64
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9433,7 +9433,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
9433
9433
  client: {
9434
9434
  id: "gateway-client",
9435
9435
  displayName: "okx-a2a-node",
9436
- version: "0.2.0",
9436
+ version: "0.2.1-beta-8ae7a0d300-260807181806",
9437
9437
  platform: "node",
9438
9438
  mode: "backend",
9439
9439
  instanceId
@@ -9444,7 +9444,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
9444
9444
  commands: [],
9445
9445
  permissions: {},
9446
9446
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
9447
- userAgent: `okx-a2a-node/${"0.2.0"}`,
9447
+ userAgent: `okx-a2a-node/${"0.2.1-beta-8ae7a0d300-260807181806"}`,
9448
9448
  auth: {
9449
9449
  ...config.token ? { token: config.token } : {},
9450
9450
  ...config.password ? { password: config.password } : {}
@@ -25471,12 +25471,15 @@ var init_events = __esm({
25471
25471
  INBOUND_DELIVERED: "Inbound delivered to session",
25472
25472
  DM_DELIVERED: "DM delivered to session",
25473
25473
  OUTBOUND_ELIGIBILITY_CHECKED: "Outbound eligibility checked",
25474
+ MESSAGE_ELIGIBILITY_CHECKED: "Message eligibility checked",
25474
25475
  AGENT_CLIENT_CREATED: "Agent client created",
25475
25476
  AGENT_INSTALLATION_SUMMARY: "Agent installation summary",
25476
25477
  XMTP_HISTORY_SYNC_REQUESTED: "XMTP history sync requested",
25477
25478
  XMTP_DEBUG_SYNC_REQUESTED: "XMTP debug sync requested",
25478
25479
  XMTP_DEBUG_SYNC_COMPLETED: "XMTP debug sync completed",
25479
25480
  AGENT_CLIENT_REMOVED: "Agent client removed",
25481
+ AGENT_STREAM_RECOVERED: "Agent stream recovered",
25482
+ AGENT_STREAM_RECOVERY_REPLAY_COMPLETED: "Agent stream recovery replay completed",
25480
25483
  ADDRESS_SYNC_COMPLETED: "Address sync completed",
25481
25484
  AGENT_REFRESH_SESSION_EXPIRED: "Agent refresh aborted: onchainos session expired, local clients taken offline",
25482
25485
  OFFLINE_REPLAY_MESSAGE_REPLAYED: "Offline replay message replayed",
@@ -25511,6 +25514,8 @@ var init_events = __esm({
25511
25514
  CONVERSATIONS_STREAM_FAILED: "conversations.stream failed",
25512
25515
  AGENT_START_FAILED: "agent.start failed",
25513
25516
  AGENT_STREAM_ERROR: "agent stream error",
25517
+ AGENT_UNHANDLED_ERROR: "Agent unhandled error",
25518
+ AGENT_STREAM_RECOVERY_TIMEOUT: "Agent stream recovery timeout",
25514
25519
  BOOTSTRAP_FAILED: "Bootstrap failed",
25515
25520
  ONCHAINOS_SESSION_EXPIRED: "Onchainos session expired",
25516
25521
  ONCHAINOS_CLI_ERROR: "Onchainos CLI error",
@@ -25520,6 +25525,7 @@ var init_events = __esm({
25520
25525
  MESSAGE_PARSE_FAILED: "Message parse failed",
25521
25526
  MESSAGE_HANDLER_ERROR: "Message handler error",
25522
25527
  INBOUND_DISPATCH_FAILED: "Inbound dispatch failed",
25528
+ INBOUND_REPLAY_GATE_DRAIN_FAILED: "Inbound replay gate drain failed",
25523
25529
  // Default name stamped on events that reach Sentry without going through this
25524
25530
  // logger — uncaught exceptions and unhandled rejections. Without it those
25525
25531
  // events carry no eventName and cannot be classified.
@@ -25817,7 +25823,10 @@ var init_xmtp_test_metrics = __esm({
25817
25823
  "Address sync completed",
25818
25824
  "Agent client created",
25819
25825
  "Agent client removed",
25820
- "Agent stream error",
25826
+ "agent stream error",
25827
+ "Agent stream recovered",
25828
+ "Agent stream recovery replay completed",
25829
+ "Agent stream recovery timeout",
25821
25830
  "conversations.stream failed",
25822
25831
  "Inbound delivered to session",
25823
25832
  "Inbound dispatch failed",
@@ -25836,6 +25845,190 @@ var init_xmtp_test_metrics = __esm({
25836
25845
  }
25837
25846
  });
25838
25847
 
25848
+ // ../core/src/sentry-logger/error-diagnostics.ts
25849
+ function asErrorLike(value) {
25850
+ return value instanceof Error ? value : void 0;
25851
+ }
25852
+ function diagnosticToken(value) {
25853
+ if (typeof value !== "string" && typeof value !== "number") {
25854
+ return void 0;
25855
+ }
25856
+ const token = String(value).trim();
25857
+ if (!token || /^(?:null|undefined|unknown)$/i.test(token)) {
25858
+ return void 0;
25859
+ }
25860
+ if (!/^[A-Za-z0-9_.:-]+$/.test(token)) {
25861
+ return void 0;
25862
+ }
25863
+ return token.slice(0, MAX_DIAGNOSTIC_TOKEN_LENGTH);
25864
+ }
25865
+ function syscallToken(value) {
25866
+ if (typeof value !== "string") {
25867
+ return void 0;
25868
+ }
25869
+ const operation = diagnosticToken(value.trim().split(/\s+/, 1)[0])?.toLowerCase();
25870
+ return operation && KNOWN_SYSCALLS.has(operation) ? operation : void 0;
25871
+ }
25872
+ function knownErrorCode(value) {
25873
+ const code = diagnosticToken(value)?.toUpperCase();
25874
+ return code && KNOWN_ERROR_CODES.has(code) ? code : void 0;
25875
+ }
25876
+ function readCode(error) {
25877
+ return knownErrorCode(error?.code) ?? knownErrorCode(error?.errno);
25878
+ }
25879
+ function classifyError(error, code) {
25880
+ const normalizedCode = code?.toUpperCase();
25881
+ const name = error?.name.toLowerCase() ?? "";
25882
+ const message = error?.message.toLowerCase() ?? "";
25883
+ if (normalizedCode && RESOURCE_EXHAUSTED_CODES.has(normalizedCode)) {
25884
+ return "resource_exhausted";
25885
+ }
25886
+ if (normalizedCode && CONNECTION_INTERRUPTED_CODES.has(normalizedCode)) {
25887
+ return "connection_interrupted";
25888
+ }
25889
+ if (normalizedCode && NETWORK_UNAVAILABLE_CODES.has(normalizedCode)) {
25890
+ return "network_unavailable";
25891
+ }
25892
+ if (normalizedCode && TIMEOUT_CODES.has(normalizedCode)) {
25893
+ return "timeout";
25894
+ }
25895
+ if (normalizedCode && PERMISSION_CODES.has(normalizedCode)) {
25896
+ return "permission_denied";
25897
+ }
25898
+ if (normalizedCode === "ENOENT") {
25899
+ return "missing_dependency";
25900
+ }
25901
+ if (normalizedCode === "ABORT_ERR" || name === "aborterror") {
25902
+ return "cancelled";
25903
+ }
25904
+ if (name === "syntaxerror" || /(?:json|response).*(?:parse|invalid)/.test(message)) {
25905
+ return "invalid_response";
25906
+ }
25907
+ if (/session\s+(?:is\s+)?expired|login\s+expired/.test(message)) {
25908
+ return "session_expired";
25909
+ }
25910
+ if (/timed?\s*out|timeout/.test(message)) {
25911
+ return "timeout";
25912
+ }
25913
+ return "unknown";
25914
+ }
25915
+ function retryabilityFor(category) {
25916
+ switch (category) {
25917
+ case "connection_interrupted":
25918
+ case "network_unavailable":
25919
+ case "resource_exhausted":
25920
+ case "timeout":
25921
+ return "retryable";
25922
+ case "invalid_response":
25923
+ case "missing_dependency":
25924
+ case "permission_denied":
25925
+ case "session_expired":
25926
+ return "non_retryable";
25927
+ default:
25928
+ return "unknown";
25929
+ }
25930
+ }
25931
+ function causeChain(error) {
25932
+ const chain = [];
25933
+ const seen = /* @__PURE__ */ new Set();
25934
+ let current = error;
25935
+ while (current && chain.length < MAX_CAUSE_DEPTH && !seen.has(current)) {
25936
+ chain.push(current);
25937
+ seen.add(current);
25938
+ current = asErrorLike(current.cause);
25939
+ }
25940
+ return chain;
25941
+ }
25942
+ function buildErrorDiagnostics(eventName, error, existingExtra) {
25943
+ const errorLike = asErrorLike(error);
25944
+ const legacy = {};
25945
+ if (errorLike) {
25946
+ legacy.errorName = errorLike.name;
25947
+ legacy.errorMessageLength = String(errorLike.message.length);
25948
+ }
25949
+ if (!errorLike || eventName === LogEvent.AGENT_STREAM_ERROR) {
25950
+ return legacy;
25951
+ }
25952
+ const chain = causeChain(errorLike);
25953
+ const primary = chain[0];
25954
+ const cause = chain.length > 1 ? chain[chain.length - 1] : void 0;
25955
+ const fallbackExitCode = diagnosticToken(existingExtra.exitCode);
25956
+ const primaryCode = readCode(primary);
25957
+ const primaryCategory = classifyError(primary, primaryCode);
25958
+ const primarySyscall = syscallToken(primary.syscall);
25959
+ const causeCode = readCode(cause);
25960
+ const causeCategory = cause ? classifyError(cause, causeCode) : void 0;
25961
+ const causeSyscall = syscallToken(cause?.syscall);
25962
+ const classifiedCategory = primaryCategory === "unknown" && causeCategory ? causeCategory : primaryCategory;
25963
+ const effectiveCategory = classifiedCategory === "unknown" && fallbackExitCode && fallbackExitCode !== "0" ? "process_exit" : classifiedCategory;
25964
+ const effectiveCode = primaryCode ?? causeCode;
25965
+ const effectiveSyscall = primarySyscall ?? causeSyscall;
25966
+ return {
25967
+ ...legacy,
25968
+ errorCategory: effectiveCategory,
25969
+ errorRetryability: retryabilityFor(effectiveCategory),
25970
+ errorCauseDepth: String(Math.max(0, chain.length - 1)),
25971
+ ...effectiveCode ? { errorCode: effectiveCode } : {},
25972
+ ...effectiveSyscall ? { errorSyscall: effectiveSyscall } : {},
25973
+ ...cause ? {
25974
+ causeErrorMessageLength: String(cause.message.length),
25975
+ causeErrorCategory: causeCategory ?? "unknown",
25976
+ ...causeCode ? { causeErrorCode: causeCode } : {},
25977
+ ...causeSyscall ? { causeErrorSyscall: causeSyscall } : {}
25978
+ } : {}
25979
+ };
25980
+ }
25981
+ var MAX_DIAGNOSTIC_TOKEN_LENGTH, MAX_CAUSE_DEPTH, RESOURCE_EXHAUSTED_CODES, CONNECTION_INTERRUPTED_CODES, NETWORK_UNAVAILABLE_CODES, TIMEOUT_CODES, PERMISSION_CODES, KNOWN_ERROR_CODES, KNOWN_SYSCALLS;
25982
+ var init_error_diagnostics = __esm({
25983
+ "../core/src/sentry-logger/error-diagnostics.ts"() {
25984
+ "use strict";
25985
+ init_events();
25986
+ MAX_DIAGNOSTIC_TOKEN_LENGTH = 64;
25987
+ MAX_CAUSE_DEPTH = 4;
25988
+ RESOURCE_EXHAUSTED_CODES = /* @__PURE__ */ new Set(["EAGAIN", "EMFILE", "ENFILE", "ENOMEM"]);
25989
+ CONNECTION_INTERRUPTED_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "EPIPE"]);
25990
+ NETWORK_UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
25991
+ "EAI_AGAIN",
25992
+ "ECONNREFUSED",
25993
+ "EHOSTUNREACH",
25994
+ "ENETDOWN",
25995
+ "ENETUNREACH",
25996
+ "ENOTFOUND"
25997
+ ]);
25998
+ TIMEOUT_CODES = /* @__PURE__ */ new Set(["ETIMEDOUT", "ESOCKETTIMEDOUT"]);
25999
+ PERMISSION_CODES = /* @__PURE__ */ new Set(["EACCES", "EPERM"]);
26000
+ KNOWN_ERROR_CODES = /* @__PURE__ */ new Set([
26001
+ ...RESOURCE_EXHAUSTED_CODES,
26002
+ ...CONNECTION_INTERRUPTED_CODES,
26003
+ ...NETWORK_UNAVAILABLE_CODES,
26004
+ ...TIMEOUT_CODES,
26005
+ ...PERMISSION_CODES,
26006
+ "ABORT_ERR",
26007
+ "ENOENT"
26008
+ ]);
26009
+ KNOWN_SYSCALLS = /* @__PURE__ */ new Set([
26010
+ "access",
26011
+ "bind",
26012
+ "close",
26013
+ "connect",
26014
+ "getaddrinfo",
26015
+ "kill",
26016
+ "listen",
26017
+ "mkdir",
26018
+ "open",
26019
+ "read",
26020
+ "readdir",
26021
+ "readlink",
26022
+ "rename",
26023
+ "rmdir",
26024
+ "spawn",
26025
+ "stat",
26026
+ "unlink",
26027
+ "write"
26028
+ ]);
26029
+ }
26030
+ });
26031
+
25839
26032
  // ../core/src/sentry-logger/index.ts
25840
26033
  function applyFatalEventDefaults(event) {
25841
26034
  try {
@@ -25881,6 +26074,7 @@ var init_sentry_logger = __esm({
25881
26074
  init_events();
25882
26075
  init_log_fields();
25883
26076
  init_xmtp_test_metrics();
26077
+ init_error_diagnostics();
25884
26078
  init_events();
25885
26079
  init_log_fields();
25886
26080
  FLOW_ID = (0, import_node_crypto5.randomUUID)();
@@ -25968,14 +26162,23 @@ var init_sentry_logger = __esm({
25968
26162
  "agentId",
25969
26163
  "agentPlatform",
25970
26164
  "cacheName",
26165
+ "causeCode",
26166
+ "causeType",
26167
+ "causeVariant",
25971
26168
  "checkpoint",
25972
26169
  "code",
25973
26170
  "communicationClass",
25974
26171
  "component",
25975
26172
  "eventFamily",
25976
26173
  "eventName",
26174
+ "endpoint",
26175
+ "errorCategory",
26176
+ "errorCode",
26177
+ "errorRetryability",
26178
+ "errorSyscall",
25977
26179
  "exitCode",
25978
26180
  "fromAgentId",
26181
+ "grpcMethod",
25979
26182
  "jobId",
25980
26183
  "kind",
25981
26184
  "method",
@@ -25999,17 +26202,24 @@ var init_sentry_logger = __esm({
25999
26202
  "source",
26000
26203
  "stage",
26001
26204
  "status",
26205
+ "streamErrorCode",
26206
+ "streamType",
26002
26207
  "subcommand",
26003
26208
  "systemEvent",
26004
26209
  "taskId",
26005
26210
  "taskMode",
26006
26211
  "toAgentId",
26007
26212
  "transport",
26213
+ "transportFailure",
26214
+ "transportProtocol",
26008
26215
  "type",
26009
26216
  "workloadClass"
26010
26217
  ]);
26011
26218
  SENTRY_FINGERPRINT_KEYS = [
26012
26219
  "eventName",
26220
+ "errorCategory",
26221
+ "errorCode",
26222
+ "errorSyscall",
26013
26223
  "cacheName",
26014
26224
  "component",
26015
26225
  "method",
@@ -26057,6 +26267,7 @@ var init_sentry_logger = __esm({
26057
26267
  LogEvent.HERMES_SESSION_ROUTE_BINDING,
26058
26268
  LogEvent.INBOUND_BLOCKED_ADDRESS_MISMATCH,
26059
26269
  LogEvent.INBOUND_BLOCKED_INELIGIBLE,
26270
+ LogEvent.MESSAGE_ELIGIBILITY_CHECKED,
26060
26271
  LogEvent.INBOUND_BLOCKED_SENSITIVE,
26061
26272
  LogEvent.INBOUND_DROP_DM_INVALID_PAYLOAD,
26062
26273
  LogEvent.INBOUND_DROP_DM_NON_SYSTEM,
@@ -26072,6 +26283,9 @@ var init_sentry_logger = __esm({
26072
26283
  LogEvent.MESSAGE_SENT,
26073
26284
  LogEvent.AGENT_CLIENT_CREATED,
26074
26285
  LogEvent.AGENT_CLIENT_REMOVED,
26286
+ LogEvent.AGENT_STREAM_ERROR,
26287
+ LogEvent.AGENT_STREAM_RECOVERED,
26288
+ LogEvent.AGENT_STREAM_RECOVERY_REPLAY_COMPLETED,
26075
26289
  LogEvent.ADDRESS_SYNC_COMPLETED,
26076
26290
  LogEvent.AGENTS_REFRESHED,
26077
26291
  LogEvent.AGENTS_WAKEUP_NOTIFIED,
@@ -26203,19 +26417,24 @@ var init_sentry_logger = __esm({
26203
26417
  }
26204
26418
  captureError(message, error, extraWithFlow) {
26205
26419
  try {
26420
+ const errorDiagnostics = buildErrorDiagnostics(
26421
+ message,
26422
+ error,
26423
+ extraWithFlow
26424
+ );
26425
+ const diagnosticExtra = {
26426
+ ...extraWithFlow,
26427
+ ...errorDiagnostics
26428
+ };
26206
26429
  Sentry.withScope((scope) => {
26207
26430
  scope.setLevel("error");
26208
- _SentryLogger.applyDiagnostics(scope, message, extraWithFlow);
26431
+ _SentryLogger.applyDiagnostics(scope, message, diagnosticExtra);
26209
26432
  Sentry.captureException(_SentryLogger.createSentryEvent(message), {
26210
26433
  // eventName is unconditional: callers that pass no Error object are
26211
26434
  // still classified events, and without it they reach SLS anonymous.
26212
26435
  extra: {
26213
- ...extraWithFlow,
26214
- eventName: message,
26215
- ...error ? {
26216
- errorName: error.name,
26217
- errorMessageLength: String(error.message.length)
26218
- } : {}
26436
+ ...diagnosticExtra,
26437
+ eventName: message
26219
26438
  }
26220
26439
  });
26221
26440
  });
@@ -27414,7 +27633,7 @@ var init_sentry_config = __esm({
27414
27633
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
27415
27634
  SENTRY_CONFIG = {
27416
27635
  projectName: "okx/openclaw-okx-a2a-extension",
27417
- release: "0.2.0",
27636
+ release: "0.2.1-beta-8ae7a0d300-260807181806",
27418
27637
  environment,
27419
27638
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
27420
27639
  };
@@ -38584,7 +38803,7 @@ async function exportDiagnosticLogs(options) {
38584
38803
  node: process.version,
38585
38804
  platform: process.platform,
38586
38805
  arch: process.arch,
38587
- packageVersion: true ? "0.2.0" : "unknown",
38806
+ packageVersion: true ? "0.2.1-beta-8ae7a0d300-260807181806" : "unknown",
38588
38807
  sensitiveContentIncluded: options.includeSensitiveContent,
38589
38808
  listenerAndLlmContentIncluded: true,
38590
38809
  credentialsAlwaysRedacted: true,
@@ -39466,6 +39685,47 @@ var init_logs_export = __esm({
39466
39685
  }
39467
39686
  });
39468
39687
 
39688
+ // src/capabilities-cli.ts
39689
+ var capabilities_cli_exports = {};
39690
+ __export(capabilities_cli_exports, {
39691
+ getA2ACapabilities: () => getA2ACapabilities,
39692
+ handleCapabilitiesCommand: () => handleCapabilitiesCommand
39693
+ });
39694
+ function getA2ACapabilities() {
39695
+ return {
39696
+ messageEligibleOfflineReplay: {
39697
+ ok: true,
39698
+ fixCommands: [],
39699
+ message: ""
39700
+ }
39701
+ };
39702
+ }
39703
+ function handleCapabilitiesCommand(args) {
39704
+ if (args.some((arg) => arg === "-h" || arg === "--help")) {
39705
+ process.stdout.write(CAPABILITIES_USAGE);
39706
+ return;
39707
+ }
39708
+ const unknown = args.filter((arg) => arg !== "--json");
39709
+ if (unknown.length > 0) {
39710
+ throw new Error(`Unknown capabilities option: ${unknown[0]}`);
39711
+ }
39712
+ const capabilities = getA2ACapabilities();
39713
+ if (args.includes("--json")) {
39714
+ process.stdout.write(`${JSON.stringify(capabilities)}
39715
+ `);
39716
+ return;
39717
+ }
39718
+ process.stdout.write(`${JSON.stringify(capabilities, null, 2)}
39719
+ `);
39720
+ }
39721
+ var CAPABILITIES_USAGE;
39722
+ var init_capabilities_cli = __esm({
39723
+ "src/capabilities-cli.ts"() {
39724
+ "use strict";
39725
+ CAPABILITIES_USAGE = "Usage: okx-a2a capabilities [--json]\n\nPrint this package's machine-readable A2A capability negotiation status.\nOnchainOS runs `okx-a2a capabilities --json` to detect supported capabilities.\n";
39726
+ }
39727
+ });
39728
+
39469
39729
  // ../core/src/xmtp-sdk/onchainos/bin.ts
39470
39730
  async function resolve9() {
39471
39731
  if (resolvedBin) {
@@ -39688,7 +39948,7 @@ async function exec(args) {
39688
39948
  );
39689
39949
  logger.error(
39690
39950
  LogEvent.ONCHAINOS_CLI_ERROR,
39691
- new Error("onchainos command failed"),
39951
+ err2 instanceof Error ? err2 : new Error("onchainos command failed"),
39692
39952
  {
39693
39953
  component: "onchainos_cli",
39694
39954
  source: "onchainos",
@@ -54416,6 +54676,171 @@ var init_dist4 = __esm({
54416
54676
  }
54417
54677
  });
54418
54678
 
54679
+ // ../core/src/xmtp-sdk/stream-recovery-telemetry.ts
54680
+ function isAgentStreamRecoveryError(error) {
54681
+ return error instanceof Error && error.constructor.name === "AgentStreamingError" && typeof error.code === "number" && [1002, 1004, 1005].includes(error.code);
54682
+ }
54683
+ function extractStreamErrorTelemetry(error) {
54684
+ const telemetry = {};
54685
+ if (typeof error.code === "number" && Number.isSafeInteger(error.code)) {
54686
+ telemetry.streamErrorCode = String(error.code);
54687
+ const streamType = {
54688
+ 1002: "conversation",
54689
+ 1004: "message",
54690
+ 1005: "setup"
54691
+ };
54692
+ if (streamType[error.code]) {
54693
+ telemetry.streamType = streamType[error.code];
54694
+ }
54695
+ }
54696
+ const cause = error.cause;
54697
+ if (!(cause instanceof Error)) {
54698
+ return telemetry;
54699
+ }
54700
+ telemetry.causeMessageLength = String(cause.message.length);
54701
+ const causeCode = cause.code;
54702
+ if (typeof causeCode === "string" && KNOWN_CAUSE_CODES.has(causeCode)) {
54703
+ telemetry.causeCode = causeCode;
54704
+ }
54705
+ const causeKind = cause.message.match(
54706
+ /^\[([A-Za-z][A-Za-z0-9]{0,63})::([A-Za-z][A-Za-z0-9]{0,63})\]/
54707
+ );
54708
+ const knownCauseKind = causeKind ? KNOWN_CAUSE_KINDS.get(`${causeKind[1]}::${causeKind[2]}`) : void 0;
54709
+ if (knownCauseKind) {
54710
+ telemetry.causeType = knownCauseKind[0];
54711
+ telemetry.causeVariant = knownCauseKind[1];
54712
+ }
54713
+ const grpcMethod = cause.message.match(
54714
+ /endpoint\s+["']\/[A-Za-z0-9_.]+\/([A-Za-z][A-Za-z0-9_]{0,63})["']/
54715
+ );
54716
+ if (grpcMethod && KNOWN_GRPC_METHODS.has(grpcMethod[1])) {
54717
+ telemetry.grpcMethod = grpcMethod[1];
54718
+ telemetry.endpoint = grpcMethod[1];
54719
+ }
54720
+ if (/\bh2 protocol error\b/i.test(cause.message)) {
54721
+ telemetry.transportProtocol = "h2";
54722
+ }
54723
+ const transportFailure = TRANSPORT_FAILURE_PATTERNS.find(
54724
+ ([pattern]) => pattern.test(cause.message)
54725
+ );
54726
+ if (transportFailure) {
54727
+ telemetry.transportFailure = transportFailure[1];
54728
+ telemetry.reason = transportFailure[1];
54729
+ }
54730
+ return telemetry;
54731
+ }
54732
+ function createKeyedFlight(discriminator, operation) {
54733
+ let resolve14;
54734
+ let reject;
54735
+ const promise = new Promise((resolvePromise, rejectPromise) => {
54736
+ resolve14 = resolvePromise;
54737
+ reject = rejectPromise;
54738
+ });
54739
+ return { discriminator, operation, promise, resolve: resolve14, reject };
54740
+ }
54741
+ var TRANSPORT_FAILURE_PATTERNS, KNOWN_CAUSE_CODES, KNOWN_CAUSE_KINDS, KNOWN_GRPC_METHODS, StreamRecoveryTelemetry, KeyedSingleFlight;
54742
+ var init_stream_recovery_telemetry = __esm({
54743
+ "../core/src/xmtp-sdk/stream-recovery-telemetry.ts"() {
54744
+ "use strict";
54745
+ TRANSPORT_FAILURE_PATTERNS = [
54746
+ [/broken\s*pipe|BrokenPipe/i, "broken_pipe"],
54747
+ [/connection\s*reset|ConnectionReset/i, "connection_reset"],
54748
+ [/timed?\s*out|TimedOut/i, "timeout"],
54749
+ [/unexpected\s*eof|UnexpectedEof/i, "unexpected_eof"]
54750
+ ];
54751
+ KNOWN_CAUSE_CODES = /* @__PURE__ */ new Set(["GenericFailure"]);
54752
+ KNOWN_CAUSE_KINDS = /* @__PURE__ */ new Map([
54753
+ ["SubscribeError::BoxError", ["SubscribeError", "BoxError"]]
54754
+ ]);
54755
+ KNOWN_GRPC_METHODS = /* @__PURE__ */ new Set([
54756
+ "SubscribeGroupMessages",
54757
+ "SubscribeWelcomeMessages"
54758
+ ]);
54759
+ StreamRecoveryTelemetry = class {
54760
+ nextSequence = 1;
54761
+ activeRecovery;
54762
+ get activeSequence() {
54763
+ return this.activeRecovery?.sequence;
54764
+ }
54765
+ markError(nowMs = Date.now()) {
54766
+ if (this.activeRecovery) {
54767
+ return {
54768
+ recoverySequence: this.activeRecovery.sequence,
54769
+ alreadyRecovering: true
54770
+ };
54771
+ }
54772
+ const sequence = this.nextSequence++;
54773
+ this.activeRecovery = { sequence, startedAtMs: nowMs };
54774
+ return { recoverySequence: sequence, alreadyRecovering: false };
54775
+ }
54776
+ markStarted(nowMs = Date.now()) {
54777
+ if (!this.activeRecovery) {
54778
+ return void 0;
54779
+ }
54780
+ const completed = {
54781
+ recoverySequence: this.activeRecovery.sequence,
54782
+ recoveryDurationMs: Math.max(0, nowMs - this.activeRecovery.startedAtMs)
54783
+ };
54784
+ this.activeRecovery = void 0;
54785
+ return completed;
54786
+ }
54787
+ cancel() {
54788
+ this.activeRecovery = void 0;
54789
+ }
54790
+ };
54791
+ KeyedSingleFlight = class {
54792
+ inFlight = /* @__PURE__ */ new Map();
54793
+ run(key, discriminator, operation) {
54794
+ const normalizedKey = key.toLowerCase();
54795
+ const state = this.inFlight.get(normalizedKey);
54796
+ if (!state) {
54797
+ const active = createKeyedFlight(discriminator, operation);
54798
+ this.inFlight.set(normalizedKey, { active, queued: [] });
54799
+ this.execute(normalizedKey, active);
54800
+ return active.promise;
54801
+ }
54802
+ if (state.active.discriminator === discriminator) {
54803
+ return state.active.promise;
54804
+ }
54805
+ const queued = state.queued.find(
54806
+ (flight2) => flight2.discriminator === discriminator
54807
+ );
54808
+ if (queued) {
54809
+ return queued.promise;
54810
+ }
54811
+ const flight = createKeyedFlight(discriminator, operation);
54812
+ state.queued.push(flight);
54813
+ return flight.promise;
54814
+ }
54815
+ execute(key, flight) {
54816
+ void Promise.resolve().then(flight.operation).then(
54817
+ (value) => {
54818
+ flight.resolve(value);
54819
+ this.advance(key, flight);
54820
+ },
54821
+ (error) => {
54822
+ flight.reject(error);
54823
+ this.advance(key, flight);
54824
+ }
54825
+ );
54826
+ }
54827
+ advance(key, completed) {
54828
+ const state = this.inFlight.get(key);
54829
+ if (state?.active !== completed) {
54830
+ return;
54831
+ }
54832
+ const next = state.queued.shift();
54833
+ if (!next) {
54834
+ this.inFlight.delete(key);
54835
+ return;
54836
+ }
54837
+ state.active = next;
54838
+ this.execute(key, next);
54839
+ }
54840
+ };
54841
+ }
54842
+ });
54843
+
54419
54844
  // ../core/src/xmtp-sdk/extract-job-id.ts
54420
54845
  function extractJobIdFromContent(content2) {
54421
54846
  const raw = typeof content2 === "string" ? content2 : stringifyJsonForEnvelope(content2);
@@ -55216,6 +55641,9 @@ function parseGroupPayload(content2) {
55216
55641
  return null;
55217
55642
  }
55218
55643
  }
55644
+ function isOfflineReplayForTrigger(trigger) {
55645
+ return trigger === "startup";
55646
+ }
55219
55647
  function createOfflineReplayAddressSummary(address) {
55220
55648
  return {
55221
55649
  address,
@@ -55233,7 +55661,7 @@ function createOfflineReplayAddressSummary(address) {
55233
55661
  durationMs: 0
55234
55662
  };
55235
55663
  }
55236
- var import_node_fs22, import_node_path26, DEFAULT_DATA_DIR, XMTP_INSTALLATION_WARNING_THRESHOLD, XMTP_INSTALLATION_NEAR_LIMIT_THRESHOLD, SENSITIVE_WORDS_LAZY_RETRY_COOLDOWN_MS, SESSION_EXPIRED_RE, InboundReplayGate, SYSTEM_CONFIG_DEFAULTS, SEMVER_RE, isRecord5, isPositiveNumber, SYSTEM_CONFIG_VALIDATORS, XmtpService;
55664
+ var import_node_fs22, import_node_path26, DEFAULT_DATA_DIR, XMTP_INSTALLATION_WARNING_THRESHOLD, XMTP_INSTALLATION_NEAR_LIMIT_THRESHOLD, SENSITIVE_WORDS_LAZY_RETRY_COOLDOWN_MS, SESSION_EXPIRED_RE, InboundReplayGate, SYSTEM_CONFIG_DEFAULTS, STREAM_RECOVERY_ALERT_THRESHOLD_MS, REPLAY_GATE_DRAIN_MAX_ATTEMPTS, REPLAY_GATE_DRAIN_RETRY_DELAY_MS, SEMVER_RE, isRecord5, isPositiveNumber, SYSTEM_CONFIG_VALIDATORS, XmtpService;
55237
55665
  var init_xmtp_sdk = __esm({
55238
55666
  "../core/src/xmtp-sdk/index.ts"() {
55239
55667
  "use strict";
@@ -55243,6 +55671,7 @@ var init_xmtp_sdk = __esm({
55243
55671
  init_dist4();
55244
55672
  init_dist2();
55245
55673
  init_sentry_logger();
55674
+ init_stream_recovery_telemetry();
55246
55675
  init_extract_job_id();
55247
55676
  init_self_filter();
55248
55677
  init_logging();
@@ -55260,10 +55689,11 @@ var init_xmtp_sdk = __esm({
55260
55689
  this.inbound = inbound;
55261
55690
  }
55262
55691
  inbound;
55263
- buffering = true;
55692
+ holds = 1;
55264
55693
  queue = [];
55694
+ drainInFlight;
55265
55695
  middleware = async (ctx, next) => {
55266
- if (!this.buffering) {
55696
+ if (this.holds === 0 && !this.drainInFlight) {
55267
55697
  await this.inbound(ctx, next);
55268
55698
  return;
55269
55699
  }
@@ -55272,19 +55702,55 @@ var init_xmtp_sdk = __esm({
55272
55702
  get bufferedCount() {
55273
55703
  return this.queue.length;
55274
55704
  }
55705
+ abandonFailedAndRelease() {
55706
+ const abandoned = this.queue.length > 0 ? 1 : 0;
55707
+ if (abandoned > 0) {
55708
+ this.queue.shift();
55709
+ }
55710
+ if (this.holds > 0) {
55711
+ this.holds--;
55712
+ }
55713
+ return abandoned;
55714
+ }
55715
+ hold() {
55716
+ this.holds++;
55717
+ }
55275
55718
  async drain() {
55719
+ if (this.holds > 0) {
55720
+ this.holds--;
55721
+ }
55722
+ return this.drainReleased();
55723
+ }
55724
+ async drainReleased() {
55725
+ if (this.holds > 0) {
55726
+ return 0;
55727
+ }
55728
+ if (this.drainInFlight) {
55729
+ return this.drainInFlight;
55730
+ }
55731
+ const drain = Promise.resolve().then(() => this.drainQueue());
55732
+ this.drainInFlight = drain;
55733
+ try {
55734
+ return await drain;
55735
+ } finally {
55736
+ if (this.drainInFlight === drain) {
55737
+ this.drainInFlight = void 0;
55738
+ }
55739
+ }
55740
+ }
55741
+ async drainQueue() {
55276
55742
  let drained = 0;
55277
- while (this.queue.length > 0) {
55743
+ while (this.holds === 0 && this.queue.length > 0) {
55278
55744
  const item = this.queue.shift();
55279
55745
  try {
55280
55746
  await this.inbound(item.ctx, item.next);
55281
55747
  } catch (err2) {
55282
55748
  this.queue.unshift(item);
55749
+ this.holds = Math.max(1, this.holds);
55283
55750
  throw err2;
55284
55751
  }
55285
55752
  drained++;
55286
55753
  }
55287
- this.buffering = false;
55288
55754
  return drained;
55289
55755
  }
55290
55756
  };
@@ -55300,6 +55766,9 @@ var init_xmtp_sdk = __esm({
55300
55766
  }
55301
55767
  }
55302
55768
  };
55769
+ STREAM_RECOVERY_ALERT_THRESHOLD_MS = 3e4;
55770
+ REPLAY_GATE_DRAIN_MAX_ATTEMPTS = 3;
55771
+ REPLAY_GATE_DRAIN_RETRY_DELAY_MS = 100;
55303
55772
  SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/;
55304
55773
  isRecord5 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
55305
55774
  isPositiveNumber = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
@@ -55344,7 +55813,11 @@ var init_xmtp_sdk = __esm({
55344
55813
  };
55345
55814
  dataDir = DEFAULT_DATA_DIR;
55346
55815
  syncByAddress = /* @__PURE__ */ new Map();
55816
+ offlineReplaySingleFlight = new KeyedSingleFlight();
55347
55817
  inboundReplayGates = /* @__PURE__ */ new Map();
55818
+ streamRecoveryCleanupByAddress = /* @__PURE__ */ new Map();
55819
+ nextClientGeneration = 1;
55820
+ clientGenerationByClient = /* @__PURE__ */ new WeakMap();
55348
55821
  agentListFingerprint;
55349
55822
  // Only one refreshAgents can be in flight at a time; concurrent calls
55350
55823
  // coalesce onto the same promise. This avoids duplicate Agent.create()
@@ -55799,6 +56272,7 @@ var init_xmtp_sdk = __esm({
55799
56272
  const client = this.clients.get(address);
55800
56273
  if (client) {
55801
56274
  try {
56275
+ this.cancelStreamRecoveryForAddress(address);
55802
56276
  this.stoppedAddresses.add(address.toLowerCase());
55803
56277
  client.stop();
55804
56278
  logWithTimestamp(
@@ -55972,7 +56446,10 @@ var init_xmtp_sdk = __esm({
55972
56446
  logWithTimestamp(
55973
56447
  `[xmtp-sdk] refresh: ${address} has a prior sync timestamp, triggering offline replay`
55974
56448
  );
55975
- void this.replayOfflineMessagesForAddress(address).catch((err2) => {
56449
+ void this.replayOfflineMessagesForAddressSingleFlight(
56450
+ address,
56451
+ "agent_refresh"
56452
+ ).catch((err2) => {
55976
56453
  logWithTimestamp(`[xmtp-sdk] refresh: ${address} offline replay failed:`, err2);
55977
56454
  logger.error(
55978
56455
  LogEvent.OFFLINE_REPLAY_FAILED,
@@ -56014,6 +56491,7 @@ var init_xmtp_sdk = __esm({
56014
56491
  }
56015
56492
  const addressKey = address.toLowerCase();
56016
56493
  try {
56494
+ this.cancelStreamRecoveryForAddress(address);
56017
56495
  this.stoppedAddresses.add(addressKey);
56018
56496
  await Promise.resolve(oldClient.stop());
56019
56497
  this.clients.delete(address);
@@ -56072,7 +56550,10 @@ var init_xmtp_sdk = __esm({
56072
56550
  await this.startListeningForAddresses([address]);
56073
56551
  let replaySummary;
56074
56552
  try {
56075
- replaySummary = await this.replayOfflineMessagesForAddress(address);
56553
+ replaySummary = await this.replayOfflineMessagesForAddressSingleFlight(
56554
+ address,
56555
+ "client_recycle"
56556
+ );
56076
56557
  } finally {
56077
56558
  await this.drainInboundReplayGate(address);
56078
56559
  }
@@ -56082,6 +56563,7 @@ var init_xmtp_sdk = __esm({
56082
56563
  recycled++;
56083
56564
  } catch (err2) {
56084
56565
  failed++;
56566
+ this.cancelStreamRecoveryForAddress(address);
56085
56567
  this.clients.delete(address);
56086
56568
  logWithTimestamp(`[xmtp-sdk] recycle: failed to recreate client: ${address}`, err2);
56087
56569
  logger.error(
@@ -56482,16 +56964,80 @@ var init_xmtp_sdk = __esm({
56482
56964
  async startListening() {
56483
56965
  await this.startListeningForAddresses([...this.clients.keys()]);
56484
56966
  }
56485
- async drainInboundReplayGate(address) {
56967
+ async drainInboundReplayGate(address, expectedGate) {
56486
56968
  const gate = this.inboundReplayGates.get(address.toLowerCase());
56487
- if (!gate) {
56969
+ if (!gate || expectedGate && gate !== expectedGate) {
56488
56970
  return 0;
56489
56971
  }
56490
- const drained = await gate.drain();
56972
+ let attempt = 0;
56973
+ let totalDrained = 0;
56974
+ let releaseHold = true;
56975
+ while (true) {
56976
+ try {
56977
+ const drained = releaseHold ? await gate.drain() : await gate.drainReleased();
56978
+ totalDrained += drained;
56979
+ logWithTimestamp(
56980
+ `[xmtp-sdk:${address}] replay barrier released, buffered=${totalDrained} attempt=${attempt + 1}`
56981
+ );
56982
+ return totalDrained;
56983
+ } catch (err2) {
56984
+ attempt++;
56985
+ logWithTimestamp(
56986
+ `[xmtp-sdk:${address}] replay barrier drain failed attempt=${attempt}/${REPLAY_GATE_DRAIN_MAX_ATTEMPTS}:`,
56987
+ err2
56988
+ );
56989
+ if (attempt < REPLAY_GATE_DRAIN_MAX_ATTEMPTS) {
56990
+ releaseHold = true;
56991
+ await new Promise(
56992
+ (resolve14) => setTimeout(resolve14, REPLAY_GATE_DRAIN_RETRY_DELAY_MS * attempt)
56993
+ );
56994
+ continue;
56995
+ }
56996
+ const abandoned = gate.abandonFailedAndRelease();
56997
+ logger.error(
56998
+ LogEvent.INBOUND_REPLAY_GATE_DRAIN_FAILED,
56999
+ err2 instanceof Error ? err2 : void 0,
57000
+ {
57001
+ walletAddress: address,
57002
+ attemptCount: String(REPLAY_GATE_DRAIN_MAX_ATTEMPTS),
57003
+ abandonedCount: String(abandoned),
57004
+ stage: "inboundReplayGate/fail-open",
57005
+ reason: "drain_retry_exhausted"
57006
+ }
57007
+ );
57008
+ logWithTimestamp(
57009
+ `[xmtp-sdk:${address}] replay barrier fail-open after ${REPLAY_GATE_DRAIN_MAX_ATTEMPTS} failed attempts, abandoned=${abandoned}`
57010
+ );
57011
+ attempt = 0;
57012
+ releaseHold = false;
57013
+ }
57014
+ }
57015
+ }
57016
+ holdInboundReplayGate(address) {
57017
+ const gate = this.inboundReplayGates.get(address.toLowerCase());
57018
+ if (!gate) {
57019
+ return void 0;
57020
+ }
57021
+ gate.hold();
56491
57022
  logWithTimestamp(
56492
- `[xmtp-sdk:${address}] startup replay barrier opened, buffered=${drained}`
57023
+ `[xmtp-sdk:${address}] replay barrier held, buffered=${gate.bufferedCount}`
56493
57024
  );
56494
- return drained;
57025
+ return gate;
57026
+ }
57027
+ cancelStreamRecoveryForAddress(address) {
57028
+ const addressKey = address.toLowerCase();
57029
+ const cleanup = this.streamRecoveryCleanupByAddress.get(addressKey);
57030
+ this.streamRecoveryCleanupByAddress.delete(addressKey);
57031
+ cleanup?.();
57032
+ }
57033
+ getClientGeneration(client) {
57034
+ const existing = this.clientGenerationByClient.get(client);
57035
+ if (existing !== void 0) {
57036
+ return existing;
57037
+ }
57038
+ const generation = this.nextClientGeneration++;
57039
+ this.clientGenerationByClient.set(client, generation);
57040
+ return generation;
56495
57041
  }
56496
57042
  notifyAgentsWakeupAfterReplay() {
56497
57043
  const notifyAgentsWakeup2 = this.tools.notifyAgentsWakeup;
@@ -56525,7 +57071,7 @@ var init_xmtp_sdk = __esm({
56525
57071
  * cannot overtake the stored backlog.
56526
57072
  */
56527
57073
  async completeStartupReplay() {
56528
- const summary = await this.replayOfflineMessages();
57074
+ const summary = await this.replayOfflineMessages("startup");
56529
57075
  for (const address of this.clients.keys()) {
56530
57076
  await this.drainInboundReplayGate(address);
56531
57077
  }
@@ -56536,6 +57082,8 @@ var init_xmtp_sdk = __esm({
56536
57082
  const valid = addresses.filter((addr) => this.clients.has(addr));
56537
57083
  await parallelMap(valid, async (address) => {
56538
57084
  const agent = this.clients.get(address);
57085
+ const clientGeneration = this.getClientGeneration(agent);
57086
+ const addressKey = address.toLowerCase();
56539
57087
  const tag = `[xmtp-sdk:${address}]`;
56540
57088
  const onchainosAgent = this.getAgentByAddress(address);
56541
57089
  const identity = {
@@ -56638,14 +57186,131 @@ var init_xmtp_sdk = __esm({
56638
57186
  logWithTimestamp(`${tag} reconnecting in ${delay}ms (attempt=${attempt + 1})`);
56639
57187
  setTimeout(() => void startWithRetry(attempt + 1), delay);
56640
57188
  };
57189
+ const streamRecovery = new StreamRecoveryTelemetry();
57190
+ let recoveryTimeout;
57191
+ let pendingRecoveryGate;
57192
+ let recoveryDisposed = false;
57193
+ this.cancelStreamRecoveryForAddress(address);
57194
+ const cleanupStreamRecovery = () => {
57195
+ recoveryDisposed = true;
57196
+ streamRecovery.cancel();
57197
+ if (recoveryTimeout) {
57198
+ clearTimeout(recoveryTimeout);
57199
+ recoveryTimeout = void 0;
57200
+ }
57201
+ };
57202
+ this.streamRecoveryCleanupByAddress.set(
57203
+ addressKey,
57204
+ cleanupStreamRecovery
57205
+ );
56641
57206
  agent.on("unhandledError", (err2) => {
57207
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey)) {
57208
+ return;
57209
+ }
57210
+ if (!isAgentStreamRecoveryError(err2)) {
57211
+ logWithTimestamp(`${tag} agent unhandled error:`, err2);
57212
+ logger.error(LogEvent.AGENT_UNHANDLED_ERROR, err2, agentExtras(identity));
57213
+ return;
57214
+ }
57215
+ const recovery = streamRecovery.markError();
57216
+ const errorTelemetry = extractStreamErrorTelemetry(err2);
57217
+ if (!recovery.alreadyRecovering) {
57218
+ pendingRecoveryGate = this.holdInboundReplayGate(address);
57219
+ }
56642
57220
  logWithTimestamp(`${tag} agent stream error:`, err2);
56643
- logger.error(LogEvent.AGENT_STREAM_ERROR, err2, agentExtras(identity));
57221
+ logger.info(LogEvent.AGENT_STREAM_ERROR, {
57222
+ ...agentExtras(identity),
57223
+ ...errorTelemetry,
57224
+ clientGeneration: String(clientGeneration),
57225
+ recoverySequence: String(recovery.recoverySequence),
57226
+ recoveryAlreadyActive: String(recovery.alreadyRecovering)
57227
+ });
57228
+ if (!recovery.alreadyRecovering) {
57229
+ recoveryTimeout = setTimeout(() => {
57230
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey) || streamRecovery.activeSequence !== recovery.recoverySequence) {
57231
+ return;
57232
+ }
57233
+ logger.error(LogEvent.AGENT_STREAM_RECOVERY_TIMEOUT, void 0, {
57234
+ ...agentExtras(identity),
57235
+ ...errorTelemetry,
57236
+ clientGeneration: String(clientGeneration),
57237
+ recoverySequence: String(recovery.recoverySequence),
57238
+ recoveryDurationMs: String(STREAM_RECOVERY_ALERT_THRESHOLD_MS),
57239
+ thresholdMs: String(STREAM_RECOVERY_ALERT_THRESHOLD_MS)
57240
+ });
57241
+ }, STREAM_RECOVERY_ALERT_THRESHOLD_MS);
57242
+ }
56644
57243
  });
56645
57244
  agent.on("start", () => {
57245
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey)) {
57246
+ return;
57247
+ }
57248
+ const recovery = streamRecovery.markStarted();
57249
+ if (recoveryTimeout) {
57250
+ clearTimeout(recoveryTimeout);
57251
+ recoveryTimeout = void 0;
57252
+ }
56646
57253
  logWithTimestamp(
56647
57254
  `${tag} agent stream started agentId=${identity.onchainosAgentId ?? "(unknown)"} inboxId=${identity.inboxId ?? "(unknown)"} role=${identity.role ?? "(unknown)"} consentStates=Allowed,Unknown lastSync=${formatLogTimestamp2(this.syncByAddress.get(address))}`
56648
57255
  );
57256
+ if (recovery) {
57257
+ const recoveryGate = pendingRecoveryGate;
57258
+ pendingRecoveryGate = void 0;
57259
+ logger.info(LogEvent.AGENT_STREAM_RECOVERED, {
57260
+ ...agentExtras(identity),
57261
+ clientGeneration: String(clientGeneration),
57262
+ recoverySequence: String(recovery.recoverySequence),
57263
+ recoveryDurationMs: String(recovery.recoveryDurationMs)
57264
+ });
57265
+ void (async () => {
57266
+ try {
57267
+ const summary = await this.replayOfflineMessagesForAddressSingleFlight(
57268
+ address,
57269
+ "stream_recovery",
57270
+ `${clientGeneration}:${recovery.recoverySequence}`
57271
+ );
57272
+ if (!recoveryDisposed && this.clients.get(address) === agent) {
57273
+ logger.info(LogEvent.AGENT_STREAM_RECOVERY_REPLAY_COMPLETED, {
57274
+ ...agentExtras(identity),
57275
+ clientGeneration: String(clientGeneration),
57276
+ recoverySequence: String(recovery.recoverySequence),
57277
+ replayed: String(summary.replayed),
57278
+ skipped: String(summary.skipped),
57279
+ conversationCount: String(summary.conversations),
57280
+ replayDurationMs: String(summary.durationMs)
57281
+ });
57282
+ }
57283
+ } catch (err2) {
57284
+ logWithTimestamp(`${tag} recovery offline replay failed:`, err2);
57285
+ logger.error(
57286
+ LogEvent.OFFLINE_REPLAY_FAILED,
57287
+ err2 instanceof Error ? err2 : new Error(String(err2)),
57288
+ {
57289
+ ...agentExtras(identity),
57290
+ clientGeneration: String(clientGeneration),
57291
+ recoverySequence: String(recovery.recoverySequence),
57292
+ stage: "streamRecovery/replay"
57293
+ }
57294
+ );
57295
+ } finally {
57296
+ try {
57297
+ await this.drainInboundReplayGate(address, recoveryGate);
57298
+ } catch (err2) {
57299
+ logWithTimestamp(`${tag} recovery replay gate drain failed:`, err2);
57300
+ logger.error(
57301
+ LogEvent.OFFLINE_REPLAY_FAILED,
57302
+ err2 instanceof Error ? err2 : new Error(String(err2)),
57303
+ {
57304
+ ...agentExtras(identity),
57305
+ clientGeneration: String(clientGeneration),
57306
+ recoverySequence: String(recovery.recoverySequence),
57307
+ stage: "streamRecovery/drain-gate"
57308
+ }
57309
+ );
57310
+ }
57311
+ }
57312
+ })();
57313
+ }
56649
57314
  if (process.env.XMTP_FORCE_DEBUG === "true") {
56650
57315
  void logDetails(agent).catch((err2) => {
56651
57316
  logWithTimestamp(`${tag} agent debug details failed:`, err2);
@@ -56663,9 +57328,10 @@ var init_xmtp_sdk = __esm({
56663
57328
  logWithTimestamp(`${tag} message listener started`);
56664
57329
  });
56665
57330
  }
56666
- async replayOfflineMessagesForAddress(address) {
57331
+ async replayOfflineMessagesForAddress(address, trigger = "periodic_repair") {
56667
57332
  const addressReplayStartedAt = Date.now();
56668
57333
  const summary = createOfflineReplayAddressSummary(address);
57334
+ const isOfflineReplay = isOfflineReplayForTrigger(trigger);
56669
57335
  const agent = this.clients.get(address);
56670
57336
  if (!agent) {
56671
57337
  summary.durationMs = Date.now() - addressReplayStartedAt;
@@ -56789,7 +57455,8 @@ var init_xmtp_sdk = __esm({
56789
57455
  };
56790
57456
  const handleStartedAt = Date.now();
56791
57457
  const handled = await processOffline(fakeCtx, handlerDeps, {
56792
- skipNotify: true
57458
+ skipNotify: true,
57459
+ isOfflineReplay
56793
57460
  });
56794
57461
  const handleMs = Date.now() - handleStartedAt;
56795
57462
  summary.handleMs += handleMs;
@@ -56871,14 +57538,28 @@ var init_xmtp_sdk = __esm({
56871
57538
  );
56872
57539
  return summary;
56873
57540
  }
56874
- async replayOfflineMessages() {
57541
+ replayOfflineMessagesForAddressSingleFlight(address, trigger = "periodic_repair", recoveryIdentity) {
57542
+ const eligibilityClass = trigger === "stream_recovery" ? `stream_recovery:${recoveryIdentity ?? "unknown"}` : isOfflineReplayForTrigger(trigger) ? "startup_offline" : "online_repair";
57543
+ return this.offlineReplaySingleFlight.run(
57544
+ address,
57545
+ eligibilityClass,
57546
+ async () => {
57547
+ await this._backupReady;
57548
+ return this.replayOfflineMessagesForAddress(address, trigger);
57549
+ }
57550
+ );
57551
+ }
57552
+ async replayOfflineMessages(trigger = "periodic_repair") {
56875
57553
  await this._backupReady;
56876
57554
  const replayStartedAt = Date.now();
56877
57555
  let totalReplayed = 0;
56878
57556
  let totalSkipped = 0;
56879
57557
  const addresses = [];
56880
57558
  for (const address of this.clients.keys()) {
56881
- const summary2 = await this.replayOfflineMessagesForAddress(address);
57559
+ const summary2 = await this.replayOfflineMessagesForAddressSingleFlight(
57560
+ address,
57561
+ trigger
57562
+ );
56882
57563
  addresses.push(summary2);
56883
57564
  const { replayed, skipped: skipped2 } = summary2;
56884
57565
  totalReplayed += replayed;
@@ -56987,6 +57668,111 @@ var init_session_expired = __esm({
56987
57668
  }
56988
57669
  });
56989
57670
 
57671
+ // ../core/src/xmtp-sdk/onchainos/offline-replay-capability.ts
57672
+ function messageEligibleHelpAdvertisesOfflineReplay(helpText) {
57673
+ if (typeof helpText !== "string" || helpText.length === 0) {
57674
+ return false;
57675
+ }
57676
+ return helpText.toLowerCase().includes(MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN);
57677
+ }
57678
+ function buildMessageEligibleArgs(params, opts) {
57679
+ const args = [
57680
+ "agent",
57681
+ "message-eligible",
57682
+ "--agent-id",
57683
+ params.agentId,
57684
+ "--client-agent-id",
57685
+ params.clientAgentId,
57686
+ "--provider-agent-id",
57687
+ params.providerAgentId,
57688
+ "--client-communication-address",
57689
+ params.clientCommunicationAddress,
57690
+ "--provider-communication-address",
57691
+ params.providerCommunicationAddress,
57692
+ "--job-id",
57693
+ params.jobId,
57694
+ "--group-id",
57695
+ params.groupId,
57696
+ "--direction",
57697
+ params.direction,
57698
+ "--provider-security-rate",
57699
+ String(params.providerSecurityRate)
57700
+ ];
57701
+ if (opts.offlineReplaySupported) {
57702
+ args.push(MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG, String(params.isOfflineReplay));
57703
+ }
57704
+ return args;
57705
+ }
57706
+ function resolveOfflineReplayCapabilityMarker(supported) {
57707
+ return supported ? "supported" : "legacy_downgrade";
57708
+ }
57709
+ function maybeRecommendOnchainosUpgrade(supported) {
57710
+ if (supported || recommendedPairs.has(UPGRADE_RECOMMENDATION_PAIR_KEY)) {
57711
+ return;
57712
+ }
57713
+ const hook = XmtpService.getInstance().tools.notifyUpgradeRecommendation;
57714
+ if (!hook) {
57715
+ logWithTimestamp(
57716
+ "[onchainos] tools.notifyUpgradeRecommendation not injected \u2014 deferring offline-replay upgrade notice"
57717
+ );
57718
+ return;
57719
+ }
57720
+ recommendedPairs.add(UPGRADE_RECOMMENDATION_PAIR_KEY);
57721
+ try {
57722
+ void Promise.resolve(
57723
+ hook({
57724
+ component: "onchainos",
57725
+ capability: MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN,
57726
+ reason: UPGRADE_RECOMMENDATION_REASON
57727
+ })
57728
+ ).catch((err2) => {
57729
+ logWithTimestamp("[onchainos] tools.notifyUpgradeRecommendation async delivery failed:", err2);
57730
+ });
57731
+ } catch (err2) {
57732
+ logWithTimestamp("[onchainos] tools.notifyUpgradeRecommendation threw:", err2);
57733
+ }
57734
+ }
57735
+ function detectMessageEligibleOfflineReplaySupport(execFn = exec) {
57736
+ if (!capabilityProbe) {
57737
+ capabilityProbe = (async () => {
57738
+ try {
57739
+ const { stdout, stderr } = await execFn(["agent", "message-eligible", "--help"]);
57740
+ const supported = messageEligibleHelpAdvertisesOfflineReplay(`${stdout}
57741
+ ${stderr}`);
57742
+ logWithTimestamp(
57743
+ `[onchainos] message-eligible offline-replay capability: ${supported ? "supported" : "legacy_downgrade"}`
57744
+ );
57745
+ return supported;
57746
+ } catch (err2) {
57747
+ logWithTimestamp(
57748
+ "[onchainos] message-eligible --help probe failed; treating offline-replay as unsupported (legacy):",
57749
+ err2
57750
+ );
57751
+ return false;
57752
+ }
57753
+ })().then((supported) => {
57754
+ maybeRecommendOnchainosUpgrade(supported);
57755
+ return supported;
57756
+ });
57757
+ }
57758
+ return capabilityProbe;
57759
+ }
57760
+ var MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG, MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN, UPGRADE_RECOMMENDATION_PAIR_KEY, UPGRADE_RECOMMENDATION_REASON, capabilityProbe, recommendedPairs;
57761
+ var init_offline_replay_capability = __esm({
57762
+ "../core/src/xmtp-sdk/onchainos/offline-replay-capability.ts"() {
57763
+ "use strict";
57764
+ init_log();
57765
+ init_bin();
57766
+ init_xmtp_sdk();
57767
+ MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG = "--is-offline-replay";
57768
+ MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN = "is-offline-replay";
57769
+ UPGRADE_RECOMMENDATION_PAIR_KEY = "onchainos:message-eligible-offline-replay";
57770
+ UPGRADE_RECOMMENDATION_REASON = "OnchainOS does not support the offline-replay eligibility argument; using the legacy message-eligible contract.";
57771
+ capabilityProbe = null;
57772
+ recommendedPairs = /* @__PURE__ */ new Set();
57773
+ }
57774
+ });
57775
+
56990
57776
  // ../core/src/xmtp-sdk/onchainos/system-config.ts
56991
57777
  function notifyExpired(command, options) {
56992
57778
  if (options?.notifySessionExpired) {
@@ -57385,35 +58171,32 @@ async function sendHeartbeat(chainIndex) {
57385
58171
  }
57386
58172
  async function checkMessageEligible(params) {
57387
58173
  logWithTimestamp(
57388
- `[onchainos] checking message-eligible: job=${params.jobId} direction=${params.direction} providerSecurityRate=${params.providerSecurityRate}`
58174
+ `[onchainos] checking message-eligible: job=${params.jobId} direction=${params.direction} providerSecurityRate=${params.providerSecurityRate} isOfflineReplay=${params.isOfflineReplay}`
57389
58175
  );
58176
+ const offlineReplaySupported = await detectMessageEligibleOfflineReplaySupport();
58177
+ const offlineReplayCapability = resolveOfflineReplayCapabilityMarker(offlineReplaySupported);
58178
+ const emitEligibilityChecked = (outcome, eligible) => {
58179
+ logger.info(LogEvent.MESSAGE_ELIGIBILITY_CHECKED, {
58180
+ component: "onchainos_cli",
58181
+ source: "onchainos",
58182
+ stage: "eligibility_check",
58183
+ direction: params.direction,
58184
+ jobId: params.jobId,
58185
+ isOfflineReplay: String(params.isOfflineReplay),
58186
+ offlineReplayCapability,
58187
+ outcome,
58188
+ eligible
58189
+ });
58190
+ };
57390
58191
  let stdout;
57391
58192
  let stderr;
57392
58193
  try {
57393
- ({ stdout, stderr } = await exec([
57394
- "agent",
57395
- "message-eligible",
57396
- "--agent-id",
57397
- params.agentId,
57398
- "--client-agent-id",
57399
- params.clientAgentId,
57400
- "--provider-agent-id",
57401
- params.providerAgentId,
57402
- "--client-communication-address",
57403
- params.clientCommunicationAddress,
57404
- "--provider-communication-address",
57405
- params.providerCommunicationAddress,
57406
- "--job-id",
57407
- params.jobId,
57408
- "--group-id",
57409
- params.groupId,
57410
- "--direction",
57411
- params.direction,
57412
- "--provider-security-rate",
57413
- String(params.providerSecurityRate)
57414
- ]));
58194
+ ({ stdout, stderr } = await exec(
58195
+ buildMessageEligibleArgs(params, { offlineReplaySupported })
58196
+ ));
57415
58197
  } catch (err2) {
57416
58198
  guardCatchSessionExpired2(err2, "message-eligible");
58199
+ emitEligibilityChecked("failed", "unknown");
57417
58200
  throw messageEligibleUnavailableError({
57418
58201
  reason: "message-eligible-cli-error",
57419
58202
  stdout: typeof err2?.stdout === "string" ? err2.stdout : "",
@@ -57438,6 +58221,7 @@ async function checkMessageEligible(params) {
57438
58221
  providerSecurityRate: String(params.providerSecurityRate)
57439
58222
  });
57440
58223
  } catch (err2) {
58224
+ emitEligibilityChecked("failed", "unknown");
57441
58225
  throw messageEligibleUnavailableError({
57442
58226
  reason: "message-eligible-json-parse-failed",
57443
58227
  stdout,
@@ -57467,6 +58251,7 @@ async function checkMessageEligible(params) {
57467
58251
  reason: "ok=false"
57468
58252
  })
57469
58253
  );
58254
+ emitEligibilityChecked("failed", "unknown");
57470
58255
  throw messageEligibleUnavailableError({
57471
58256
  reason: "message-eligible-ok-false",
57472
58257
  stdout,
@@ -57476,6 +58261,7 @@ async function checkMessageEligible(params) {
57476
58261
  logWithTimestamp(
57477
58262
  `[onchainos] message-eligible result: eligible=${res.data.eligible}`
57478
58263
  );
58264
+ emitEligibilityChecked("success", String(res.data.eligible));
57479
58265
  return res.data;
57480
58266
  }
57481
58267
  async function fetchSensitiveWords() {
@@ -57529,6 +58315,7 @@ var init_onchainos = __esm({
57529
58315
  init_xmtp_sdk();
57530
58316
  init_cli_response();
57531
58317
  init_session_expired();
58318
+ init_offline_replay_capability();
57532
58319
  init_cli_response();
57533
58320
  init_session_expired();
57534
58321
  init_system_config();
@@ -58394,7 +59181,9 @@ async function assertOutboundEligible(params) {
58394
59181
  jobId,
58395
59182
  groupId,
58396
59183
  direction: isSenderClient ? "client_to_provider" : "provider_to_client",
58397
- providerSecurityRate
59184
+ providerSecurityRate,
59185
+ // Outbound messages are never startup backlog recovery.
59186
+ isOfflineReplay: false
58398
59187
  });
58399
59188
  logWithTimestamp(
58400
59189
  `[okx-agent-task] outbound message-eligible: senderAgentId=${senderAgent.agentId} receiverAgentId=${receiverAgent.agentId} job=${jobId} group=${groupId} result=${JSON.stringify(result)}`
@@ -63996,6 +64785,7 @@ async function verifyInboundA2AGroupMessage(params) {
63996
64785
  messageId,
63997
64786
  consentState,
63998
64787
  trustedSystemSender = false,
64788
+ isOfflineReplay = false,
63999
64789
  timing
64000
64790
  } = params;
64001
64791
  if (!isA2AEnvelope(payload)) {
@@ -64099,7 +64889,8 @@ async function verifyInboundA2AGroupMessage(params) {
64099
64889
  jobId,
64100
64890
  groupId,
64101
64891
  direction: isSenderClient ? "client_to_provider" : "provider_to_client",
64102
- providerSecurityRate
64892
+ providerSecurityRate,
64893
+ isOfflineReplay
64103
64894
  });
64104
64895
  timing?.mark("eligibilityCheck", eligibilityStartedAt);
64105
64896
  logWithTimestamp(
@@ -64548,6 +65339,7 @@ async function processFileMessage(ctx, deps, options = {}) {
64548
65339
  messageId,
64549
65340
  consentState: ctx.conversation instanceof Group ? ctx.conversation.consentState() : void 0,
64550
65341
  trustedSystemSender,
65342
+ isOfflineReplay: options.isOfflineReplay ?? false,
64551
65343
  timing
64552
65344
  });
64553
65345
  if (!accepted) {
@@ -65272,14 +66064,28 @@ async function runListenerWithLock(options, paths) {
65272
66064
  reason: "append_backup_failed"
65273
66065
  });
65274
66066
  });
66067
+ },
66068
+ notifyUpgradeRecommendation: (input) => {
66069
+ const content2 = "The installed OnchainOS version does not support offline-replay preferences. Subscription messages will continue to replay normally. Upgrade OnchainOS to enable this capability.\n\nSuggested command: onchainos upgrade";
66070
+ void notifySystemMessageToUser({
66071
+ title: "OnchainOS upgrade recommended",
66072
+ content: content2,
66073
+ store: sessionStore,
66074
+ idempotencyKey: UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY
66075
+ }).catch((err2) => {
66076
+ logWithTimestamp(
66077
+ `[okx-agent-task] upgrade recommendation notify failed component=${input.component} capability=${input.capability}:`,
66078
+ err2
66079
+ );
66080
+ });
65275
66081
  }
65276
66082
  });
65277
- service.setPluginVersion("0.2.0");
66083
+ service.setPluginVersion("0.2.1-beta-8ae7a0d300-260807181806");
65278
66084
  await service.init();
65279
66085
  const pluginVersionStatus = service.pluginVersionStatus;
65280
66086
  if (pluginVersionStatus.unavailable) {
65281
66087
  throw new Error(
65282
- `@okxweb3/a2a-node v${"0.2.0"} is below the required minimum v${pluginVersionStatus.minVersion}`
66088
+ `@okxweb3/a2a-node v${"0.2.1-beta-8ae7a0d300-260807181806"} is below the required minimum v${pluginVersionStatus.minVersion}`
65283
66089
  );
65284
66090
  }
65285
66091
  const systemConfig = service.getSystemConfig();
@@ -65297,12 +66103,14 @@ async function runListenerWithLock(options, paths) {
65297
66103
  onchainosAgentId: "*",
65298
66104
  reason: "system-config missing sentryDsn",
65299
66105
  pluginId: "@okxweb3/a2a-node",
65300
- pluginVersion: "0.2.0"
66106
+ pluginVersion: "0.2.1-beta-8ae7a0d300-260807181806"
65301
66107
  });
65302
66108
  }
65303
66109
  logWithTimestamp(
65304
66110
  `[okx-agent-task] listener initialized, clients=${service.getClients().size}, home=${store.homeDir}`
65305
66111
  );
66112
+ void detectMessageEligibleOfflineReplaySupport().catch(() => {
66113
+ });
65306
66114
  const startupReplaySummary = await service.completeStartupReplay();
65307
66115
  logWithTimestamp(
65308
66116
  `[okx-agent-task] startup replay complete clients=${startupReplaySummary.clients} replayed=${startupReplaySummary.replayed} skipped=${startupReplaySummary.skipped} duration=${startupReplaySummary.durationMs}ms`
@@ -65440,7 +66248,7 @@ async function runListenerWithLock(options, paths) {
65440
66248
  );
65441
66249
  return;
65442
66250
  }
65443
- const replayResult = await timeSettled(() => service.replayOfflineMessages());
66251
+ const replayResult = await timeSettled(() => service.replayOfflineMessages("periodic_repair"));
65444
66252
  const replayMs = replayResult.durationMs;
65445
66253
  if (replayResult.error) {
65446
66254
  logger.error(LogEvent.OFFLINE_REPLAY_FAILED, replayResult.error instanceof Error ? replayResult.error : new Error(String(replayResult.error)), {
@@ -65671,7 +66479,7 @@ async function timeSettled(fn) {
65671
66479
  return { durationMs: Date.now() - startedAt, error };
65672
66480
  }
65673
66481
  }
65674
- var import_node_fs25, import_promises13, import_node_os11, import_node_path30, DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC, DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC, XMTP_CLIENT_RECYCLE_INTERVAL_ENV, HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS, SHUTDOWN_FORCE_RESOLVE_MS;
66482
+ var import_node_fs25, import_promises13, import_node_os11, import_node_path30, DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC, DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC, XMTP_CLIENT_RECYCLE_INTERVAL_ENV, HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS, UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY, SHUTDOWN_FORCE_RESOLVE_MS;
65675
66483
  var init_listener = __esm({
65676
66484
  "src/listener.ts"() {
65677
66485
  "use strict";
@@ -65682,6 +66490,7 @@ var init_listener = __esm({
65682
66490
  import_node_path30 = require("node:path");
65683
66491
  init_onchainos();
65684
66492
  init_system_config();
66493
+ init_offline_replay_capability();
65685
66494
  init_signer();
65686
66495
  init_xmtp_sdk();
65687
66496
  init_file_store();
@@ -65690,6 +66499,7 @@ var init_listener = __esm({
65690
66499
  init_command_store();
65691
66500
  init_ai_dispatch_queue();
65692
66501
  init_message_handler();
66502
+ init_agent_message_notice();
65693
66503
  init_paths();
65694
66504
  init_user_attention_ipc();
65695
66505
  init_user_attention_watchers();
@@ -65702,6 +66512,7 @@ var init_listener = __esm({
65702
66512
  DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC = 6 * 60 * 60;
65703
66513
  XMTP_CLIENT_RECYCLE_INTERVAL_ENV = "OKX_A2A_XMTP_CLIENT_RECYCLE_INTERVAL_SEC";
65704
66514
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS = 2e3;
66515
+ UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY = "system:onchainos-offline-replay-upgrade";
65705
66516
  SHUTDOWN_FORCE_RESOLVE_MS = 5e3;
65706
66517
  }
65707
66518
  });
@@ -65960,6 +66771,7 @@ function summarizeXmtpTestEvents(runId, events) {
65960
66771
  const nodeDurations = /* @__PURE__ */ new Map();
65961
66772
  const replayLags = /* @__PURE__ */ new Map();
65962
66773
  const replayHandles = /* @__PURE__ */ new Map();
66774
+ const streamRecoveries = /* @__PURE__ */ new Map();
65963
66775
  const agentMap = /* @__PURE__ */ new Map();
65964
66776
  for (const event of events) {
65965
66777
  const key = deliveryKey(event);
@@ -65992,6 +66804,18 @@ function summarizeXmtpTestEvents(runId, events) {
65992
66804
  event.handlerTotalMs
65993
66805
  );
65994
66806
  }
66807
+ if (event.eventName === "Agent stream recovered") {
66808
+ setDuration(
66809
+ streamRecoveries,
66810
+ [
66811
+ eventAgentId(event),
66812
+ stringValue(event.clientGeneration) || "unknown-client",
66813
+ stringValue(event.recoverySequence) || "unknown-recovery",
66814
+ event.timestamp
66815
+ ].join(":"),
66816
+ event.recoveryDurationMs
66817
+ );
66818
+ }
65995
66819
  }
65996
66820
  const agents = [...agentMap.entries()].filter(([, value]) => value.live.size > 0 || value.replay.size > 0).map(([agentId, value]) => {
65997
66821
  const total = (/* @__PURE__ */ new Set([...value.live, ...value.replay])).size;
@@ -66013,7 +66837,9 @@ function summarizeXmtpTestEvents(runId, events) {
66013
66837
  duplicateEvents: countEvent(events, "Inbound ignored: duplicate message"),
66014
66838
  replayMessageFailures: countEvent(events, "Offline replay message failed"),
66015
66839
  replayScanFailures: countEvent(events, "Offline replay failed"),
66016
- streamFailures: countEvent(events, "conversations.stream failed") + countEvent(events, "Agent stream error"),
66840
+ streamFailures: countEvent(events, "conversations.stream failed") + countEvent(events, "agent stream error"),
66841
+ streamRecoveries: countEvent(events, "Agent stream recovered"),
66842
+ streamRecoveryTimeouts: countEvent(events, "Agent stream recovery timeout"),
66017
66843
  connectionFailures: countEvent(events, "XMTP connection failed"),
66018
66844
  successfulReplayScans: countEvent(events, "Address sync completed"),
66019
66845
  replayScansWithMessages: events.filter((event) => event.eventName === "Address sync completed" && numericValue(event.replayed) > 0).length,
@@ -66021,6 +66847,7 @@ function summarizeXmtpTestEvents(runId, events) {
66021
66847
  nodeHandlerMs: summarizeDurations([...nodeDurations.values()]),
66022
66848
  replayLagMs: summarizeDurations([...replayLags.values()]),
66023
66849
  replayHandleMs: summarizeDurations([...replayHandles.values()]),
66850
+ streamRecoveryMs: summarizeDurations([...streamRecoveries.values()]),
66024
66851
  agents
66025
66852
  };
66026
66853
  }
@@ -66035,6 +66862,8 @@ Reliability
66035
66862
  Replay message failures ${summary.replayMessageFailures}
66036
66863
  Replay scan failures ${summary.replayScanFailures}
66037
66864
  Stream failures ${summary.streamFailures}
66865
+ Stream recoveries ${summary.streamRecoveries}
66866
+ Recovery timeouts (>30s) ${summary.streamRecoveryTimeouts}
66038
66867
  XMTP connection failures ${summary.connectionFailures}
66039
66868
  Replay scans ${summary.successfulReplayScans}
66040
66869
  Scans with replay ${summary.replayScansWithMessages}
@@ -66043,7 +66872,8 @@ Timing
66043
66872
  XMTP delivery p50/p95/max ${formatDurationSummary(summary.xmtpDeliveryMs)}
66044
66873
  Node handler p50/p95/max ${formatDurationSummary(summary.nodeHandlerMs)}
66045
66874
  Replay lag p50/p95/max ${formatDurationSummary(summary.replayLagMs)}
66046
- Replay handler p50/p95/max ${formatDurationSummary(summary.replayHandleMs)}`);
66875
+ Replay handler p50/p95/max ${formatDurationSummary(summary.replayHandleMs)}
66876
+ Stream recovery p50/p95/max ${formatDurationSummary(summary.streamRecoveryMs)}`);
66047
66877
  if (summary.agents.length > 0) {
66048
66878
  console.log("\nAgents");
66049
66879
  for (const agent of summary.agents) {
@@ -113372,7 +114202,7 @@ async function getCurrentNodeCliVersion() {
113372
114202
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
113373
114203
  }
113374
114204
  function getBundledNodeCliVersion() {
113375
- return true ? "0.2.0" : null;
114205
+ return true ? "0.2.1-beta-8ae7a0d300-260807181806" : null;
113376
114206
  }
113377
114207
  function readConfiguredAiProvider() {
113378
114208
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -113582,7 +114412,7 @@ async function updateHermes(release, options) {
113582
114412
  }
113583
114413
  }
113584
114414
  async function installGatewayPluginForDoctor(target) {
113585
- const release = isPrereleaseVersion("0.2.0") ? "beta" : "latest";
114415
+ const release = isPrereleaseVersion("0.2.1-beta-8ae7a0d300-260807181806") ? "beta" : "latest";
113586
114416
  const insideTargetGateway = detectGatewayInvocation() === target;
113587
114417
  const options = {
113588
114418
  restart: !insideTargetGateway,
@@ -114570,7 +115400,7 @@ async function runDoctor(options = {}) {
114570
115400
  platform: options.platform ?? process.platform,
114571
115401
  env: options.env ?? process.env,
114572
115402
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
114573
- cliVersion: options.cliVersion ?? (true ? "0.2.0" : "0.0.0"),
115403
+ cliVersion: options.cliVersion ?? (true ? "0.2.1-beta-8ae7a0d300-260807181806" : "0.0.0"),
114574
115404
  fixMode: options.fix === true,
114575
115405
  nonInteractive: options.nonInteractive === true,
114576
115406
  packageChanged: false,
@@ -115538,7 +116368,7 @@ init_sentry_logger();
115538
116368
  init_sentry_config();
115539
116369
  var CURRENT_GATEWAY_SESSION_KEYS_ENV4 = "OKX_A2A_CURRENT_GATEWAY_SESSION_KEYS";
115540
116370
  function printUsage3() {
115541
- console.log(`okx-a2a ${"0.2.0"}
116371
+ console.log(`okx-a2a ${"0.2.1-beta-8ae7a0d300-260807181806"}
115542
116372
 
115543
116373
  Usage:
115544
116374
  okx-a2a <command> [options]
@@ -115568,6 +116398,7 @@ Commands:
115568
116398
  Alias for runtime switch-current
115569
116399
  job-provider Manage job-to-provider bindings
115570
116400
  xmtp-send Queue an XMTP message through the running daemon
116401
+ capabilities Print machine-readable A2A capability negotiation status (used by OnchainOS)
115571
116402
 
115572
116403
  Global options:
115573
116404
  -h, --help Show help
@@ -115577,7 +116408,7 @@ Run \`okx-a2a <command> -h\` for command-specific help.
115577
116408
  `);
115578
116409
  }
115579
116410
  function printVersion() {
115580
- console.log("0.2.0");
116411
+ console.log("0.2.1-beta-8ae7a0d300-260807181806");
115581
116412
  }
115582
116413
  function printDaemonUsage() {
115583
116414
  console.log(`Usage: okx-a2a daemon <start|restart|stop|status|autostart> [options]
@@ -116924,6 +117755,11 @@ async function main() {
116924
117755
  if (hasHelpFlag5(args) && !shouldDeferHelpToNestedHandler(command, args) && printCommandUsage(command)) {
116925
117756
  return;
116926
117757
  }
117758
+ if (command === "capabilities") {
117759
+ const { handleCapabilitiesCommand: handleCapabilitiesCommand2 } = await Promise.resolve().then(() => (init_capabilities_cli(), capabilities_cli_exports));
117760
+ handleCapabilitiesCommand2(process.argv.slice(3));
117761
+ return;
117762
+ }
116927
117763
  if (command === "daemon") {
116928
117764
  if (hasHelpFlag5(args)) {
116929
117765
  printDaemonUsage();
@@ -116983,7 +117819,7 @@ async function main() {
116983
117819
  if (command === "xmtp-test") {
116984
117820
  const { handleXmtpTestCommand: handleXmtpTestCommand2 } = await Promise.resolve().then(() => (init_xmtp_test_cli(), xmtp_test_cli_exports));
116985
117821
  await handleXmtpTestCommand2(process.argv.slice(3), {
116986
- packageVersion: "0.2.0",
117822
+ packageVersion: "0.2.1-beta-8ae7a0d300-260807181806",
116987
117823
  agentSdkVersion: "2.3.0",
116988
117824
  nodeSdkVersion: "6.1.0",
116989
117825
  nodeBindingsVersion: "1.11.0"