@okxweb3/a2a-node 0.2.3 → 0.2.4-beta-e7a52faf6f-260812104443

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 +384 -109
  2. package/dist/index.js +370 -98
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -26604,7 +26604,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
26604
26604
  client: {
26605
26605
  id: "gateway-client",
26606
26606
  displayName: "okx-a2a-node",
26607
- version: "0.2.3",
26607
+ version: "0.2.4-beta-e7a52faf6f-260812104443",
26608
26608
  platform: "node",
26609
26609
  mode: "backend",
26610
26610
  instanceId
@@ -26615,7 +26615,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
26615
26615
  commands: [],
26616
26616
  permissions: {},
26617
26617
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
26618
- userAgent: `okx-a2a-node/${"0.2.3"}`,
26618
+ userAgent: `okx-a2a-node/${"0.2.4-beta-e7a52faf6f-260812104443"}`,
26619
26619
  auth: {
26620
26620
  ...config.token ? { token: config.token } : {},
26621
26621
  ...config.password ? { password: config.password } : {}
@@ -28499,7 +28499,7 @@ var init_sentry_config = __esm({
28499
28499
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
28500
28500
  SENTRY_CONFIG = {
28501
28501
  projectName: "okx/openclaw-okx-a2a-extension",
28502
- release: "0.2.3",
28502
+ release: "0.2.4-beta-e7a52faf6f-260812104443",
28503
28503
  environment,
28504
28504
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
28505
28505
  };
@@ -39669,7 +39669,7 @@ async function exportDiagnosticLogs(options) {
39669
39669
  node: process.version,
39670
39670
  platform: process.platform,
39671
39671
  arch: process.arch,
39672
- packageVersion: true ? "0.2.3" : "unknown",
39672
+ packageVersion: true ? "0.2.4-beta-e7a52faf6f-260812104443" : "unknown",
39673
39673
  sensitiveContentIncluded: options.includeSensitiveContent,
39674
39674
  listenerAndLlmContentIncluded: true,
39675
39675
  credentialsAlwaysRedacted: true,
@@ -56122,6 +56122,69 @@ var init_installation_prune = __esm({
56122
56122
  }
56123
56123
  });
56124
56124
 
56125
+ // ../core/src/xmtp-sdk/onchainos/session-expired.ts
56126
+ function isSessionExpired(stdout) {
56127
+ try {
56128
+ const res = JSON.parse(stdout);
56129
+ return res.ok === false && SESSION_EXPIRED_RE.test(String(res.error ?? ""));
56130
+ } catch {
56131
+ return SESSION_EXPIRED_RE.test(stdout);
56132
+ }
56133
+ }
56134
+ function classifyOnchainosAuthError(err2) {
56135
+ if (!err2) {
56136
+ return null;
56137
+ }
56138
+ const values = [String(err2)];
56139
+ if (typeof err2 === "object") {
56140
+ const anyErr = err2;
56141
+ values.push(anyErr.stdout, anyErr.stderr, anyErr.message);
56142
+ }
56143
+ for (const value of values) {
56144
+ if (typeof value !== "string") {
56145
+ continue;
56146
+ }
56147
+ for (const [reason, pattern] of AUTH_FAILURE_PATTERNS) {
56148
+ if (pattern.test(value)) {
56149
+ return reason;
56150
+ }
56151
+ }
56152
+ }
56153
+ return null;
56154
+ }
56155
+ function isOnchainosAuthError(err2) {
56156
+ return classifyOnchainosAuthError(err2) !== null;
56157
+ }
56158
+ function reportSessionExpired(command) {
56159
+ logger.error(
56160
+ LogEvent.ONCHAINOS_SESSION_EXPIRED,
56161
+ new Error("onchainos session expired"),
56162
+ {
56163
+ component: "onchainos_cli",
56164
+ source: "onchainos",
56165
+ stage: "session_expired",
56166
+ operation: command,
56167
+ command,
56168
+ communicationClass: "onchainos_auth_issue",
56169
+ reason: "session_expired"
56170
+ }
56171
+ );
56172
+ }
56173
+ var SESSION_EXPIRED_RE, AUTH_FAILURE_PATTERNS;
56174
+ var init_session_expired = __esm({
56175
+ "../core/src/xmtp-sdk/onchainos/session-expired.ts"() {
56176
+ "use strict";
56177
+ init_sentry_logger();
56178
+ SESSION_EXPIRED_RE = /session expired/i;
56179
+ AUTH_FAILURE_PATTERNS = [
56180
+ ["session_expired", /session expired/i],
56181
+ ["not_logged_in", /not logged in/i],
56182
+ ["login_required", /login required/i],
56183
+ ["authentication_required", /authentication required/i]
56184
+ ];
56185
+ }
56186
+ });
56187
+
56125
56188
  // ../core/src/xmtp-sdk/index.ts
56126
56189
  function cachePath(dataDir, fileName) {
56127
56190
  return (0, import_node_path26.join)(dataDir, fileName);
@@ -56149,21 +56212,6 @@ function stringifyJsonForLog(value) {
56149
56212
  function ensureCacheDir(dataDir) {
56150
56213
  ensureA2aTaskDir(dataDir);
56151
56214
  }
56152
- function isSessionExpiredError(err2) {
56153
- if (!err2) {
56154
- return false;
56155
- }
56156
- if (typeof err2 === "object") {
56157
- const anyErr = err2;
56158
- if (typeof anyErr.stdout === "string" && SESSION_EXPIRED_RE.test(anyErr.stdout)) {
56159
- return true;
56160
- }
56161
- if (typeof anyErr.message === "string" && SESSION_EXPIRED_RE.test(anyErr.message)) {
56162
- return true;
56163
- }
56164
- }
56165
- return SESSION_EXPIRED_RE.test(String(err2));
56166
- }
56167
56215
  function loadSensitiveWordsFromCache(dataDir) {
56168
56216
  try {
56169
56217
  const path2 = cachePath(dataDir, "sensitive-words.json");
@@ -56541,7 +56589,7 @@ function createOfflineReplayAddressSummary(address) {
56541
56589
  durationMs: 0
56542
56590
  };
56543
56591
  }
56544
- var import_node_fs22, import_node_path26, DEFAULT_DATA_DIR, XMTP_INSTALLATION_WARNING_THRESHOLD, XMTP_INSTALLATION_NEAR_LIMIT_THRESHOLD, REPLAY_SCAN_INITIAL_BACKOFF_MS, REPLAY_SCAN_MAX_BACKOFF_MS, 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;
56592
+ var import_node_fs22, import_node_path26, DEFAULT_DATA_DIR, XMTP_INSTALLATION_WARNING_THRESHOLD, XMTP_INSTALLATION_NEAR_LIMIT_THRESHOLD, REPLAY_SCAN_INITIAL_BACKOFF_MS, REPLAY_SCAN_MAX_BACKOFF_MS, SENSITIVE_WORDS_LAZY_RETRY_COOLDOWN_MS, 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;
56545
56593
  var init_xmtp_sdk = __esm({
56546
56594
  "../core/src/xmtp-sdk/index.ts"() {
56547
56595
  "use strict";
@@ -56559,13 +56607,13 @@ var init_xmtp_sdk = __esm({
56559
56607
  init_concurrency();
56560
56608
  init_a2a_paths();
56561
56609
  init_installation_prune();
56610
+ init_session_expired();
56562
56611
  DEFAULT_DATA_DIR = resolveA2aTaskPaths().xmtpDir;
56563
56612
  XMTP_INSTALLATION_WARNING_THRESHOLD = 2;
56564
56613
  XMTP_INSTALLATION_NEAR_LIMIT_THRESHOLD = 8;
56565
56614
  REPLAY_SCAN_INITIAL_BACKOFF_MS = 3e4;
56566
56615
  REPLAY_SCAN_MAX_BACKOFF_MS = 5 * 6e4;
56567
56616
  SENSITIVE_WORDS_LAZY_RETRY_COOLDOWN_MS = 10 * 60 * 1e3;
56568
- SESSION_EXPIRED_RE = /session expired/i;
56569
56617
  InboundReplayGate = class {
56570
56618
  constructor(inbound) {
56571
56619
  this.inbound = inbound;
@@ -57131,12 +57179,14 @@ var init_xmtp_sdk = __esm({
57131
57179
  newAgents = agentList.agents;
57132
57180
  changed = agentList.changed;
57133
57181
  } catch (err2) {
57134
- if (isSessionExpiredError(err2)) {
57182
+ const authFailureReason = classifyOnchainosAuthError(err2);
57183
+ if (authFailureReason) {
57135
57184
  logWithTimestamp(
57136
- "[xmtp-sdk] refresh: onchainos session expired, taking all local clients offline"
57185
+ `[xmtp-sdk] refresh: onchainos auth unavailable (${authFailureReason}), taking all local clients offline`
57137
57186
  );
57138
57187
  logger.info(LogEvent.AGENT_REFRESH_SESSION_EXPIRED, {
57139
57188
  onchainosAgentId: "*",
57189
+ authFailureReason,
57140
57190
  previousClientCount: String(this.clients.size),
57141
57191
  previousAgentCount: String(this.allAgents.length),
57142
57192
  previousAgentIds: this.allAgents.map((a) => a.agentId).join(","),
@@ -58704,39 +58754,6 @@ var init_cli_response = __esm({
58704
58754
  }
58705
58755
  });
58706
58756
 
58707
- // ../core/src/xmtp-sdk/onchainos/session-expired.ts
58708
- function isSessionExpired(stdout) {
58709
- try {
58710
- const res = JSON.parse(stdout);
58711
- return res.ok === false && SESSION_EXPIRED_RE2.test(String(res.error ?? ""));
58712
- } catch {
58713
- return SESSION_EXPIRED_RE2.test(stdout);
58714
- }
58715
- }
58716
- function reportSessionExpired(command) {
58717
- logger.error(
58718
- LogEvent.ONCHAINOS_SESSION_EXPIRED,
58719
- new Error("onchainos session expired"),
58720
- {
58721
- component: "onchainos_cli",
58722
- source: "onchainos",
58723
- stage: "session_expired",
58724
- operation: command,
58725
- command,
58726
- communicationClass: "onchainos_auth_issue",
58727
- reason: "session_expired"
58728
- }
58729
- );
58730
- }
58731
- var SESSION_EXPIRED_RE2;
58732
- var init_session_expired = __esm({
58733
- "../core/src/xmtp-sdk/onchainos/session-expired.ts"() {
58734
- "use strict";
58735
- init_sentry_logger();
58736
- SESSION_EXPIRED_RE2 = /session expired/i;
58737
- }
58738
- });
58739
-
58740
58757
  // ../core/src/xmtp-sdk/onchainos/offline-replay-capability.ts
58741
58758
  function messageEligibleHelpAdvertisesOfflineReplay(helpText) {
58742
58759
  if (typeof helpText !== "string" || helpText.length === 0) {
@@ -58904,6 +58921,49 @@ var init_system_config = __esm({
58904
58921
  }
58905
58922
  });
58906
58923
 
58924
+ // ../core/src/xmtp-sdk/onchainos/wallet-status.ts
58925
+ function parseWalletLoginStatus(stdout) {
58926
+ const response = parseCliJson(stdout, "wallet-status");
58927
+ if (!response.ok) {
58928
+ const error = new Error("onchainos wallet status failed");
58929
+ logger.error(
58930
+ LogEvent.ONCHAINOS_CLI_ERROR,
58931
+ error,
58932
+ cliOkFalseExtras(stdout, "wallet-status")
58933
+ );
58934
+ throw error;
58935
+ }
58936
+ if (typeof response.data?.loggedIn !== "boolean") {
58937
+ const error = new Error("onchainos wallet status returned invalid loggedIn state");
58938
+ logger.error(LogEvent.ONCHAINOS_CLI_ERROR, error, {
58939
+ component: "onchainos_cli",
58940
+ source: "onchainos",
58941
+ stage: "response_validation",
58942
+ communicationClass: "onchainos_cli_or_api_issue",
58943
+ operation: "wallet-status",
58944
+ reason: "invalid_logged_in_state"
58945
+ });
58946
+ throw error;
58947
+ }
58948
+ return response.data.loggedIn;
58949
+ }
58950
+ async function queryWalletLoginStatus() {
58951
+ logWithTimestamp("[onchainos] checking wallet login status");
58952
+ const { stdout } = await exec(["wallet", "status"]);
58953
+ const loggedIn = parseWalletLoginStatus(stdout);
58954
+ logWithTimestamp(`[onchainos] wallet login status: ${loggedIn ? "logged in" : "logged out"}`);
58955
+ return loggedIn;
58956
+ }
58957
+ var init_wallet_status = __esm({
58958
+ "../core/src/xmtp-sdk/onchainos/wallet-status.ts"() {
58959
+ "use strict";
58960
+ init_log();
58961
+ init_sentry_logger();
58962
+ init_bin();
58963
+ init_cli_response();
58964
+ }
58965
+ });
58966
+
58907
58967
  // ../core/src/xmtp-sdk/onchainos/index.ts
58908
58968
  function messageEligibleUnavailableError(input) {
58909
58969
  const lines = [
@@ -59030,7 +59090,10 @@ async function fetchAgentPage(page) {
59030
59090
  );
59031
59091
  if (!res.ok) {
59032
59092
  guardSessionExpired2(stdout, "list-agents");
59033
- const parseErr = new Error("onchainos agent get failed");
59093
+ const detail = typeof res.error === "string" ? res.error.trim() : "";
59094
+ const parseErr = new Error(
59095
+ detail ? `onchainos agent get failed: ${detail}` : "onchainos agent get failed"
59096
+ );
59034
59097
  logger.error(
59035
59098
  LogEvent.ONCHAINOS_CLI_ERROR,
59036
59099
  parseErr,
@@ -59388,6 +59451,7 @@ var init_onchainos = __esm({
59388
59451
  init_cli_response();
59389
59452
  init_session_expired();
59390
59453
  init_system_config();
59454
+ init_wallet_status();
59391
59455
  MESSAGE_ELIGIBILITY_SERVICE_UNAVAILABLE_AI_HINT = "Message eligibility service is temporarily unavailable. Please try again later.";
59392
59456
  lastExpiredNotifyAt = 0;
59393
59457
  EXPIRED_NOTIFY_COOLDOWN_MS = 5 * 60 * 1e3;
@@ -64725,7 +64789,7 @@ async function handleDaemonCommand(command, params) {
64725
64789
  const payload = await handleXmtpDebugCommand(command, params.service);
64726
64790
  return { ok: true, payload };
64727
64791
  }
64728
- const refresh = await params.service.refreshAgents();
64792
+ const refresh = await (params.refreshAgents?.() ?? params.service.refreshAgents());
64729
64793
  return {
64730
64794
  ok: true,
64731
64795
  payload: {
@@ -67003,9 +67067,14 @@ var init_user_attention_watchers = __esm({
67003
67067
  // src/listener.ts
67004
67068
  var listener_exports = {};
67005
67069
  __export(listener_exports, {
67070
+ AUTH_RECOVERY_PROBE_INTERVAL_MS: () => AUTH_RECOVERY_PROBE_INTERVAL_MS,
67071
+ AgentRefreshAuthGate: () => AgentRefreshAuthGate,
67072
+ AgentRefreshCoordinator: () => AgentRefreshCoordinator,
67006
67073
  DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC: () => DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC,
67007
67074
  DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC: () => DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC,
67008
67075
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS: () => HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS,
67076
+ ListenerDeferredTaskScheduler: () => ListenerDeferredTaskScheduler,
67077
+ ListenerMaintenanceGate: () => ListenerMaintenanceGate,
67009
67078
  XMTP_CLIENT_RECYCLE_INTERVAL_ENV: () => XMTP_CLIENT_RECYCLE_INTERVAL_ENV,
67010
67079
  getHermesGatewayPluginStatus: () => getHermesGatewayPluginStatus,
67011
67080
  hermesConfigEnablesOkxA2a: () => hermesConfigEnablesOkxA2a,
@@ -67013,8 +67082,15 @@ __export(listener_exports, {
67013
67082
  isHermesGatewayPluginEnabled: () => isHermesGatewayPluginEnabled,
67014
67083
  resolveOfflineReplayIntervalSec: () => resolveOfflineReplayIntervalSec,
67015
67084
  resolveXmtpClientRecycleIntervalSec: () => resolveXmtpClientRecycleIntervalSec,
67085
+ runHeartbeatRefreshCycle: () => runHeartbeatRefreshCycle,
67016
67086
  runListener: () => runListener
67017
67087
  });
67088
+ async function runHeartbeatRefreshCycle(options) {
67089
+ const heartbeat = await timeSettled(options.heartbeat);
67090
+ const refreshAllowed = heartbeat.value === "performed" || heartbeat.value === "refresh_safe";
67091
+ const refresh = options.refresh && refreshAllowed ? await timeSettled(options.refresh) : void 0;
67092
+ return { heartbeat, refresh };
67093
+ }
67018
67094
  function resolveOfflineReplayIntervalSec(systemConfig) {
67019
67095
  const interval = systemConfig.offlineReplayInterval;
67020
67096
  if (typeof interval !== "number" || !Number.isFinite(interval) || interval <= 0) {
@@ -67212,10 +67288,25 @@ async function runListenerWithLock(options, paths) {
67212
67288
  }
67213
67289
  });
67214
67290
  logWithTimestamp("[okx-agent-task] user_attention watcher coordinator scanning SQLite watcher state");
67291
+ let stopping = false;
67215
67292
  const service = XmtpService.getInstance();
67293
+ const agentRefreshCoordinator = new AgentRefreshCoordinator({
67294
+ lookup: (previousFingerprint) => listAllAgentsWithMetadata(previousFingerprint),
67295
+ refresh: () => service.refreshAgents(),
67296
+ checkLoggedIn: () => queryWalletLoginStatus(),
67297
+ isStopping: () => stopping,
67298
+ onAuthBlocked: () => {
67299
+ logWithTimestamp(
67300
+ "[okx-agent-task] agent refresh auth blocked; periodic agent get paused. Wallet login recovery will be checked every 60s; run `okx-a2a agent refresh` to recover immediately"
67301
+ );
67302
+ },
67303
+ onAuthRecovered: () => {
67304
+ logWithTimestamp("[okx-agent-task] agent refresh auth recovered; periodic refresh resumed");
67305
+ }
67306
+ });
67216
67307
  service.setDataDir(paths.xmtpDir);
67217
67308
  service.setTools({
67218
- listAllAgentsWithMetadata: async (previousFingerprint) => listAllAgentsWithMetadata(previousFingerprint),
67309
+ listAllAgentsWithMetadata: (previousFingerprint) => agentRefreshCoordinator.lookup(previousFingerprint),
67219
67310
  fetchSensitiveWords: async () => fetchSensitiveWords(),
67220
67311
  fetchSystemConfig: async () => fetchSystemConfig({
67221
67312
  notifySessionExpired: (command) => service.tools.notifySessionExpired?.(command)
@@ -67242,7 +67333,7 @@ async function runListenerWithLock(options, paths) {
67242
67333
  }
67243
67334
  }),
67244
67335
  notifySessionExpired: (command) => {
67245
- const rawText = `The onchainos login session has expired while running ${command}. Run \`onchainos wallet login\` and restart \`okx-a2a\`.`;
67336
+ const rawText = `The onchainos login session has expired while running ${command}. Run \`onchainos wallet login\`; A2A will recover within 60 seconds, or run \`okx-a2a agent refresh\` to recover immediately.`;
67246
67337
  void store.appendBackup(buildSystemStoredMessage({
67247
67338
  reason: "onchainos-session-expired",
67248
67339
  rawText,
@@ -67270,12 +67361,12 @@ async function runListenerWithLock(options, paths) {
67270
67361
  });
67271
67362
  }
67272
67363
  });
67273
- service.setPluginVersion("0.2.3");
67364
+ service.setPluginVersion("0.2.4-beta-e7a52faf6f-260812104443");
67274
67365
  await service.init();
67275
67366
  const pluginVersionStatus = service.pluginVersionStatus;
67276
67367
  if (pluginVersionStatus.unavailable) {
67277
67368
  throw new Error(
67278
- `@okxweb3/a2a-node v${"0.2.3"} is below the required minimum v${pluginVersionStatus.minVersion}`
67369
+ `@okxweb3/a2a-node v${"0.2.4-beta-e7a52faf6f-260812104443"} is below the required minimum v${pluginVersionStatus.minVersion}`
67279
67370
  );
67280
67371
  }
67281
67372
  const systemConfig = service.getSystemConfig();
@@ -67293,7 +67384,7 @@ async function runListenerWithLock(options, paths) {
67293
67384
  onchainosAgentId: "*",
67294
67385
  reason: "system-config missing sentryDsn",
67295
67386
  pluginId: "@okxweb3/a2a-node",
67296
- pluginVersion: "0.2.3"
67387
+ pluginVersion: "0.2.4-beta-e7a52faf6f-260812104443"
67297
67388
  });
67298
67389
  }
67299
67390
  logWithTimestamp(
@@ -67305,37 +67396,56 @@ async function runListenerWithLock(options, paths) {
67305
67396
  logWithTimestamp(
67306
67397
  `[okx-agent-task] startup replay complete clients=${startupReplaySummary.clients} replayed=${startupReplaySummary.replayed} skipped=${startupReplaySummary.skipped} duration=${startupReplaySummary.durationMs}ms`
67307
67398
  );
67308
- let maintenanceInFlight = null;
67309
67399
  let lastRecycleReplayAtMs = 0;
67310
- let xmtpRecyclePending = false;
67311
- const tryEnterMaintenanceTask = (task) => {
67312
- if (task !== "xmtp_recycle" && xmtpRecyclePending) {
67313
- return false;
67314
- }
67315
- if (maintenanceInFlight) {
67316
- return false;
67400
+ const maintenanceGate = new ListenerMaintenanceGate();
67401
+ const deferredMaintenanceTasks = new ListenerDeferredTaskScheduler();
67402
+ const leaveMaintenanceTask = (task) => {
67403
+ const pendingTask = maintenanceGate.leave(task);
67404
+ if (stopping) {
67405
+ return;
67317
67406
  }
67318
- if (task === "xmtp_recycle") {
67319
- xmtpRecyclePending = false;
67407
+ if (pendingTask === "xmtp_recycle") {
67408
+ deferredMaintenanceTasks.schedule(() => void xmtpRecycleTick());
67409
+ } else if (pendingTask === "offline_replay") {
67410
+ deferredMaintenanceTasks.schedule(() => void offlineReplayTick());
67320
67411
  }
67321
- maintenanceInFlight = task;
67322
- return true;
67323
67412
  };
67324
- const leaveMaintenanceTask = (task) => {
67325
- if (maintenanceInFlight === task) {
67326
- maintenanceInFlight = null;
67327
- if (xmtpRecyclePending && !stopping) {
67328
- setTimeout(() => void xmtpRecycleTick(), 0);
67413
+ const syncTick = async () => {
67414
+ if (stopping || agentRefreshCoordinator.blocked) {
67415
+ return;
67416
+ }
67417
+ if (!maintenanceGate.tryEnter("sync")) {
67418
+ return;
67419
+ }
67420
+ const tickStartedAt = Date.now();
67421
+ try {
67422
+ const refresh = await agentRefreshCoordinator.runScheduled();
67423
+ if (!refresh) {
67424
+ return;
67329
67425
  }
67426
+ logWithTimestamp(
67427
+ `[okx-agent-task] sync tick agents=${refresh.agentCount} changed=${refresh.changed ? "yes" : "no"} added=${refresh.added.length} removed=${refresh.removed.length}`
67428
+ );
67429
+ } catch (err2) {
67430
+ logger.error(LogEvent.DAEMON_TICK_FAILED, err2 instanceof Error ? err2 : new Error(String(err2)), {
67431
+ component: "node_listener",
67432
+ stage: "sync_tick",
67433
+ durationMs: String(Date.now() - tickStartedAt),
67434
+ reason: "sync_tick_exception"
67435
+ });
67436
+ logWithTimestamp(`[okx-agent-task] sync tick failed total=${Date.now() - tickStartedAt}ms:`, err2);
67437
+ } finally {
67438
+ leaveMaintenanceTask("sync");
67330
67439
  }
67331
67440
  };
67332
- const syncTick = async () => {
67333
- if (!tryEnterMaintenanceTask("sync")) {
67334
- return;
67441
+ let heartbeatInFlight = false;
67442
+ const heartbeatTick = async () => {
67443
+ if (heartbeatInFlight || stopping) {
67444
+ return "blocked";
67335
67445
  }
67446
+ heartbeatInFlight = true;
67336
67447
  const tickStartedAt = Date.now();
67337
67448
  try {
67338
- const refresh = await service.refreshAgents();
67339
67449
  const hasActiveAgents = service.getClients().size > 0;
67340
67450
  let shouldHeartbeat = hasActiveAgents;
67341
67451
  const heartbeatProvider = resolveConfiguredAiProvider({ store: sessionStore }) ?? detectGatewayInvocation();
@@ -67350,7 +67460,7 @@ async function runListenerWithLock(options, paths) {
67350
67460
  new Error(`heartbeat gateway check failed: provider=hermes reason=${hermesStatus.reason}`),
67351
67461
  {
67352
67462
  component: "node_listener",
67353
- stage: "sync_tick",
67463
+ stage: "heartbeat_tick",
67354
67464
  reason: hermesStatus.reason ?? "unknown",
67355
67465
  provider: "hermes",
67356
67466
  hermesHome: hermesStatus.hermesHome,
@@ -67374,7 +67484,7 @@ async function runListenerWithLock(options, paths) {
67374
67484
  new Error(`heartbeat skipped: provider=${heartbeatProvider ?? "(not set)"} gateway unavailable`),
67375
67485
  {
67376
67486
  component: "node_listener",
67377
- stage: "sync_tick",
67487
+ stage: "heartbeat_tick",
67378
67488
  reason: "gateway_unavailable",
67379
67489
  provider: heartbeatProvider ?? "(not set)",
67380
67490
  chainIndex: ONCHAINOS_CHAIN_INDEX,
@@ -67390,7 +67500,7 @@ async function runListenerWithLock(options, paths) {
67390
67500
  new Error("heartbeat skipped: no active agents"),
67391
67501
  {
67392
67502
  component: "node_listener",
67393
- stage: "sync_tick",
67503
+ stage: "heartbeat_tick",
67394
67504
  reason: "no_active_agents",
67395
67505
  provider: heartbeatProvider ?? "(not set)",
67396
67506
  chainIndex: ONCHAINOS_CHAIN_INDEX
@@ -67403,7 +67513,7 @@ async function runListenerWithLock(options, paths) {
67403
67513
  if (shouldHeartbeat && heartbeatResult.error) {
67404
67514
  logger.error(LogEvent.HEARTBEAT_FAILED, heartbeatResult.error instanceof Error ? heartbeatResult.error : new Error(String(heartbeatResult.error)), {
67405
67515
  component: "node_listener",
67406
- stage: "sync_tick",
67516
+ stage: "heartbeat_tick",
67407
67517
  reason: heartbeatResult.error instanceof Error ? heartbeatResult.error.name : "unknown_error",
67408
67518
  durationMs: String(heartbeatMs),
67409
67519
  chainIndex: ONCHAINOS_CHAIN_INDEX,
@@ -67411,23 +67521,27 @@ async function runListenerWithLock(options, paths) {
67411
67521
  });
67412
67522
  logWithTimestamp(`[okx-agent-task] heartbeat failed duration=${heartbeatMs}ms:`, heartbeatResult.error);
67413
67523
  }
67414
- logWithTimestamp(
67415
- `[okx-agent-task] sync tick agents=${refresh.agentCount} changed=${refresh.changed ? "yes" : "no"} added=${refresh.added.length} removed=${refresh.removed.length}`
67416
- );
67524
+ return shouldHeartbeat ? "performed" : "refresh_safe";
67417
67525
  } catch (err2) {
67418
- logger.error(LogEvent.DAEMON_TICK_FAILED, err2 instanceof Error ? err2 : new Error(String(err2)), {
67526
+ logger.error(LogEvent.HEARTBEAT_FAILED, err2 instanceof Error ? err2 : new Error(String(err2)), {
67419
67527
  component: "node_listener",
67420
- stage: "sync_tick",
67528
+ stage: "heartbeat_tick",
67421
67529
  durationMs: String(Date.now() - tickStartedAt),
67422
- reason: "sync_tick_exception"
67530
+ reason: "heartbeat_tick_exception",
67531
+ chainIndex: ONCHAINOS_CHAIN_INDEX,
67532
+ communicationClass: "onchainos_or_network"
67423
67533
  });
67424
- logWithTimestamp(`[okx-agent-task] sync tick failed total=${Date.now() - tickStartedAt}ms:`, err2);
67534
+ logWithTimestamp(`[okx-agent-task] heartbeat tick failed total=${Date.now() - tickStartedAt}ms:`, err2);
67535
+ return "blocked";
67425
67536
  } finally {
67426
- leaveMaintenanceTask("sync");
67537
+ heartbeatInFlight = false;
67427
67538
  }
67428
67539
  };
67429
67540
  const offlineReplayTick = async () => {
67430
- if (!tryEnterMaintenanceTask("offline_replay")) {
67541
+ if (stopping) {
67542
+ return;
67543
+ }
67544
+ if (!maintenanceGate.tryEnter("offline_replay")) {
67431
67545
  return;
67432
67546
  }
67433
67547
  const tickStartedAt = Date.now();
@@ -67469,8 +67583,10 @@ async function runListenerWithLock(options, paths) {
67469
67583
  }
67470
67584
  };
67471
67585
  const xmtpRecycleTick = async () => {
67472
- if (!tryEnterMaintenanceTask("xmtp_recycle")) {
67473
- xmtpRecyclePending = true;
67586
+ if (stopping) {
67587
+ return;
67588
+ }
67589
+ if (!maintenanceGate.tryEnter("xmtp_recycle")) {
67474
67590
  return;
67475
67591
  }
67476
67592
  const tickStartedAt = Date.now();
@@ -67514,7 +67630,6 @@ async function runListenerWithLock(options, paths) {
67514
67630
  let timer;
67515
67631
  let offlineReplayTimer;
67516
67632
  let xmtpClientRecycleTimer;
67517
- let stopping = false;
67518
67633
  const scheduleSyncTimer = (nextIntervalSec) => {
67519
67634
  if (stopping) {
67520
67635
  return;
@@ -67525,7 +67640,10 @@ async function runListenerWithLock(options, paths) {
67525
67640
  intervalSec = Math.max(10, nextIntervalSec);
67526
67641
  logWithTimestamp(`[okx-agent-task] scheduled sync interval ${intervalSec}s`);
67527
67642
  timer = setInterval(() => {
67528
- void syncTick();
67643
+ void runHeartbeatRefreshCycle({
67644
+ heartbeat: heartbeatTick,
67645
+ refresh: syncTick
67646
+ });
67529
67647
  }, intervalSec * 1e3);
67530
67648
  };
67531
67649
  const scheduleOfflineReplayTimer = (nextIntervalSec) => {
@@ -67607,9 +67725,32 @@ async function runListenerWithLock(options, paths) {
67607
67725
  const systemConfigRefreshTimer = setInterval(() => {
67608
67726
  void refreshSystemConfigTick();
67609
67727
  }, 60 * 60 * 1e3);
67610
- void syncTick();
67728
+ const authRecoveryProbeTimer = setInterval(() => {
67729
+ if (stopping || !agentRefreshCoordinator.blocked) {
67730
+ return;
67731
+ }
67732
+ void agentRefreshCoordinator.runRecoveryProbe().then((refresh) => {
67733
+ if (!refresh) {
67734
+ return;
67735
+ }
67736
+ logWithTimestamp(
67737
+ `[okx-agent-task] auth recovery refresh agents=${refresh.agentCount} added=${refresh.added.length} removed=${refresh.removed.length}`
67738
+ );
67739
+ }).catch((err2) => {
67740
+ logger.error(LogEvent.DAEMON_TICK_FAILED, err2 instanceof Error ? err2 : new Error(String(err2)), {
67741
+ component: "node_listener",
67742
+ stage: "auth_recovery_probe",
67743
+ reason: "auth_recovery_probe_failed"
67744
+ });
67745
+ logWithTimestamp("[okx-agent-task] auth recovery probe failed:", err2);
67746
+ });
67747
+ }, AUTH_RECOVERY_PROBE_INTERVAL_MS);
67748
+ void runHeartbeatRefreshCycle({
67749
+ heartbeat: heartbeatTick
67750
+ });
67611
67751
  const commandProcessor = startCommandProcessor({
67612
67752
  service,
67753
+ refreshAgents: () => agentRefreshCoordinator.runManual(),
67613
67754
  store,
67614
67755
  sessionStore,
67615
67756
  homeDir: options.homeDir
@@ -67628,6 +67769,7 @@ async function runListenerWithLock(options, paths) {
67628
67769
  resolve14();
67629
67770
  }, SHUTDOWN_FORCE_RESOLVE_MS);
67630
67771
  void (async () => {
67772
+ deferredMaintenanceTasks.cancelAll();
67631
67773
  if (timer) {
67632
67774
  clearInterval(timer);
67633
67775
  }
@@ -67638,6 +67780,7 @@ async function runListenerWithLock(options, paths) {
67638
67780
  clearInterval(xmtpClientRecycleTimer);
67639
67781
  }
67640
67782
  clearInterval(systemConfigRefreshTimer);
67783
+ clearInterval(authRecoveryProbeTimer);
67641
67784
  commandProcessor.stop();
67642
67785
  await watcherCoordinator.stop();
67643
67786
  try {
@@ -67669,7 +67812,7 @@ async function timeSettled(fn) {
67669
67812
  return { durationMs: Date.now() - startedAt, error };
67670
67813
  }
67671
67814
  }
67672
- var import_node_fs25, import_promises13, import_node_os11, import_node_path30, DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC, DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC, XMTP_CLIENT_RECYCLE_INTERVAL_ENV, HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS, UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY, SHUTDOWN_FORCE_RESOLVE_MS;
67815
+ var import_node_fs25, import_promises13, import_node_os11, import_node_path30, DEFAULT_OFFLINE_REPLAY_INTERVAL_SEC, DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC, XMTP_CLIENT_RECYCLE_INTERVAL_ENV, HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS, AUTH_RECOVERY_PROBE_INTERVAL_MS, AgentRefreshAuthGate, AgentRefreshCoordinator, ListenerDeferredTaskScheduler, ListenerMaintenanceGate, UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY, SHUTDOWN_FORCE_RESOLVE_MS;
67673
67816
  var init_listener = __esm({
67674
67817
  "src/listener.ts"() {
67675
67818
  "use strict";
@@ -67702,6 +67845,138 @@ var init_listener = __esm({
67702
67845
  DEFAULT_XMTP_CLIENT_RECYCLE_INTERVAL_SEC = 6 * 60 * 60;
67703
67846
  XMTP_CLIENT_RECYCLE_INTERVAL_ENV = "OKX_A2A_XMTP_CLIENT_RECYCLE_INTERVAL_SEC";
67704
67847
  HEARTBEAT_GATEWAY_CHECK_TIMEOUT_MS = 2e3;
67848
+ AUTH_RECOVERY_PROBE_INTERVAL_MS = 6e4;
67849
+ AgentRefreshAuthGate = class {
67850
+ authBlocked = false;
67851
+ get blocked() {
67852
+ return this.authBlocked;
67853
+ }
67854
+ recordSuccess() {
67855
+ this.authBlocked = false;
67856
+ }
67857
+ recordFailure(err2) {
67858
+ if (!isOnchainosAuthError(err2)) {
67859
+ return false;
67860
+ }
67861
+ this.authBlocked = true;
67862
+ return true;
67863
+ }
67864
+ };
67865
+ AgentRefreshCoordinator = class {
67866
+ constructor(options) {
67867
+ this.options = options;
67868
+ }
67869
+ options;
67870
+ authGate = new AgentRefreshAuthGate();
67871
+ refreshInFlight = null;
67872
+ recoveryProbeInFlight = null;
67873
+ get blocked() {
67874
+ return this.authGate.blocked;
67875
+ }
67876
+ async lookup(previousFingerprint) {
67877
+ const wasBlocked = this.authGate.blocked;
67878
+ try {
67879
+ const result = await this.options.lookup(previousFingerprint);
67880
+ this.authGate.recordSuccess();
67881
+ if (wasBlocked) {
67882
+ this.options.onAuthRecovered?.();
67883
+ }
67884
+ return result;
67885
+ } catch (err2) {
67886
+ if (this.authGate.recordFailure(err2) && !wasBlocked) {
67887
+ this.options.onAuthBlocked?.();
67888
+ }
67889
+ throw err2;
67890
+ }
67891
+ }
67892
+ async runScheduled() {
67893
+ if (this.options.isStopping() || this.authGate.blocked) {
67894
+ return null;
67895
+ }
67896
+ return this.runRefresh();
67897
+ }
67898
+ async runManual() {
67899
+ return this.runRefresh();
67900
+ }
67901
+ runRecoveryProbe() {
67902
+ if (this.options.isStopping() || !this.authGate.blocked || !this.options.checkLoggedIn) {
67903
+ return Promise.resolve(null);
67904
+ }
67905
+ if (this.recoveryProbeInFlight) {
67906
+ return this.recoveryProbeInFlight;
67907
+ }
67908
+ this.recoveryProbeInFlight = (async () => {
67909
+ const loggedIn = await this.options.checkLoggedIn?.();
67910
+ if (this.options.isStopping() || !this.authGate.blocked || !loggedIn) {
67911
+ return null;
67912
+ }
67913
+ const refresh = await this.runRefresh();
67914
+ return this.authGate.blocked ? null : refresh;
67915
+ })().finally(() => {
67916
+ this.recoveryProbeInFlight = null;
67917
+ });
67918
+ return this.recoveryProbeInFlight;
67919
+ }
67920
+ runRefresh() {
67921
+ if (this.refreshInFlight) {
67922
+ return this.refreshInFlight;
67923
+ }
67924
+ this.refreshInFlight = this.options.refresh().finally(() => {
67925
+ this.refreshInFlight = null;
67926
+ });
67927
+ return this.refreshInFlight;
67928
+ }
67929
+ };
67930
+ ListenerDeferredTaskScheduler = class {
67931
+ timers = /* @__PURE__ */ new Set();
67932
+ schedule(task) {
67933
+ const timer = setTimeout(() => {
67934
+ this.timers.delete(timer);
67935
+ task();
67936
+ }, 0);
67937
+ this.timers.add(timer);
67938
+ }
67939
+ cancelAll() {
67940
+ for (const timer of this.timers) {
67941
+ clearTimeout(timer);
67942
+ }
67943
+ this.timers.clear();
67944
+ }
67945
+ };
67946
+ ListenerMaintenanceGate = class {
67947
+ inFlight = null;
67948
+ offlineReplayPending = false;
67949
+ xmtpRecyclePending = false;
67950
+ tryEnter(task) {
67951
+ if (task !== "xmtp_recycle" && this.xmtpRecyclePending || this.inFlight) {
67952
+ if (task === "offline_replay") {
67953
+ this.offlineReplayPending = true;
67954
+ } else if (task === "xmtp_recycle") {
67955
+ this.xmtpRecyclePending = true;
67956
+ }
67957
+ return false;
67958
+ }
67959
+ if (task === "xmtp_recycle") {
67960
+ this.xmtpRecyclePending = false;
67961
+ }
67962
+ this.inFlight = task;
67963
+ return true;
67964
+ }
67965
+ leave(task) {
67966
+ if (this.inFlight !== task) {
67967
+ return null;
67968
+ }
67969
+ this.inFlight = null;
67970
+ if (this.xmtpRecyclePending) {
67971
+ return "xmtp_recycle";
67972
+ }
67973
+ if (this.offlineReplayPending) {
67974
+ this.offlineReplayPending = false;
67975
+ return "offline_replay";
67976
+ }
67977
+ return null;
67978
+ }
67979
+ };
67705
67980
  UPGRADE_RECOMMENDATION_IDEMPOTENCY_KEY = "system:onchainos-offline-replay-upgrade";
67706
67981
  SHUTDOWN_FORCE_RESOLVE_MS = 5e3;
67707
67982
  }
@@ -115480,7 +115755,7 @@ async function getCurrentNodeCliVersion() {
115480
115755
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
115481
115756
  }
115482
115757
  function getBundledNodeCliVersion() {
115483
- return true ? "0.2.3" : null;
115758
+ return true ? "0.2.4-beta-e7a52faf6f-260812104443" : null;
115484
115759
  }
115485
115760
  function readConfiguredAiProvider() {
115486
115761
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -115690,7 +115965,7 @@ async function updateHermes(release, options) {
115690
115965
  }
115691
115966
  }
115692
115967
  async function installGatewayPluginForDoctor(target) {
115693
- const release = isPrereleaseVersion("0.2.3") ? "beta" : "latest";
115968
+ const release = isPrereleaseVersion("0.2.4-beta-e7a52faf6f-260812104443") ? "beta" : "latest";
115694
115969
  const insideTargetGateway = detectGatewayInvocation() === target;
115695
115970
  const options = {
115696
115971
  restart: !insideTargetGateway,
@@ -116733,7 +117008,7 @@ async function runDoctor(options = {}) {
116733
117008
  platform: options.platform ?? process.platform,
116734
117009
  env: options.env ?? process.env,
116735
117010
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
116736
- cliVersion: options.cliVersion ?? (true ? "0.2.3" : "0.0.0"),
117011
+ cliVersion: options.cliVersion ?? (true ? "0.2.4-beta-e7a52faf6f-260812104443" : "0.0.0"),
116737
117012
  fixMode: options.fix === true,
116738
117013
  nonInteractive: options.nonInteractive === true,
116739
117014
  packageChanged: false,
@@ -117730,7 +118005,7 @@ init_sentry_logger();
117730
118005
  init_sentry_config();
117731
118006
  var CURRENT_GATEWAY_SESSION_KEYS_ENV4 = "OKX_A2A_CURRENT_GATEWAY_SESSION_KEYS";
117732
118007
  function printUsage3() {
117733
- console.log(`okx-a2a ${"0.2.3"}
118008
+ console.log(`okx-a2a ${"0.2.4-beta-e7a52faf6f-260812104443"}
117734
118009
 
117735
118010
  Usage:
117736
118011
  okx-a2a <command> [options]
@@ -117770,7 +118045,7 @@ Run \`okx-a2a <command> -h\` for command-specific help.
117770
118045
  `);
117771
118046
  }
117772
118047
  function printVersion() {
117773
- console.log("0.2.3");
118048
+ console.log("0.2.4-beta-e7a52faf6f-260812104443");
117774
118049
  }
117775
118050
  function printDaemonUsage() {
117776
118051
  console.log(`Usage: okx-a2a daemon <start|restart|stop|status|autostart> [options]
@@ -119206,7 +119481,7 @@ async function main() {
119206
119481
  if (command === "xmtp-test") {
119207
119482
  const { handleXmtpTestCommand: handleXmtpTestCommand2 } = await Promise.resolve().then(() => (init_xmtp_test_cli(), xmtp_test_cli_exports));
119208
119483
  await handleXmtpTestCommand2(process.argv.slice(3), {
119209
- packageVersion: "0.2.3",
119484
+ packageVersion: "0.2.4-beta-e7a52faf6f-260812104443",
119210
119485
  agentSdkVersion: "2.3.0",
119211
119486
  nodeSdkVersion: "6.1.0",
119212
119487
  nodeBindingsVersion: "1.11.0"