@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/index.js CHANGED
@@ -19597,12 +19597,15 @@ var init_events = __esm({
19597
19597
  INBOUND_DELIVERED: "Inbound delivered to session",
19598
19598
  DM_DELIVERED: "DM delivered to session",
19599
19599
  OUTBOUND_ELIGIBILITY_CHECKED: "Outbound eligibility checked",
19600
+ MESSAGE_ELIGIBILITY_CHECKED: "Message eligibility checked",
19600
19601
  AGENT_CLIENT_CREATED: "Agent client created",
19601
19602
  AGENT_INSTALLATION_SUMMARY: "Agent installation summary",
19602
19603
  XMTP_HISTORY_SYNC_REQUESTED: "XMTP history sync requested",
19603
19604
  XMTP_DEBUG_SYNC_REQUESTED: "XMTP debug sync requested",
19604
19605
  XMTP_DEBUG_SYNC_COMPLETED: "XMTP debug sync completed",
19605
19606
  AGENT_CLIENT_REMOVED: "Agent client removed",
19607
+ AGENT_STREAM_RECOVERED: "Agent stream recovered",
19608
+ AGENT_STREAM_RECOVERY_REPLAY_COMPLETED: "Agent stream recovery replay completed",
19606
19609
  ADDRESS_SYNC_COMPLETED: "Address sync completed",
19607
19610
  AGENT_REFRESH_SESSION_EXPIRED: "Agent refresh aborted: onchainos session expired, local clients taken offline",
19608
19611
  OFFLINE_REPLAY_MESSAGE_REPLAYED: "Offline replay message replayed",
@@ -19637,6 +19640,8 @@ var init_events = __esm({
19637
19640
  CONVERSATIONS_STREAM_FAILED: "conversations.stream failed",
19638
19641
  AGENT_START_FAILED: "agent.start failed",
19639
19642
  AGENT_STREAM_ERROR: "agent stream error",
19643
+ AGENT_UNHANDLED_ERROR: "Agent unhandled error",
19644
+ AGENT_STREAM_RECOVERY_TIMEOUT: "Agent stream recovery timeout",
19640
19645
  BOOTSTRAP_FAILED: "Bootstrap failed",
19641
19646
  ONCHAINOS_SESSION_EXPIRED: "Onchainos session expired",
19642
19647
  ONCHAINOS_CLI_ERROR: "Onchainos CLI error",
@@ -19646,6 +19651,7 @@ var init_events = __esm({
19646
19651
  MESSAGE_PARSE_FAILED: "Message parse failed",
19647
19652
  MESSAGE_HANDLER_ERROR: "Message handler error",
19648
19653
  INBOUND_DISPATCH_FAILED: "Inbound dispatch failed",
19654
+ INBOUND_REPLAY_GATE_DRAIN_FAILED: "Inbound replay gate drain failed",
19649
19655
  // Default name stamped on events that reach Sentry without going through this
19650
19656
  // logger — uncaught exceptions and unhandled rejections. Without it those
19651
19657
  // events carry no eventName and cannot be classified.
@@ -19943,7 +19949,10 @@ var init_xmtp_test_metrics = __esm({
19943
19949
  "Address sync completed",
19944
19950
  "Agent client created",
19945
19951
  "Agent client removed",
19946
- "Agent stream error",
19952
+ "agent stream error",
19953
+ "Agent stream recovered",
19954
+ "Agent stream recovery replay completed",
19955
+ "Agent stream recovery timeout",
19947
19956
  "conversations.stream failed",
19948
19957
  "Inbound delivered to session",
19949
19958
  "Inbound dispatch failed",
@@ -19962,6 +19971,190 @@ var init_xmtp_test_metrics = __esm({
19962
19971
  }
19963
19972
  });
19964
19973
 
19974
+ // ../core/src/sentry-logger/error-diagnostics.ts
19975
+ function asErrorLike(value) {
19976
+ return value instanceof Error ? value : void 0;
19977
+ }
19978
+ function diagnosticToken(value) {
19979
+ if (typeof value !== "string" && typeof value !== "number") {
19980
+ return void 0;
19981
+ }
19982
+ const token = String(value).trim();
19983
+ if (!token || /^(?:null|undefined|unknown)$/i.test(token)) {
19984
+ return void 0;
19985
+ }
19986
+ if (!/^[A-Za-z0-9_.:-]+$/.test(token)) {
19987
+ return void 0;
19988
+ }
19989
+ return token.slice(0, MAX_DIAGNOSTIC_TOKEN_LENGTH);
19990
+ }
19991
+ function syscallToken(value) {
19992
+ if (typeof value !== "string") {
19993
+ return void 0;
19994
+ }
19995
+ const operation = diagnosticToken(value.trim().split(/\s+/, 1)[0])?.toLowerCase();
19996
+ return operation && KNOWN_SYSCALLS.has(operation) ? operation : void 0;
19997
+ }
19998
+ function knownErrorCode(value) {
19999
+ const code = diagnosticToken(value)?.toUpperCase();
20000
+ return code && KNOWN_ERROR_CODES.has(code) ? code : void 0;
20001
+ }
20002
+ function readCode(error) {
20003
+ return knownErrorCode(error?.code) ?? knownErrorCode(error?.errno);
20004
+ }
20005
+ function classifyError(error, code) {
20006
+ const normalizedCode = code?.toUpperCase();
20007
+ const name = error?.name.toLowerCase() ?? "";
20008
+ const message = error?.message.toLowerCase() ?? "";
20009
+ if (normalizedCode && RESOURCE_EXHAUSTED_CODES.has(normalizedCode)) {
20010
+ return "resource_exhausted";
20011
+ }
20012
+ if (normalizedCode && CONNECTION_INTERRUPTED_CODES.has(normalizedCode)) {
20013
+ return "connection_interrupted";
20014
+ }
20015
+ if (normalizedCode && NETWORK_UNAVAILABLE_CODES.has(normalizedCode)) {
20016
+ return "network_unavailable";
20017
+ }
20018
+ if (normalizedCode && TIMEOUT_CODES.has(normalizedCode)) {
20019
+ return "timeout";
20020
+ }
20021
+ if (normalizedCode && PERMISSION_CODES.has(normalizedCode)) {
20022
+ return "permission_denied";
20023
+ }
20024
+ if (normalizedCode === "ENOENT") {
20025
+ return "missing_dependency";
20026
+ }
20027
+ if (normalizedCode === "ABORT_ERR" || name === "aborterror") {
20028
+ return "cancelled";
20029
+ }
20030
+ if (name === "syntaxerror" || /(?:json|response).*(?:parse|invalid)/.test(message)) {
20031
+ return "invalid_response";
20032
+ }
20033
+ if (/session\s+(?:is\s+)?expired|login\s+expired/.test(message)) {
20034
+ return "session_expired";
20035
+ }
20036
+ if (/timed?\s*out|timeout/.test(message)) {
20037
+ return "timeout";
20038
+ }
20039
+ return "unknown";
20040
+ }
20041
+ function retryabilityFor(category) {
20042
+ switch (category) {
20043
+ case "connection_interrupted":
20044
+ case "network_unavailable":
20045
+ case "resource_exhausted":
20046
+ case "timeout":
20047
+ return "retryable";
20048
+ case "invalid_response":
20049
+ case "missing_dependency":
20050
+ case "permission_denied":
20051
+ case "session_expired":
20052
+ return "non_retryable";
20053
+ default:
20054
+ return "unknown";
20055
+ }
20056
+ }
20057
+ function causeChain(error) {
20058
+ const chain = [];
20059
+ const seen = /* @__PURE__ */ new Set();
20060
+ let current = error;
20061
+ while (current && chain.length < MAX_CAUSE_DEPTH && !seen.has(current)) {
20062
+ chain.push(current);
20063
+ seen.add(current);
20064
+ current = asErrorLike(current.cause);
20065
+ }
20066
+ return chain;
20067
+ }
20068
+ function buildErrorDiagnostics(eventName, error, existingExtra) {
20069
+ const errorLike = asErrorLike(error);
20070
+ const legacy = {};
20071
+ if (errorLike) {
20072
+ legacy.errorName = errorLike.name;
20073
+ legacy.errorMessageLength = String(errorLike.message.length);
20074
+ }
20075
+ if (!errorLike || eventName === LogEvent.AGENT_STREAM_ERROR) {
20076
+ return legacy;
20077
+ }
20078
+ const chain = causeChain(errorLike);
20079
+ const primary = chain[0];
20080
+ const cause = chain.length > 1 ? chain[chain.length - 1] : void 0;
20081
+ const fallbackExitCode = diagnosticToken(existingExtra.exitCode);
20082
+ const primaryCode = readCode(primary);
20083
+ const primaryCategory = classifyError(primary, primaryCode);
20084
+ const primarySyscall = syscallToken(primary.syscall);
20085
+ const causeCode = readCode(cause);
20086
+ const causeCategory = cause ? classifyError(cause, causeCode) : void 0;
20087
+ const causeSyscall = syscallToken(cause?.syscall);
20088
+ const classifiedCategory = primaryCategory === "unknown" && causeCategory ? causeCategory : primaryCategory;
20089
+ const effectiveCategory = classifiedCategory === "unknown" && fallbackExitCode && fallbackExitCode !== "0" ? "process_exit" : classifiedCategory;
20090
+ const effectiveCode = primaryCode ?? causeCode;
20091
+ const effectiveSyscall = primarySyscall ?? causeSyscall;
20092
+ return {
20093
+ ...legacy,
20094
+ errorCategory: effectiveCategory,
20095
+ errorRetryability: retryabilityFor(effectiveCategory),
20096
+ errorCauseDepth: String(Math.max(0, chain.length - 1)),
20097
+ ...effectiveCode ? { errorCode: effectiveCode } : {},
20098
+ ...effectiveSyscall ? { errorSyscall: effectiveSyscall } : {},
20099
+ ...cause ? {
20100
+ causeErrorMessageLength: String(cause.message.length),
20101
+ causeErrorCategory: causeCategory ?? "unknown",
20102
+ ...causeCode ? { causeErrorCode: causeCode } : {},
20103
+ ...causeSyscall ? { causeErrorSyscall: causeSyscall } : {}
20104
+ } : {}
20105
+ };
20106
+ }
20107
+ 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;
20108
+ var init_error_diagnostics = __esm({
20109
+ "../core/src/sentry-logger/error-diagnostics.ts"() {
20110
+ "use strict";
20111
+ init_events();
20112
+ MAX_DIAGNOSTIC_TOKEN_LENGTH = 64;
20113
+ MAX_CAUSE_DEPTH = 4;
20114
+ RESOURCE_EXHAUSTED_CODES = /* @__PURE__ */ new Set(["EAGAIN", "EMFILE", "ENFILE", "ENOMEM"]);
20115
+ CONNECTION_INTERRUPTED_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "EPIPE"]);
20116
+ NETWORK_UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
20117
+ "EAI_AGAIN",
20118
+ "ECONNREFUSED",
20119
+ "EHOSTUNREACH",
20120
+ "ENETDOWN",
20121
+ "ENETUNREACH",
20122
+ "ENOTFOUND"
20123
+ ]);
20124
+ TIMEOUT_CODES = /* @__PURE__ */ new Set(["ETIMEDOUT", "ESOCKETTIMEDOUT"]);
20125
+ PERMISSION_CODES = /* @__PURE__ */ new Set(["EACCES", "EPERM"]);
20126
+ KNOWN_ERROR_CODES = /* @__PURE__ */ new Set([
20127
+ ...RESOURCE_EXHAUSTED_CODES,
20128
+ ...CONNECTION_INTERRUPTED_CODES,
20129
+ ...NETWORK_UNAVAILABLE_CODES,
20130
+ ...TIMEOUT_CODES,
20131
+ ...PERMISSION_CODES,
20132
+ "ABORT_ERR",
20133
+ "ENOENT"
20134
+ ]);
20135
+ KNOWN_SYSCALLS = /* @__PURE__ */ new Set([
20136
+ "access",
20137
+ "bind",
20138
+ "close",
20139
+ "connect",
20140
+ "getaddrinfo",
20141
+ "kill",
20142
+ "listen",
20143
+ "mkdir",
20144
+ "open",
20145
+ "read",
20146
+ "readdir",
20147
+ "readlink",
20148
+ "rename",
20149
+ "rmdir",
20150
+ "spawn",
20151
+ "stat",
20152
+ "unlink",
20153
+ "write"
20154
+ ]);
20155
+ }
20156
+ });
20157
+
19965
20158
  // ../core/src/sentry-logger/index.ts
19966
20159
  function applyFatalEventDefaults(event) {
19967
20160
  try {
@@ -20007,6 +20200,7 @@ var init_sentry_logger = __esm({
20007
20200
  init_events();
20008
20201
  init_log_fields();
20009
20202
  init_xmtp_test_metrics();
20203
+ init_error_diagnostics();
20010
20204
  init_events();
20011
20205
  init_log_fields();
20012
20206
  FLOW_ID = (0, import_node_crypto3.randomUUID)();
@@ -20094,14 +20288,23 @@ var init_sentry_logger = __esm({
20094
20288
  "agentId",
20095
20289
  "agentPlatform",
20096
20290
  "cacheName",
20291
+ "causeCode",
20292
+ "causeType",
20293
+ "causeVariant",
20097
20294
  "checkpoint",
20098
20295
  "code",
20099
20296
  "communicationClass",
20100
20297
  "component",
20101
20298
  "eventFamily",
20102
20299
  "eventName",
20300
+ "endpoint",
20301
+ "errorCategory",
20302
+ "errorCode",
20303
+ "errorRetryability",
20304
+ "errorSyscall",
20103
20305
  "exitCode",
20104
20306
  "fromAgentId",
20307
+ "grpcMethod",
20105
20308
  "jobId",
20106
20309
  "kind",
20107
20310
  "method",
@@ -20125,17 +20328,24 @@ var init_sentry_logger = __esm({
20125
20328
  "source",
20126
20329
  "stage",
20127
20330
  "status",
20331
+ "streamErrorCode",
20332
+ "streamType",
20128
20333
  "subcommand",
20129
20334
  "systemEvent",
20130
20335
  "taskId",
20131
20336
  "taskMode",
20132
20337
  "toAgentId",
20133
20338
  "transport",
20339
+ "transportFailure",
20340
+ "transportProtocol",
20134
20341
  "type",
20135
20342
  "workloadClass"
20136
20343
  ]);
20137
20344
  SENTRY_FINGERPRINT_KEYS = [
20138
20345
  "eventName",
20346
+ "errorCategory",
20347
+ "errorCode",
20348
+ "errorSyscall",
20139
20349
  "cacheName",
20140
20350
  "component",
20141
20351
  "method",
@@ -20183,6 +20393,7 @@ var init_sentry_logger = __esm({
20183
20393
  LogEvent.HERMES_SESSION_ROUTE_BINDING,
20184
20394
  LogEvent.INBOUND_BLOCKED_ADDRESS_MISMATCH,
20185
20395
  LogEvent.INBOUND_BLOCKED_INELIGIBLE,
20396
+ LogEvent.MESSAGE_ELIGIBILITY_CHECKED,
20186
20397
  LogEvent.INBOUND_BLOCKED_SENSITIVE,
20187
20398
  LogEvent.INBOUND_DROP_DM_INVALID_PAYLOAD,
20188
20399
  LogEvent.INBOUND_DROP_DM_NON_SYSTEM,
@@ -20198,6 +20409,9 @@ var init_sentry_logger = __esm({
20198
20409
  LogEvent.MESSAGE_SENT,
20199
20410
  LogEvent.AGENT_CLIENT_CREATED,
20200
20411
  LogEvent.AGENT_CLIENT_REMOVED,
20412
+ LogEvent.AGENT_STREAM_ERROR,
20413
+ LogEvent.AGENT_STREAM_RECOVERED,
20414
+ LogEvent.AGENT_STREAM_RECOVERY_REPLAY_COMPLETED,
20201
20415
  LogEvent.ADDRESS_SYNC_COMPLETED,
20202
20416
  LogEvent.AGENTS_REFRESHED,
20203
20417
  LogEvent.AGENTS_WAKEUP_NOTIFIED,
@@ -20329,19 +20543,24 @@ var init_sentry_logger = __esm({
20329
20543
  }
20330
20544
  captureError(message, error, extraWithFlow) {
20331
20545
  try {
20546
+ const errorDiagnostics = buildErrorDiagnostics(
20547
+ message,
20548
+ error,
20549
+ extraWithFlow
20550
+ );
20551
+ const diagnosticExtra = {
20552
+ ...extraWithFlow,
20553
+ ...errorDiagnostics
20554
+ };
20332
20555
  Sentry.withScope((scope) => {
20333
20556
  scope.setLevel("error");
20334
- _SentryLogger.applyDiagnostics(scope, message, extraWithFlow);
20557
+ _SentryLogger.applyDiagnostics(scope, message, diagnosticExtra);
20335
20558
  Sentry.captureException(_SentryLogger.createSentryEvent(message), {
20336
20559
  // eventName is unconditional: callers that pass no Error object are
20337
20560
  // still classified events, and without it they reach SLS anonymous.
20338
20561
  extra: {
20339
- ...extraWithFlow,
20340
- eventName: message,
20341
- ...error ? {
20342
- errorName: error.name,
20343
- errorMessageLength: String(error.message.length)
20344
- } : {}
20562
+ ...diagnosticExtra,
20563
+ eventName: message
20345
20564
  }
20346
20565
  });
20347
20566
  });
@@ -30930,7 +31149,7 @@ var init_sentry_config = __esm({
30930
31149
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
30931
31150
  SENTRY_CONFIG = {
30932
31151
  projectName: "okx/openclaw-okx-a2a-extension",
30933
- release: "0.2.0",
31152
+ release: "0.2.1-beta-8ae7a0d300-260807181806",
30934
31153
  environment,
30935
31154
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
30936
31155
  };
@@ -31860,7 +32079,7 @@ async function getCurrentNodeCliVersion() {
31860
32079
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
31861
32080
  }
31862
32081
  function getBundledNodeCliVersion() {
31863
- return true ? "0.2.0" : null;
32082
+ return true ? "0.2.1-beta-8ae7a0d300-260807181806" : null;
31864
32083
  }
31865
32084
  function readConfiguredAiProvider() {
31866
32085
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -32070,7 +32289,7 @@ async function updateHermes(release, options) {
32070
32289
  }
32071
32290
  }
32072
32291
  async function installGatewayPluginForDoctor(target) {
32073
- const release = isPrereleaseVersion("0.2.0") ? "beta" : "latest";
32292
+ const release = isPrereleaseVersion("0.2.1-beta-8ae7a0d300-260807181806") ? "beta" : "latest";
32074
32293
  const insideTargetGateway = detectGatewayInvocation() === target;
32075
32294
  const options = {
32076
32295
  restart: !insideTargetGateway,
@@ -42740,6 +42959,8 @@ __export(index_exports, {
42740
42959
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS: () => HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS,
42741
42960
  InboundReplayGate: () => InboundReplayGate,
42742
42961
  InvalidXmtpMessageStore: () => InvalidXmtpMessageStore,
42962
+ MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG: () => MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG,
42963
+ MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN: () => MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN,
42743
42964
  NATIVE_LAUNCHER_EXE_NAME: () => NATIVE_LAUNCHER_EXE_NAME,
42744
42965
  OPENCLAW_GATEWAY_ROUTE_GROUP_ID: () => OPENCLAW_GATEWAY_ROUTE_GROUP_ID,
42745
42966
  SYSTEM_NOTIFICATION_SESSION_KEY: () => SYSTEM_NOTIFICATION_SESSION_KEY,
@@ -42757,6 +42978,7 @@ __export(index_exports, {
42757
42978
  XMTP_CLIENT_RECYCLE_INTERVAL_ENV: () => XMTP_CLIENT_RECYCLE_INTERVAL_ENV,
42758
42979
  XmtpDm: () => Dm,
42759
42980
  XmtpGroup: () => Group,
42981
+ XmtpService: () => XmtpService,
42760
42982
  acquireDaemonLock: () => acquireDaemonLock,
42761
42983
  activeDaemonPointerPath: () => activeDaemonPointerPath,
42762
42984
  aiRunSentryExtra: () => aiRunSentryExtra,
@@ -42777,6 +42999,7 @@ __export(index_exports, {
42777
42999
  buildInboundTerminalExtras: () => buildInboundTerminalExtras,
42778
43000
  buildLaunchdPlist: () => buildLaunchdPlist,
42779
43001
  buildLauncherEntryJs: () => buildLauncherEntryJs,
43002
+ buildMessageEligibleArgs: () => buildMessageEligibleArgs,
42780
43003
  buildPendingConversationBackupDispatch: () => buildPendingConversationBackupDispatch,
42781
43004
  buildSchtasksCreateArgs: () => buildSchtasksCreateArgs,
42782
43005
  buildSchtasksDeleteArgs: () => buildSchtasksDeleteArgs,
@@ -42808,6 +43031,7 @@ __export(index_exports, {
42808
43031
  detectAiProviders: () => detectAiProviders,
42809
43032
  detectCurrentAiProvider: () => detectCurrentAiProvider,
42810
43033
  detectGatewayInvocation: () => detectGatewayInvocation,
43034
+ detectMessageEligibleOfflineReplaySupport: () => detectMessageEligibleOfflineReplaySupport,
42811
43035
  detectRuntime: () => detectRuntime,
42812
43036
  ensureDaemonReady: () => ensureDaemonReady,
42813
43037
  ensureDefaultAiProvider: () => ensureDefaultAiProvider,
@@ -42821,9 +43045,11 @@ __export(index_exports, {
42821
43045
  findWindowsNativeOkxA2aExe: () => findWindowsNativeOkxA2aExe,
42822
43046
  followFile: () => followFile,
42823
43047
  formatDoctorReportForHumans: () => formatDoctorReportForHumans,
43048
+ getA2ACapabilities: () => getA2ACapabilities,
42824
43049
  getDaemonStatus: () => getDaemonStatus,
42825
43050
  getHermesGatewayPluginStatus: () => getHermesGatewayPluginStatus,
42826
43051
  getSessionBusyTracker: () => getSessionBusyTracker,
43052
+ handleCapabilitiesCommand: () => handleCapabilitiesCommand,
42827
43053
  handleDoctorCommand: () => handleDoctorCommand,
42828
43054
  handleXmtpDebugCommand: () => handleXmtpDebugCommand,
42829
43055
  handleXmtpSendCommand: () => handleXmtpSendCommand,
@@ -42837,6 +43063,7 @@ __export(index_exports, {
42837
43063
  isDefaultTaskHome: () => isDefaultTaskHome,
42838
43064
  isGatewayAvailableForHeartbeat: () => isGatewayAvailableForHeartbeat,
42839
43065
  isHermesGatewayPluginEnabled: () => isHermesGatewayPluginEnabled,
43066
+ isOfflineReplayForTrigger: () => isOfflineReplayForTrigger,
42840
43067
  isOpenClawA2aPluginAvailable: () => isOpenClawA2aPluginAvailable,
42841
43068
  isOpenClawGatewayAvailable: () => isOpenClawGatewayAvailable,
42842
43069
  isOriginalParentProcessAlive: () => isOriginalParentProcessAlive,
@@ -42848,6 +43075,8 @@ __export(index_exports, {
42848
43075
  launchdPlistHasCurrentRestartPolicy: () => launchdPlistHasCurrentRestartPolicy,
42849
43076
  logWinCompat: () => logWinCompat,
42850
43077
  logXmtpMessageSent: () => logXmtpMessageSent,
43078
+ maybeRecommendOnchainosUpgrade: () => maybeRecommendOnchainosUpgrade,
43079
+ messageEligibleHelpAdvertisesOfflineReplay: () => messageEligibleHelpAdvertisesOfflineReplay,
42851
43080
  nodeVersionSatisfies: () => nodeVersionSatisfies,
42852
43081
  normalizeAiProvider: () => normalizeAiProvider,
42853
43082
  normalizeHermesOkxA2aPluginConfig: () => normalizeHermesOkxA2aPluginConfig,
@@ -42873,6 +43102,7 @@ __export(index_exports, {
42873
43102
  removeUserAttentionWatcher: () => removeUserAttentionWatcher,
42874
43103
  reportDoctorRunToSentry: () => reportDoctorRunToSentry,
42875
43104
  reportUserNotificationFailure: () => reportUserNotificationFailure,
43105
+ resetMessageEligibleOfflineReplayCapabilityForTests: () => resetMessageEligibleOfflineReplayCapabilityForTests,
42876
43106
  resetResolvedOnchainosBinForTests: () => resetResolvedOnchainosBinForTests,
42877
43107
  resolveAiPermissionPreset: () => resolveAiPermissionPreset,
42878
43108
  resolveAiProviderCommand: () => resolveAiProviderCommand,
@@ -42884,6 +43114,7 @@ __export(index_exports, {
42884
43114
  resolveDoctorTarget: () => resolveDoctorTarget,
42885
43115
  resolveEnvelopePayload: () => resolveEnvelopePayload,
42886
43116
  resolveNpmCmdShimScript: () => resolveNpmCmdShimScript,
43117
+ resolveOfflineReplayCapabilityMarker: () => resolveOfflineReplayCapabilityMarker,
42887
43118
  resolveOfflineReplayIntervalSec: () => resolveOfflineReplayIntervalSec,
42888
43119
  resolveOpenClawGatewayConfig: () => resolveOpenClawGatewayConfig,
42889
43120
  resolveOpenClawGatewayRoute: () => resolveOpenClawGatewayRoute,
@@ -53982,6 +54213,165 @@ var cache = new LimitedMap(1e3);
53982
54213
  // ../core/src/xmtp-sdk/index.ts
53983
54214
  init_sentry_logger();
53984
54215
 
54216
+ // ../core/src/xmtp-sdk/stream-recovery-telemetry.ts
54217
+ function isAgentStreamRecoveryError(error) {
54218
+ return error instanceof Error && error.constructor.name === "AgentStreamingError" && typeof error.code === "number" && [1002, 1004, 1005].includes(error.code);
54219
+ }
54220
+ var TRANSPORT_FAILURE_PATTERNS = [
54221
+ [/broken\s*pipe|BrokenPipe/i, "broken_pipe"],
54222
+ [/connection\s*reset|ConnectionReset/i, "connection_reset"],
54223
+ [/timed?\s*out|TimedOut/i, "timeout"],
54224
+ [/unexpected\s*eof|UnexpectedEof/i, "unexpected_eof"]
54225
+ ];
54226
+ var KNOWN_CAUSE_CODES = /* @__PURE__ */ new Set(["GenericFailure"]);
54227
+ var KNOWN_CAUSE_KINDS = /* @__PURE__ */ new Map([
54228
+ ["SubscribeError::BoxError", ["SubscribeError", "BoxError"]]
54229
+ ]);
54230
+ var KNOWN_GRPC_METHODS = /* @__PURE__ */ new Set([
54231
+ "SubscribeGroupMessages",
54232
+ "SubscribeWelcomeMessages"
54233
+ ]);
54234
+ function extractStreamErrorTelemetry(error) {
54235
+ const telemetry = {};
54236
+ if (typeof error.code === "number" && Number.isSafeInteger(error.code)) {
54237
+ telemetry.streamErrorCode = String(error.code);
54238
+ const streamType = {
54239
+ 1002: "conversation",
54240
+ 1004: "message",
54241
+ 1005: "setup"
54242
+ };
54243
+ if (streamType[error.code]) {
54244
+ telemetry.streamType = streamType[error.code];
54245
+ }
54246
+ }
54247
+ const cause = error.cause;
54248
+ if (!(cause instanceof Error)) {
54249
+ return telemetry;
54250
+ }
54251
+ telemetry.causeMessageLength = String(cause.message.length);
54252
+ const causeCode = cause.code;
54253
+ if (typeof causeCode === "string" && KNOWN_CAUSE_CODES.has(causeCode)) {
54254
+ telemetry.causeCode = causeCode;
54255
+ }
54256
+ const causeKind = cause.message.match(
54257
+ /^\[([A-Za-z][A-Za-z0-9]{0,63})::([A-Za-z][A-Za-z0-9]{0,63})\]/
54258
+ );
54259
+ const knownCauseKind = causeKind ? KNOWN_CAUSE_KINDS.get(`${causeKind[1]}::${causeKind[2]}`) : void 0;
54260
+ if (knownCauseKind) {
54261
+ telemetry.causeType = knownCauseKind[0];
54262
+ telemetry.causeVariant = knownCauseKind[1];
54263
+ }
54264
+ const grpcMethod = cause.message.match(
54265
+ /endpoint\s+["']\/[A-Za-z0-9_.]+\/([A-Za-z][A-Za-z0-9_]{0,63})["']/
54266
+ );
54267
+ if (grpcMethod && KNOWN_GRPC_METHODS.has(grpcMethod[1])) {
54268
+ telemetry.grpcMethod = grpcMethod[1];
54269
+ telemetry.endpoint = grpcMethod[1];
54270
+ }
54271
+ if (/\bh2 protocol error\b/i.test(cause.message)) {
54272
+ telemetry.transportProtocol = "h2";
54273
+ }
54274
+ const transportFailure = TRANSPORT_FAILURE_PATTERNS.find(
54275
+ ([pattern]) => pattern.test(cause.message)
54276
+ );
54277
+ if (transportFailure) {
54278
+ telemetry.transportFailure = transportFailure[1];
54279
+ telemetry.reason = transportFailure[1];
54280
+ }
54281
+ return telemetry;
54282
+ }
54283
+ var StreamRecoveryTelemetry = class {
54284
+ nextSequence = 1;
54285
+ activeRecovery;
54286
+ get activeSequence() {
54287
+ return this.activeRecovery?.sequence;
54288
+ }
54289
+ markError(nowMs = Date.now()) {
54290
+ if (this.activeRecovery) {
54291
+ return {
54292
+ recoverySequence: this.activeRecovery.sequence,
54293
+ alreadyRecovering: true
54294
+ };
54295
+ }
54296
+ const sequence = this.nextSequence++;
54297
+ this.activeRecovery = { sequence, startedAtMs: nowMs };
54298
+ return { recoverySequence: sequence, alreadyRecovering: false };
54299
+ }
54300
+ markStarted(nowMs = Date.now()) {
54301
+ if (!this.activeRecovery) {
54302
+ return void 0;
54303
+ }
54304
+ const completed = {
54305
+ recoverySequence: this.activeRecovery.sequence,
54306
+ recoveryDurationMs: Math.max(0, nowMs - this.activeRecovery.startedAtMs)
54307
+ };
54308
+ this.activeRecovery = void 0;
54309
+ return completed;
54310
+ }
54311
+ cancel() {
54312
+ this.activeRecovery = void 0;
54313
+ }
54314
+ };
54315
+ var KeyedSingleFlight = class {
54316
+ inFlight = /* @__PURE__ */ new Map();
54317
+ run(key, discriminator, operation) {
54318
+ const normalizedKey = key.toLowerCase();
54319
+ const state = this.inFlight.get(normalizedKey);
54320
+ if (!state) {
54321
+ const active = createKeyedFlight(discriminator, operation);
54322
+ this.inFlight.set(normalizedKey, { active, queued: [] });
54323
+ this.execute(normalizedKey, active);
54324
+ return active.promise;
54325
+ }
54326
+ if (state.active.discriminator === discriminator) {
54327
+ return state.active.promise;
54328
+ }
54329
+ const queued = state.queued.find(
54330
+ (flight2) => flight2.discriminator === discriminator
54331
+ );
54332
+ if (queued) {
54333
+ return queued.promise;
54334
+ }
54335
+ const flight = createKeyedFlight(discriminator, operation);
54336
+ state.queued.push(flight);
54337
+ return flight.promise;
54338
+ }
54339
+ execute(key, flight) {
54340
+ void Promise.resolve().then(flight.operation).then(
54341
+ (value) => {
54342
+ flight.resolve(value);
54343
+ this.advance(key, flight);
54344
+ },
54345
+ (error) => {
54346
+ flight.reject(error);
54347
+ this.advance(key, flight);
54348
+ }
54349
+ );
54350
+ }
54351
+ advance(key, completed) {
54352
+ const state = this.inFlight.get(key);
54353
+ if (state?.active !== completed) {
54354
+ return;
54355
+ }
54356
+ const next = state.queued.shift();
54357
+ if (!next) {
54358
+ this.inFlight.delete(key);
54359
+ return;
54360
+ }
54361
+ state.active = next;
54362
+ this.execute(key, next);
54363
+ }
54364
+ };
54365
+ function createKeyedFlight(discriminator, operation) {
54366
+ let resolve11;
54367
+ let reject;
54368
+ const promise = new Promise((resolvePromise, rejectPromise) => {
54369
+ resolve11 = resolvePromise;
54370
+ reject = rejectPromise;
54371
+ });
54372
+ return { discriminator, operation, promise, resolve: resolve11, reject };
54373
+ }
54374
+
53985
54375
  // ../core/src/xmtp-sdk/extract-job-id.ts
53986
54376
  function extractJobIdFromContent(content) {
53987
54377
  const raw = typeof content === "string" ? content : stringifyJsonForEnvelope(content);
@@ -54588,10 +54978,11 @@ var InboundReplayGate = class {
54588
54978
  this.inbound = inbound;
54589
54979
  }
54590
54980
  inbound;
54591
- buffering = true;
54981
+ holds = 1;
54592
54982
  queue = [];
54983
+ drainInFlight;
54593
54984
  middleware = async (ctx, next) => {
54594
- if (!this.buffering) {
54985
+ if (this.holds === 0 && !this.drainInFlight) {
54595
54986
  await this.inbound(ctx, next);
54596
54987
  return;
54597
54988
  }
@@ -54600,19 +54991,55 @@ var InboundReplayGate = class {
54600
54991
  get bufferedCount() {
54601
54992
  return this.queue.length;
54602
54993
  }
54994
+ abandonFailedAndRelease() {
54995
+ const abandoned = this.queue.length > 0 ? 1 : 0;
54996
+ if (abandoned > 0) {
54997
+ this.queue.shift();
54998
+ }
54999
+ if (this.holds > 0) {
55000
+ this.holds--;
55001
+ }
55002
+ return abandoned;
55003
+ }
55004
+ hold() {
55005
+ this.holds++;
55006
+ }
54603
55007
  async drain() {
55008
+ if (this.holds > 0) {
55009
+ this.holds--;
55010
+ }
55011
+ return this.drainReleased();
55012
+ }
55013
+ async drainReleased() {
55014
+ if (this.holds > 0) {
55015
+ return 0;
55016
+ }
55017
+ if (this.drainInFlight) {
55018
+ return this.drainInFlight;
55019
+ }
55020
+ const drain = Promise.resolve().then(() => this.drainQueue());
55021
+ this.drainInFlight = drain;
55022
+ try {
55023
+ return await drain;
55024
+ } finally {
55025
+ if (this.drainInFlight === drain) {
55026
+ this.drainInFlight = void 0;
55027
+ }
55028
+ }
55029
+ }
55030
+ async drainQueue() {
54604
55031
  let drained = 0;
54605
- while (this.queue.length > 0) {
55032
+ while (this.holds === 0 && this.queue.length > 0) {
54606
55033
  const item = this.queue.shift();
54607
55034
  try {
54608
55035
  await this.inbound(item.ctx, item.next);
54609
55036
  } catch (err) {
54610
55037
  this.queue.unshift(item);
55038
+ this.holds = Math.max(1, this.holds);
54611
55039
  throw err;
54612
55040
  }
54613
55041
  drained++;
54614
55042
  }
54615
- this.buffering = false;
54616
55043
  return drained;
54617
55044
  }
54618
55045
  };
@@ -54628,6 +55055,9 @@ var SYSTEM_CONFIG_DEFAULTS = {
54628
55055
  }
54629
55056
  }
54630
55057
  };
55058
+ var STREAM_RECOVERY_ALERT_THRESHOLD_MS = 3e4;
55059
+ var REPLAY_GATE_DRAIN_MAX_ATTEMPTS = 3;
55060
+ var REPLAY_GATE_DRAIN_RETRY_DELAY_MS = 100;
54631
55061
  var 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-]+)*)?$/;
54632
55062
  function normalizeSemverInput(v) {
54633
55063
  return v.trim().replace(/^v/, "");
@@ -54808,6 +55238,9 @@ function parseGroupPayload(content) {
54808
55238
  return null;
54809
55239
  }
54810
55240
  }
55241
+ function isOfflineReplayForTrigger(trigger) {
55242
+ return trigger === "startup";
55243
+ }
54811
55244
  var XmtpService = class _XmtpService {
54812
55245
  static instance = null;
54813
55246
  env = process.env.XMTP_ENV ?? "production";
@@ -54833,7 +55266,11 @@ var XmtpService = class _XmtpService {
54833
55266
  };
54834
55267
  dataDir = DEFAULT_DATA_DIR;
54835
55268
  syncByAddress = /* @__PURE__ */ new Map();
55269
+ offlineReplaySingleFlight = new KeyedSingleFlight();
54836
55270
  inboundReplayGates = /* @__PURE__ */ new Map();
55271
+ streamRecoveryCleanupByAddress = /* @__PURE__ */ new Map();
55272
+ nextClientGeneration = 1;
55273
+ clientGenerationByClient = /* @__PURE__ */ new WeakMap();
54837
55274
  agentListFingerprint;
54838
55275
  // Only one refreshAgents can be in flight at a time; concurrent calls
54839
55276
  // coalesce onto the same promise. This avoids duplicate Agent.create()
@@ -55288,6 +55725,7 @@ var XmtpService = class _XmtpService {
55288
55725
  const client = this.clients.get(address);
55289
55726
  if (client) {
55290
55727
  try {
55728
+ this.cancelStreamRecoveryForAddress(address);
55291
55729
  this.stoppedAddresses.add(address.toLowerCase());
55292
55730
  client.stop();
55293
55731
  logWithTimestamp(
@@ -55461,7 +55899,10 @@ var XmtpService = class _XmtpService {
55461
55899
  logWithTimestamp(
55462
55900
  `[xmtp-sdk] refresh: ${address} has a prior sync timestamp, triggering offline replay`
55463
55901
  );
55464
- void this.replayOfflineMessagesForAddress(address).catch((err) => {
55902
+ void this.replayOfflineMessagesForAddressSingleFlight(
55903
+ address,
55904
+ "agent_refresh"
55905
+ ).catch((err) => {
55465
55906
  logWithTimestamp(`[xmtp-sdk] refresh: ${address} offline replay failed:`, err);
55466
55907
  logger.error(
55467
55908
  LogEvent.OFFLINE_REPLAY_FAILED,
@@ -55503,6 +55944,7 @@ var XmtpService = class _XmtpService {
55503
55944
  }
55504
55945
  const addressKey = address.toLowerCase();
55505
55946
  try {
55947
+ this.cancelStreamRecoveryForAddress(address);
55506
55948
  this.stoppedAddresses.add(addressKey);
55507
55949
  await Promise.resolve(oldClient.stop());
55508
55950
  this.clients.delete(address);
@@ -55561,7 +56003,10 @@ var XmtpService = class _XmtpService {
55561
56003
  await this.startListeningForAddresses([address]);
55562
56004
  let replaySummary;
55563
56005
  try {
55564
- replaySummary = await this.replayOfflineMessagesForAddress(address);
56006
+ replaySummary = await this.replayOfflineMessagesForAddressSingleFlight(
56007
+ address,
56008
+ "client_recycle"
56009
+ );
55565
56010
  } finally {
55566
56011
  await this.drainInboundReplayGate(address);
55567
56012
  }
@@ -55571,6 +56016,7 @@ var XmtpService = class _XmtpService {
55571
56016
  recycled++;
55572
56017
  } catch (err) {
55573
56018
  failed++;
56019
+ this.cancelStreamRecoveryForAddress(address);
55574
56020
  this.clients.delete(address);
55575
56021
  logWithTimestamp(`[xmtp-sdk] recycle: failed to recreate client: ${address}`, err);
55576
56022
  logger.error(
@@ -55971,16 +56417,80 @@ var XmtpService = class _XmtpService {
55971
56417
  async startListening() {
55972
56418
  await this.startListeningForAddresses([...this.clients.keys()]);
55973
56419
  }
55974
- async drainInboundReplayGate(address) {
56420
+ async drainInboundReplayGate(address, expectedGate) {
55975
56421
  const gate = this.inboundReplayGates.get(address.toLowerCase());
55976
- if (!gate) {
56422
+ if (!gate || expectedGate && gate !== expectedGate) {
55977
56423
  return 0;
55978
56424
  }
55979
- const drained = await gate.drain();
56425
+ let attempt = 0;
56426
+ let totalDrained = 0;
56427
+ let releaseHold = true;
56428
+ while (true) {
56429
+ try {
56430
+ const drained = releaseHold ? await gate.drain() : await gate.drainReleased();
56431
+ totalDrained += drained;
56432
+ logWithTimestamp(
56433
+ `[xmtp-sdk:${address}] replay barrier released, buffered=${totalDrained} attempt=${attempt + 1}`
56434
+ );
56435
+ return totalDrained;
56436
+ } catch (err) {
56437
+ attempt++;
56438
+ logWithTimestamp(
56439
+ `[xmtp-sdk:${address}] replay barrier drain failed attempt=${attempt}/${REPLAY_GATE_DRAIN_MAX_ATTEMPTS}:`,
56440
+ err
56441
+ );
56442
+ if (attempt < REPLAY_GATE_DRAIN_MAX_ATTEMPTS) {
56443
+ releaseHold = true;
56444
+ await new Promise(
56445
+ (resolve11) => setTimeout(resolve11, REPLAY_GATE_DRAIN_RETRY_DELAY_MS * attempt)
56446
+ );
56447
+ continue;
56448
+ }
56449
+ const abandoned = gate.abandonFailedAndRelease();
56450
+ logger.error(
56451
+ LogEvent.INBOUND_REPLAY_GATE_DRAIN_FAILED,
56452
+ err instanceof Error ? err : void 0,
56453
+ {
56454
+ walletAddress: address,
56455
+ attemptCount: String(REPLAY_GATE_DRAIN_MAX_ATTEMPTS),
56456
+ abandonedCount: String(abandoned),
56457
+ stage: "inboundReplayGate/fail-open",
56458
+ reason: "drain_retry_exhausted"
56459
+ }
56460
+ );
56461
+ logWithTimestamp(
56462
+ `[xmtp-sdk:${address}] replay barrier fail-open after ${REPLAY_GATE_DRAIN_MAX_ATTEMPTS} failed attempts, abandoned=${abandoned}`
56463
+ );
56464
+ attempt = 0;
56465
+ releaseHold = false;
56466
+ }
56467
+ }
56468
+ }
56469
+ holdInboundReplayGate(address) {
56470
+ const gate = this.inboundReplayGates.get(address.toLowerCase());
56471
+ if (!gate) {
56472
+ return void 0;
56473
+ }
56474
+ gate.hold();
55980
56475
  logWithTimestamp(
55981
- `[xmtp-sdk:${address}] startup replay barrier opened, buffered=${drained}`
56476
+ `[xmtp-sdk:${address}] replay barrier held, buffered=${gate.bufferedCount}`
55982
56477
  );
55983
- return drained;
56478
+ return gate;
56479
+ }
56480
+ cancelStreamRecoveryForAddress(address) {
56481
+ const addressKey = address.toLowerCase();
56482
+ const cleanup = this.streamRecoveryCleanupByAddress.get(addressKey);
56483
+ this.streamRecoveryCleanupByAddress.delete(addressKey);
56484
+ cleanup?.();
56485
+ }
56486
+ getClientGeneration(client) {
56487
+ const existing = this.clientGenerationByClient.get(client);
56488
+ if (existing !== void 0) {
56489
+ return existing;
56490
+ }
56491
+ const generation = this.nextClientGeneration++;
56492
+ this.clientGenerationByClient.set(client, generation);
56493
+ return generation;
55984
56494
  }
55985
56495
  notifyAgentsWakeupAfterReplay() {
55986
56496
  const notifyAgentsWakeup2 = this.tools.notifyAgentsWakeup;
@@ -56014,7 +56524,7 @@ var XmtpService = class _XmtpService {
56014
56524
  * cannot overtake the stored backlog.
56015
56525
  */
56016
56526
  async completeStartupReplay() {
56017
- const summary = await this.replayOfflineMessages();
56527
+ const summary = await this.replayOfflineMessages("startup");
56018
56528
  for (const address of this.clients.keys()) {
56019
56529
  await this.drainInboundReplayGate(address);
56020
56530
  }
@@ -56025,6 +56535,8 @@ var XmtpService = class _XmtpService {
56025
56535
  const valid = addresses.filter((addr) => this.clients.has(addr));
56026
56536
  await parallelMap(valid, async (address) => {
56027
56537
  const agent = this.clients.get(address);
56538
+ const clientGeneration = this.getClientGeneration(agent);
56539
+ const addressKey = address.toLowerCase();
56028
56540
  const tag = `[xmtp-sdk:${address}]`;
56029
56541
  const onchainosAgent = this.getAgentByAddress(address);
56030
56542
  const identity = {
@@ -56127,14 +56639,131 @@ var XmtpService = class _XmtpService {
56127
56639
  logWithTimestamp(`${tag} reconnecting in ${delay}ms (attempt=${attempt + 1})`);
56128
56640
  setTimeout(() => void startWithRetry(attempt + 1), delay);
56129
56641
  };
56642
+ const streamRecovery = new StreamRecoveryTelemetry();
56643
+ let recoveryTimeout;
56644
+ let pendingRecoveryGate;
56645
+ let recoveryDisposed = false;
56646
+ this.cancelStreamRecoveryForAddress(address);
56647
+ const cleanupStreamRecovery = () => {
56648
+ recoveryDisposed = true;
56649
+ streamRecovery.cancel();
56650
+ if (recoveryTimeout) {
56651
+ clearTimeout(recoveryTimeout);
56652
+ recoveryTimeout = void 0;
56653
+ }
56654
+ };
56655
+ this.streamRecoveryCleanupByAddress.set(
56656
+ addressKey,
56657
+ cleanupStreamRecovery
56658
+ );
56130
56659
  agent.on("unhandledError", (err) => {
56660
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey)) {
56661
+ return;
56662
+ }
56663
+ if (!isAgentStreamRecoveryError(err)) {
56664
+ logWithTimestamp(`${tag} agent unhandled error:`, err);
56665
+ logger.error(LogEvent.AGENT_UNHANDLED_ERROR, err, agentExtras(identity));
56666
+ return;
56667
+ }
56668
+ const recovery = streamRecovery.markError();
56669
+ const errorTelemetry = extractStreamErrorTelemetry(err);
56670
+ if (!recovery.alreadyRecovering) {
56671
+ pendingRecoveryGate = this.holdInboundReplayGate(address);
56672
+ }
56131
56673
  logWithTimestamp(`${tag} agent stream error:`, err);
56132
- logger.error(LogEvent.AGENT_STREAM_ERROR, err, agentExtras(identity));
56674
+ logger.info(LogEvent.AGENT_STREAM_ERROR, {
56675
+ ...agentExtras(identity),
56676
+ ...errorTelemetry,
56677
+ clientGeneration: String(clientGeneration),
56678
+ recoverySequence: String(recovery.recoverySequence),
56679
+ recoveryAlreadyActive: String(recovery.alreadyRecovering)
56680
+ });
56681
+ if (!recovery.alreadyRecovering) {
56682
+ recoveryTimeout = setTimeout(() => {
56683
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey) || streamRecovery.activeSequence !== recovery.recoverySequence) {
56684
+ return;
56685
+ }
56686
+ logger.error(LogEvent.AGENT_STREAM_RECOVERY_TIMEOUT, void 0, {
56687
+ ...agentExtras(identity),
56688
+ ...errorTelemetry,
56689
+ clientGeneration: String(clientGeneration),
56690
+ recoverySequence: String(recovery.recoverySequence),
56691
+ recoveryDurationMs: String(STREAM_RECOVERY_ALERT_THRESHOLD_MS),
56692
+ thresholdMs: String(STREAM_RECOVERY_ALERT_THRESHOLD_MS)
56693
+ });
56694
+ }, STREAM_RECOVERY_ALERT_THRESHOLD_MS);
56695
+ }
56133
56696
  });
56134
56697
  agent.on("start", () => {
56698
+ if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey)) {
56699
+ return;
56700
+ }
56701
+ const recovery = streamRecovery.markStarted();
56702
+ if (recoveryTimeout) {
56703
+ clearTimeout(recoveryTimeout);
56704
+ recoveryTimeout = void 0;
56705
+ }
56135
56706
  logWithTimestamp(
56136
56707
  `${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))}`
56137
56708
  );
56709
+ if (recovery) {
56710
+ const recoveryGate = pendingRecoveryGate;
56711
+ pendingRecoveryGate = void 0;
56712
+ logger.info(LogEvent.AGENT_STREAM_RECOVERED, {
56713
+ ...agentExtras(identity),
56714
+ clientGeneration: String(clientGeneration),
56715
+ recoverySequence: String(recovery.recoverySequence),
56716
+ recoveryDurationMs: String(recovery.recoveryDurationMs)
56717
+ });
56718
+ void (async () => {
56719
+ try {
56720
+ const summary = await this.replayOfflineMessagesForAddressSingleFlight(
56721
+ address,
56722
+ "stream_recovery",
56723
+ `${clientGeneration}:${recovery.recoverySequence}`
56724
+ );
56725
+ if (!recoveryDisposed && this.clients.get(address) === agent) {
56726
+ logger.info(LogEvent.AGENT_STREAM_RECOVERY_REPLAY_COMPLETED, {
56727
+ ...agentExtras(identity),
56728
+ clientGeneration: String(clientGeneration),
56729
+ recoverySequence: String(recovery.recoverySequence),
56730
+ replayed: String(summary.replayed),
56731
+ skipped: String(summary.skipped),
56732
+ conversationCount: String(summary.conversations),
56733
+ replayDurationMs: String(summary.durationMs)
56734
+ });
56735
+ }
56736
+ } catch (err) {
56737
+ logWithTimestamp(`${tag} recovery offline replay failed:`, err);
56738
+ logger.error(
56739
+ LogEvent.OFFLINE_REPLAY_FAILED,
56740
+ err instanceof Error ? err : new Error(String(err)),
56741
+ {
56742
+ ...agentExtras(identity),
56743
+ clientGeneration: String(clientGeneration),
56744
+ recoverySequence: String(recovery.recoverySequence),
56745
+ stage: "streamRecovery/replay"
56746
+ }
56747
+ );
56748
+ } finally {
56749
+ try {
56750
+ await this.drainInboundReplayGate(address, recoveryGate);
56751
+ } catch (err) {
56752
+ logWithTimestamp(`${tag} recovery replay gate drain failed:`, err);
56753
+ logger.error(
56754
+ LogEvent.OFFLINE_REPLAY_FAILED,
56755
+ err instanceof Error ? err : new Error(String(err)),
56756
+ {
56757
+ ...agentExtras(identity),
56758
+ clientGeneration: String(clientGeneration),
56759
+ recoverySequence: String(recovery.recoverySequence),
56760
+ stage: "streamRecovery/drain-gate"
56761
+ }
56762
+ );
56763
+ }
56764
+ }
56765
+ })();
56766
+ }
56138
56767
  if (process.env.XMTP_FORCE_DEBUG === "true") {
56139
56768
  void logDetails(agent).catch((err) => {
56140
56769
  logWithTimestamp(`${tag} agent debug details failed:`, err);
@@ -56152,9 +56781,10 @@ var XmtpService = class _XmtpService {
56152
56781
  logWithTimestamp(`${tag} message listener started`);
56153
56782
  });
56154
56783
  }
56155
- async replayOfflineMessagesForAddress(address) {
56784
+ async replayOfflineMessagesForAddress(address, trigger = "periodic_repair") {
56156
56785
  const addressReplayStartedAt = Date.now();
56157
56786
  const summary = createOfflineReplayAddressSummary(address);
56787
+ const isOfflineReplay = isOfflineReplayForTrigger(trigger);
56158
56788
  const agent = this.clients.get(address);
56159
56789
  if (!agent) {
56160
56790
  summary.durationMs = Date.now() - addressReplayStartedAt;
@@ -56278,7 +56908,8 @@ var XmtpService = class _XmtpService {
56278
56908
  };
56279
56909
  const handleStartedAt = Date.now();
56280
56910
  const handled = await processOffline(fakeCtx, handlerDeps, {
56281
- skipNotify: true
56911
+ skipNotify: true,
56912
+ isOfflineReplay
56282
56913
  });
56283
56914
  const handleMs = Date.now() - handleStartedAt;
56284
56915
  summary.handleMs += handleMs;
@@ -56360,14 +56991,28 @@ var XmtpService = class _XmtpService {
56360
56991
  );
56361
56992
  return summary;
56362
56993
  }
56363
- async replayOfflineMessages() {
56994
+ replayOfflineMessagesForAddressSingleFlight(address, trigger = "periodic_repair", recoveryIdentity) {
56995
+ const eligibilityClass = trigger === "stream_recovery" ? `stream_recovery:${recoveryIdentity ?? "unknown"}` : isOfflineReplayForTrigger(trigger) ? "startup_offline" : "online_repair";
56996
+ return this.offlineReplaySingleFlight.run(
56997
+ address,
56998
+ eligibilityClass,
56999
+ async () => {
57000
+ await this._backupReady;
57001
+ return this.replayOfflineMessagesForAddress(address, trigger);
57002
+ }
57003
+ );
57004
+ }
57005
+ async replayOfflineMessages(trigger = "periodic_repair") {
56364
57006
  await this._backupReady;
56365
57007
  const replayStartedAt = Date.now();
56366
57008
  let totalReplayed = 0;
56367
57009
  let totalSkipped = 0;
56368
57010
  const addresses = [];
56369
57011
  for (const address of this.clients.keys()) {
56370
- const summary2 = await this.replayOfflineMessagesForAddress(address);
57012
+ const summary2 = await this.replayOfflineMessagesForAddressSingleFlight(
57013
+ address,
57014
+ trigger
57015
+ );
56371
57016
  addresses.push(summary2);
56372
57017
  const { replayed, skipped: skipped2 } = summary2;
56373
57018
  totalReplayed += replayed;
@@ -56705,7 +57350,7 @@ async function exec(args) {
56705
57350
  );
56706
57351
  logger.error(
56707
57352
  LogEvent.ONCHAINOS_CLI_ERROR,
56708
- new Error("onchainos command failed"),
57353
+ err instanceof Error ? err : new Error("onchainos command failed"),
56709
57354
  {
56710
57355
  component: "onchainos_cli",
56711
57356
  source: "onchainos",
@@ -56808,6 +57453,107 @@ function reportSessionExpired(command) {
56808
57453
  );
56809
57454
  }
56810
57455
 
57456
+ // ../core/src/xmtp-sdk/onchainos/offline-replay-capability.ts
57457
+ init_log();
57458
+ var MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG = "--is-offline-replay";
57459
+ var MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN = "is-offline-replay";
57460
+ var UPGRADE_RECOMMENDATION_PAIR_KEY = "onchainos:message-eligible-offline-replay";
57461
+ var UPGRADE_RECOMMENDATION_REASON = "OnchainOS does not support the offline-replay eligibility argument; using the legacy message-eligible contract.";
57462
+ function messageEligibleHelpAdvertisesOfflineReplay(helpText) {
57463
+ if (typeof helpText !== "string" || helpText.length === 0) {
57464
+ return false;
57465
+ }
57466
+ return helpText.toLowerCase().includes(MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN);
57467
+ }
57468
+ function buildMessageEligibleArgs(params, opts) {
57469
+ const args = [
57470
+ "agent",
57471
+ "message-eligible",
57472
+ "--agent-id",
57473
+ params.agentId,
57474
+ "--client-agent-id",
57475
+ params.clientAgentId,
57476
+ "--provider-agent-id",
57477
+ params.providerAgentId,
57478
+ "--client-communication-address",
57479
+ params.clientCommunicationAddress,
57480
+ "--provider-communication-address",
57481
+ params.providerCommunicationAddress,
57482
+ "--job-id",
57483
+ params.jobId,
57484
+ "--group-id",
57485
+ params.groupId,
57486
+ "--direction",
57487
+ params.direction,
57488
+ "--provider-security-rate",
57489
+ String(params.providerSecurityRate)
57490
+ ];
57491
+ if (opts.offlineReplaySupported) {
57492
+ args.push(MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG, String(params.isOfflineReplay));
57493
+ }
57494
+ return args;
57495
+ }
57496
+ function resolveOfflineReplayCapabilityMarker(supported) {
57497
+ return supported ? "supported" : "legacy_downgrade";
57498
+ }
57499
+ var capabilityProbe = null;
57500
+ var recommendedPairs = /* @__PURE__ */ new Set();
57501
+ function resetMessageEligibleOfflineReplayCapabilityForTests() {
57502
+ capabilityProbe = null;
57503
+ recommendedPairs.clear();
57504
+ }
57505
+ function maybeRecommendOnchainosUpgrade(supported) {
57506
+ if (supported || recommendedPairs.has(UPGRADE_RECOMMENDATION_PAIR_KEY)) {
57507
+ return;
57508
+ }
57509
+ const hook = XmtpService.getInstance().tools.notifyUpgradeRecommendation;
57510
+ if (!hook) {
57511
+ logWithTimestamp(
57512
+ "[onchainos] tools.notifyUpgradeRecommendation not injected \u2014 deferring offline-replay upgrade notice"
57513
+ );
57514
+ return;
57515
+ }
57516
+ recommendedPairs.add(UPGRADE_RECOMMENDATION_PAIR_KEY);
57517
+ try {
57518
+ void Promise.resolve(
57519
+ hook({
57520
+ component: "onchainos",
57521
+ capability: MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN,
57522
+ reason: UPGRADE_RECOMMENDATION_REASON
57523
+ })
57524
+ ).catch((err) => {
57525
+ logWithTimestamp("[onchainos] tools.notifyUpgradeRecommendation async delivery failed:", err);
57526
+ });
57527
+ } catch (err) {
57528
+ logWithTimestamp("[onchainos] tools.notifyUpgradeRecommendation threw:", err);
57529
+ }
57530
+ }
57531
+ function detectMessageEligibleOfflineReplaySupport(execFn = exec) {
57532
+ if (!capabilityProbe) {
57533
+ capabilityProbe = (async () => {
57534
+ try {
57535
+ const { stdout, stderr } = await execFn(["agent", "message-eligible", "--help"]);
57536
+ const supported = messageEligibleHelpAdvertisesOfflineReplay(`${stdout}
57537
+ ${stderr}`);
57538
+ logWithTimestamp(
57539
+ `[onchainos] message-eligible offline-replay capability: ${supported ? "supported" : "legacy_downgrade"}`
57540
+ );
57541
+ return supported;
57542
+ } catch (err) {
57543
+ logWithTimestamp(
57544
+ "[onchainos] message-eligible --help probe failed; treating offline-replay as unsupported (legacy):",
57545
+ err
57546
+ );
57547
+ return false;
57548
+ }
57549
+ })().then((supported) => {
57550
+ maybeRecommendOnchainosUpgrade(supported);
57551
+ return supported;
57552
+ });
57553
+ }
57554
+ return capabilityProbe;
57555
+ }
57556
+
56811
57557
  // ../core/src/xmtp-sdk/onchainos/system-config.ts
56812
57558
  init_log();
56813
57559
  init_sentry_logger();
@@ -57204,35 +57950,32 @@ async function sendHeartbeat(chainIndex) {
57204
57950
  }
57205
57951
  async function checkMessageEligible(params) {
57206
57952
  logWithTimestamp(
57207
- `[onchainos] checking message-eligible: job=${params.jobId} direction=${params.direction} providerSecurityRate=${params.providerSecurityRate}`
57953
+ `[onchainos] checking message-eligible: job=${params.jobId} direction=${params.direction} providerSecurityRate=${params.providerSecurityRate} isOfflineReplay=${params.isOfflineReplay}`
57208
57954
  );
57955
+ const offlineReplaySupported = await detectMessageEligibleOfflineReplaySupport();
57956
+ const offlineReplayCapability = resolveOfflineReplayCapabilityMarker(offlineReplaySupported);
57957
+ const emitEligibilityChecked = (outcome, eligible) => {
57958
+ logger.info(LogEvent.MESSAGE_ELIGIBILITY_CHECKED, {
57959
+ component: "onchainos_cli",
57960
+ source: "onchainos",
57961
+ stage: "eligibility_check",
57962
+ direction: params.direction,
57963
+ jobId: params.jobId,
57964
+ isOfflineReplay: String(params.isOfflineReplay),
57965
+ offlineReplayCapability,
57966
+ outcome,
57967
+ eligible
57968
+ });
57969
+ };
57209
57970
  let stdout;
57210
57971
  let stderr;
57211
57972
  try {
57212
- ({ stdout, stderr } = await exec([
57213
- "agent",
57214
- "message-eligible",
57215
- "--agent-id",
57216
- params.agentId,
57217
- "--client-agent-id",
57218
- params.clientAgentId,
57219
- "--provider-agent-id",
57220
- params.providerAgentId,
57221
- "--client-communication-address",
57222
- params.clientCommunicationAddress,
57223
- "--provider-communication-address",
57224
- params.providerCommunicationAddress,
57225
- "--job-id",
57226
- params.jobId,
57227
- "--group-id",
57228
- params.groupId,
57229
- "--direction",
57230
- params.direction,
57231
- "--provider-security-rate",
57232
- String(params.providerSecurityRate)
57233
- ]));
57973
+ ({ stdout, stderr } = await exec(
57974
+ buildMessageEligibleArgs(params, { offlineReplaySupported })
57975
+ ));
57234
57976
  } catch (err) {
57235
57977
  guardCatchSessionExpired2(err, "message-eligible");
57978
+ emitEligibilityChecked("failed", "unknown");
57236
57979
  throw messageEligibleUnavailableError({
57237
57980
  reason: "message-eligible-cli-error",
57238
57981
  stdout: typeof err?.stdout === "string" ? err.stdout : "",
@@ -57257,6 +58000,7 @@ async function checkMessageEligible(params) {
57257
58000
  providerSecurityRate: String(params.providerSecurityRate)
57258
58001
  });
57259
58002
  } catch (err) {
58003
+ emitEligibilityChecked("failed", "unknown");
57260
58004
  throw messageEligibleUnavailableError({
57261
58005
  reason: "message-eligible-json-parse-failed",
57262
58006
  stdout,
@@ -57286,6 +58030,7 @@ async function checkMessageEligible(params) {
57286
58030
  reason: "ok=false"
57287
58031
  })
57288
58032
  );
58033
+ emitEligibilityChecked("failed", "unknown");
57289
58034
  throw messageEligibleUnavailableError({
57290
58035
  reason: "message-eligible-ok-false",
57291
58036
  stdout,
@@ -57295,6 +58040,7 @@ async function checkMessageEligible(params) {
57295
58040
  logWithTimestamp(
57296
58041
  `[onchainos] message-eligible result: eligible=${res.data.eligible}`
57297
58042
  );
58043
+ emitEligibilityChecked("success", String(res.data.eligible));
57298
58044
  return res.data;
57299
58045
  }
57300
58046
  async function fetchSensitiveWords() {
@@ -58117,7 +58863,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
58117
58863
  client: {
58118
58864
  id: "gateway-client",
58119
58865
  displayName: "okx-a2a-node",
58120
- version: "0.2.0",
58866
+ version: "0.2.1-beta-8ae7a0d300-260807181806",
58121
58867
  platform: "node",
58122
58868
  mode: "backend",
58123
58869
  instanceId
@@ -58128,7 +58874,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
58128
58874
  commands: [],
58129
58875
  permissions: {},
58130
58876
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
58131
- userAgent: `okx-a2a-node/${"0.2.0"}`,
58877
+ userAgent: `okx-a2a-node/${"0.2.1-beta-8ae7a0d300-260807181806"}`,
58132
58878
  auth: {
58133
58879
  ...config.token ? { token: config.token } : {},
58134
58880
  ...config.password ? { password: config.password } : {}
@@ -59890,7 +60636,9 @@ async function assertOutboundEligible(params) {
59890
60636
  jobId,
59891
60637
  groupId,
59892
60638
  direction: isSenderClient ? "client_to_provider" : "provider_to_client",
59893
- providerSecurityRate
60639
+ providerSecurityRate,
60640
+ // Outbound messages are never startup backlog recovery.
60641
+ isOfflineReplay: false
59894
60642
  });
59895
60643
  logWithTimestamp(
59896
60644
  `[okx-agent-task] outbound message-eligible: senderAgentId=${senderAgent.agentId} receiverAgentId=${receiverAgent.agentId} job=${jobId} group=${groupId} result=${JSON.stringify(result)}`
@@ -62729,6 +63477,7 @@ async function verifyInboundA2AGroupMessage(params) {
62729
63477
  messageId,
62730
63478
  consentState,
62731
63479
  trustedSystemSender = false,
63480
+ isOfflineReplay = false,
62732
63481
  timing
62733
63482
  } = params;
62734
63483
  if (!isA2AEnvelope(payload)) {
@@ -62832,7 +63581,8 @@ async function verifyInboundA2AGroupMessage(params) {
62832
63581
  jobId,
62833
63582
  groupId,
62834
63583
  direction: isSenderClient ? "client_to_provider" : "provider_to_client",
62835
- providerSecurityRate
63584
+ providerSecurityRate,
63585
+ isOfflineReplay
62836
63586
  });
62837
63587
  timing?.mark("eligibilityCheck", eligibilityStartedAt);
62838
63588
  logWithTimestamp(
@@ -63281,6 +64031,7 @@ async function processFileMessage(ctx, deps, options = {}) {
63281
64031
  messageId,
63282
64032
  consentState: ctx.conversation instanceof Group ? ctx.conversation.consentState() : void 0,
63283
64033
  trustedSystemSender,
64034
+ isOfflineReplay: options.isOfflineReplay ?? false,
63284
64035
  timing
63285
64036
  });
63286
64037
  if (!accepted) {
@@ -63864,6 +64615,7 @@ var DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC = 300;
63864
64615
  var DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC = 6 * 60 * 60;
63865
64616
  var XMTP_CLIENT_RECYCLE_INTERVAL_ENV = "OKX_A2A_XMTP_CLIENT_RECYCLE_INTERVAL_SEC";
63866
64617
  var HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS = 2e3;
64618
+ var UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY = "system:onchainos-offline-replay-upgrade";
63867
64619
  function resolveOfflineReplayIntervalSec(systemConfig) {
63868
64620
  const interval = systemConfig.offlineReplayInterval;
63869
64621
  if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0) {
@@ -64104,14 +64856,28 @@ async function runListenerWithLock(options, paths) {
64104
64856
  reason: "append_backup_failed"
64105
64857
  });
64106
64858
  });
64859
+ },
64860
+ notifyUpgradeRecommendation: (input) => {
64861
+ const content = "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";
64862
+ void notifySystemMessageToUser({
64863
+ title: "OnchainOS upgrade recommended",
64864
+ content,
64865
+ store: sessionStore,
64866
+ idempotencyKey: UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY
64867
+ }).catch((err) => {
64868
+ logWithTimestamp(
64869
+ `[okx-agent-task] upgrade recommendation notify failed component=${input.component} capability=${input.capability}:`,
64870
+ err
64871
+ );
64872
+ });
64107
64873
  }
64108
64874
  });
64109
- service.setPluginVersion("0.2.0");
64875
+ service.setPluginVersion("0.2.1-beta-8ae7a0d300-260807181806");
64110
64876
  await service.init();
64111
64877
  const pluginVersionStatus = service.pluginVersionStatus;
64112
64878
  if (pluginVersionStatus.unavailable) {
64113
64879
  throw new Error(
64114
- `@okxweb3/a2a-node v${"0.2.0"} is below the required minimum v${pluginVersionStatus.minVersion}`
64880
+ `@okxweb3/a2a-node v${"0.2.1-beta-8ae7a0d300-260807181806"} is below the required minimum v${pluginVersionStatus.minVersion}`
64115
64881
  );
64116
64882
  }
64117
64883
  const systemConfig = service.getSystemConfig();
@@ -64129,12 +64895,14 @@ async function runListenerWithLock(options, paths) {
64129
64895
  onchainosAgentId: "*",
64130
64896
  reason: "system-config missing sentryDsn",
64131
64897
  pluginId: "@okxweb3/a2a-node",
64132
- pluginVersion: "0.2.0"
64898
+ pluginVersion: "0.2.1-beta-8ae7a0d300-260807181806"
64133
64899
  });
64134
64900
  }
64135
64901
  logWithTimestamp(
64136
64902
  `[okx-agent-task] listener initialized, clients=${service.getClients().size}, home=${store.homeDir}`
64137
64903
  );
64904
+ void detectMessageEligibleOfflineReplaySupport().catch(() => {
64905
+ });
64138
64906
  const startupReplaySummary = await service.completeStartupReplay();
64139
64907
  logWithTimestamp(
64140
64908
  `[okx-agent-task] startup replay complete clients=${startupReplaySummary.clients} replayed=${startupReplaySummary.replayed} skipped=${startupReplaySummary.skipped} duration=${startupReplaySummary.durationMs}ms`
@@ -64272,7 +65040,7 @@ async function runListenerWithLock(options, paths) {
64272
65040
  );
64273
65041
  return;
64274
65042
  }
64275
- const replayResult = await timeSettled(() => service.replayOfflineMessages());
65043
+ const replayResult = await timeSettled(() => service.replayOfflineMessages("periodic_repair"));
64276
65044
  const replayMs = replayResult.durationMs;
64277
65045
  if (replayResult.error) {
64278
65046
  logger.error(LogEvent.OFFLINE_REPLAY_FAILED, replayResult.error instanceof Error ? replayResult.error : new Error(String(replayResult.error)), {
@@ -64949,6 +65717,7 @@ function summarizeXmtpTestEvents(runId, events) {
64949
65717
  const nodeDurations = /* @__PURE__ */ new Map();
64950
65718
  const replayLags = /* @__PURE__ */ new Map();
64951
65719
  const replayHandles = /* @__PURE__ */ new Map();
65720
+ const streamRecoveries = /* @__PURE__ */ new Map();
64952
65721
  const agentMap = /* @__PURE__ */ new Map();
64953
65722
  for (const event of events) {
64954
65723
  const key = deliveryKey(event);
@@ -64981,6 +65750,18 @@ function summarizeXmtpTestEvents(runId, events) {
64981
65750
  event.handlerTotalMs
64982
65751
  );
64983
65752
  }
65753
+ if (event.eventName === "Agent stream recovered") {
65754
+ setDuration(
65755
+ streamRecoveries,
65756
+ [
65757
+ eventAgentId(event),
65758
+ stringValue(event.clientGeneration) || "unknown-client",
65759
+ stringValue(event.recoverySequence) || "unknown-recovery",
65760
+ event.timestamp
65761
+ ].join(":"),
65762
+ event.recoveryDurationMs
65763
+ );
65764
+ }
64984
65765
  }
64985
65766
  const agents = [...agentMap.entries()].filter(([, value]) => value.live.size > 0 || value.replay.size > 0).map(([agentId, value]) => {
64986
65767
  const total = (/* @__PURE__ */ new Set([...value.live, ...value.replay])).size;
@@ -65002,7 +65783,9 @@ function summarizeXmtpTestEvents(runId, events) {
65002
65783
  duplicateEvents: countEvent(events, "Inbound ignored: duplicate message"),
65003
65784
  replayMessageFailures: countEvent(events, "Offline replay message failed"),
65004
65785
  replayScanFailures: countEvent(events, "Offline replay failed"),
65005
- streamFailures: countEvent(events, "conversations.stream failed") + countEvent(events, "Agent stream error"),
65786
+ streamFailures: countEvent(events, "conversations.stream failed") + countEvent(events, "agent stream error"),
65787
+ streamRecoveries: countEvent(events, "Agent stream recovered"),
65788
+ streamRecoveryTimeouts: countEvent(events, "Agent stream recovery timeout"),
65006
65789
  connectionFailures: countEvent(events, "XMTP connection failed"),
65007
65790
  successfulReplayScans: countEvent(events, "Address sync completed"),
65008
65791
  replayScansWithMessages: events.filter((event) => event.eventName === "Address sync completed" && numericValue(event.replayed) > 0).length,
@@ -65010,6 +65793,7 @@ function summarizeXmtpTestEvents(runId, events) {
65010
65793
  nodeHandlerMs: summarizeDurations([...nodeDurations.values()]),
65011
65794
  replayLagMs: summarizeDurations([...replayLags.values()]),
65012
65795
  replayHandleMs: summarizeDurations([...replayHandles.values()]),
65796
+ streamRecoveryMs: summarizeDurations([...streamRecoveries.values()]),
65013
65797
  agents
65014
65798
  };
65015
65799
  }
@@ -65024,6 +65808,8 @@ Reliability
65024
65808
  Replay message failures ${summary.replayMessageFailures}
65025
65809
  Replay scan failures ${summary.replayScanFailures}
65026
65810
  Stream failures ${summary.streamFailures}
65811
+ Stream recoveries ${summary.streamRecoveries}
65812
+ Recovery timeouts (>30s) ${summary.streamRecoveryTimeouts}
65027
65813
  XMTP connection failures ${summary.connectionFailures}
65028
65814
  Replay scans ${summary.successfulReplayScans}
65029
65815
  Scans with replay ${summary.replayScansWithMessages}
@@ -65032,7 +65818,8 @@ Timing
65032
65818
  XMTP delivery p50/p95/max ${formatDurationSummary(summary.xmtpDeliveryMs)}
65033
65819
  Node handler p50/p95/max ${formatDurationSummary(summary.nodeHandlerMs)}
65034
65820
  Replay lag p50/p95/max ${formatDurationSummary(summary.replayLagMs)}
65035
- Replay handler p50/p95/max ${formatDurationSummary(summary.replayHandleMs)}`);
65821
+ Replay handler p50/p95/max ${formatDurationSummary(summary.replayHandleMs)}
65822
+ Stream recovery p50/p95/max ${formatDurationSummary(summary.streamRecoveryMs)}`);
65036
65823
  if (summary.agents.length > 0) {
65037
65824
  console.log("\nAgents");
65038
65825
  for (const agent of summary.agents) {
@@ -65378,6 +66165,36 @@ init_ai_command();
65378
66165
  init_update_cli();
65379
66166
  init_win_spawn();
65380
66167
 
66168
+ // src/capabilities-cli.ts
66169
+ function getA2ACapabilities() {
66170
+ return {
66171
+ messageEligibleOfflineReplay: {
66172
+ ok: true,
66173
+ fixCommands: [],
66174
+ message: ""
66175
+ }
66176
+ };
66177
+ }
66178
+ var 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";
66179
+ function handleCapabilitiesCommand(args) {
66180
+ if (args.some((arg) => arg === "-h" || arg === "--help")) {
66181
+ process.stdout.write(CAPABILITIES_USAGE);
66182
+ return;
66183
+ }
66184
+ const unknown = args.filter((arg) => arg !== "--json");
66185
+ if (unknown.length > 0) {
66186
+ throw new Error(`Unknown capabilities option: ${unknown[0]}`);
66187
+ }
66188
+ const capabilities = getA2ACapabilities();
66189
+ if (args.includes("--json")) {
66190
+ process.stdout.write(`${JSON.stringify(capabilities)}
66191
+ `);
66192
+ return;
66193
+ }
66194
+ process.stdout.write(`${JSON.stringify(capabilities, null, 2)}
66195
+ `);
66196
+ }
66197
+
65381
66198
  // src/logs-export.ts
65382
66199
  var import_node_child_process10 = require("node:child_process");
65383
66200
  var import_node_fs27 = require("node:fs");
@@ -66698,7 +67515,7 @@ async function exportDiagnosticLogs(options) {
66698
67515
  node: process.version,
66699
67516
  platform: process.platform,
66700
67517
  arch: process.arch,
66701
- packageVersion: true ? "0.2.0" : "unknown",
67518
+ packageVersion: true ? "0.2.1-beta-8ae7a0d300-260807181806" : "unknown",
66702
67519
  sensitiveContentIncluded: options.includeSensitiveContent,
66703
67520
  listenerAndLlmContentIncluded: true,
66704
67521
  credentialsAlwaysRedacted: true,
@@ -68423,7 +69240,7 @@ async function runDoctor(options = {}) {
68423
69240
  platform: options.platform ?? process.platform,
68424
69241
  env: options.env ?? process.env,
68425
69242
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
68426
- cliVersion: options.cliVersion ?? (true ? "0.2.0" : "0.0.0"),
69243
+ cliVersion: options.cliVersion ?? (true ? "0.2.1-beta-8ae7a0d300-260807181806" : "0.0.0"),
68427
69244
  fixMode: options.fix === true,
68428
69245
  nonInteractive: options.nonInteractive === true,
68429
69246
  packageChanged: false,
@@ -68657,6 +69474,8 @@ init_autostart_windows();
68657
69474
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS,
68658
69475
  InboundReplayGate,
68659
69476
  InvalidXmtpMessageStore,
69477
+ MESSAGE_ELIGIBLE_OFFLINE_REPLAY_FLAG,
69478
+ MESSAGE_ELIGIBLE_OFFLINE_REPLAY_HELP_TOKEN,
68660
69479
  NATIVE_LAUNCHER_EXE_NAME,
68661
69480
  OPENCLAW_GATEWAY_ROUTE_GROUP_ID,
68662
69481
  SYSTEM_NOTIFICATION_SESSION_KEY,
@@ -68674,6 +69493,7 @@ init_autostart_windows();
68674
69493
  XMTP_CLIENT_RECYCLE_INTERVAL_ENV,
68675
69494
  XmtpDm,
68676
69495
  XmtpGroup,
69496
+ XmtpService,
68677
69497
  acquireDaemonLock,
68678
69498
  activeDaemonPointerPath,
68679
69499
  aiRunSentryExtra,
@@ -68694,6 +69514,7 @@ init_autostart_windows();
68694
69514
  buildInboundTerminalExtras,
68695
69515
  buildLaunchdPlist,
68696
69516
  buildLauncherEntryJs,
69517
+ buildMessageEligibleArgs,
68697
69518
  buildPendingConversationBackupDispatch,
68698
69519
  buildSchtasksCreateArgs,
68699
69520
  buildSchtasksDeleteArgs,
@@ -68725,6 +69546,7 @@ init_autostart_windows();
68725
69546
  detectAiProviders,
68726
69547
  detectCurrentAiProvider,
68727
69548
  detectGatewayInvocation,
69549
+ detectMessageEligibleOfflineReplaySupport,
68728
69550
  detectRuntime,
68729
69551
  ensureDaemonReady,
68730
69552
  ensureDefaultAiProvider,
@@ -68738,9 +69560,11 @@ init_autostart_windows();
68738
69560
  findWindowsNativeOkxA2aExe,
68739
69561
  followFile,
68740
69562
  formatDoctorReportForHumans,
69563
+ getA2ACapabilities,
68741
69564
  getDaemonStatus,
68742
69565
  getHermesGatewayPluginStatus,
68743
69566
  getSessionBusyTracker,
69567
+ handleCapabilitiesCommand,
68744
69568
  handleDoctorCommand,
68745
69569
  handleXmtpDebugCommand,
68746
69570
  handleXmtpSendCommand,
@@ -68754,6 +69578,7 @@ init_autostart_windows();
68754
69578
  isDefaultTaskHome,
68755
69579
  isGatewayAvailableForHeartbeat,
68756
69580
  isHermesGatewayPluginEnabled,
69581
+ isOfflineReplayForTrigger,
68757
69582
  isOpenClawA2aPluginAvailable,
68758
69583
  isOpenClawGatewayAvailable,
68759
69584
  isOriginalParentProcessAlive,
@@ -68765,6 +69590,8 @@ init_autostart_windows();
68765
69590
  launchdPlistHasCurrentRestartPolicy,
68766
69591
  logWinCompat,
68767
69592
  logXmtpMessageSent,
69593
+ maybeRecommendOnchainosUpgrade,
69594
+ messageEligibleHelpAdvertisesOfflineReplay,
68768
69595
  nodeVersionSatisfies,
68769
69596
  normalizeAiProvider,
68770
69597
  normalizeHermesOkxA2aPluginConfig,
@@ -68790,6 +69617,7 @@ init_autostart_windows();
68790
69617
  removeUserAttentionWatcher,
68791
69618
  reportDoctorRunToSentry,
68792
69619
  reportUserNotificationFailure,
69620
+ resetMessageEligibleOfflineReplayCapabilityForTests,
68793
69621
  resetResolvedOnchainosBinForTests,
68794
69622
  resolveAiPermissionPreset,
68795
69623
  resolveAiProviderCommand,
@@ -68801,6 +69629,7 @@ init_autostart_windows();
68801
69629
  resolveDoctorTarget,
68802
69630
  resolveEnvelopePayload,
68803
69631
  resolveNpmCmdShimScript,
69632
+ resolveOfflineReplayCapabilityMarker,
68804
69633
  resolveOfflineReplayIntervalSec,
68805
69634
  resolveOpenClawGatewayConfig,
68806
69635
  resolveOpenClawGatewayRoute,