@okxweb3/a2a-node 0.2.1-beta-8ae7a0d300-260807161635 → 0.2.1

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 +744 -44
  2. package/dist/index.js +733 -40
  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.1-beta-8ae7a0d300-260807161635",
9436
+ version: "0.2.1",
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.1-beta-8ae7a0d300-260807161635"}`,
9447
+ userAgent: `okx-a2a-node/${"0.2.1"}`,
9448
9448
  auth: {
9449
9449
  ...config.token ? { token: config.token } : {},
9450
9450
  ...config.password ? { password: config.password } : {}
@@ -25478,6 +25478,8 @@ var init_events = __esm({
25478
25478
  XMTP_DEBUG_SYNC_REQUESTED: "XMTP debug sync requested",
25479
25479
  XMTP_DEBUG_SYNC_COMPLETED: "XMTP debug sync completed",
25480
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",
25481
25483
  ADDRESS_SYNC_COMPLETED: "Address sync completed",
25482
25484
  AGENT_REFRESH_SESSION_EXPIRED: "Agent refresh aborted: onchainos session expired, local clients taken offline",
25483
25485
  OFFLINE_REPLAY_MESSAGE_REPLAYED: "Offline replay message replayed",
@@ -25512,6 +25514,8 @@ var init_events = __esm({
25512
25514
  CONVERSATIONS_STREAM_FAILED: "conversations.stream failed",
25513
25515
  AGENT_START_FAILED: "agent.start failed",
25514
25516
  AGENT_STREAM_ERROR: "agent stream error",
25517
+ AGENT_UNHANDLED_ERROR: "Agent unhandled error",
25518
+ AGENT_STREAM_RECOVERY_TIMEOUT: "Agent stream recovery timeout",
25515
25519
  BOOTSTRAP_FAILED: "Bootstrap failed",
25516
25520
  ONCHAINOS_SESSION_EXPIRED: "Onchainos session expired",
25517
25521
  ONCHAINOS_CLI_ERROR: "Onchainos CLI error",
@@ -25521,6 +25525,7 @@ var init_events = __esm({
25521
25525
  MESSAGE_PARSE_FAILED: "Message parse failed",
25522
25526
  MESSAGE_HANDLER_ERROR: "Message handler error",
25523
25527
  INBOUND_DISPATCH_FAILED: "Inbound dispatch failed",
25528
+ INBOUND_REPLAY_GATE_DRAIN_FAILED: "Inbound replay gate drain failed",
25524
25529
  // Default name stamped on events that reach Sentry without going through this
25525
25530
  // logger — uncaught exceptions and unhandled rejections. Without it those
25526
25531
  // events carry no eventName and cannot be classified.
@@ -25818,7 +25823,10 @@ var init_xmtp_test_metrics = __esm({
25818
25823
  "Address sync completed",
25819
25824
  "Agent client created",
25820
25825
  "Agent client removed",
25821
- "Agent stream error",
25826
+ "agent stream error",
25827
+ "Agent stream recovered",
25828
+ "Agent stream recovery replay completed",
25829
+ "Agent stream recovery timeout",
25822
25830
  "conversations.stream failed",
25823
25831
  "Inbound delivered to session",
25824
25832
  "Inbound dispatch failed",
@@ -25837,6 +25845,190 @@ var init_xmtp_test_metrics = __esm({
25837
25845
  }
25838
25846
  });
25839
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
+
25840
26032
  // ../core/src/sentry-logger/index.ts
25841
26033
  function applyFatalEventDefaults(event) {
25842
26034
  try {
@@ -25882,6 +26074,7 @@ var init_sentry_logger = __esm({
25882
26074
  init_events();
25883
26075
  init_log_fields();
25884
26076
  init_xmtp_test_metrics();
26077
+ init_error_diagnostics();
25885
26078
  init_events();
25886
26079
  init_log_fields();
25887
26080
  FLOW_ID = (0, import_node_crypto5.randomUUID)();
@@ -25969,14 +26162,23 @@ var init_sentry_logger = __esm({
25969
26162
  "agentId",
25970
26163
  "agentPlatform",
25971
26164
  "cacheName",
26165
+ "causeCode",
26166
+ "causeType",
26167
+ "causeVariant",
25972
26168
  "checkpoint",
25973
26169
  "code",
25974
26170
  "communicationClass",
25975
26171
  "component",
25976
26172
  "eventFamily",
25977
26173
  "eventName",
26174
+ "endpoint",
26175
+ "errorCategory",
26176
+ "errorCode",
26177
+ "errorRetryability",
26178
+ "errorSyscall",
25978
26179
  "exitCode",
25979
26180
  "fromAgentId",
26181
+ "grpcMethod",
25980
26182
  "jobId",
25981
26183
  "kind",
25982
26184
  "method",
@@ -26000,17 +26202,24 @@ var init_sentry_logger = __esm({
26000
26202
  "source",
26001
26203
  "stage",
26002
26204
  "status",
26205
+ "streamErrorCode",
26206
+ "streamType",
26003
26207
  "subcommand",
26004
26208
  "systemEvent",
26005
26209
  "taskId",
26006
26210
  "taskMode",
26007
26211
  "toAgentId",
26008
26212
  "transport",
26213
+ "transportFailure",
26214
+ "transportProtocol",
26009
26215
  "type",
26010
26216
  "workloadClass"
26011
26217
  ]);
26012
26218
  SENTRY_FINGERPRINT_KEYS = [
26013
26219
  "eventName",
26220
+ "errorCategory",
26221
+ "errorCode",
26222
+ "errorSyscall",
26014
26223
  "cacheName",
26015
26224
  "component",
26016
26225
  "method",
@@ -26074,6 +26283,9 @@ var init_sentry_logger = __esm({
26074
26283
  LogEvent.MESSAGE_SENT,
26075
26284
  LogEvent.AGENT_CLIENT_CREATED,
26076
26285
  LogEvent.AGENT_CLIENT_REMOVED,
26286
+ LogEvent.AGENT_STREAM_ERROR,
26287
+ LogEvent.AGENT_STREAM_RECOVERED,
26288
+ LogEvent.AGENT_STREAM_RECOVERY_REPLAY_COMPLETED,
26077
26289
  LogEvent.ADDRESS_SYNC_COMPLETED,
26078
26290
  LogEvent.AGENTS_REFRESHED,
26079
26291
  LogEvent.AGENTS_WAKEUP_NOTIFIED,
@@ -26205,19 +26417,24 @@ var init_sentry_logger = __esm({
26205
26417
  }
26206
26418
  captureError(message, error, extraWithFlow) {
26207
26419
  try {
26420
+ const errorDiagnostics = buildErrorDiagnostics(
26421
+ message,
26422
+ error,
26423
+ extraWithFlow
26424
+ );
26425
+ const diagnosticExtra = {
26426
+ ...extraWithFlow,
26427
+ ...errorDiagnostics
26428
+ };
26208
26429
  Sentry.withScope((scope) => {
26209
26430
  scope.setLevel("error");
26210
- _SentryLogger.applyDiagnostics(scope, message, extraWithFlow);
26431
+ _SentryLogger.applyDiagnostics(scope, message, diagnosticExtra);
26211
26432
  Sentry.captureException(_SentryLogger.createSentryEvent(message), {
26212
26433
  // eventName is unconditional: callers that pass no Error object are
26213
26434
  // still classified events, and without it they reach SLS anonymous.
26214
26435
  extra: {
26215
- ...extraWithFlow,
26216
- eventName: message,
26217
- ...error ? {
26218
- errorName: error.name,
26219
- errorMessageLength: String(error.message.length)
26220
- } : {}
26436
+ ...diagnosticExtra,
26437
+ eventName: message
26221
26438
  }
26222
26439
  });
26223
26440
  });
@@ -27416,7 +27633,7 @@ var init_sentry_config = __esm({
27416
27633
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
27417
27634
  SENTRY_CONFIG = {
27418
27635
  projectName: "okx/openclaw-okx-a2a-extension",
27419
- release: "0.2.1-beta-8ae7a0d300-260807161635",
27636
+ release: "0.2.1",
27420
27637
  environment,
27421
27638
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
27422
27639
  };
@@ -38586,7 +38803,7 @@ async function exportDiagnosticLogs(options) {
38586
38803
  node: process.version,
38587
38804
  platform: process.platform,
38588
38805
  arch: process.arch,
38589
- packageVersion: true ? "0.2.1-beta-8ae7a0d300-260807161635" : "unknown",
38806
+ packageVersion: true ? "0.2.1" : "unknown",
38590
38807
  sensitiveContentIncluded: options.includeSensitiveContent,
38591
38808
  listenerAndLlmContentIncluded: true,
38592
38809
  credentialsAlwaysRedacted: true,
@@ -39731,7 +39948,7 @@ async function exec(args) {
39731
39948
  );
39732
39949
  logger.error(
39733
39950
  LogEvent.ONCHAINOS_CLI_ERROR,
39734
- new Error("onchainos command failed"),
39951
+ err2 instanceof Error ? err2 : new Error("onchainos command failed"),
39735
39952
  {
39736
39953
  component: "onchainos_cli",
39737
39954
  source: "onchainos",
@@ -54459,6 +54676,171 @@ var init_dist4 = __esm({
54459
54676
  }
54460
54677
  });
54461
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
+
54462
54844
  // ../core/src/xmtp-sdk/extract-job-id.ts
54463
54845
  function extractJobIdFromContent(content2) {
54464
54846
  const raw = typeof content2 === "string" ? content2 : stringifyJsonForEnvelope(content2);
@@ -55262,6 +55644,12 @@ function parseGroupPayload(content2) {
55262
55644
  function isOfflineReplayForTrigger(trigger) {
55263
55645
  return trigger === "startup";
55264
55646
  }
55647
+ function replaySingleFlightDiscriminator(trigger, clientGeneration, recoveryIdentity) {
55648
+ if (trigger === "stream_recovery") {
55649
+ return `stream_recovery:${clientGeneration}:${recoveryIdentity ?? "unknown"}`;
55650
+ }
55651
+ return isOfflineReplayForTrigger(trigger) ? `startup_offline:${clientGeneration}` : `online_repair:${clientGeneration}`;
55652
+ }
55265
55653
  function createOfflineReplayAddressSummary(address) {
55266
55654
  return {
55267
55655
  address,
@@ -55279,7 +55667,7 @@ function createOfflineReplayAddressSummary(address) {
55279
55667
  durationMs: 0
55280
55668
  };
55281
55669
  }
55282
- 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;
55670
+ 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;
55283
55671
  var init_xmtp_sdk = __esm({
55284
55672
  "../core/src/xmtp-sdk/index.ts"() {
55285
55673
  "use strict";
@@ -55289,6 +55677,7 @@ var init_xmtp_sdk = __esm({
55289
55677
  init_dist4();
55290
55678
  init_dist2();
55291
55679
  init_sentry_logger();
55680
+ init_stream_recovery_telemetry();
55292
55681
  init_extract_job_id();
55293
55682
  init_self_filter();
55294
55683
  init_logging();
@@ -55306,10 +55695,11 @@ var init_xmtp_sdk = __esm({
55306
55695
  this.inbound = inbound;
55307
55696
  }
55308
55697
  inbound;
55309
- buffering = true;
55698
+ holds = 1;
55310
55699
  queue = [];
55700
+ drainInFlight;
55311
55701
  middleware = async (ctx, next) => {
55312
- if (!this.buffering) {
55702
+ if (this.holds === 0 && !this.drainInFlight) {
55313
55703
  await this.inbound(ctx, next);
55314
55704
  return;
55315
55705
  }
@@ -55318,19 +55708,55 @@ var init_xmtp_sdk = __esm({
55318
55708
  get bufferedCount() {
55319
55709
  return this.queue.length;
55320
55710
  }
55711
+ abandonFailedAndRelease() {
55712
+ const abandoned = this.queue.length > 0 ? 1 : 0;
55713
+ if (abandoned > 0) {
55714
+ this.queue.shift();
55715
+ }
55716
+ if (this.holds > 0) {
55717
+ this.holds--;
55718
+ }
55719
+ return abandoned;
55720
+ }
55721
+ hold() {
55722
+ this.holds++;
55723
+ }
55321
55724
  async drain() {
55725
+ if (this.holds > 0) {
55726
+ this.holds--;
55727
+ }
55728
+ return this.drainReleased();
55729
+ }
55730
+ async drainReleased() {
55731
+ if (this.holds > 0) {
55732
+ return 0;
55733
+ }
55734
+ if (this.drainInFlight) {
55735
+ return this.drainInFlight;
55736
+ }
55737
+ const drain = Promise.resolve().then(() => this.drainQueue());
55738
+ this.drainInFlight = drain;
55739
+ try {
55740
+ return await drain;
55741
+ } finally {
55742
+ if (this.drainInFlight === drain) {
55743
+ this.drainInFlight = void 0;
55744
+ }
55745
+ }
55746
+ }
55747
+ async drainQueue() {
55322
55748
  let drained = 0;
55323
- while (this.queue.length > 0) {
55749
+ while (this.holds === 0 && this.queue.length > 0) {
55324
55750
  const item = this.queue.shift();
55325
55751
  try {
55326
55752
  await this.inbound(item.ctx, item.next);
55327
55753
  } catch (err2) {
55328
55754
  this.queue.unshift(item);
55755
+ this.holds++;
55329
55756
  throw err2;
55330
55757
  }
55331
55758
  drained++;
55332
55759
  }
55333
- this.buffering = false;
55334
55760
  return drained;
55335
55761
  }
55336
55762
  };
@@ -55346,6 +55772,9 @@ var init_xmtp_sdk = __esm({
55346
55772
  }
55347
55773
  }
55348
55774
  };
55775
+ STREAM_RECOVERY_ALERT_THRESHOLD_MS = 3e4;
55776
+ REPLAY_GATE_DRAIN_MAX_ATTEMPTS = 3;
55777
+ REPLAY_GATE_DRAIN_RETRY_DELAY_MS = 100;
55349
55778
  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-]+)*)?$/;
55350
55779
  isRecord5 = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
55351
55780
  isPositiveNumber = (value) => typeof value === "number" && Number.isFinite(value) && value > 0;
@@ -55390,7 +55819,15 @@ var init_xmtp_sdk = __esm({
55390
55819
  };
55391
55820
  dataDir = DEFAULT_DATA_DIR;
55392
55821
  syncByAddress = /* @__PURE__ */ new Map();
55822
+ offlineReplaySingleFlight = new KeyedSingleFlight();
55393
55823
  inboundReplayGates = /* @__PURE__ */ new Map();
55824
+ streamRecoveryCleanupByAddress = /* @__PURE__ */ new Map();
55825
+ nextClientGeneration = 1;
55826
+ clientGenerationByClient = /* @__PURE__ */ new WeakMap();
55827
+ resolveStartupReplayCompletion;
55828
+ startupReplayCompletion = new Promise((resolve14) => {
55829
+ this.resolveStartupReplayCompletion = resolve14;
55830
+ });
55394
55831
  agentListFingerprint;
55395
55832
  // Only one refreshAgents can be in flight at a time; concurrent calls
55396
55833
  // coalesce onto the same promise. This avoids duplicate Agent.create()
@@ -55845,6 +56282,7 @@ var init_xmtp_sdk = __esm({
55845
56282
  const client = this.clients.get(address);
55846
56283
  if (client) {
55847
56284
  try {
56285
+ this.cancelStreamRecoveryForAddress(address);
55848
56286
  this.stoppedAddresses.add(address.toLowerCase());
55849
56287
  client.stop();
55850
56288
  logWithTimestamp(
@@ -56018,7 +56456,16 @@ var init_xmtp_sdk = __esm({
56018
56456
  logWithTimestamp(
56019
56457
  `[xmtp-sdk] refresh: ${address} has a prior sync timestamp, triggering offline replay`
56020
56458
  );
56021
- void this.replayOfflineMessagesForAddress(address, "agent_refresh").catch((err2) => {
56459
+ const expectedClient = this.clients.get(address) ?? null;
56460
+ const expectedGate = this.inboundReplayGates.get(
56461
+ address.toLowerCase()
56462
+ );
56463
+ void this.replayOfflineMessagesForAddressSingleFlight(
56464
+ address,
56465
+ "agent_refresh",
56466
+ void 0,
56467
+ expectedClient
56468
+ ).catch((err2) => {
56022
56469
  logWithTimestamp(`[xmtp-sdk] refresh: ${address} offline replay failed:`, err2);
56023
56470
  logger.error(
56024
56471
  LogEvent.OFFLINE_REPLAY_FAILED,
@@ -56028,7 +56475,7 @@ var init_xmtp_sdk = __esm({
56028
56475
  stage: "refreshAgents/replay"
56029
56476
  }
56030
56477
  );
56031
- }).then(() => this.drainInboundReplayGate(address)).catch((err2) => {
56478
+ }).then(() => this.drainInboundReplayGate(address, expectedGate)).catch((err2) => {
56032
56479
  logWithTimestamp(`[xmtp-sdk] refresh: ${address} replay gate drain failed:`, err2);
56033
56480
  });
56034
56481
  }
@@ -56060,6 +56507,7 @@ var init_xmtp_sdk = __esm({
56060
56507
  }
56061
56508
  const addressKey = address.toLowerCase();
56062
56509
  try {
56510
+ this.cancelStreamRecoveryForAddress(address);
56063
56511
  this.stoppedAddresses.add(addressKey);
56064
56512
  await Promise.resolve(oldClient.stop());
56065
56513
  this.clients.delete(address);
@@ -56116,11 +56564,17 @@ var init_xmtp_sdk = __esm({
56116
56564
  phase: "recycle"
56117
56565
  });
56118
56566
  await this.startListeningForAddresses([address]);
56567
+ const replayGate = this.inboundReplayGates.get(addressKey);
56119
56568
  let replaySummary;
56120
56569
  try {
56121
- replaySummary = await this.replayOfflineMessagesForAddress(address, "client_recycle");
56570
+ replaySummary = await this.replayOfflineMessagesForAddressSingleFlight(
56571
+ address,
56572
+ "client_recycle",
56573
+ void 0,
56574
+ agent
56575
+ );
56122
56576
  } finally {
56123
- await this.drainInboundReplayGate(address);
56577
+ await this.drainInboundReplayGate(address, replayGate);
56124
56578
  }
56125
56579
  addresses.push(replaySummary);
56126
56580
  replayed += replaySummary.replayed;
@@ -56128,6 +56582,7 @@ var init_xmtp_sdk = __esm({
56128
56582
  recycled++;
56129
56583
  } catch (err2) {
56130
56584
  failed++;
56585
+ this.cancelStreamRecoveryForAddress(address);
56131
56586
  this.clients.delete(address);
56132
56587
  logWithTimestamp(`[xmtp-sdk] recycle: failed to recreate client: ${address}`, err2);
56133
56588
  logger.error(
@@ -56528,16 +56983,83 @@ var init_xmtp_sdk = __esm({
56528
56983
  async startListening() {
56529
56984
  await this.startListeningForAddresses([...this.clients.keys()]);
56530
56985
  }
56531
- async drainInboundReplayGate(address) {
56986
+ async drainInboundReplayGate(address, expectedGate) {
56987
+ if (!expectedGate) {
56988
+ return 0;
56989
+ }
56532
56990
  const gate = this.inboundReplayGates.get(address.toLowerCase());
56533
- if (!gate) {
56991
+ if (!gate || gate !== expectedGate) {
56534
56992
  return 0;
56535
56993
  }
56536
- const drained = await gate.drain();
56994
+ let attempt = 0;
56995
+ let totalDrained = 0;
56996
+ let releaseHold = true;
56997
+ while (true) {
56998
+ try {
56999
+ const drained = releaseHold ? await gate.drain() : await gate.drainReleased();
57000
+ totalDrained += drained;
57001
+ logWithTimestamp(
57002
+ `[xmtp-sdk:${address}] replay barrier released, buffered=${totalDrained} attempt=${attempt + 1}`
57003
+ );
57004
+ return totalDrained;
57005
+ } catch (err2) {
57006
+ attempt++;
57007
+ logWithTimestamp(
57008
+ `[xmtp-sdk:${address}] replay barrier drain failed attempt=${attempt}/${REPLAY_GATE_DRAIN_MAX_ATTEMPTS}:`,
57009
+ err2
57010
+ );
57011
+ if (attempt < REPLAY_GATE_DRAIN_MAX_ATTEMPTS) {
57012
+ releaseHold = true;
57013
+ await new Promise(
57014
+ (resolve14) => setTimeout(resolve14, REPLAY_GATE_DRAIN_RETRY_DELAY_MS * attempt)
57015
+ );
57016
+ continue;
57017
+ }
57018
+ const abandoned = gate.abandonFailedAndRelease();
57019
+ logger.error(
57020
+ LogEvent.INBOUND_REPLAY_GATE_DRAIN_FAILED,
57021
+ err2 instanceof Error ? err2 : void 0,
57022
+ {
57023
+ walletAddress: address,
57024
+ attemptCount: String(REPLAY_GATE_DRAIN_MAX_ATTEMPTS),
57025
+ abandonedCount: String(abandoned),
57026
+ stage: "inboundReplayGate/fail-open",
57027
+ reason: "drain_retry_exhausted"
57028
+ }
57029
+ );
57030
+ logWithTimestamp(
57031
+ `[xmtp-sdk:${address}] replay barrier fail-open after ${REPLAY_GATE_DRAIN_MAX_ATTEMPTS} failed attempts, abandoned=${abandoned}`
57032
+ );
57033
+ attempt = 0;
57034
+ releaseHold = false;
57035
+ }
57036
+ }
57037
+ }
57038
+ holdInboundReplayGate(address) {
57039
+ const gate = this.inboundReplayGates.get(address.toLowerCase());
57040
+ if (!gate) {
57041
+ return void 0;
57042
+ }
57043
+ gate.hold();
56537
57044
  logWithTimestamp(
56538
- `[xmtp-sdk:${address}] startup replay barrier opened, buffered=${drained}`
57045
+ `[xmtp-sdk:${address}] replay barrier held, buffered=${gate.bufferedCount}`
56539
57046
  );
56540
- return drained;
57047
+ return gate;
57048
+ }
57049
+ cancelStreamRecoveryForAddress(address) {
57050
+ const addressKey = address.toLowerCase();
57051
+ const cleanup = this.streamRecoveryCleanupByAddress.get(addressKey);
57052
+ this.streamRecoveryCleanupByAddress.delete(addressKey);
57053
+ cleanup?.();
57054
+ }
57055
+ getClientGeneration(client) {
57056
+ const existing = this.clientGenerationByClient.get(client);
57057
+ if (existing !== void 0) {
57058
+ return existing;
57059
+ }
57060
+ const generation = this.nextClientGeneration++;
57061
+ this.clientGenerationByClient.set(client, generation);
57062
+ return generation;
56541
57063
  }
56542
57064
  notifyAgentsWakeupAfterReplay() {
56543
57065
  const notifyAgentsWakeup2 = this.tools.notifyAgentsWakeup;
@@ -56571,10 +57093,18 @@ var init_xmtp_sdk = __esm({
56571
57093
  * cannot overtake the stored backlog.
56572
57094
  */
56573
57095
  async completeStartupReplay() {
57096
+ const startupGates = new Map(
57097
+ [...this.clients.keys()].map((address) => [
57098
+ address,
57099
+ this.inboundReplayGates.get(address.toLowerCase())
57100
+ ])
57101
+ );
56574
57102
  const summary = await this.replayOfflineMessages("startup");
56575
- for (const address of this.clients.keys()) {
56576
- await this.drainInboundReplayGate(address);
57103
+ for (const [address, expectedGate] of startupGates) {
57104
+ await this.drainInboundReplayGate(address, expectedGate);
56577
57105
  }
57106
+ this.resolveStartupReplayCompletion?.();
57107
+ this.resolveStartupReplayCompletion = void 0;
56578
57108
  this.notifyAgentsWakeupAfterReplay();
56579
57109
  return summary;
56580
57110
  }
@@ -56582,6 +57112,8 @@ var init_xmtp_sdk = __esm({
56582
57112
  const valid = addresses.filter((addr) => this.clients.has(addr));
56583
57113
  await parallelMap(valid, async (address) => {
56584
57114
  const agent = this.clients.get(address);
57115
+ const clientGeneration = this.getClientGeneration(agent);
57116
+ const addressKey = address.toLowerCase();
56585
57117
  const tag = `[xmtp-sdk:${address}]`;
56586
57118
  const onchainosAgent = this.getAgentByAddress(address);
56587
57119
  const identity = {
@@ -56684,14 +57216,131 @@ var init_xmtp_sdk = __esm({
56684
57216
  logWithTimestamp(`${tag} reconnecting in ${delay}ms (attempt=${attempt + 1})`);
56685
57217
  setTimeout(() => void startWithRetry(attempt + 1), delay);
56686
57218
  };
57219
+ const streamRecovery = new StreamRecoveryTelemetry();
57220
+ let recoveryTimeout;
57221
+ let pendingRecoveryGate;
57222
+ let recoveryDisposed = false;
57223
+ this.cancelStreamRecoveryForAddress(address);
57224
+ const cleanupStreamRecovery = () => {
57225
+ recoveryDisposed = true;
57226
+ streamRecovery.cancel();
57227
+ if (recoveryTimeout) {
57228
+ clearTimeout(recoveryTimeout);
57229
+ recoveryTimeout = void 0;
57230
+ }
57231
+ };
57232
+ this.streamRecoveryCleanupByAddress.set(
57233
+ addressKey,
57234
+ cleanupStreamRecovery
57235
+ );
56687
57236
  agent.on("unhandledError", (err2) => {
57237
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey)) {
57238
+ return;
57239
+ }
57240
+ if (!isAgentStreamRecoveryError(err2)) {
57241
+ logWithTimestamp(`${tag} agent unhandled error:`, err2);
57242
+ logger.error(LogEvent.AGENT_UNHANDLED_ERROR, err2, agentExtras(identity));
57243
+ return;
57244
+ }
57245
+ const recovery = streamRecovery.markError();
57246
+ const errorTelemetry = extractStreamErrorTelemetry(err2);
57247
+ if (!recovery.alreadyRecovering) {
57248
+ pendingRecoveryGate = this.holdInboundReplayGate(address);
57249
+ }
56688
57250
  logWithTimestamp(`${tag} agent stream error:`, err2);
56689
- logger.error(LogEvent.AGENT_STREAM_ERROR, err2, agentExtras(identity));
57251
+ logger.info(LogEvent.AGENT_STREAM_ERROR, {
57252
+ ...agentExtras(identity),
57253
+ ...errorTelemetry,
57254
+ clientGeneration: String(clientGeneration),
57255
+ recoverySequence: String(recovery.recoverySequence),
57256
+ recoveryAlreadyActive: String(recovery.alreadyRecovering)
57257
+ });
57258
+ if (!recovery.alreadyRecovering) {
57259
+ recoveryTimeout = setTimeout(() => {
57260
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey) || streamRecovery.activeSequence !== recovery.recoverySequence) {
57261
+ return;
57262
+ }
57263
+ logger.error(LogEvent.AGENT_STREAM_RECOVERY_TIMEOUT, void 0, {
57264
+ ...agentExtras(identity),
57265
+ ...errorTelemetry,
57266
+ clientGeneration: String(clientGeneration),
57267
+ recoverySequence: String(recovery.recoverySequence),
57268
+ recoveryDurationMs: String(STREAM_RECOVERY_ALERT_THRESHOLD_MS),
57269
+ thresholdMs: String(STREAM_RECOVERY_ALERT_THRESHOLD_MS)
57270
+ });
57271
+ }, STREAM_RECOVERY_ALERT_THRESHOLD_MS);
57272
+ }
56690
57273
  });
56691
57274
  agent.on("start", () => {
57275
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey)) {
57276
+ return;
57277
+ }
57278
+ const recovery = streamRecovery.markStarted();
57279
+ if (recoveryTimeout) {
57280
+ clearTimeout(recoveryTimeout);
57281
+ recoveryTimeout = void 0;
57282
+ }
56692
57283
  logWithTimestamp(
56693
57284
  `${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))}`
56694
57285
  );
57286
+ if (recovery) {
57287
+ const recoveryGate = pendingRecoveryGate;
57288
+ pendingRecoveryGate = void 0;
57289
+ logger.info(LogEvent.AGENT_STREAM_RECOVERED, {
57290
+ ...agentExtras(identity),
57291
+ clientGeneration: String(clientGeneration),
57292
+ recoverySequence: String(recovery.recoverySequence),
57293
+ recoveryDurationMs: String(recovery.recoveryDurationMs)
57294
+ });
57295
+ void (async () => {
57296
+ try {
57297
+ const summary = await this.replayOfflineMessagesForStreamRecovery(
57298
+ address,
57299
+ String(recovery.recoverySequence),
57300
+ agent
57301
+ );
57302
+ if (!recoveryDisposed && this.clients.get(address) === agent) {
57303
+ logger.info(LogEvent.AGENT_STREAM_RECOVERY_REPLAY_COMPLETED, {
57304
+ ...agentExtras(identity),
57305
+ clientGeneration: String(clientGeneration),
57306
+ recoverySequence: String(recovery.recoverySequence),
57307
+ replayed: String(summary.replayed),
57308
+ skipped: String(summary.skipped),
57309
+ conversationCount: String(summary.conversations),
57310
+ replayDurationMs: String(summary.durationMs)
57311
+ });
57312
+ }
57313
+ } catch (err2) {
57314
+ logWithTimestamp(`${tag} recovery offline replay failed:`, err2);
57315
+ logger.error(
57316
+ LogEvent.OFFLINE_REPLAY_FAILED,
57317
+ err2 instanceof Error ? err2 : new Error(String(err2)),
57318
+ {
57319
+ ...agentExtras(identity),
57320
+ clientGeneration: String(clientGeneration),
57321
+ recoverySequence: String(recovery.recoverySequence),
57322
+ stage: "streamRecovery/replay"
57323
+ }
57324
+ );
57325
+ } finally {
57326
+ try {
57327
+ await this.drainInboundReplayGate(address, recoveryGate);
57328
+ } catch (err2) {
57329
+ logWithTimestamp(`${tag} recovery replay gate drain failed:`, err2);
57330
+ logger.error(
57331
+ LogEvent.OFFLINE_REPLAY_FAILED,
57332
+ err2 instanceof Error ? err2 : new Error(String(err2)),
57333
+ {
57334
+ ...agentExtras(identity),
57335
+ clientGeneration: String(clientGeneration),
57336
+ recoverySequence: String(recovery.recoverySequence),
57337
+ stage: "streamRecovery/drain-gate"
57338
+ }
57339
+ );
57340
+ }
57341
+ }
57342
+ })();
57343
+ }
56695
57344
  if (process.env.XMTP_FORCE_DEBUG === "true") {
56696
57345
  void logDetails(agent).catch((err2) => {
56697
57346
  logWithTimestamp(`${tag} agent debug details failed:`, err2);
@@ -56709,12 +57358,12 @@ var init_xmtp_sdk = __esm({
56709
57358
  logWithTimestamp(`${tag} message listener started`);
56710
57359
  });
56711
57360
  }
56712
- async replayOfflineMessagesForAddress(address, trigger = "periodic_repair") {
57361
+ async replayOfflineMessagesForAddress(address, expectedClient, trigger = "periodic_repair") {
56713
57362
  const addressReplayStartedAt = Date.now();
56714
57363
  const summary = createOfflineReplayAddressSummary(address);
56715
57364
  const isOfflineReplay = isOfflineReplayForTrigger(trigger);
56716
57365
  const agent = this.clients.get(address);
56717
- if (!agent) {
57366
+ if (!agent || agent !== expectedClient) {
56718
57367
  summary.durationMs = Date.now() - addressReplayStartedAt;
56719
57368
  return summary;
56720
57369
  }
@@ -56919,6 +57568,35 @@ var init_xmtp_sdk = __esm({
56919
57568
  );
56920
57569
  return summary;
56921
57570
  }
57571
+ replayOfflineMessagesForAddressSingleFlight(address, trigger = "periodic_repair", recoveryIdentity, expectedClient = this.clients.get(address) ?? null) {
57572
+ const clientGeneration = expectedClient ? this.getClientGeneration(expectedClient) : "missing";
57573
+ const eligibilityClass = replaySingleFlightDiscriminator(
57574
+ trigger,
57575
+ clientGeneration,
57576
+ recoveryIdentity
57577
+ );
57578
+ return this.offlineReplaySingleFlight.run(
57579
+ address,
57580
+ eligibilityClass,
57581
+ async () => {
57582
+ await this._backupReady;
57583
+ return this.replayOfflineMessagesForAddress(
57584
+ address,
57585
+ expectedClient,
57586
+ trigger
57587
+ );
57588
+ }
57589
+ );
57590
+ }
57591
+ async replayOfflineMessagesForStreamRecovery(address, recoveryIdentity, expectedClient) {
57592
+ await this.startupReplayCompletion;
57593
+ return this.replayOfflineMessagesForAddressSingleFlight(
57594
+ address,
57595
+ "stream_recovery",
57596
+ recoveryIdentity,
57597
+ expectedClient
57598
+ );
57599
+ }
56922
57600
  async replayOfflineMessages(trigger = "periodic_repair") {
56923
57601
  await this._backupReady;
56924
57602
  const replayStartedAt = Date.now();
@@ -56926,7 +57604,10 @@ var init_xmtp_sdk = __esm({
56926
57604
  let totalSkipped = 0;
56927
57605
  const addresses = [];
56928
57606
  for (const address of this.clients.keys()) {
56929
- const summary2 = await this.replayOfflineMessagesForAddress(address, trigger);
57607
+ const summary2 = await this.replayOfflineMessagesForAddressSingleFlight(
57608
+ address,
57609
+ trigger
57610
+ );
56930
57611
  addresses.push(summary2);
56931
57612
  const { replayed, skipped: skipped2 } = summary2;
56932
57613
  totalReplayed += replayed;
@@ -65447,12 +66128,12 @@ async function runListenerWithLock(options, paths) {
65447
66128
  });
65448
66129
  }
65449
66130
  });
65450
- service.setPluginVersion("0.2.1-beta-8ae7a0d300-260807161635");
66131
+ service.setPluginVersion("0.2.1");
65451
66132
  await service.init();
65452
66133
  const pluginVersionStatus = service.pluginVersionStatus;
65453
66134
  if (pluginVersionStatus.unavailable) {
65454
66135
  throw new Error(
65455
- `@okxweb3/a2a-node v${"0.2.1-beta-8ae7a0d300-260807161635"} is below the required minimum v${pluginVersionStatus.minVersion}`
66136
+ `@okxweb3/a2a-node v${"0.2.1"} is below the required minimum v${pluginVersionStatus.minVersion}`
65456
66137
  );
65457
66138
  }
65458
66139
  const systemConfig = service.getSystemConfig();
@@ -65470,7 +66151,7 @@ async function runListenerWithLock(options, paths) {
65470
66151
  onchainosAgentId: "*",
65471
66152
  reason: "system-config missing sentryDsn",
65472
66153
  pluginId: "@okxweb3/a2a-node",
65473
- pluginVersion: "0.2.1-beta-8ae7a0d300-260807161635"
66154
+ pluginVersion: "0.2.1"
65474
66155
  });
65475
66156
  }
65476
66157
  logWithTimestamp(
@@ -66138,6 +66819,7 @@ function summarizeXmtpTestEvents(runId, events) {
66138
66819
  const nodeDurations = /* @__PURE__ */ new Map();
66139
66820
  const replayLags = /* @__PURE__ */ new Map();
66140
66821
  const replayHandles = /* @__PURE__ */ new Map();
66822
+ const streamRecoveries = /* @__PURE__ */ new Map();
66141
66823
  const agentMap = /* @__PURE__ */ new Map();
66142
66824
  for (const event of events) {
66143
66825
  const key = deliveryKey(event);
@@ -66170,6 +66852,18 @@ function summarizeXmtpTestEvents(runId, events) {
66170
66852
  event.handlerTotalMs
66171
66853
  );
66172
66854
  }
66855
+ if (event.eventName === "Agent stream recovered") {
66856
+ setDuration(
66857
+ streamRecoveries,
66858
+ [
66859
+ eventAgentId(event),
66860
+ stringValue(event.clientGeneration) || "unknown-client",
66861
+ stringValue(event.recoverySequence) || "unknown-recovery",
66862
+ event.timestamp
66863
+ ].join(":"),
66864
+ event.recoveryDurationMs
66865
+ );
66866
+ }
66173
66867
  }
66174
66868
  const agents = [...agentMap.entries()].filter(([, value]) => value.live.size > 0 || value.replay.size > 0).map(([agentId, value]) => {
66175
66869
  const total = (/* @__PURE__ */ new Set([...value.live, ...value.replay])).size;
@@ -66191,7 +66885,9 @@ function summarizeXmtpTestEvents(runId, events) {
66191
66885
  duplicateEvents: countEvent(events, "Inbound ignored: duplicate message"),
66192
66886
  replayMessageFailures: countEvent(events, "Offline replay message failed"),
66193
66887
  replayScanFailures: countEvent(events, "Offline replay failed"),
66194
- streamFailures: countEvent(events, "conversations.stream failed") + countEvent(events, "Agent stream error"),
66888
+ streamFailures: countEvent(events, "conversations.stream failed") + countEvent(events, "agent stream error"),
66889
+ streamRecoveries: countEvent(events, "Agent stream recovered"),
66890
+ streamRecoveryTimeouts: countEvent(events, "Agent stream recovery timeout"),
66195
66891
  connectionFailures: countEvent(events, "XMTP connection failed"),
66196
66892
  successfulReplayScans: countEvent(events, "Address sync completed"),
66197
66893
  replayScansWithMessages: events.filter((event) => event.eventName === "Address sync completed" && numericValue(event.replayed) > 0).length,
@@ -66199,6 +66895,7 @@ function summarizeXmtpTestEvents(runId, events) {
66199
66895
  nodeHandlerMs: summarizeDurations([...nodeDurations.values()]),
66200
66896
  replayLagMs: summarizeDurations([...replayLags.values()]),
66201
66897
  replayHandleMs: summarizeDurations([...replayHandles.values()]),
66898
+ streamRecoveryMs: summarizeDurations([...streamRecoveries.values()]),
66202
66899
  agents
66203
66900
  };
66204
66901
  }
@@ -66213,6 +66910,8 @@ Reliability
66213
66910
  Replay message failures ${summary.replayMessageFailures}
66214
66911
  Replay scan failures ${summary.replayScanFailures}
66215
66912
  Stream failures ${summary.streamFailures}
66913
+ Stream recoveries ${summary.streamRecoveries}
66914
+ Recovery timeouts (>30s) ${summary.streamRecoveryTimeouts}
66216
66915
  XMTP connection failures ${summary.connectionFailures}
66217
66916
  Replay scans ${summary.successfulReplayScans}
66218
66917
  Scans with replay ${summary.replayScansWithMessages}
@@ -66221,7 +66920,8 @@ Timing
66221
66920
  XMTP delivery p50/p95/max ${formatDurationSummary(summary.xmtpDeliveryMs)}
66222
66921
  Node handler p50/p95/max ${formatDurationSummary(summary.nodeHandlerMs)}
66223
66922
  Replay lag p50/p95/max ${formatDurationSummary(summary.replayLagMs)}
66224
- Replay handler p50/p95/max ${formatDurationSummary(summary.replayHandleMs)}`);
66923
+ Replay handler p50/p95/max ${formatDurationSummary(summary.replayHandleMs)}
66924
+ Stream recovery p50/p95/max ${formatDurationSummary(summary.streamRecoveryMs)}`);
66225
66925
  if (summary.agents.length > 0) {
66226
66926
  console.log("\nAgents");
66227
66927
  for (const agent of summary.agents) {
@@ -113550,7 +114250,7 @@ async function getCurrentNodeCliVersion() {
113550
114250
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
113551
114251
  }
113552
114252
  function getBundledNodeCliVersion() {
113553
- return true ? "0.2.1-beta-8ae7a0d300-260807161635" : null;
114253
+ return true ? "0.2.1" : null;
113554
114254
  }
113555
114255
  function readConfiguredAiProvider() {
113556
114256
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -113760,7 +114460,7 @@ async function updateHermes(release, options) {
113760
114460
  }
113761
114461
  }
113762
114462
  async function installGatewayPluginForDoctor(target) {
113763
- const release = isPrereleaseVersion("0.2.1-beta-8ae7a0d300-260807161635") ? "beta" : "latest";
114463
+ const release = isPrereleaseVersion("0.2.1") ? "beta" : "latest";
113764
114464
  const insideTargetGateway = detectGatewayInvocation() === target;
113765
114465
  const options = {
113766
114466
  restart: !insideTargetGateway,
@@ -114748,7 +115448,7 @@ async function runDoctor(options = {}) {
114748
115448
  platform: options.platform ?? process.platform,
114749
115449
  env: options.env ?? process.env,
114750
115450
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
114751
- cliVersion: options.cliVersion ?? (true ? "0.2.1-beta-8ae7a0d300-260807161635" : "0.0.0"),
115451
+ cliVersion: options.cliVersion ?? (true ? "0.2.1" : "0.0.0"),
114752
115452
  fixMode: options.fix === true,
114753
115453
  nonInteractive: options.nonInteractive === true,
114754
115454
  packageChanged: false,
@@ -115716,7 +116416,7 @@ init_sentry_logger();
115716
116416
  init_sentry_config();
115717
116417
  var CURRENT_GATEWAY_SESSION_KEYS_ENV4 = "OKX_A2A_CURRENT_GATEWAY_SESSION_KEYS";
115718
116418
  function printUsage3() {
115719
- console.log(`okx-a2a ${"0.2.1-beta-8ae7a0d300-260807161635"}
116419
+ console.log(`okx-a2a ${"0.2.1"}
115720
116420
 
115721
116421
  Usage:
115722
116422
  okx-a2a <command> [options]
@@ -115756,7 +116456,7 @@ Run \`okx-a2a <command> -h\` for command-specific help.
115756
116456
  `);
115757
116457
  }
115758
116458
  function printVersion() {
115759
- console.log("0.2.1-beta-8ae7a0d300-260807161635");
116459
+ console.log("0.2.1");
115760
116460
  }
115761
116461
  function printDaemonUsage() {
115762
116462
  console.log(`Usage: okx-a2a daemon <start|restart|stop|status|autostart> [options]
@@ -117167,7 +117867,7 @@ async function main() {
117167
117867
  if (command === "xmtp-test") {
117168
117868
  const { handleXmtpTestCommand: handleXmtpTestCommand2 } = await Promise.resolve().then(() => (init_xmtp_test_cli(), xmtp_test_cli_exports));
117169
117869
  await handleXmtpTestCommand2(process.argv.slice(3), {
117170
- packageVersion: "0.2.1-beta-8ae7a0d300-260807161635",
117870
+ packageVersion: "0.2.1",
117171
117871
  agentSdkVersion: "2.3.0",
117172
117872
  nodeSdkVersion: "6.1.0",
117173
117873
  nodeBindingsVersion: "1.11.0"