@okxweb3/a2a-node 0.2.17 → 0.2.18

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 +597 -180
  2. package/dist/index.js +587 -170
  3. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -27018,7 +27018,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
27018
27018
  client: {
27019
27019
  id: "gateway-client",
27020
27020
  displayName: "okx-a2a-node",
27021
- version: "0.2.17",
27021
+ version: "0.2.18",
27022
27022
  platform: "node",
27023
27023
  mode: "backend",
27024
27024
  instanceId
@@ -27029,7 +27029,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
27029
27029
  commands: [],
27030
27030
  permissions: {},
27031
27031
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
27032
- userAgent: `okx-a2a-node/${"0.2.17"}`,
27032
+ userAgent: `okx-a2a-node/${"0.2.18"}`,
27033
27033
  auth: {
27034
27034
  ...config.token ? { token: config.token } : {},
27035
27035
  ...config.password ? { password: config.password } : {}
@@ -29089,7 +29089,7 @@ var init_sentry_config = __esm({
29089
29089
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
29090
29090
  SENTRY_CONFIG = {
29091
29091
  projectName: "okx/openclaw-okx-a2a-extension",
29092
- release: "0.2.17",
29092
+ release: "0.2.18",
29093
29093
  environment,
29094
29094
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
29095
29095
  };
@@ -40259,7 +40259,7 @@ async function exportDiagnosticLogs(options) {
40259
40259
  node: process.version,
40260
40260
  platform: process.platform,
40261
40261
  arch: process.arch,
40262
- packageVersion: true ? "0.2.17" : "unknown",
40262
+ packageVersion: true ? "0.2.18" : "unknown",
40263
40263
  sensitiveContentIncluded: options.includeSensitiveContent,
40264
40264
  listenerAndLlmContentIncluded: true,
40265
40265
  credentialsAlwaysRedacted: true,
@@ -42950,7 +42950,7 @@ function createAsyncStreamProxy(stream) {
42950
42950
  }
42951
42951
  });
42952
42952
  }
42953
- var import_node_bindings, import_node_bindings2, import_node_crypto9, import_types, import_node_path26, import_node_process, HistorySyncUrls, envMap, reverseEnvMap, envToString, createBackend, InboxReassignError, AccountAlreadyAssociatedError, MissingContentTypeError, SignerUnavailableError, ClientNotInitializedError, StreamFailedError, StreamInvalidRetryAttemptsError, generateInboxId, getInboxIdForIdentifier, isReaction, isReply, isText, isRemoteAttachment, isAttachment, isMultiRemoteAttachment, isTransactionReference, isGroupUpdated, isReadReceipt, isLeaveRequest, isWalletSendCalls, isIntent, isActions, isMarkdown, CodecRegistry, getContentFromDecodedMessageContent, getContentTypeFromDecodedMessageContent, DecodedMessage, AsyncStream, usableProperties, isUsableProperty, wait, DEFAULT_RETRY_DELAY, DEFAULT_RETRY_ATTEMPTS, createStream, Conversation, Dm, Group, Conversations, DebugInformation, Preferences, networkOptionKeys, hasBackend, resolveBackend$1, createClient, resolveBackend, createEphemeralIdentifier, toInboxUpdatesCountMap, Client;
42953
+ var import_node_bindings, import_node_bindings2, import_node_crypto9, import_types, import_node_path26, import_node_process, HistorySyncUrls, envMap, reverseEnvMap, envToString, createBackend, InboxReassignError, AccountAlreadyAssociatedError, MissingContentTypeError, SignerUnavailableError, ClientNotInitializedError, StreamFailedError, StreamInvalidRetryAttemptsError, generateInboxId, getInboxIdForIdentifier, isReaction, isReply, isText, isRemoteAttachment, isAttachment, isMultiRemoteAttachment, isTransactionReference, isGroupUpdated, isReadReceipt, isLeaveRequest, isWalletSendCalls, isIntent, isActions, isMarkdown, CodecRegistry, getContentFromDecodedMessageContent, getContentTypeFromDecodedMessageContent, DecodedMessage, AsyncStream, usableProperties, isUsableProperty, DEFAULT_RETRY_DELAY, DEFAULT_RETRY_ATTEMPTS, createStream, Conversation, Dm, Group, Conversations, DebugInformation, Preferences, networkOptionKeys, hasBackend, resolveBackend$1, createClient, resolveBackend, createEphemeralIdentifier, toInboxUpdatesCountMap, Client;
42954
42954
  var init_dist2 = __esm({
42955
42955
  "../../node_modules/@xmtp/node-sdk/dist/index.js"() {
42956
42956
  import_node_bindings = require("@xmtp/node-bindings");
@@ -43360,7 +43360,6 @@ var init_dist2 = __esm({
43360
43360
  isUsableProperty = (prop) => {
43361
43361
  return usableProperties.includes(prop);
43362
43362
  };
43363
- wait = (ms) => new Promise((resolve14) => setTimeout(resolve14, ms));
43364
43363
  DEFAULT_RETRY_DELAY = 6e4;
43365
43364
  DEFAULT_RETRY_ATTEMPTS = 10;
43366
43365
  createStream = async (streamFunction, streamValueMutator, options) => {
@@ -43369,82 +43368,161 @@ var init_dist2 = __esm({
43369
43368
  throw new StreamInvalidRetryAttemptsError();
43370
43369
  }
43371
43370
  const asyncStream = new AsyncStream();
43371
+ let stopped = false;
43372
+ let current;
43373
+ let recovering = false;
43374
+ let cancelDelay;
43375
+ const isStopped = () => stopped;
43376
+ const isCurrent = (native) => !stopped && current === native;
43377
+ asyncStream.onDone = () => {
43378
+ if (stopped) {
43379
+ return;
43380
+ }
43381
+ stopped = true;
43382
+ cancelDelay?.();
43383
+ const native = current;
43384
+ current = void 0;
43385
+ native?.closer?.end();
43386
+ };
43387
+ const delay = (ms) => new Promise((resolve14) => {
43388
+ const timer = setTimeout(() => {
43389
+ cancelDelay = void 0;
43390
+ resolve14();
43391
+ }, ms);
43392
+ cancelDelay = () => {
43393
+ clearTimeout(timer);
43394
+ cancelDelay = void 0;
43395
+ resolve14();
43396
+ };
43397
+ });
43398
+ const reportError = (error) => {
43399
+ if (!stopped) {
43400
+ onError?.(error);
43401
+ }
43402
+ };
43403
+ const deliver = (value) => {
43404
+ if (stopped || value === void 0) {
43405
+ return;
43406
+ }
43407
+ asyncStream.push(value);
43408
+ onValue?.(value);
43409
+ };
43372
43410
  const streamCallback = (error, value) => {
43411
+ if (stopped) {
43412
+ return;
43413
+ }
43373
43414
  if (error) {
43374
43415
  onError?.(error);
43375
43416
  return;
43376
43417
  }
43377
- if (value !== void 0) {
43378
- try {
43379
- if (streamValueMutator) {
43380
- const mutatedValue = streamValueMutator(value);
43381
- if ((0, import_types.isPromise)(mutatedValue)) {
43382
- void mutatedValue.then((mutatedValue2) => {
43383
- if (mutatedValue2 !== void 0) {
43384
- asyncStream.push(mutatedValue2);
43385
- onValue?.(mutatedValue2);
43386
- }
43387
- }).catch((error2) => {
43388
- onError?.(error2);
43389
- });
43390
- } else {
43391
- if (mutatedValue !== void 0) {
43392
- asyncStream.push(mutatedValue);
43393
- onValue?.(mutatedValue);
43394
- }
43395
- }
43418
+ if (value === void 0) {
43419
+ return;
43420
+ }
43421
+ try {
43422
+ if (streamValueMutator) {
43423
+ const mutatedValue = streamValueMutator(value);
43424
+ if ((0, import_types.isPromise)(mutatedValue)) {
43425
+ void mutatedValue.then(deliver).catch(reportError);
43396
43426
  } else {
43397
- asyncStream.push(value);
43398
- onValue?.(value);
43427
+ deliver(mutatedValue);
43399
43428
  }
43400
- } catch (error2) {
43401
- onError?.(error2);
43429
+ } else {
43430
+ deliver(value);
43402
43431
  }
43432
+ } catch (error2) {
43433
+ reportError(error2);
43403
43434
  }
43404
43435
  };
43405
- const retry2 = async (retries = retryAttempts) => {
43406
- if (retries === 0) {
43407
- void asyncStream.end();
43408
- onError?.(new StreamFailedError(retryAttempts));
43409
- return;
43436
+ const fail = (error) => {
43437
+ void asyncStream.end();
43438
+ onError?.(error);
43439
+ };
43440
+ const open2 = async () => {
43441
+ if (stopped) {
43442
+ return false;
43443
+ }
43444
+ const native = { ready: false, closed: false };
43445
+ current = native;
43446
+ const closer = await streamFunction(streamCallback, () => {
43447
+ onNativeClose(native);
43448
+ });
43449
+ native.closer = closer;
43450
+ if (!isCurrent(native)) {
43451
+ closer.end();
43452
+ return false;
43453
+ }
43454
+ await closer.waitForReady();
43455
+ if (!isCurrent(native)) {
43456
+ closer.end();
43457
+ return false;
43410
43458
  }
43411
- await wait(retryDelay);
43412
- onRetry?.(retryAttempts - retries + 1, retryAttempts);
43459
+ if (native.closed || closer.isClosed()) {
43460
+ closer.end();
43461
+ return false;
43462
+ }
43463
+ native.ready = true;
43464
+ return true;
43465
+ };
43466
+ const restart = async () => {
43413
43467
  try {
43414
- const streamCloser = await streamFunction(streamCallback, () => {
43415
- onFail?.();
43416
- void retry2();
43417
- });
43418
- await streamCloser.waitForReady();
43419
- asyncStream.onDone = () => {
43420
- streamCloser.end();
43421
- };
43422
- onRestart?.();
43423
- } catch (error) {
43424
- onError?.(error);
43425
- void retry2(retries - 1);
43468
+ for (let attempt = 1; attempt <= retryAttempts; attempt++) {
43469
+ await delay(retryDelay);
43470
+ if (isStopped()) {
43471
+ return;
43472
+ }
43473
+ onRetry?.(attempt, retryAttempts);
43474
+ let restarted = false;
43475
+ try {
43476
+ restarted = await open2();
43477
+ } catch (error) {
43478
+ reportError(error);
43479
+ }
43480
+ if (isStopped()) {
43481
+ return;
43482
+ }
43483
+ if (restarted) {
43484
+ try {
43485
+ onRestart?.();
43486
+ } catch (error) {
43487
+ reportError(error);
43488
+ }
43489
+ return;
43490
+ }
43491
+ }
43492
+ fail(new StreamFailedError(retryAttempts));
43493
+ } finally {
43494
+ recovering = false;
43426
43495
  }
43427
43496
  };
43428
- const startRetry = () => {
43429
- if (retryOnFail) {
43430
- void retry2();
43431
- } else {
43432
- void asyncStream.end();
43433
- onError?.(new StreamFailedError(0));
43497
+ const recover = () => {
43498
+ if (stopped || recovering) {
43499
+ return;
43434
43500
  }
43501
+ if (!retryOnFail) {
43502
+ fail(new StreamFailedError(0));
43503
+ return;
43504
+ }
43505
+ recovering = true;
43506
+ void restart();
43435
43507
  };
43508
+ function onNativeClose(native) {
43509
+ if (!isCurrent(native) || native.closed) {
43510
+ return;
43511
+ }
43512
+ native.closed = true;
43513
+ onFail?.();
43514
+ if (native.ready) {
43515
+ recover();
43516
+ }
43517
+ }
43518
+ let started = false;
43436
43519
  try {
43437
- const streamCloser = await streamFunction(streamCallback, () => {
43438
- onFail?.();
43439
- startRetry();
43440
- });
43441
- await streamCloser.waitForReady();
43442
- asyncStream.onDone = () => {
43443
- streamCloser.end();
43444
- };
43520
+ started = await open2();
43445
43521
  } catch (error) {
43446
- onError?.(error);
43447
- startRetry();
43522
+ reportError(error);
43523
+ }
43524
+ if (!started) {
43525
+ recover();
43448
43526
  }
43449
43527
  return createAsyncStreamProxy(asyncStream);
43450
43528
  };
@@ -45614,7 +45692,7 @@ var require_retry_promise = __commonJS({
45614
45692
  return true;
45615
45693
  }
45616
45694
  };
45617
- function wait3(ms) {
45695
+ function wait2(ms) {
45618
45696
  return __awaiter(this, void 0, void 0, function() {
45619
45697
  return __generator(this, function(_a) {
45620
45698
  return [2, new Promise(function(resolve14) {
@@ -45623,7 +45701,7 @@ var require_retry_promise = __commonJS({
45623
45701
  });
45624
45702
  });
45625
45703
  }
45626
- exports2.wait = wait3;
45704
+ exports2.wait = wait2;
45627
45705
  function retry2(f, config) {
45628
45706
  return __awaiter(this, void 0, void 0, function() {
45629
45707
  var effectiveConfig;
@@ -45713,7 +45791,7 @@ var require_retry_promise = __commonJS({
45713
45791
  return [3, 5];
45714
45792
  case 5:
45715
45793
  millisToWait = delay(i + 1, config.delay);
45716
- return [4, wait3(millisToWait > config.maxBackOff ? config.maxBackOff : millisToWait)];
45794
+ return [4, wait2(millisToWait > config.maxBackOff ? config.maxBackOff : millisToWait)];
45717
45795
  case 6:
45718
45796
  _a.sent();
45719
45797
  if (done()) {
@@ -53587,7 +53665,7 @@ var init_withResolvers = __esm({
53587
53665
  });
53588
53666
 
53589
53667
  // ../../node_modules/viem/_esm/utils/promise/createBatchScheduler.js
53590
- function createBatchScheduler({ fn, id, shouldSplitBatch, wait: wait3 = 0, sort }) {
53668
+ function createBatchScheduler({ fn, id, shouldSplitBatch, wait: wait2 = 0, sort }) {
53591
53669
  const exec2 = async () => {
53592
53670
  const scheduler = getScheduler();
53593
53671
  flush2();
@@ -53625,7 +53703,7 @@ function createBatchScheduler({ fn, id, shouldSplitBatch, wait: wait3 = 0, sort
53625
53703
  return promise;
53626
53704
  }
53627
53705
  setScheduler({ args, resolve: resolve14, reject });
53628
- setTimeout(exec2, wait3);
53706
+ setTimeout(exec2, wait2);
53629
53707
  return promise;
53630
53708
  }
53631
53709
  };
@@ -53691,7 +53769,7 @@ var init_observe = __esm({
53691
53769
  });
53692
53770
 
53693
53771
  // ../../node_modules/viem/_esm/utils/wait.js
53694
- async function wait2(time) {
53772
+ async function wait(time) {
53695
53773
  return new Promise((res) => setTimeout(res, time));
53696
53774
  }
53697
53775
  var init_wait = __esm({
@@ -53708,12 +53786,12 @@ function poll(fn, { emitOnBegin, initialWaitTime, interval }) {
53708
53786
  if (emitOnBegin)
53709
53787
  data = await fn({ unpoll: unwatch });
53710
53788
  const initialWait = await initialWaitTime?.(data) ?? interval;
53711
- await wait2(initialWait);
53789
+ await wait(initialWait);
53712
53790
  const poll2 = async () => {
53713
53791
  if (!active)
53714
53792
  return;
53715
53793
  await fn({ unpoll: unwatch });
53716
- await wait2(interval);
53794
+ await wait(interval);
53717
53795
  poll2();
53718
53796
  };
53719
53797
  poll2();
@@ -54077,7 +54155,7 @@ function withRetry(fn, { delay: delay_ = 100, retryCount = 2, shouldRetry: shoul
54077
54155
  const retry2 = async ({ error }) => {
54078
54156
  const delay = typeof delay_ === "function" ? delay_({ count, error }) : delay_;
54079
54157
  if (delay)
54080
- await wait2(delay);
54158
+ await wait(delay);
54081
54159
  attemptRetry({ count: count + 1 });
54082
54160
  };
54083
54161
  try {
@@ -56542,7 +56620,7 @@ var init_transport = __esm({
56542
56620
  function http(url, config = {}) {
56543
56621
  const { batch, fetchFn, fetchOptions, key = "http", methods, name = "HTTP JSON-RPC", onFetchRequest, onFetchResponse, retryDelay, raw } = config;
56544
56622
  return ({ chain, retryCount: retryCount_, timeout: timeout_ }) => {
56545
- const { batchSize = 1e3, wait: wait3 = 0 } = typeof batch === "object" ? batch : {};
56623
+ const { batchSize = 1e3, wait: wait2 = 0 } = typeof batch === "object" ? batch : {};
56546
56624
  const retryCount = config.retryCount ?? retryCount_;
56547
56625
  const timeout = timeout_ ?? config.timeout ?? 1e4;
56548
56626
  const url_ = url || chain?.rpcUrls.default.http[0];
@@ -56563,7 +56641,7 @@ function http(url, config = {}) {
56563
56641
  const body = { method, params };
56564
56642
  const { schedule } = createBatchScheduler({
56565
56643
  id: url_,
56566
- wait: wait3,
56644
+ wait: wait2,
56567
56645
  shouldSplitBatch(requests) {
56568
56646
  return requests.length > batchSize;
56569
56647
  },
@@ -56902,7 +56980,7 @@ var init_dist4 = __esm({
56902
56980
  init_accounts();
56903
56981
  init_chains();
56904
56982
  import_node_crypto10 = require("node:crypto");
56905
- version2 = "2.3.0";
56983
+ version2 = "2.3.1";
56906
56984
  fromSelf = (message, client) => {
56907
56985
  return message.senderInboxId === client.inboxId;
56908
56986
  };
@@ -58850,6 +58928,9 @@ function replayScanBackoffWithJitterMs(consecutiveFailures, randomValue = Math.r
58850
58928
  const jitteredMs = Math.round(baseMs * (0.9 + boundedRandom * 0.2));
58851
58929
  return Math.min(REPLAY_SCAN_MAX_BACKOFF_MS, jitteredMs);
58852
58930
  }
58931
+ function isListenHealthy(outcome) {
58932
+ return outcome === "started" || outcome === "retrying";
58933
+ }
58853
58934
  function isOfflineReplayForTrigger(trigger) {
58854
58935
  return trigger === "startup";
58855
58936
  }
@@ -58877,7 +58958,7 @@ function createOfflineReplayAddressSummary(address) {
58877
58958
  durationMs: 0
58878
58959
  };
58879
58960
  }
58880
- var import_node_fs24, import_node_path28, 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, STREAM_RECOVERY_STABILITY_WINDOW_MS, REPLAY_GATE_DRAIN_MAX_ATTEMPTS, REPLAY_GATE_DRAIN_RETRY_DELAY_MS, SEMVER_RE, isRecord5, isPositiveNumber, SYSTEM_CONFIG_VALIDATORS, XmtpService;
58961
+ var import_node_fs24, import_node_path28, 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, STREAM_RECOVERY_STABILITY_WINDOW_MS, REPLAY_GATE_DRAIN_MAX_ATTEMPTS, REPLAY_GATE_DRAIN_RETRY_DELAY_MS, SEMVER_RE, isRecord5, isPositiveNumber, SYSTEM_CONFIG_VALIDATORS, XmtpClientCreateBlockedError, XmtpService;
58881
58962
  var init_xmtp_sdk = __esm({
58882
58963
  "../core/src/xmtp-sdk/index.ts"() {
58883
58964
  "use strict";
@@ -59009,6 +59090,18 @@ var init_xmtp_sdk = __esm({
59009
59090
  minAvailablePluginVersion: (value) => typeof value === "string",
59010
59091
  extConfig: (value) => isRecord5(value) && (value.tips === void 0 || isRecord5(value.tips) && Object.values(value.tips).every((item) => typeof item === "string"))
59011
59092
  };
59093
+ XmtpClientCreateBlockedError = class extends Error {
59094
+ reason;
59095
+ address;
59096
+ agentId;
59097
+ constructor(reason, address, agentId) {
59098
+ super(`xmtp client create blocked (${reason})`);
59099
+ this.name = "XmtpClientCreateBlockedError";
59100
+ this.reason = reason;
59101
+ this.address = address;
59102
+ this.agentId = agentId;
59103
+ }
59104
+ };
59012
59105
  XmtpService = class _XmtpService {
59013
59106
  static instance = null;
59014
59107
  env = process.env.XMTP_ENV ?? "production";
@@ -59046,6 +59139,25 @@ var init_xmtp_sdk = __esm({
59046
59139
  streamRecoverySetTimeout = (callback, delayMs) => setTimeout(callback, delayMs);
59047
59140
  streamRecoveryClearTimeout = (timer) => clearTimeout(timer);
59048
59141
  streamStartSetTimeout = (callback, delayMs) => setTimeout(callback, delayMs);
59142
+ xmtpAgentFactory = createAgentWithInstallationPrune;
59143
+ // Old clients whose close() was not confirmed. Keyed by lower-cased address.
59144
+ // The reference is kept only for controlled cleanup; it is never served.
59145
+ pendingReleaseByAddress = /* @__PURE__ */ new Map();
59146
+ // Addresses whose pending release was confirmed late (after the caller gave
59147
+ // up waiting) and still need a replacement client on the next recycle.
59148
+ releaseConfirmedNeedsCreate = /* @__PURE__ */ new Set();
59149
+ // Lower-cased addresses owned by a create. The slot is held from before the
59150
+ // SDK factory runs until the new client is registered in `clients` (or the
59151
+ // create failed), so no flow can observe "no lock and no client".
59152
+ clientCreateInFlight = /* @__PURE__ */ new Set();
59153
+ // Clients whose close() was confirmed; a repeated release is a no-op.
59154
+ releasedClients = /* @__PURE__ */ new WeakSet();
59155
+ // One listener setup per client, shared by concurrent init/refresh callers.
59156
+ listenTaskByClient = /* @__PURE__ */ new WeakMap();
59157
+ inboundReplayGateOwners = /* @__PURE__ */ new WeakMap();
59158
+ releaseWaitTimeoutMs = 3e4;
59159
+ releaseWaitSetTimeout = (callback, delayMs) => setTimeout(callback, delayMs);
59160
+ releaseWaitClearTimeout = (timer) => clearTimeout(timer);
59049
59161
  nextClientGeneration = 1;
59050
59162
  clientGenerationByClient = /* @__PURE__ */ new WeakMap();
59051
59163
  resolveStartupReplayCompletion;
@@ -59194,6 +59306,56 @@ var init_xmtp_sdk = __esm({
59194
59306
  return { ok: remaining.length === 0, failures: remaining };
59195
59307
  }
59196
59308
  async createClientForAgent(agentInfo, options = {}) {
59309
+ const address = agentInfo.communicationAddress;
59310
+ const addressKey = address.toLowerCase();
59311
+ let blockedReason;
59312
+ if (this.pendingReleaseByAddress.has(addressKey)) {
59313
+ blockedReason = "release_pending";
59314
+ } else if (this.clientCreateInFlight.has(addressKey)) {
59315
+ blockedReason = "create_in_flight";
59316
+ } else if (this.getClientByAddress(address)) {
59317
+ blockedReason = "client_active";
59318
+ }
59319
+ if (blockedReason) {
59320
+ throw new XmtpClientCreateBlockedError(
59321
+ blockedReason,
59322
+ address,
59323
+ agentInfo.agentId
59324
+ );
59325
+ }
59326
+ this.clientCreateInFlight.add(addressKey);
59327
+ try {
59328
+ const created = await this.createClientForAgentGuarded(agentInfo, options);
59329
+ await this.registerCreatedClient(created, options.logPrefix ?? "[xmtp-sdk]");
59330
+ return created;
59331
+ } finally {
59332
+ this.clientCreateInFlight.delete(addressKey);
59333
+ }
59334
+ }
59335
+ // Ownership hand-off, still inside the create slot: the new client becomes
59336
+ // visible in `clients` before the slot is released. Never overwrites an
59337
+ // existing registration; a conflicting new object is released, not dropped.
59338
+ async registerCreatedClient(created, phase) {
59339
+ const { address, agent, agentInfo } = created;
59340
+ const addressKey = address.toLowerCase();
59341
+ const existing = this.getClientByAddress(address);
59342
+ if (existing && existing !== agent || this.pendingReleaseByAddress.has(addressKey)) {
59343
+ const reason = existing ? "client_active" : "release_pending";
59344
+ logWithTimestamp(
59345
+ `[xmtp-sdk] create registration conflict (${reason}): ${address} (agentId=${agentInfo.agentId}), releasing the new client`
59346
+ );
59347
+ await this.releaseClient(
59348
+ address,
59349
+ agent,
59350
+ agentInfo.agentId,
59351
+ `${phase}/register-conflict`
59352
+ );
59353
+ throw new XmtpClientCreateBlockedError(reason, address, agentInfo.agentId);
59354
+ }
59355
+ this.stoppedAddresses.delete(addressKey);
59356
+ this.clients.set(address, agent);
59357
+ }
59358
+ async createClientForAgentGuarded(agentInfo, options = {}) {
59197
59359
  const address = agentInfo.communicationAddress;
59198
59360
  const phase = options.logPrefix ?? "[xmtp-sdk]";
59199
59361
  const cooldown = getXmtpCreationCooldown();
@@ -59222,7 +59384,7 @@ var init_xmtp_sdk = __esm({
59222
59384
  try {
59223
59385
  const signer = this.tools.createSigner(agentInfo);
59224
59386
  const dbPath = (inboxId2) => `${this.dataDir}/${inboxId2}-${this.env}.db3`;
59225
- const agent = await createAgentWithInstallationPrune(signer, {
59387
+ const agent = await this.xmtpAgentFactory(signer, {
59226
59388
  dbPath,
59227
59389
  env: this.env
59228
59390
  }, {
@@ -59231,6 +59393,7 @@ var init_xmtp_sdk = __esm({
59231
59393
  logPrefix: options.logPrefix
59232
59394
  });
59233
59395
  cooldown.recordCreateSuccess(decision);
59396
+ this.releaseConfirmedNeedsCreate.delete(address.toLowerCase());
59234
59397
  if (decision.mode === "probe") {
59235
59398
  logger.info(LogEvent.XMTP_RATE_LIMIT_RECOVERED, {
59236
59399
  onchainosAgentId: agentInfo.agentId,
@@ -59268,6 +59431,128 @@ var init_xmtp_sdk = __esm({
59268
59431
  throw err2;
59269
59432
  }
59270
59433
  }
59434
+ logReleaseNotConfirmed(address, agentId, phase, reason, err2) {
59435
+ logger.error(
59436
+ LogEvent.XMTP_CLIENT_CLOSE_FAILED,
59437
+ err2 instanceof Error ? err2 : new Error(`xmtp client close not confirmed (${reason})`),
59438
+ { walletAddress: address, onchainosAgentId: agentId, phase, reason }
59439
+ );
59440
+ }
59441
+ // Single release entry (recycle / refresh remove / failed replacement).
59442
+ // Confirmation is ONLY a successful client.close(); a timeout, a missing
59443
+ // close() or a close failure keeps the old reference in
59444
+ // pendingReleaseByAddress and blocks new creates for that address.
59445
+ async releaseClient(address, client, agentId, phase) {
59446
+ const key = address.toLowerCase();
59447
+ if (this.releasedClients.has(client)) {
59448
+ this.unpublishClient(address, client, phase);
59449
+ return true;
59450
+ }
59451
+ const existing = this.pendingReleaseByAddress.get(key);
59452
+ if (existing?.inFlight) {
59453
+ this.unpublishClient(address, client, phase);
59454
+ return false;
59455
+ }
59456
+ const entry = existing ?? { client, agentId, phase };
59457
+ entry.client = client;
59458
+ entry.agentId = agentId;
59459
+ entry.phase = phase;
59460
+ this.pendingReleaseByAddress.set(key, entry);
59461
+ this.unpublishClient(address, client, phase);
59462
+ let timedOut = false;
59463
+ const task = stopAndCloseXmtpClient(client, {
59464
+ address,
59465
+ agentId,
59466
+ phase,
59467
+ onCloseError: (err2) => this.logReleaseNotConfirmed(address, agentId, phase, "close_threw", err2)
59468
+ }).then((result) => {
59469
+ if (!result.stopped) {
59470
+ logWithTimestamp(
59471
+ `[xmtp-sdk] release: stop failed but close proceeded: ${address} (agentId=${agentId}) phase=${phase}`
59472
+ );
59473
+ }
59474
+ entry.inFlight = void 0;
59475
+ if (result.closed) {
59476
+ this.releasedClients.add(client);
59477
+ }
59478
+ if (result.closed && this.pendingReleaseByAddress.get(key) === entry) {
59479
+ this.pendingReleaseByAddress.delete(key);
59480
+ if (timedOut) {
59481
+ this.releaseConfirmedNeedsCreate.add(key);
59482
+ }
59483
+ }
59484
+ return result;
59485
+ });
59486
+ entry.inFlight = task.then((result) => result.closed);
59487
+ const timeoutHandle = {};
59488
+ const timeoutPromise = new Promise((resolve14) => {
59489
+ timeoutHandle.timer = this.releaseWaitSetTimeout(
59490
+ () => resolve14(null),
59491
+ this.releaseWaitTimeoutMs
59492
+ );
59493
+ });
59494
+ const outcome = await Promise.race([task, timeoutPromise]);
59495
+ if (timeoutHandle.timer !== void 0) {
59496
+ this.releaseWaitClearTimeout(timeoutHandle.timer);
59497
+ }
59498
+ if (outcome === null) {
59499
+ timedOut = true;
59500
+ this.logReleaseNotConfirmed(address, agentId, phase, "close_timeout");
59501
+ return false;
59502
+ }
59503
+ if (outcome.closed) {
59504
+ if (this.pendingReleaseByAddress.get(key) === entry) {
59505
+ this.pendingReleaseByAddress.delete(key);
59506
+ }
59507
+ return true;
59508
+ }
59509
+ if (!outcome.closeFailed) {
59510
+ this.logReleaseNotConfirmed(address, agentId, phase, "close_unavailable");
59511
+ }
59512
+ return false;
59513
+ }
59514
+ // Stops serving `client` to new operations. Every removal is identity
59515
+ // checked, so a late caller can never remove a newer instance (or its replay
59516
+ // gate / recovery / retry state) registered for the same address.
59517
+ unpublishClient(address, client, phase) {
59518
+ const key = address.toLowerCase();
59519
+ let removed = false;
59520
+ for (const [registeredAddress, registered] of this.clients) {
59521
+ if (registered === client && registeredAddress.toLowerCase() === key) {
59522
+ this.clients.delete(registeredAddress);
59523
+ removed = true;
59524
+ }
59525
+ }
59526
+ const gate = this.inboundReplayGates.get(key);
59527
+ if (gate) {
59528
+ const owner = this.inboundReplayGateOwners.get(gate);
59529
+ if (owner === client || owner === void 0 && removed) {
59530
+ this.inboundReplayGates.delete(key);
59531
+ }
59532
+ }
59533
+ this.cancelStreamRecoveryForAddress(address, client);
59534
+ if (removed) {
59535
+ this.replayScanFailuresByAddress.delete(key);
59536
+ this.clearReplayScanRetry(
59537
+ address,
59538
+ phase.startsWith("recycle") ? "client_recycled" : "client_removed"
59539
+ );
59540
+ }
59541
+ if (!this.getClientByAddress(address)) {
59542
+ this.stoppedAddresses.add(key);
59543
+ }
59544
+ }
59545
+ async settlePendingRelease(address) {
59546
+ const entry = this.pendingReleaseByAddress.get(address.toLowerCase());
59547
+ if (!entry) {
59548
+ return true;
59549
+ }
59550
+ if (entry.inFlight) {
59551
+ return false;
59552
+ }
59553
+ const retryPhase = entry.phase.endsWith("/retry") ? entry.phase : `${entry.phase}/retry`;
59554
+ return this.releaseClient(address, entry.client, entry.agentId, retryPhase);
59555
+ }
59271
59556
  async recordInstallationHealth(params) {
59272
59557
  const { address, agent, agentInfo, phase, attempt } = params;
59273
59558
  try {
@@ -59388,8 +59673,6 @@ var init_xmtp_sdk = __esm({
59388
59673
  const r = results[i];
59389
59674
  if (r.status === "fulfilled") {
59390
59675
  const { address, agent, agentInfo } = r.value;
59391
- this.stoppedAddresses.delete(address.toLowerCase());
59392
- this.clients.set(address, agent);
59393
59676
  logWithTimestamp(
59394
59677
  `[xmtp-sdk] identity initialized: ${address} (agentId=${agentInfo.agentId})`
59395
59678
  );
@@ -59415,6 +59698,10 @@ var init_xmtp_sdk = __esm({
59415
59698
  phase: "init",
59416
59699
  attempt: "1"
59417
59700
  });
59701
+ } else if (r.reason instanceof XmtpClientCreateBlockedError) {
59702
+ logWithTimestamp(
59703
+ `[xmtp-sdk] identity init: create blocked (${r.reason.reason}): ${agents[i].communicationAddress} (agentId=${agents[i].agentId})`
59704
+ );
59418
59705
  } else {
59419
59706
  failedAgents.push(agents[i]);
59420
59707
  if (r.reason instanceof XmtpCreationCooldownSkipError) {
@@ -59451,8 +59738,6 @@ var init_xmtp_sdk = __esm({
59451
59738
  const r = retryResults[i];
59452
59739
  if (r.status === "fulfilled") {
59453
59740
  const { address, agent, agentInfo } = r.value;
59454
- this.stoppedAddresses.delete(address.toLowerCase());
59455
- this.clients.set(address, agent);
59456
59741
  logWithTimestamp(
59457
59742
  `[xmtp-sdk] retry succeeded: ${address} (agentId=${agentInfo.agentId})`
59458
59743
  );
@@ -59478,6 +59763,11 @@ var init_xmtp_sdk = __esm({
59478
59763
  phase: "init",
59479
59764
  attempt: "2"
59480
59765
  });
59766
+ } else if (r.reason instanceof XmtpClientCreateBlockedError) {
59767
+ const info = failedAgents[i];
59768
+ logWithTimestamp(
59769
+ `[xmtp-sdk] retry create blocked (${r.reason.reason}): ${info.communicationAddress} (agentId=${info.agentId})`
59770
+ );
59481
59771
  } else if (r.reason instanceof XmtpCreationCooldownSkipError) {
59482
59772
  const info = failedAgents[i];
59483
59773
  logWithTimestamp(
@@ -59560,36 +59850,38 @@ var init_xmtp_sdk = __esm({
59560
59850
  throw err2;
59561
59851
  }
59562
59852
  }
59853
+ for (const pendingKey of [...this.pendingReleaseByAddress.keys()]) {
59854
+ const settled = await this.settlePendingRelease(pendingKey);
59855
+ logWithTimestamp(
59856
+ `[xmtp-sdk] refresh: pending release ${pendingKey} ${settled ? "confirmed" : "still pending"}`
59857
+ );
59858
+ }
59563
59859
  const oldAddresses = new Set(
59564
- this.allAgents.map((a) => a.communicationAddress)
59860
+ this.allAgents.map((a) => a.communicationAddress.toLowerCase())
59861
+ );
59862
+ const newAddresses = new Set(
59863
+ newAgents.map((a) => a.communicationAddress.toLowerCase())
59565
59864
  );
59566
- const newAddresses = new Set(newAgents.map((a) => a.communicationAddress));
59567
59865
  const added = newAgents.filter(
59568
- (a) => !oldAddresses.has(a.communicationAddress) || !this.clients.has(a.communicationAddress)
59866
+ (a) => !oldAddresses.has(a.communicationAddress.toLowerCase()) || !this.getClientByAddress(a.communicationAddress)
59569
59867
  );
59570
59868
  const removed = this.allAgents.filter(
59571
- (a) => !newAddresses.has(a.communicationAddress)
59869
+ (a) => !newAddresses.has(a.communicationAddress.toLowerCase())
59572
59870
  );
59573
59871
  this.allAgents = newAgents;
59574
59872
  for (const agentInfo of removed) {
59575
59873
  const address = agentInfo.communicationAddress;
59576
- const client = this.clients.get(address);
59874
+ const client = this.getClientByAddress(address);
59577
59875
  if (client) {
59578
59876
  try {
59579
- this.cancelStreamRecoveryForAddress(address);
59580
- this.stoppedAddresses.add(address.toLowerCase());
59581
- await stopAndCloseXmtpClient(client, {
59877
+ const released = await this.releaseClient(
59582
59878
  address,
59583
- agentId: agentInfo.agentId,
59584
- phase: "refresh/remove",
59585
- onCloseError: (err2) => logger.error(
59586
- LogEvent.XMTP_CLIENT_CLOSE_FAILED,
59587
- err2 instanceof Error ? err2 : new Error(String(err2)),
59588
- { walletAddress: address, onchainosAgentId: agentInfo.agentId, phase: "refresh/remove" }
59589
- )
59590
- });
59879
+ client,
59880
+ agentInfo.agentId,
59881
+ "refresh/remove"
59882
+ );
59591
59883
  logWithTimestamp(
59592
- `[xmtp-sdk] refresh: stopped client: ${address} (agentId=${agentInfo.agentId})`
59884
+ `[xmtp-sdk] refresh: stopped client: ${address} (agentId=${agentInfo.agentId}) released=${released}`
59593
59885
  );
59594
59886
  } catch (err2) {
59595
59887
  logWithTimestamp(`[xmtp-sdk] refresh: failed to stop client: ${address}`, err2);
@@ -59599,10 +59891,6 @@ var init_xmtp_sdk = __esm({
59599
59891
  phase: "stop-client"
59600
59892
  });
59601
59893
  }
59602
- this.clients.delete(address);
59603
- this.inboundReplayGates.delete(address.toLowerCase());
59604
- this.replayScanFailuresByAddress.delete(address.toLowerCase());
59605
- this.clearReplayScanRetry(address, "client_removed");
59606
59894
  logger.info(LogEvent.AGENT_CLIENT_REMOVED, {
59607
59895
  ...agentExtras({
59608
59896
  walletAddress: address,
@@ -59644,8 +59932,6 @@ var init_xmtp_sdk = __esm({
59644
59932
  const r = addResults[i];
59645
59933
  if (r.status === "fulfilled") {
59646
59934
  const { address, agentInfo } = r.value;
59647
- this.stoppedAddresses.delete(address.toLowerCase());
59648
- this.clients.set(address, r.value.agent);
59649
59935
  logWithTimestamp(
59650
59936
  `[xmtp-sdk] refresh: identity initialized: ${address} (agentId=${agentInfo.agentId})`
59651
59937
  );
@@ -59672,6 +59958,10 @@ var init_xmtp_sdk = __esm({
59672
59958
  phase: "refresh",
59673
59959
  attempt: "1"
59674
59960
  });
59961
+ } else if (r.reason instanceof XmtpClientCreateBlockedError) {
59962
+ logWithTimestamp(
59963
+ `[xmtp-sdk] refresh: create blocked (${r.reason.reason}): ${added[i].communicationAddress} (agentId=${added[i].agentId})`
59964
+ );
59675
59965
  } else {
59676
59966
  failedAgents.push(added[i]);
59677
59967
  if (r.reason instanceof XmtpCreationCooldownSkipError) {
@@ -59711,8 +60001,6 @@ var init_xmtp_sdk = __esm({
59711
60001
  const r = retryResults[i];
59712
60002
  if (r.status === "fulfilled") {
59713
60003
  const { address, agentInfo } = r.value;
59714
- this.stoppedAddresses.delete(address.toLowerCase());
59715
- this.clients.set(address, r.value.agent);
59716
60004
  logWithTimestamp(
59717
60005
  `[xmtp-sdk] refresh: retry succeeded: ${address} (agentId=${agentInfo.agentId})`
59718
60006
  );
@@ -59739,6 +60027,11 @@ var init_xmtp_sdk = __esm({
59739
60027
  phase: "refresh",
59740
60028
  attempt: "2"
59741
60029
  });
60030
+ } else if (r.reason instanceof XmtpClientCreateBlockedError) {
60031
+ const info = failedAgents[i];
60032
+ logWithTimestamp(
60033
+ `[xmtp-sdk] refresh: retry create blocked (${r.reason.reason}): ${info.communicationAddress} (agentId=${info.agentId})`
60034
+ );
59742
60035
  } else if (r.reason instanceof XmtpCreationCooldownSkipError) {
59743
60036
  const info = failedAgents[i];
59744
60037
  logWithTimestamp(
@@ -59764,7 +60057,8 @@ var init_xmtp_sdk = __esm({
59764
60057
  }
59765
60058
  }
59766
60059
  await this.startListeningForAddresses(
59767
- added.map((a) => a.communicationAddress)
60060
+ added.map((a) => a.communicationAddress),
60061
+ "refresh/listen-failed"
59768
60062
  );
59769
60063
  for (const agentInfo of added) {
59770
60064
  const address = agentInfo.communicationAddress;
@@ -59829,30 +60123,42 @@ var init_xmtp_sdk = __esm({
59829
60123
  }
59830
60124
  for (const agentInfo of this.allAgents) {
59831
60125
  const address = agentInfo.communicationAddress;
59832
- const oldClient = this.clients.get(address);
59833
- if (!oldClient) {
60126
+ const addressKey = address.toLowerCase();
60127
+ const oldClient = this.getClientByAddress(address);
60128
+ const hasPendingRelease = this.pendingReleaseByAddress.has(addressKey) || this.releaseConfirmedNeedsCreate.has(addressKey);
60129
+ if (!oldClient && !hasPendingRelease) {
59834
60130
  continue;
59835
60131
  }
59836
- const addressKey = address.toLowerCase();
59837
- try {
59838
- this.cancelStreamRecoveryForAddress(address);
59839
- this.stoppedAddresses.add(addressKey);
59840
- await stopAndCloseXmtpClient(oldClient, {
59841
- address,
59842
- agentId: agentInfo.agentId,
59843
- phase: "recycle",
59844
- onCloseError: (err2) => logger.error(
59845
- LogEvent.XMTP_CLIENT_CLOSE_FAILED,
59846
- err2 instanceof Error ? err2 : new Error(String(err2)),
59847
- { walletAddress: address, onchainosAgentId: agentInfo.agentId, phase: "recycle" }
59848
- )
59849
- });
59850
- this.clients.delete(address);
59851
- this.inboundReplayGates.delete(addressKey);
59852
- this.replayScanFailuresByAddress.delete(addressKey);
59853
- this.clearReplayScanRetry(address, "client_recycled");
60132
+ if (!oldClient) {
60133
+ let settled = false;
60134
+ try {
60135
+ settled = await this.settlePendingRelease(address);
60136
+ } catch (err2) {
60137
+ logWithTimestamp(`[xmtp-sdk] recycle: pending release retry failed: ${address}`, err2);
60138
+ }
60139
+ if (!settled) {
60140
+ failed++;
60141
+ continue;
60142
+ }
60143
+ } else {
60144
+ let released = false;
60145
+ try {
60146
+ released = await this.releaseClient(
60147
+ address,
60148
+ oldClient,
60149
+ agentInfo.agentId,
60150
+ "recycle"
60151
+ );
60152
+ } catch (err2) {
60153
+ logWithTimestamp(`[xmtp-sdk] recycle: failed to stop client: ${address}`, err2);
60154
+ logger.error(LogEvent.AGENT_REFRESH_FAILED, err2 instanceof Error ? err2 : new Error(String(err2)), {
60155
+ walletAddress: address,
60156
+ onchainosAgentId: agentInfo.agentId,
60157
+ phase: "recycle/stop-client"
60158
+ });
60159
+ }
59854
60160
  logWithTimestamp(
59855
- `[xmtp-sdk] recycle: stopped client: ${address} (agentId=${agentInfo.agentId})`
60161
+ `[xmtp-sdk] recycle: stopped client: ${address} (agentId=${agentInfo.agentId}) released=${released}`
59856
60162
  );
59857
60163
  logger.info(LogEvent.AGENT_CLIENT_REMOVED, {
59858
60164
  ...agentExtras({
@@ -59862,23 +60168,18 @@ var init_xmtp_sdk = __esm({
59862
60168
  }),
59863
60169
  phase: "recycle"
59864
60170
  });
59865
- } catch (err2) {
59866
- failed++;
59867
- this.stoppedAddresses.delete(addressKey);
59868
- logWithTimestamp(`[xmtp-sdk] recycle: failed to stop client: ${address}`, err2);
59869
- logger.error(LogEvent.AGENT_REFRESH_FAILED, err2 instanceof Error ? err2 : new Error(String(err2)), {
59870
- walletAddress: address,
59871
- onchainosAgentId: agentInfo.agentId,
59872
- phase: "recycle/stop-client"
59873
- });
59874
- continue;
60171
+ if (!released) {
60172
+ failed++;
60173
+ continue;
60174
+ }
59875
60175
  }
60176
+ let newAgent;
60177
+ let newAgentReleased = false;
59876
60178
  try {
59877
60179
  const { agent } = await this.createClientForAgent(agentInfo, {
59878
60180
  logPrefix: "[xmtp-sdk:recycle]"
59879
60181
  });
59880
- this.stoppedAddresses.delete(addressKey);
59881
- this.clients.set(address, agent);
60182
+ newAgent = agent;
59882
60183
  logWithTimestamp(
59883
60184
  `[xmtp-sdk] recycle: identity initialized: ${address} (agentId=${agentInfo.agentId})`
59884
60185
  );
@@ -59902,7 +60203,13 @@ var init_xmtp_sdk = __esm({
59902
60203
  agentInfo,
59903
60204
  phase: "recycle"
59904
60205
  });
59905
- await this.startListeningForAddresses([address]);
60206
+ const listen = (await this.startListeningForAddresses([address], "recycle/listen-failed")).get(address);
60207
+ if (listen?.client !== agent || !isListenHealthy(listen.outcome)) {
60208
+ newAgentReleased = listen?.client === agent && listen.outcome === "failed";
60209
+ throw new Error(
60210
+ `xmtp listener not started (${listen?.client === agent ? listen.outcome : "client_replaced"})`
60211
+ );
60212
+ }
59906
60213
  const replayGate = this.inboundReplayGates.get(addressKey);
59907
60214
  let replaySummary;
59908
60215
  try {
@@ -59921,8 +60228,21 @@ var init_xmtp_sdk = __esm({
59921
60228
  recycled++;
59922
60229
  } catch (err2) {
59923
60230
  failed++;
59924
- this.cancelStreamRecoveryForAddress(address);
59925
- this.clients.delete(address);
60231
+ if (newAgent && !newAgentReleased) {
60232
+ try {
60233
+ await this.releaseClient(
60234
+ address,
60235
+ newAgent,
60236
+ agentInfo.agentId,
60237
+ "recycle/create-failed"
60238
+ );
60239
+ } catch (releaseErr) {
60240
+ logWithTimestamp(
60241
+ `[xmtp-sdk] recycle: failed to release new client: ${address}`,
60242
+ releaseErr
60243
+ );
60244
+ }
60245
+ }
59926
60246
  logWithTimestamp(`[xmtp-sdk] recycle: failed to recreate client: ${address}`, err2);
59927
60247
  logger.error(
59928
60248
  LogEvent.XMTP_CONNECTION_FAILED,
@@ -60326,7 +60646,7 @@ var init_xmtp_sdk = __esm({
60326
60646
  }
60327
60647
  }
60328
60648
  async startListening() {
60329
- await this.startListeningForAddresses([...this.clients.keys()]);
60649
+ await this.startListeningForAddresses([...this.clients.keys()], "listen-failed");
60330
60650
  }
60331
60651
  async drainInboundReplayGate(address, expectedGate) {
60332
60652
  if (!expectedGate) {
@@ -60391,11 +60711,22 @@ var init_xmtp_sdk = __esm({
60391
60711
  );
60392
60712
  return gate;
60393
60713
  }
60394
- cancelStreamRecoveryForAddress(address) {
60714
+ // With `client`, only that client's recovery is cancelled, so a late caller
60715
+ // cannot cancel the recovery of a newer instance on the same address.
60716
+ cancelStreamRecoveryForAddress(address, client) {
60395
60717
  const addressKey = address.toLowerCase();
60396
- const cleanup = this.streamRecoveryCleanupByAddress.get(addressKey);
60718
+ const entry = this.streamRecoveryCleanupByAddress.get(addressKey);
60719
+ if (!entry || client && entry.client !== client) {
60720
+ return;
60721
+ }
60397
60722
  this.streamRecoveryCleanupByAddress.delete(addressKey);
60398
- cleanup?.();
60723
+ entry.cleanup();
60724
+ }
60725
+ // True while `client` is the registered client of `address` (case-insensitive)
60726
+ // and the address is neither stopped nor being released.
60727
+ isCurrentListenOwner(address, client) {
60728
+ const key = address.toLowerCase();
60729
+ return this.getClientByAddress(address) === client && !this.stoppedAddresses.has(key) && !this.pendingReleaseByAddress.has(key);
60399
60730
  }
60400
60731
  getClientGeneration(client) {
60401
60732
  const existing = this.clientGenerationByClient.get(client);
@@ -60547,10 +60878,17 @@ var init_xmtp_sdk = __esm({
60547
60878
  this.notifyAgentsWakeupAfterReplay();
60548
60879
  return summary;
60549
60880
  }
60550
- async startListeningForAddresses(addresses) {
60881
+ // Starts (at most once per client) the listener of every registered address
60882
+ // and reports each outcome. A "failed" client, i.e. one with neither a live
60883
+ // listener nor an owned retry task, is released with `releasePhase` before
60884
+ // this returns, so it is never served as a healthy instance.
60885
+ async startListeningForAddresses(addresses, releasePhase = "listen-failed") {
60551
60886
  const valid = addresses.filter((addr) => this.clients.has(addr));
60552
- await parallelMap(valid, async (address) => {
60553
- const agent = this.clients.get(address);
60887
+ const targets = valid.map((address) => ({
60888
+ address,
60889
+ client: this.clients.get(address)
60890
+ }));
60891
+ const listenForClient = async (address, agent) => {
60554
60892
  const clientGeneration = this.getClientGeneration(agent);
60555
60893
  const addressKey = address.toLowerCase();
60556
60894
  const tag = `[xmtp-sdk:${address}]`;
@@ -60561,6 +60899,11 @@ var init_xmtp_sdk = __esm({
60561
60899
  onchainosAgentId: onchainosAgent?.agentId,
60562
60900
  role: onchainosAgent?.role
60563
60901
  };
60902
+ const ownsAddress = () => this.isCurrentListenOwner(address, agent);
60903
+ if (!ownsAddress()) {
60904
+ logWithTimestamp(`${tag} listener setup skipped: client no longer current`);
60905
+ return "skipped";
60906
+ }
60564
60907
  try {
60565
60908
  await agent.client.conversations.syncAll([
60566
60909
  import_node_bindings2.ConsentState.Allowed,
@@ -60568,13 +60911,21 @@ var init_xmtp_sdk = __esm({
60568
60911
  ]);
60569
60912
  logWithTimestamp(`${tag} conversations synced (non-Denied)`);
60570
60913
  } catch (err2) {
60571
- logWithTimestamp(`${tag} conversation sync failed, skipping this client:`, err2);
60914
+ if (!ownsAddress()) {
60915
+ logWithTimestamp(`${tag} stale conversation sync failed after replacement, ignored:`, err2);
60916
+ return "skipped";
60917
+ }
60918
+ logWithTimestamp(`${tag} conversation sync failed, listener not started:`, err2);
60572
60919
  logger.error(
60573
60920
  LogEvent.CONVERSATIONS_STREAM_FAILED,
60574
60921
  err2 instanceof Error ? err2 : void 0,
60575
60922
  agentExtras(identity)
60576
60923
  );
60577
- return;
60924
+ return "failed";
60925
+ }
60926
+ if (!ownsAddress()) {
60927
+ logWithTimestamp(`${tag} listener setup skipped after sync: client no longer current`);
60928
+ return "skipped";
60578
60929
  }
60579
60930
  agent.use(loggingMiddleware(identity));
60580
60931
  agent.use(selfFilterMiddleware());
@@ -60603,6 +60954,7 @@ var init_xmtp_sdk = __esm({
60603
60954
  });
60604
60955
  const gate = new InboundReplayGate(inbound);
60605
60956
  this.inboundReplayGates.set(address.toLowerCase(), gate);
60957
+ this.inboundReplayGateOwners.set(gate, agent);
60606
60958
  agent.use(gate.middleware);
60607
60959
  } else {
60608
60960
  logWithTimestamp(
@@ -60620,16 +60972,25 @@ var init_xmtp_sdk = __esm({
60620
60972
  const MAX_RETRIES = 5;
60621
60973
  const BASE_DELAY_MS = 1e3;
60622
60974
  const startWithRetry = async (attempt = 0) => {
60623
- if (this.stoppedAddresses.has(address.toLowerCase()) || !this.clients.has(address)) {
60975
+ if (!ownsAddress()) {
60624
60976
  logWithTimestamp(`${tag} start skipped: client stopped or removed`);
60625
- return;
60977
+ return "skipped";
60626
60978
  }
60627
60979
  try {
60628
60980
  const liveStreamOptions = {
60629
60981
  consentStates: [import_node_bindings2.ConsentState.Allowed, import_node_bindings2.ConsentState.Unknown]
60630
60982
  };
60631
60983
  await agent.start(liveStreamOptions);
60984
+ if (!ownsAddress()) {
60985
+ logWithTimestamp(`${tag} stale agent.start resolved after replacement, ignored`);
60986
+ return "skipped";
60987
+ }
60988
+ return "started";
60632
60989
  } catch (err2) {
60990
+ if (!ownsAddress()) {
60991
+ logWithTimestamp(`${tag} stale agent.start failed after replacement, ignored:`, err2);
60992
+ return "skipped";
60993
+ }
60633
60994
  logWithTimestamp(`${tag} agent.start error (attempt=${attempt}):`, err2);
60634
60995
  logger.error(
60635
60996
  LogEvent.AGENT_START_FAILED,
@@ -60639,17 +61000,27 @@ var init_xmtp_sdk = __esm({
60639
61000
  attempt: String(attempt)
60640
61001
  }
60641
61002
  );
60642
- scheduleRetry(attempt);
61003
+ return scheduleRetry(attempt);
60643
61004
  }
60644
61005
  };
60645
61006
  const scheduleRetry = (attempt) => {
60646
- if (this.stoppedAddresses.has(address.toLowerCase()) || !this.clients.has(address)) {
61007
+ if (!ownsAddress()) {
60647
61008
  logWithTimestamp(`${tag} reconnect skipped: client stopped or removed`);
60648
- return;
61009
+ return "skipped";
60649
61010
  }
60650
61011
  if (attempt >= MAX_RETRIES) {
60651
- logWithTimestamp(`${tag} reached max retries (${MAX_RETRIES}), giving up reconnect`);
60652
- return;
61012
+ logWithTimestamp(
61013
+ `${tag} reached max retries (${MAX_RETRIES}), giving up reconnect; releasing client`
61014
+ );
61015
+ void this.releaseClient(
61016
+ address,
61017
+ agent,
61018
+ identity.onchainosAgentId ?? "",
61019
+ "listen/retry-exhausted"
61020
+ ).catch((err2) => {
61021
+ logWithTimestamp(`${tag} release after retry exhaustion failed:`, err2);
61022
+ });
61023
+ return "failed";
60653
61024
  }
60654
61025
  const delay = BASE_DELAY_MS * 2 ** attempt;
60655
61026
  logWithTimestamp(`${tag} reconnecting in ${delay}ms (attempt=${attempt + 1})`);
@@ -60657,6 +61028,7 @@ var init_xmtp_sdk = __esm({
60657
61028
  () => void startWithRetry(attempt + 1),
60658
61029
  delay
60659
61030
  );
61031
+ return "retrying";
60660
61032
  };
60661
61033
  const streamRecovery = new StreamRecoveryTelemetry();
60662
61034
  let recoveryTimeout;
@@ -60676,10 +61048,10 @@ var init_xmtp_sdk = __esm({
60676
61048
  recoveryStabilityTimeout = void 0;
60677
61049
  }
60678
61050
  };
60679
- this.streamRecoveryCleanupByAddress.set(
60680
- addressKey,
60681
- cleanupStreamRecovery
60682
- );
61051
+ this.streamRecoveryCleanupByAddress.set(addressKey, {
61052
+ client: agent,
61053
+ cleanup: cleanupStreamRecovery
61054
+ });
60683
61055
  agent.on("unhandledError", (err2) => {
60684
61056
  if (recoveryDisposed || this.clients.get(address) !== agent || this.stoppedAddresses.has(addressKey)) {
60685
61057
  return;
@@ -60841,9 +61213,54 @@ var init_xmtp_sdk = __esm({
60841
61213
  `${tag} agent message event msg=${msg?.id ?? "(unknown)"} kind=${msg?.kind ?? "(unknown)"} conv=${conversation?.id ?? msg?.conversationId ?? "(unknown)"} sender=${msg?.senderInboxId ?? "(unknown)"} isText=${typeof ctx.isText === "function" ? String(ctx.isText()) : "unknown"} preview="${previewDebugContent(msg?.content)}"`
60842
61214
  );
60843
61215
  });
60844
- await startWithRetry();
60845
- logWithTimestamp(`${tag} message listener started`);
61216
+ const outcome = await startWithRetry();
61217
+ logWithTimestamp(`${tag} message listener ${outcome}`);
61218
+ return outcome;
61219
+ };
61220
+ const settled = await parallelMap(targets, async ({ address, client }) => {
61221
+ let task = this.listenTaskByClient.get(client);
61222
+ if (!task) {
61223
+ task = listenForClient(address, client);
61224
+ this.listenTaskByClient.set(client, task);
61225
+ }
61226
+ let outcome;
61227
+ try {
61228
+ outcome = await task;
61229
+ } catch (err2) {
61230
+ logWithTimestamp(`[xmtp-sdk:${address}] listener setup failed:`, err2);
61231
+ logger.error(
61232
+ LogEvent.AGENT_START_FAILED,
61233
+ err2 instanceof Error ? err2 : new Error(String(err2)),
61234
+ {
61235
+ ...agentExtras({
61236
+ walletAddress: address,
61237
+ inboxId: client.client?.inboxId,
61238
+ onchainosAgentId: this.getAgentByAddress(address)?.agentId
61239
+ }),
61240
+ phase: "listener-setup"
61241
+ }
61242
+ );
61243
+ outcome = "failed";
61244
+ }
61245
+ if (outcome === "failed") {
61246
+ await this.releaseClient(
61247
+ address,
61248
+ client,
61249
+ this.getAgentByAddress(address)?.agentId ?? "",
61250
+ releasePhase
61251
+ );
61252
+ }
61253
+ return outcome;
61254
+ });
61255
+ const results = /* @__PURE__ */ new Map();
61256
+ targets.forEach(({ address, client }, i) => {
61257
+ const r = settled[i];
61258
+ results.set(address, {
61259
+ client,
61260
+ outcome: r.status === "fulfilled" ? r.value : "failed"
61261
+ });
60846
61262
  });
61263
+ return results;
60847
61264
  }
60848
61265
  async replayOfflineMessagesForAddress(address, expectedClient, trigger = "periodic_repair", recoveryIdentity) {
60849
61266
  const addressReplayStartedAt = Date.now();
@@ -70071,12 +70488,12 @@ async function runListenerWithLock(options, paths) {
70071
70488
  });
70072
70489
  }
70073
70490
  });
70074
- service.setPluginVersion("0.2.17");
70491
+ service.setPluginVersion("0.2.18");
70075
70492
  await service.init();
70076
70493
  const pluginVersionStatus = service.pluginVersionStatus;
70077
70494
  if (pluginVersionStatus.unavailable) {
70078
70495
  throw new Error(
70079
- `@okxweb3/a2a-node v${"0.2.17"} is below the required minimum v${pluginVersionStatus.minVersion}`
70496
+ `@okxweb3/a2a-node v${"0.2.18"} is below the required minimum v${pluginVersionStatus.minVersion}`
70080
70497
  );
70081
70498
  }
70082
70499
  const systemConfig = service.getSystemConfig();
@@ -70104,7 +70521,7 @@ async function runListenerWithLock(options, paths) {
70104
70521
  onchainosAgentId: "*",
70105
70522
  reason: "system-config missing sentryDsn",
70106
70523
  pluginId: "@okxweb3/a2a-node",
70107
- pluginVersion: "0.2.17"
70524
+ pluginVersion: "0.2.18"
70108
70525
  });
70109
70526
  }
70110
70527
  logWithTimestamp(
@@ -118447,7 +118864,7 @@ async function getCurrentNodeCliVersion() {
118447
118864
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
118448
118865
  }
118449
118866
  function getBundledNodeCliVersion() {
118450
- return true ? "0.2.17" : null;
118867
+ return true ? "0.2.18" : null;
118451
118868
  }
118452
118869
  function readConfiguredAiProvider() {
118453
118870
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -118657,7 +119074,7 @@ async function updateHermes(release, options) {
118657
119074
  }
118658
119075
  }
118659
119076
  async function installGatewayPluginForDoctor(target) {
118660
- const release = isPrereleaseVersion("0.2.17") ? "beta" : "latest";
119077
+ const release = isPrereleaseVersion("0.2.18") ? "beta" : "latest";
118661
119078
  const insideTargetGateway = detectGatewayInvocation() === target;
118662
119079
  const options = {
118663
119080
  restart: !insideTargetGateway,
@@ -119710,7 +120127,7 @@ async function runDoctor(options = {}) {
119710
120127
  platform: options.platform ?? process.platform,
119711
120128
  env: options.env ?? process.env,
119712
120129
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
119713
- cliVersion: options.cliVersion ?? (true ? "0.2.17" : "0.0.0"),
120130
+ cliVersion: options.cliVersion ?? (true ? "0.2.18" : "0.0.0"),
119714
120131
  fixMode: options.fix === true,
119715
120132
  nonInteractive: options.nonInteractive === true,
119716
120133
  packageChanged: false,
@@ -120817,7 +121234,7 @@ init_sentry_config();
120817
121234
  init_runtime_metadata();
120818
121235
  var CURRENT_GATEWAY_SESSION_KEYS_ENV4 = "OKX_A2A_CURRENT_GATEWAY_SESSION_KEYS";
120819
121236
  function printUsage3() {
120820
- console.log(`okx-a2a ${"0.2.17"}
121237
+ console.log(`okx-a2a ${"0.2.18"}
120821
121238
 
120822
121239
  Usage:
120823
121240
  okx-a2a <command> [options]
@@ -120859,7 +121276,7 @@ Run \`okx-a2a <command> -h\` for command-specific help.
120859
121276
  `);
120860
121277
  }
120861
121278
  function printVersion() {
120862
- console.log("0.2.17");
121279
+ console.log("0.2.18");
120863
121280
  }
120864
121281
  function printDaemonUsage() {
120865
121282
  console.log(`Usage: okx-a2a daemon <start|restart|stop|status|autostart> [options]
@@ -121485,12 +121902,12 @@ function assertOnlyKnownOptions(args, input) {
121485
121902
  async function queueXmtpDaemonCommand(args, type) {
121486
121903
  const json = hasFlag2(args, "--json");
121487
121904
  const timeoutMsRaw = readOption5(args, "--timeout-ms");
121488
- const wait3 = type === "xmtp-get-pending-list" || type === "xmtp-deny-pending-conversation" || type === "xmtp-refresh-agents";
121905
+ const wait2 = type === "xmtp-get-pending-list" || type === "xmtp-deny-pending-conversation" || type === "xmtp-refresh-agents";
121489
121906
  await assertDaemonRunningForQueuedCommand2(type);
121490
121907
  const commands = new CommandStore();
121491
121908
  const command = type === "xmtp-get-pending-list" ? commands.createXmtpGetPendingListCommand() : type === "xmtp-deny-pending-conversation" ? commands.createXmtpDenyPendingConversationCommand(readDenyPendingConversationOptions(args)) : commands.createXmtpRefreshAgentsCommand();
121492
121909
  await commands.submit(command);
121493
- if (!wait3) {
121910
+ if (!wait2) {
121494
121911
  outputCommandResult(json, { id: command.id, ok: true, completedAt: Date.now(), payload: { queued: true } }, `${type} queued command=${command.id}`);
121495
121912
  return;
121496
121913
  }
@@ -122354,10 +122771,10 @@ async function main() {
122354
122771
  if (command === "xmtp-test") {
122355
122772
  const { handleXmtpTestCommand: handleXmtpTestCommand2 } = await Promise.resolve().then(() => (init_xmtp_test_cli(), xmtp_test_cli_exports));
122356
122773
  await handleXmtpTestCommand2(process.argv.slice(3), {
122357
- packageVersion: "0.2.17",
122358
- agentSdkVersion: "2.3.0",
122359
- nodeSdkVersion: "6.1.0",
122360
- nodeBindingsVersion: "1.11.0"
122774
+ packageVersion: "0.2.18",
122775
+ agentSdkVersion: "2.3.1",
122776
+ nodeSdkVersion: "6.1.1",
122777
+ nodeBindingsVersion: "1.11.1"
122361
122778
  });
122362
122779
  return;
122363
122780
  }