@okxweb3/a2a-node 0.1.10-beta-bf67773ad1-260723190921 → 0.1.10

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 +362 -31
  2. package/dist/index.js +352 -25
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -4418,6 +4418,274 @@ var init_openclaw_gateway_config = __esm({
4418
4418
  }
4419
4419
  });
4420
4420
 
4421
+ // src/session-busy-tracker.ts
4422
+ function getSessionBusyTracker() {
4423
+ if (!singleton) {
4424
+ singleton = new SessionBusyTracker();
4425
+ }
4426
+ return singleton;
4427
+ }
4428
+ var RING_MAX, DEFAULT_STALE_RUN_MS, DEFAULT_STREAM_STALE_MS, DEFAULT_WATCHDOG_INTERVAL_MS, LOG_PREFIX, SessionBusyTracker, singleton;
4429
+ var init_session_busy_tracker = __esm({
4430
+ "src/session-busy-tracker.ts"() {
4431
+ "use strict";
4432
+ init_log();
4433
+ RING_MAX = 256;
4434
+ DEFAULT_STALE_RUN_MS = 10 * 60 * 1e3;
4435
+ DEFAULT_STREAM_STALE_MS = 60 * 1e3;
4436
+ DEFAULT_WATCHDOG_INTERVAL_MS = 5 * 1e3;
4437
+ LOG_PREFIX = "[okx-a2a:session-gate]";
4438
+ SessionBusyTracker = class {
4439
+ now;
4440
+ staleRunMs;
4441
+ streamStaleMs;
4442
+ watchdogIntervalMs;
4443
+ /** runIds that reached a terminal state; guards against late/out-of-order deltas. */
4444
+ finalized = [];
4445
+ runWaiters = /* @__PURE__ */ new Map();
4446
+ /** sessionKey → (runId → lastSeenMs). */
4447
+ activeRuns = /* @__PURE__ */ new Map();
4448
+ idleWaiters = /* @__PURE__ */ new Map();
4449
+ lastEventAtMs;
4450
+ lastStreamStaleFireMs = 0;
4451
+ streamStaleHandler = null;
4452
+ pendingWaiters = 0;
4453
+ watchdogTimer = null;
4454
+ constructor(options = {}) {
4455
+ this.now = options.now ?? Date.now;
4456
+ this.staleRunMs = options.staleRunMs ?? DEFAULT_STALE_RUN_MS;
4457
+ this.streamStaleMs = options.streamStaleMs ?? DEFAULT_STREAM_STALE_MS;
4458
+ this.watchdogIntervalMs = options.watchdogIntervalMs ?? DEFAULT_WATCHDOG_INTERVAL_MS;
4459
+ this.lastEventAtMs = this.now();
4460
+ }
4461
+ /** Any event frame arrived (chat/agent/tick/health/...): the stream is alive. */
4462
+ noteEvent() {
4463
+ this.lastEventAtMs = this.now();
4464
+ }
4465
+ /** Reconnect hook invoked when the event stream goes silent while a wait is pending. */
4466
+ setStreamStaleHandler(handler) {
4467
+ this.streamStaleHandler = handler;
4468
+ }
4469
+ notifyChat(payload) {
4470
+ if (!payload || typeof payload !== "object") {
4471
+ return;
4472
+ }
4473
+ const frame = payload;
4474
+ const runId = typeof frame.runId === "string" ? frame.runId : "";
4475
+ const sessionKey = typeof frame.sessionKey === "string" ? frame.sessionKey : "";
4476
+ const state = typeof frame.state === "string" ? frame.state : "";
4477
+ if (!runId) {
4478
+ return;
4479
+ }
4480
+ if (state === "final" || state === "error" || state === "aborted") {
4481
+ this.markDone(sessionKey, runId, state);
4482
+ } else {
4483
+ this.markActive(sessionKey, runId);
4484
+ }
4485
+ }
4486
+ notifyAgent(payload) {
4487
+ if (!payload || typeof payload !== "object") {
4488
+ return;
4489
+ }
4490
+ const frame = payload;
4491
+ const runId = typeof frame.runId === "string" ? frame.runId : "";
4492
+ const sessionKey = typeof frame.sessionKey === "string" ? frame.sessionKey : "";
4493
+ const phase = frame.data && typeof frame.data === "object" ? frame.data.phase : void 0;
4494
+ if (!runId) {
4495
+ return;
4496
+ }
4497
+ if (frame.stream === "lifecycle" && (phase === "end" || phase === "error")) {
4498
+ this.markDone(sessionKey, runId, String(phase));
4499
+ } else {
4500
+ this.markActive(sessionKey, runId);
4501
+ }
4502
+ }
4503
+ isSessionBusy(sessionKey) {
4504
+ const runs = this.activeRuns.get(sessionKey);
4505
+ if (!runs) {
4506
+ return false;
4507
+ }
4508
+ const now = this.now();
4509
+ for (const [runId, lastSeen] of runs) {
4510
+ if (now - lastSeen > this.staleRunMs) {
4511
+ runs.delete(runId);
4512
+ }
4513
+ }
4514
+ if (runs.size === 0) {
4515
+ this.activeRuns.delete(sessionKey);
4516
+ return false;
4517
+ }
4518
+ return true;
4519
+ }
4520
+ waitForSessionIdle(sessionKey, timeoutMs) {
4521
+ if (!this.isSessionBusy(sessionKey)) {
4522
+ return Promise.resolve("idle");
4523
+ }
4524
+ return new Promise((resolve9) => {
4525
+ let waiters = this.idleWaiters.get(sessionKey);
4526
+ if (!waiters) {
4527
+ waiters = /* @__PURE__ */ new Set();
4528
+ this.idleWaiters.set(sessionKey, waiters);
4529
+ }
4530
+ this.beginWait();
4531
+ const entry = (state) => {
4532
+ clearTimeout(timer);
4533
+ this.endWait();
4534
+ resolve9(state);
4535
+ };
4536
+ const timer = setTimeout(() => {
4537
+ const cur = this.idleWaiters.get(sessionKey);
4538
+ if (cur) {
4539
+ cur.delete(entry);
4540
+ if (cur.size === 0) {
4541
+ this.idleWaiters.delete(sessionKey);
4542
+ }
4543
+ }
4544
+ this.endWait();
4545
+ resolve9("timeout");
4546
+ }, timeoutMs);
4547
+ waiters.add(entry);
4548
+ });
4549
+ }
4550
+ waitForRunFinal(runId, timeoutMs) {
4551
+ if (this.finalized.includes(runId)) {
4552
+ return Promise.resolve("already-final");
4553
+ }
4554
+ return new Promise((resolve9) => {
4555
+ let waiters = this.runWaiters.get(runId);
4556
+ if (!waiters) {
4557
+ waiters = /* @__PURE__ */ new Set();
4558
+ this.runWaiters.set(runId, waiters);
4559
+ }
4560
+ this.beginWait();
4561
+ const entry = (state) => {
4562
+ clearTimeout(timer);
4563
+ this.endWait();
4564
+ resolve9(state);
4565
+ };
4566
+ const timer = setTimeout(() => {
4567
+ const cur = this.runWaiters.get(runId);
4568
+ if (cur) {
4569
+ cur.delete(entry);
4570
+ if (cur.size === 0) {
4571
+ this.runWaiters.delete(runId);
4572
+ }
4573
+ }
4574
+ this.endWait();
4575
+ resolve9("timeout");
4576
+ }, timeoutMs);
4577
+ waiters.add(entry);
4578
+ });
4579
+ }
4580
+ _debugSnapshot() {
4581
+ return {
4582
+ active: Array.from(this.activeRuns, ([key, runs]) => {
4583
+ return [key, Array.from(runs.keys())];
4584
+ }),
4585
+ finalizedTail: this.finalized.slice(-10)
4586
+ };
4587
+ }
4588
+ rememberFinalized(runId) {
4589
+ if (!this.finalized.includes(runId)) {
4590
+ this.finalized.push(runId);
4591
+ if (this.finalized.length > RING_MAX) {
4592
+ this.finalized.shift();
4593
+ }
4594
+ }
4595
+ }
4596
+ markActive(sessionKey, runId) {
4597
+ if (!sessionKey || !runId || this.finalized.includes(runId)) {
4598
+ return;
4599
+ }
4600
+ let runs = this.activeRuns.get(sessionKey);
4601
+ if (!runs) {
4602
+ runs = /* @__PURE__ */ new Map();
4603
+ this.activeRuns.set(sessionKey, runs);
4604
+ }
4605
+ runs.set(runId, this.now());
4606
+ }
4607
+ markDone(sessionKey, runId, state) {
4608
+ if (runId) {
4609
+ this.rememberFinalized(runId);
4610
+ const waiters = this.runWaiters.get(runId);
4611
+ if (waiters) {
4612
+ this.runWaiters.delete(runId);
4613
+ for (const fn of waiters) {
4614
+ fn(state);
4615
+ }
4616
+ }
4617
+ const runs = sessionKey ? this.activeRuns.get(sessionKey) : void 0;
4618
+ if (runs) {
4619
+ runs.delete(runId);
4620
+ if (runs.size === 0) {
4621
+ this.activeRuns.delete(sessionKey);
4622
+ }
4623
+ } else {
4624
+ for (const [key, sessionRuns] of this.activeRuns) {
4625
+ if (sessionRuns.delete(runId) && sessionRuns.size === 0) {
4626
+ this.activeRuns.delete(key);
4627
+ }
4628
+ }
4629
+ }
4630
+ }
4631
+ if (sessionKey) {
4632
+ this.releaseIdleWaiters(sessionKey);
4633
+ }
4634
+ }
4635
+ releaseIdleWaiters(sessionKey) {
4636
+ if (!sessionKey || this.isSessionBusy(sessionKey)) {
4637
+ return;
4638
+ }
4639
+ const waiters = this.idleWaiters.get(sessionKey);
4640
+ if (waiters) {
4641
+ this.idleWaiters.delete(sessionKey);
4642
+ for (const fn of waiters) {
4643
+ fn("idle");
4644
+ }
4645
+ }
4646
+ }
4647
+ beginWait() {
4648
+ this.pendingWaiters += 1;
4649
+ if (this.pendingWaiters === 1 && !this.watchdogTimer) {
4650
+ const timer = setInterval(() => {
4651
+ this.checkStreamLiveness();
4652
+ }, this.watchdogIntervalMs);
4653
+ timer.unref?.();
4654
+ this.watchdogTimer = timer;
4655
+ }
4656
+ }
4657
+ endWait() {
4658
+ this.pendingWaiters = Math.max(0, this.pendingWaiters - 1);
4659
+ if (this.pendingWaiters === 0 && this.watchdogTimer) {
4660
+ clearInterval(this.watchdogTimer);
4661
+ this.watchdogTimer = null;
4662
+ }
4663
+ }
4664
+ checkStreamLiveness() {
4665
+ const now = this.now();
4666
+ if (now - this.lastEventAtMs <= this.streamStaleMs) {
4667
+ return;
4668
+ }
4669
+ if (now - this.lastStreamStaleFireMs <= this.streamStaleMs) {
4670
+ return;
4671
+ }
4672
+ this.lastStreamStaleFireMs = now;
4673
+ errorWithTimestamp(
4674
+ `${LOG_PREFIX} event stream stale (${now - this.lastEventAtMs}ms since last event, threshold=${this.streamStaleMs}ms), invoking recovery handler`
4675
+ );
4676
+ try {
4677
+ this.streamStaleHandler?.();
4678
+ } catch (err2) {
4679
+ errorWithTimestamp(
4680
+ `${LOG_PREFIX} stream recovery handler failed: ${err2 instanceof Error ? err2.message : String(err2)}`
4681
+ );
4682
+ }
4683
+ }
4684
+ };
4685
+ singleton = null;
4686
+ }
4687
+ });
4688
+
4421
4689
  // ../../node_modules/ws/lib/constants.js
4422
4690
  var require_constants = __commonJS({
4423
4691
  "../../node_modules/ws/lib/constants.js"(exports2, module2) {
@@ -8107,6 +8375,17 @@ function createDefaultWebSocket(url) {
8107
8375
  }
8108
8376
  return new Ctor(url);
8109
8377
  }
8378
+ function ensureStreamStaleHandlerRegistered() {
8379
+ if (streamStaleHandlerRegistered) {
8380
+ return;
8381
+ }
8382
+ streamStaleHandlerRegistered = true;
8383
+ getSessionBusyTracker().setStreamStaleHandler(() => {
8384
+ errorWithTimestamp(`${LOG_PREFIX2} [session-gate] event stream stale, forcing gateway reconnect`);
8385
+ clearClient();
8386
+ void isOpenClawA2aPluginAvailable();
8387
+ });
8388
+ }
8110
8389
  function resolveOpenClawGatewayConfig(env = process.env) {
8111
8390
  const synced = readSyncedGatewayConfig(env);
8112
8391
  const explicitUrl = env.OKX_A2A_OPENCLAW_GATEWAY_URL?.trim() || void 0;
@@ -8123,12 +8402,13 @@ function resolveOpenClawGatewayConfig(env = process.env) {
8123
8402
  }
8124
8403
  function clearClient() {
8125
8404
  if (sharedClient) {
8126
- errorWithTimestamp(`${LOG_PREFIX} clear client`);
8405
+ errorWithTimestamp(`${LOG_PREFIX2} clear client`);
8127
8406
  sharedClient.close();
8128
8407
  }
8129
8408
  sharedClient = null;
8130
8409
  }
8131
8410
  async function ensureClient(timeoutMs) {
8411
+ ensureStreamStaleHandlerRegistered();
8132
8412
  if (sharedClient?.isReady()) {
8133
8413
  return sharedClient;
8134
8414
  }
@@ -8152,7 +8432,7 @@ async function ensureClient(timeoutMs) {
8152
8432
  const remainingMs = Math.max(1, timeoutMs - elapsedMs);
8153
8433
  const fallbackProtocol = firstProtocol === PREFERRED_PROTOCOL_VERSION ? LEGACY_PROTOCOL_VERSION : PREFERRED_PROTOCOL_VERSION;
8154
8434
  errorWithTimestamp(
8155
- `${LOG_PREFIX} protocol fallback from=${firstProtocol} to=${fallbackProtocol} remainingMs=${remainingMs}: ${formatErrorMessage(err2)}`
8435
+ `${LOG_PREFIX2} protocol fallback from=${firstProtocol} to=${fallbackProtocol} remainingMs=${remainingMs}: ${formatErrorMessage(err2)}`
8156
8436
  );
8157
8437
  const fallbackClient = new MinimalGatewayClient(config, fallbackProtocol);
8158
8438
  sharedClient = fallbackClient;
@@ -8163,7 +8443,7 @@ async function ensureClient(timeoutMs) {
8163
8443
  }
8164
8444
  function stopOpenClawGateway() {
8165
8445
  if (!sharedClient) {
8166
- errorWithTimestamp(`${LOG_PREFIX} stop requested with no active client`);
8446
+ errorWithTimestamp(`${LOG_PREFIX2} stop requested with no active client`);
8167
8447
  return;
8168
8448
  }
8169
8449
  clearClient();
@@ -8171,12 +8451,12 @@ function stopOpenClawGateway() {
8171
8451
  async function callOpenClawGateway(opts) {
8172
8452
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
8173
8453
  const startedAt = Date.now();
8174
- errorWithTimestamp(`${LOG_PREFIX} rpc start method=${opts.method} timeoutMs=${timeoutMs}`);
8454
+ errorWithTimestamp(`${LOG_PREFIX2} rpc start method=${opts.method} timeoutMs=${timeoutMs}`);
8175
8455
  for (let attempt = 0; ; attempt += 1) {
8176
8456
  try {
8177
8457
  const client = await ensureClient(timeoutMs);
8178
8458
  const result = await client.request(opts.method, opts.params, { timeoutMs });
8179
- errorWithTimestamp(`${LOG_PREFIX} rpc ok method=${opts.method} durationMs=${Date.now() - startedAt} attempts=${attempt + 1}`);
8459
+ errorWithTimestamp(`${LOG_PREFIX2} rpc ok method=${opts.method} durationMs=${Date.now() - startedAt} attempts=${attempt + 1}`);
8180
8460
  return result;
8181
8461
  } catch (err2) {
8182
8462
  const message = err2 instanceof Error ? err2.message : String(err2);
@@ -8186,13 +8466,13 @@ async function callOpenClawGateway(opts) {
8186
8466
  }
8187
8467
  if (isGatewayStartingError(err2) && delayMs !== void 0 && Date.now() + delayMs - startedAt < timeoutMs) {
8188
8468
  errorWithTimestamp(
8189
- `${LOG_PREFIX} rpc retry method=${opts.method} attempt=${attempt + 1} delayMs=${delayMs}: ${message}`
8469
+ `${LOG_PREFIX2} rpc retry method=${opts.method} attempt=${attempt + 1} delayMs=${delayMs}: ${message}`
8190
8470
  );
8191
8471
  await sleep2(delayMs);
8192
8472
  continue;
8193
8473
  }
8194
8474
  errorWithTimestamp(
8195
- `${LOG_PREFIX} rpc failed method=${opts.method} durationMs=${Date.now() - startedAt} attempts=${attempt + 1}: ${message}`
8475
+ `${LOG_PREFIX2} rpc failed method=${opts.method} durationMs=${Date.now() - startedAt} attempts=${attempt + 1}: ${message}`
8196
8476
  );
8197
8477
  if (isGatewayStartingError(err2)) {
8198
8478
  throw Object.assign(
@@ -8291,7 +8571,7 @@ function readSyncedGatewayConfig(env) {
8291
8571
  token: synced.token
8292
8572
  };
8293
8573
  } catch (err2) {
8294
- errorWithTimestamp(`${LOG_PREFIX} failed to read synced gateway config: ${err2 instanceof Error ? err2.message : String(err2)}`);
8574
+ errorWithTimestamp(`${LOG_PREFIX2} failed to read synced gateway config: ${err2 instanceof Error ? err2.message : String(err2)}`);
8295
8575
  return null;
8296
8576
  }
8297
8577
  }
@@ -8302,7 +8582,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
8302
8582
  client: {
8303
8583
  id: "gateway-client",
8304
8584
  displayName: "okx-a2a-node",
8305
- version: "0.1.10-beta-bf67773ad1-260723190921",
8585
+ version: "0.1.10",
8306
8586
  platform: "node",
8307
8587
  mode: "backend",
8308
8588
  instanceId
@@ -8313,7 +8593,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
8313
8593
  commands: [],
8314
8594
  permissions: {},
8315
8595
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
8316
- userAgent: `okx-a2a-node/${"0.1.10-beta-bf67773ad1-260723190921"}`,
8596
+ userAgent: `okx-a2a-node/${"0.1.10"}`,
8317
8597
  auth: {
8318
8598
  ...config.token ? { token: config.token } : {},
8319
8599
  ...config.password ? { password: config.password } : {}
@@ -8379,24 +8659,26 @@ function isSessionNotFound(err2) {
8379
8659
  const msg = String(e?.message ?? "");
8380
8660
  return (code2 === "INVALID_REQUEST" || code2 === "SESSION_NOT_FOUND") && msg.includes("session not found");
8381
8661
  }
8382
- var import_node_crypto3, DEFAULT_GATEWAY_PORT, DEFAULT_TIMEOUT_MS, DEFAULT_HEALTH_TIMEOUT_MS, PREFERRED_PROTOCOL_VERSION, LEGACY_PROTOCOL_VERSION, DEFAULT_SCOPES, LOG_PREFIX, GATEWAY_STARTING_RETRY_DELAYS_MS, wsFactory, preferredGatewayProtocolVersion, sharedClient, isOpenClawGatewayAvailable, MinimalGatewayClient;
8662
+ var import_node_crypto3, DEFAULT_GATEWAY_PORT, DEFAULT_TIMEOUT_MS, DEFAULT_HEALTH_TIMEOUT_MS, PREFERRED_PROTOCOL_VERSION, LEGACY_PROTOCOL_VERSION, DEFAULT_SCOPES, LOG_PREFIX2, GATEWAY_STARTING_RETRY_DELAYS_MS, wsFactory, preferredGatewayProtocolVersion, sharedClient, streamStaleHandlerRegistered, isOpenClawGatewayAvailable, MinimalGatewayClient;
8383
8663
  var init_openclaw_gateway = __esm({
8384
8664
  "src/openclaw-gateway.ts"() {
8385
8665
  "use strict";
8386
8666
  init_log();
8387
8667
  import_node_crypto3 = require("node:crypto");
8388
8668
  init_openclaw_gateway_config();
8669
+ init_session_busy_tracker();
8389
8670
  DEFAULT_GATEWAY_PORT = 18789;
8390
8671
  DEFAULT_TIMEOUT_MS = 2e4;
8391
8672
  DEFAULT_HEALTH_TIMEOUT_MS = 2e3;
8392
8673
  PREFERRED_PROTOCOL_VERSION = 4;
8393
8674
  LEGACY_PROTOCOL_VERSION = 3;
8394
8675
  DEFAULT_SCOPES = ["operator.admin"];
8395
- LOG_PREFIX = "[okx-a2a:gateway]";
8676
+ LOG_PREFIX2 = "[okx-a2a:gateway]";
8396
8677
  GATEWAY_STARTING_RETRY_DELAYS_MS = [300, 700, 1200, 2e3, 3e3];
8397
8678
  wsFactory = createDefaultWebSocket;
8398
8679
  preferredGatewayProtocolVersion = PREFERRED_PROTOCOL_VERSION;
8399
8680
  sharedClient = null;
8681
+ streamStaleHandlerRegistered = false;
8400
8682
  isOpenClawGatewayAvailable = isOpenClawA2aPluginAvailable;
8401
8683
  MinimalGatewayClient = class {
8402
8684
  config;
@@ -8416,7 +8698,7 @@ var init_openclaw_gateway = __esm({
8416
8698
  }
8417
8699
  async connect(timeoutMs) {
8418
8700
  errorWithTimestamp(
8419
- `${LOG_PREFIX} connect url=${this.config.url} source=${this.config.source} instanceId=${this.instanceId} protocol=${this.protocolVersion} passwordConfigured=${!!this.config.password} tokenConfigured=${!!this.config.token} scopes=${DEFAULT_SCOPES.join(",")}`
8701
+ `${LOG_PREFIX2} connect url=${this.config.url} source=${this.config.source} instanceId=${this.instanceId} protocol=${this.protocolVersion} passwordConfigured=${!!this.config.password} tokenConfigured=${!!this.config.token} scopes=${DEFAULT_SCOPES.join(",")}`
8420
8702
  );
8421
8703
  const ws = wsFactory(this.config.url);
8422
8704
  this.ws = ws;
@@ -8472,7 +8754,7 @@ var init_openclaw_gateway = __esm({
8472
8754
  }
8473
8755
  });
8474
8756
  });
8475
- errorWithTimestamp(`${LOG_PREFIX} hello ok instanceId=${this.instanceId} protocol=${this.protocolVersion}`);
8757
+ errorWithTimestamp(`${LOG_PREFIX2} hello ok instanceId=${this.instanceId} protocol=${this.protocolVersion}`);
8476
8758
  }
8477
8759
  request(method, params, options = {}) {
8478
8760
  if (method === "connect") {
@@ -8548,7 +8830,17 @@ var init_openclaw_gateway = __esm({
8548
8830
  try {
8549
8831
  frame = JSON.parse(String(raw));
8550
8832
  } catch (err2) {
8551
- errorWithTimestamp(`${LOG_PREFIX} invalid frame: ${err2 instanceof Error ? err2.message : String(err2)}`);
8833
+ errorWithTimestamp(`${LOG_PREFIX2} invalid frame: ${err2 instanceof Error ? err2.message : String(err2)}`);
8834
+ return;
8835
+ }
8836
+ if (frame.type === "event") {
8837
+ const gate = getSessionBusyTracker();
8838
+ gate.noteEvent();
8839
+ if (frame.event === "chat") {
8840
+ gate.notifyChat(frame.payload);
8841
+ } else if (frame.event === "agent") {
8842
+ gate.notifyAgent(frame.payload);
8843
+ }
8552
8844
  return;
8553
8845
  }
8554
8846
  if (frame.type !== "res") {
@@ -8570,7 +8862,7 @@ var init_openclaw_gateway = __esm({
8570
8862
  pending.reject(toGatewayError(frame.error));
8571
8863
  }
8572
8864
  handleClose(code2, reason) {
8573
- errorWithTimestamp(`${LOG_PREFIX} closed instanceId=${this.instanceId} code=${code2 ?? 0} reason=${reason || "no reason"}`);
8865
+ errorWithTimestamp(`${LOG_PREFIX2} closed instanceId=${this.instanceId} code=${code2 ?? 0} reason=${reason || "no reason"}`);
8574
8866
  this.ready = false;
8575
8867
  for (const [id, pending] of this.pending) {
8576
8868
  clearTimeout(pending.timer);
@@ -9328,8 +9620,8 @@ var require_worldwide = __commonJS({
9328
9620
  function getGlobalSingleton(name2, creator, obj) {
9329
9621
  const gbl = obj || GLOBAL_OBJ;
9330
9622
  const __SENTRY__ = gbl.__SENTRY__ = gbl.__SENTRY__ || {};
9331
- const singleton = __SENTRY__[name2] || (__SENTRY__[name2] = creator());
9332
- return singleton;
9623
+ const singleton2 = __SENTRY__[name2] || (__SENTRY__[name2] = creator());
9624
+ return singleton2;
9333
9625
  }
9334
9626
  exports2.GLOBAL_OBJ = GLOBAL_OBJ;
9335
9627
  exports2.getGlobalObject = getGlobalObject;
@@ -24824,6 +25116,13 @@ var init_sentry_logger = __esm({
24824
25116
  });
24825
25117
 
24826
25118
  // src/outbound-behavior.ts
25119
+ function resolveSessionGateFallbackMs(env = process.env) {
25120
+ const raw = Number(env.OKX_A2A_SESSION_GATE_FALLBACK_MS ?? "");
25121
+ if (Number.isFinite(raw) && raw > 0) {
25122
+ return raw;
25123
+ }
25124
+ return DEFAULT_SESSION_GATE_FALLBACK_MS;
25125
+ }
24827
25126
  function resolveUserDeliveryId(input) {
24828
25127
  return input.idempotencyKey?.trim() || (0, import_node_crypto5.randomUUID)();
24829
25128
  }
@@ -24884,7 +25183,7 @@ function createOutboundBehavior(provider, deps) {
24884
25183
  }
24885
25184
  return new SqliteOutboundBehavior(provider, deps);
24886
25185
  }
24887
- var import_node_crypto5, SqliteOutboundBehavior, DEFAULT_GATEWAY_PORT2, GATEWAY_OUTBOUND_LOG_PREFIX, GatewayOutboundBehavior;
25186
+ var import_node_crypto5, SqliteOutboundBehavior, DEFAULT_GATEWAY_PORT2, GATEWAY_OUTBOUND_LOG_PREFIX, DEFAULT_SESSION_GATE_FALLBACK_MS, GatewayOutboundBehavior;
24888
25187
  var init_outbound_behavior = __esm({
24889
25188
  "src/outbound-behavior.ts"() {
24890
25189
  "use strict";
@@ -24893,6 +25192,7 @@ var init_outbound_behavior = __esm({
24893
25192
  init_user_attention_ipc();
24894
25193
  init_openclaw_gateway();
24895
25194
  init_openclaw_session_key();
25195
+ init_session_busy_tracker();
24896
25196
  init_openclaw_route();
24897
25197
  init_sentry_logger();
24898
25198
  SqliteOutboundBehavior = class {
@@ -24968,15 +25268,25 @@ var init_outbound_behavior = __esm({
24968
25268
  callPromptUserToLatestSessions
24969
25269
  };
24970
25270
  GATEWAY_OUTBOUND_LOG_PREFIX = "[okx-a2a:gateway-outbound]";
25271
+ DEFAULT_SESSION_GATE_FALLBACK_MS = 3e5;
24971
25272
  GatewayOutboundBehavior = class {
24972
25273
  provider = "openclaw";
24973
25274
  gateway;
24974
25275
  store;
24975
25276
  sessionMetaStore;
25277
+ /** `undefined` = default (resolve the shared tracker lazily on first dispatch); `null` = disabled. */
25278
+ sessionGateOption;
24976
25279
  constructor(options = {}) {
24977
25280
  this.gateway = options.gateway ?? DEFAULT_GATEWAY_PORT2;
24978
25281
  this.store = options.store;
24979
25282
  this.sessionMetaStore = options.store;
25283
+ this.sessionGateOption = options.sessionGate;
25284
+ }
25285
+ resolveSessionGate() {
25286
+ if (this.sessionGateOption === void 0) {
25287
+ return getSessionBusyTracker();
25288
+ }
25289
+ return this.sessionGateOption;
24980
25290
  }
24981
25291
  async createSession(input) {
24982
25292
  const gatewaySessionKey = this.resolveGatewaySessionKey(input);
@@ -25029,8 +25339,19 @@ var init_outbound_behavior = __esm({
25029
25339
  errorWithTimestamp(
25030
25340
  `${GATEWAY_OUTBOUND_LOG_PREFIX} dispatchSessionMessage sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} jobId=${input.jobId ?? "(none)"} agentId=${input.agentId ?? "(none)"} messageId=${input.messageId ?? "(none)"}`
25031
25341
  );
25342
+ const sessionGate = this.resolveSessionGate();
25343
+ const gateFallbackMs = resolveSessionGateFallbackMs();
25344
+ if (sessionGate?.isSessionBusy(gatewaySessionKey)) {
25345
+ errorWithTimestamp(
25346
+ `${GATEWAY_OUTBOUND_LOG_PREFIX} [session-gate] busy, waiting idle sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} (fallbackMs=${gateFallbackMs})`
25347
+ );
25348
+ const idleState = await sessionGate.waitForSessionIdle(gatewaySessionKey, gateFallbackMs);
25349
+ errorWithTimestamp(
25350
+ `${GATEWAY_OUTBOUND_LOG_PREFIX} [session-gate] idle wait done sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} result=${idleState}`
25351
+ );
25352
+ }
25032
25353
  try {
25033
- await this.gateway.callSessionsSend({
25354
+ const sendResult = await this.gateway.callSessionsSend({
25034
25355
  key: gatewaySessionKey,
25035
25356
  message: input.content,
25036
25357
  ...input.messageId ? { idempotencyKey: input.messageId } : {}
@@ -25045,6 +25366,16 @@ var init_outbound_behavior = __esm({
25045
25366
  messageId: input.messageId ?? "",
25046
25367
  status: "delivered"
25047
25368
  }));
25369
+ const gateRunId = sendResult && typeof sendResult === "object" && typeof sendResult.runId === "string" ? sendResult.runId : input.messageId;
25370
+ if (sessionGate && typeof gateRunId === "string" && gateRunId) {
25371
+ errorWithTimestamp(
25372
+ `${GATEWAY_OUTBOUND_LOG_PREFIX} [session-gate] holding sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} runId=${gateRunId} until run final/error (fallbackMs=${gateFallbackMs})`
25373
+ );
25374
+ const gateState = await sessionGate.waitForRunFinal(gateRunId, gateFallbackMs);
25375
+ errorWithTimestamp(
25376
+ `${GATEWAY_OUTBOUND_LOG_PREFIX} [session-gate] released sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} runId=${gateRunId} state=${gateState}`
25377
+ );
25378
+ }
25048
25379
  } catch (err2) {
25049
25380
  if (!this.store || !isRetryableOpenClawGatewayError(err2)) {
25050
25381
  logger.error(LogEvent.GATEWAY_REQUEST_FAILED, err2 instanceof Error ? err2 : new Error(String(err2)), gatewayOutboundExtra("sessions.send", {
@@ -25584,7 +25915,7 @@ var init_sentry_config = __esm({
25584
25915
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
25585
25916
  SENTRY_CONFIG = {
25586
25917
  projectName: "okx/openclaw-okx-a2a-extension",
25587
- release: "0.1.10-beta-bf67773ad1-260723190921",
25918
+ release: "0.1.10",
25588
25919
  environment,
25589
25920
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
25590
25921
  };
@@ -94318,12 +94649,12 @@ async function runListenerWithLock(options, paths) {
94318
94649
  });
94319
94650
  }
94320
94651
  });
94321
- service.setPluginVersion("0.1.10-beta-bf67773ad1-260723190921");
94652
+ service.setPluginVersion("0.1.10");
94322
94653
  await service.init();
94323
94654
  const pluginVersionStatus = service.pluginVersionStatus;
94324
94655
  if (pluginVersionStatus.unavailable) {
94325
94656
  throw new Error(
94326
- `@okxweb3/a2a-node v${"0.1.10-beta-bf67773ad1-260723190921"} is below the required minimum v${pluginVersionStatus.minVersion}`
94657
+ `@okxweb3/a2a-node v${"0.1.10"} is below the required minimum v${pluginVersionStatus.minVersion}`
94327
94658
  );
94328
94659
  }
94329
94660
  const systemConfig = service.getSystemConfig();
@@ -94341,7 +94672,7 @@ async function runListenerWithLock(options, paths) {
94341
94672
  onchainosAgentId: "*",
94342
94673
  reason: "system-config missing sentryDsn",
94343
94674
  pluginId: "@okxweb3/a2a-node",
94344
- pluginVersion: "0.1.10-beta-bf67773ad1-260723190921"
94675
+ pluginVersion: "0.1.10"
94345
94676
  });
94346
94677
  }
94347
94678
  logWithTimestamp(
@@ -98363,7 +98694,7 @@ async function getCurrentNodeCliVersion() {
98363
98694
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
98364
98695
  }
98365
98696
  function getBundledNodeCliVersion() {
98366
- return true ? "0.1.10-beta-bf67773ad1-260723190921" : null;
98697
+ return true ? "0.1.10" : null;
98367
98698
  }
98368
98699
  function readConfiguredAiProvider() {
98369
98700
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -98573,7 +98904,7 @@ async function updateHermes(release, options) {
98573
98904
  }
98574
98905
  }
98575
98906
  async function installGatewayPluginForDoctor(target) {
98576
- const release = isPrereleaseVersion("0.1.10-beta-bf67773ad1-260723190921") ? "beta" : "latest";
98907
+ const release = isPrereleaseVersion("0.1.10") ? "beta" : "latest";
98577
98908
  const insideTargetGateway = detectGatewayInvocation() === target;
98578
98909
  const options = {
98579
98910
  restart: !insideTargetGateway,
@@ -99561,7 +99892,7 @@ async function runDoctor(options = {}) {
99561
99892
  platform: options.platform ?? process.platform,
99562
99893
  env: options.env ?? process.env,
99563
99894
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
99564
- cliVersion: options.cliVersion ?? (true ? "0.1.10-beta-bf67773ad1-260723190921" : "0.0.0"),
99895
+ cliVersion: options.cliVersion ?? (true ? "0.1.10" : "0.0.0"),
99565
99896
  fixMode: options.fix === true,
99566
99897
  nonInteractive: options.nonInteractive === true,
99567
99898
  packageChanged: false,
@@ -100693,7 +101024,7 @@ init_sentry_logger();
100693
101024
  init_sentry_config();
100694
101025
  var CURRENT_GATEWAY_SESSION_KEYS_ENV4 = "OKX_A2A_CURRENT_GATEWAY_SESSION_KEYS";
100695
101026
  function printUsage2() {
100696
- console.log(`okx-a2a ${"0.1.10-beta-bf67773ad1-260723190921"}
101027
+ console.log(`okx-a2a ${"0.1.10"}
100697
101028
 
100698
101029
  Usage:
100699
101030
  okx-a2a <command> [options]
@@ -100731,7 +101062,7 @@ Run \`okx-a2a <command> -h\` for command-specific help.
100731
101062
  `);
100732
101063
  }
100733
101064
  function printVersion() {
100734
- console.log("0.1.10-beta-bf67773ad1-260723190921");
101065
+ console.log("0.1.10");
100735
101066
  }
100736
101067
  function printDaemonUsage() {
100737
101068
  console.log(`Usage: okx-a2a daemon <start|restart|stop|status|autostart> [options]
@@ -101977,9 +102308,9 @@ function assertSupportedNodeVersion() {
101977
102308
  const major = Number(majorRaw);
101978
102309
  const minor = Number(minorRaw);
101979
102310
  const patch = Number(patchRaw);
101980
- const supported = major > 22 || major === 22 && (minor > 13 || minor === 13 && patch >= 0);
102311
+ const supported = major > 22 || major === 22 && (minor > 14 || minor === 14 && patch >= 0);
101981
102312
  if (!supported) {
101982
- throw new Error(`okx-a2a requires Node.js >= 22.13.0, current=${process.version}`);
102313
+ throw new Error(`okx-a2a requires Node.js >= 22.14.0, current=${process.version}`);
101983
102314
  }
101984
102315
  }
101985
102316
  var cliSentryInitAttempted = false;
package/dist/index.js CHANGED
@@ -3816,8 +3816,8 @@ var require_worldwide = __commonJS({
3816
3816
  function getGlobalSingleton(name2, creator, obj) {
3817
3817
  const gbl = obj || GLOBAL_OBJ;
3818
3818
  const __SENTRY__ = gbl.__SENTRY__ = gbl.__SENTRY__ || {};
3819
- const singleton = __SENTRY__[name2] || (__SENTRY__[name2] = creator());
3820
- return singleton;
3819
+ const singleton2 = __SENTRY__[name2] || (__SENTRY__[name2] = creator());
3820
+ return singleton2;
3821
3821
  }
3822
3822
  exports2.GLOBAL_OBJ = GLOBAL_OBJ;
3823
3823
  exports2.getGlobalObject = getGlobalObject;
@@ -71981,7 +71981,7 @@ var init_sentry_config = __esm({
71981
71981
  environment = process.env.SENTRY_ENV === "dev" ? "dev" : "prod";
71982
71982
  SENTRY_CONFIG = {
71983
71983
  projectName: "okx/openclaw-okx-a2a-extension",
71984
- release: "0.1.10-beta-bf67773ad1-260723190921",
71984
+ release: "0.1.10",
71985
71985
  environment,
71986
71986
  runtimeContainer: normalizeRuntimeContainer(process.env.OKX_A2A_RUNTIME_CONTAINER)
71987
71987
  };
@@ -72911,7 +72911,7 @@ async function getCurrentNodeCliVersion() {
72911
72911
  return await getGlobalNpmPackageVersion(UPDATE_PACKAGES.node) ?? getBundledNodeCliVersion();
72912
72912
  }
72913
72913
  function getBundledNodeCliVersion() {
72914
- return true ? "0.1.10-beta-bf67773ad1-260723190921" : null;
72914
+ return true ? "0.1.10" : null;
72915
72915
  }
72916
72916
  function readConfiguredAiProvider() {
72917
72917
  const explicit = process.env.OKX_A2A_AI_PROVIDER || process.env.OKX_AGENT_TASK_AI_CLI;
@@ -73121,7 +73121,7 @@ async function updateHermes(release, options) {
73121
73121
  }
73122
73122
  }
73123
73123
  async function installGatewayPluginForDoctor(target) {
73124
- const release = isPrereleaseVersion("0.1.10-beta-bf67773ad1-260723190921") ? "beta" : "latest";
73124
+ const release = isPrereleaseVersion("0.1.10") ? "beta" : "latest";
73125
73125
  const insideTargetGateway = detectGatewayInvocation() === target;
73126
73126
  const options = {
73127
73127
  restart: !insideTargetGateway,
@@ -74005,6 +74005,7 @@ __export(index_exports, {
74005
74005
  NATIVE_LAUNCHER_EXE_NAME: () => NATIVE_LAUNCHER_EXE_NAME,
74006
74006
  OPENCLAW_GATEWAY_ROUTE_GROUP_ID: () => OPENCLAW_GATEWAY_ROUTE_GROUP_ID,
74007
74007
  SYSTEM_NOTIFICATION_SESSION_KEY: () => SYSTEM_NOTIFICATION_SESSION_KEY,
74008
+ SessionBusyTracker: () => SessionBusyTracker,
74008
74009
  SessionStore: () => SessionStore,
74009
74010
  SqliteOutboundBehavior: () => SqliteOutboundBehavior,
74010
74011
  TASK_NAME: () => TASK_NAME,
@@ -74070,6 +74071,7 @@ __export(index_exports, {
74070
74071
  formatDoctorReportForHumans: () => formatDoctorReportForHumans,
74071
74072
  getDaemonStatus: () => getDaemonStatus,
74072
74073
  getHermesGatewayPluginStatus: () => getHermesGatewayPluginStatus,
74074
+ getSessionBusyTracker: () => getSessionBusyTracker,
74073
74075
  handleDoctorCommand: () => handleDoctorCommand,
74074
74076
  handleXmtpSendCommand: () => handleXmtpSendCommand,
74075
74077
  hasAiRuntimeMarker: () => hasAiRuntimeMarker,
@@ -89244,6 +89246,268 @@ function readSyncedOpenClawGatewayConfig(homeDir = resolveA2aTaskHome()) {
89244
89246
  };
89245
89247
  }
89246
89248
 
89249
+ // src/session-busy-tracker.ts
89250
+ init_log();
89251
+ var RING_MAX = 256;
89252
+ var DEFAULT_STALE_RUN_MS = 10 * 60 * 1e3;
89253
+ var DEFAULT_STREAM_STALE_MS = 60 * 1e3;
89254
+ var DEFAULT_WATCHDOG_INTERVAL_MS = 5 * 1e3;
89255
+ var LOG_PREFIX = "[okx-a2a:session-gate]";
89256
+ var SessionBusyTracker = class {
89257
+ now;
89258
+ staleRunMs;
89259
+ streamStaleMs;
89260
+ watchdogIntervalMs;
89261
+ /** runIds that reached a terminal state; guards against late/out-of-order deltas. */
89262
+ finalized = [];
89263
+ runWaiters = /* @__PURE__ */ new Map();
89264
+ /** sessionKey → (runId → lastSeenMs). */
89265
+ activeRuns = /* @__PURE__ */ new Map();
89266
+ idleWaiters = /* @__PURE__ */ new Map();
89267
+ lastEventAtMs;
89268
+ lastStreamStaleFireMs = 0;
89269
+ streamStaleHandler = null;
89270
+ pendingWaiters = 0;
89271
+ watchdogTimer = null;
89272
+ constructor(options = {}) {
89273
+ this.now = options.now ?? Date.now;
89274
+ this.staleRunMs = options.staleRunMs ?? DEFAULT_STALE_RUN_MS;
89275
+ this.streamStaleMs = options.streamStaleMs ?? DEFAULT_STREAM_STALE_MS;
89276
+ this.watchdogIntervalMs = options.watchdogIntervalMs ?? DEFAULT_WATCHDOG_INTERVAL_MS;
89277
+ this.lastEventAtMs = this.now();
89278
+ }
89279
+ /** Any event frame arrived (chat/agent/tick/health/...): the stream is alive. */
89280
+ noteEvent() {
89281
+ this.lastEventAtMs = this.now();
89282
+ }
89283
+ /** Reconnect hook invoked when the event stream goes silent while a wait is pending. */
89284
+ setStreamStaleHandler(handler) {
89285
+ this.streamStaleHandler = handler;
89286
+ }
89287
+ notifyChat(payload) {
89288
+ if (!payload || typeof payload !== "object") {
89289
+ return;
89290
+ }
89291
+ const frame = payload;
89292
+ const runId = typeof frame.runId === "string" ? frame.runId : "";
89293
+ const sessionKey = typeof frame.sessionKey === "string" ? frame.sessionKey : "";
89294
+ const state = typeof frame.state === "string" ? frame.state : "";
89295
+ if (!runId) {
89296
+ return;
89297
+ }
89298
+ if (state === "final" || state === "error" || state === "aborted") {
89299
+ this.markDone(sessionKey, runId, state);
89300
+ } else {
89301
+ this.markActive(sessionKey, runId);
89302
+ }
89303
+ }
89304
+ notifyAgent(payload) {
89305
+ if (!payload || typeof payload !== "object") {
89306
+ return;
89307
+ }
89308
+ const frame = payload;
89309
+ const runId = typeof frame.runId === "string" ? frame.runId : "";
89310
+ const sessionKey = typeof frame.sessionKey === "string" ? frame.sessionKey : "";
89311
+ const phase = frame.data && typeof frame.data === "object" ? frame.data.phase : void 0;
89312
+ if (!runId) {
89313
+ return;
89314
+ }
89315
+ if (frame.stream === "lifecycle" && (phase === "end" || phase === "error")) {
89316
+ this.markDone(sessionKey, runId, String(phase));
89317
+ } else {
89318
+ this.markActive(sessionKey, runId);
89319
+ }
89320
+ }
89321
+ isSessionBusy(sessionKey) {
89322
+ const runs = this.activeRuns.get(sessionKey);
89323
+ if (!runs) {
89324
+ return false;
89325
+ }
89326
+ const now = this.now();
89327
+ for (const [runId, lastSeen] of runs) {
89328
+ if (now - lastSeen > this.staleRunMs) {
89329
+ runs.delete(runId);
89330
+ }
89331
+ }
89332
+ if (runs.size === 0) {
89333
+ this.activeRuns.delete(sessionKey);
89334
+ return false;
89335
+ }
89336
+ return true;
89337
+ }
89338
+ waitForSessionIdle(sessionKey, timeoutMs) {
89339
+ if (!this.isSessionBusy(sessionKey)) {
89340
+ return Promise.resolve("idle");
89341
+ }
89342
+ return new Promise((resolve6) => {
89343
+ let waiters = this.idleWaiters.get(sessionKey);
89344
+ if (!waiters) {
89345
+ waiters = /* @__PURE__ */ new Set();
89346
+ this.idleWaiters.set(sessionKey, waiters);
89347
+ }
89348
+ this.beginWait();
89349
+ const entry = (state) => {
89350
+ clearTimeout(timer);
89351
+ this.endWait();
89352
+ resolve6(state);
89353
+ };
89354
+ const timer = setTimeout(() => {
89355
+ const cur = this.idleWaiters.get(sessionKey);
89356
+ if (cur) {
89357
+ cur.delete(entry);
89358
+ if (cur.size === 0) {
89359
+ this.idleWaiters.delete(sessionKey);
89360
+ }
89361
+ }
89362
+ this.endWait();
89363
+ resolve6("timeout");
89364
+ }, timeoutMs);
89365
+ waiters.add(entry);
89366
+ });
89367
+ }
89368
+ waitForRunFinal(runId, timeoutMs) {
89369
+ if (this.finalized.includes(runId)) {
89370
+ return Promise.resolve("already-final");
89371
+ }
89372
+ return new Promise((resolve6) => {
89373
+ let waiters = this.runWaiters.get(runId);
89374
+ if (!waiters) {
89375
+ waiters = /* @__PURE__ */ new Set();
89376
+ this.runWaiters.set(runId, waiters);
89377
+ }
89378
+ this.beginWait();
89379
+ const entry = (state) => {
89380
+ clearTimeout(timer);
89381
+ this.endWait();
89382
+ resolve6(state);
89383
+ };
89384
+ const timer = setTimeout(() => {
89385
+ const cur = this.runWaiters.get(runId);
89386
+ if (cur) {
89387
+ cur.delete(entry);
89388
+ if (cur.size === 0) {
89389
+ this.runWaiters.delete(runId);
89390
+ }
89391
+ }
89392
+ this.endWait();
89393
+ resolve6("timeout");
89394
+ }, timeoutMs);
89395
+ waiters.add(entry);
89396
+ });
89397
+ }
89398
+ _debugSnapshot() {
89399
+ return {
89400
+ active: Array.from(this.activeRuns, ([key, runs]) => {
89401
+ return [key, Array.from(runs.keys())];
89402
+ }),
89403
+ finalizedTail: this.finalized.slice(-10)
89404
+ };
89405
+ }
89406
+ rememberFinalized(runId) {
89407
+ if (!this.finalized.includes(runId)) {
89408
+ this.finalized.push(runId);
89409
+ if (this.finalized.length > RING_MAX) {
89410
+ this.finalized.shift();
89411
+ }
89412
+ }
89413
+ }
89414
+ markActive(sessionKey, runId) {
89415
+ if (!sessionKey || !runId || this.finalized.includes(runId)) {
89416
+ return;
89417
+ }
89418
+ let runs = this.activeRuns.get(sessionKey);
89419
+ if (!runs) {
89420
+ runs = /* @__PURE__ */ new Map();
89421
+ this.activeRuns.set(sessionKey, runs);
89422
+ }
89423
+ runs.set(runId, this.now());
89424
+ }
89425
+ markDone(sessionKey, runId, state) {
89426
+ if (runId) {
89427
+ this.rememberFinalized(runId);
89428
+ const waiters = this.runWaiters.get(runId);
89429
+ if (waiters) {
89430
+ this.runWaiters.delete(runId);
89431
+ for (const fn of waiters) {
89432
+ fn(state);
89433
+ }
89434
+ }
89435
+ const runs = sessionKey ? this.activeRuns.get(sessionKey) : void 0;
89436
+ if (runs) {
89437
+ runs.delete(runId);
89438
+ if (runs.size === 0) {
89439
+ this.activeRuns.delete(sessionKey);
89440
+ }
89441
+ } else {
89442
+ for (const [key, sessionRuns] of this.activeRuns) {
89443
+ if (sessionRuns.delete(runId) && sessionRuns.size === 0) {
89444
+ this.activeRuns.delete(key);
89445
+ }
89446
+ }
89447
+ }
89448
+ }
89449
+ if (sessionKey) {
89450
+ this.releaseIdleWaiters(sessionKey);
89451
+ }
89452
+ }
89453
+ releaseIdleWaiters(sessionKey) {
89454
+ if (!sessionKey || this.isSessionBusy(sessionKey)) {
89455
+ return;
89456
+ }
89457
+ const waiters = this.idleWaiters.get(sessionKey);
89458
+ if (waiters) {
89459
+ this.idleWaiters.delete(sessionKey);
89460
+ for (const fn of waiters) {
89461
+ fn("idle");
89462
+ }
89463
+ }
89464
+ }
89465
+ beginWait() {
89466
+ this.pendingWaiters += 1;
89467
+ if (this.pendingWaiters === 1 && !this.watchdogTimer) {
89468
+ const timer = setInterval(() => {
89469
+ this.checkStreamLiveness();
89470
+ }, this.watchdogIntervalMs);
89471
+ timer.unref?.();
89472
+ this.watchdogTimer = timer;
89473
+ }
89474
+ }
89475
+ endWait() {
89476
+ this.pendingWaiters = Math.max(0, this.pendingWaiters - 1);
89477
+ if (this.pendingWaiters === 0 && this.watchdogTimer) {
89478
+ clearInterval(this.watchdogTimer);
89479
+ this.watchdogTimer = null;
89480
+ }
89481
+ }
89482
+ checkStreamLiveness() {
89483
+ const now = this.now();
89484
+ if (now - this.lastEventAtMs <= this.streamStaleMs) {
89485
+ return;
89486
+ }
89487
+ if (now - this.lastStreamStaleFireMs <= this.streamStaleMs) {
89488
+ return;
89489
+ }
89490
+ this.lastStreamStaleFireMs = now;
89491
+ errorWithTimestamp(
89492
+ `${LOG_PREFIX} event stream stale (${now - this.lastEventAtMs}ms since last event, threshold=${this.streamStaleMs}ms), invoking recovery handler`
89493
+ );
89494
+ try {
89495
+ this.streamStaleHandler?.();
89496
+ } catch (err2) {
89497
+ errorWithTimestamp(
89498
+ `${LOG_PREFIX} stream recovery handler failed: ${err2 instanceof Error ? err2.message : String(err2)}`
89499
+ );
89500
+ }
89501
+ }
89502
+ };
89503
+ var singleton = null;
89504
+ function getSessionBusyTracker() {
89505
+ if (!singleton) {
89506
+ singleton = new SessionBusyTracker();
89507
+ }
89508
+ return singleton;
89509
+ }
89510
+
89247
89511
  // src/openclaw-gateway.ts
89248
89512
  var DEFAULT_GATEWAY_PORT = 18789;
89249
89513
  var DEFAULT_TIMEOUT_MS = 2e4;
@@ -89251,7 +89515,7 @@ var DEFAULT_HEALTH_TIMEOUT_MS = 2e3;
89251
89515
  var PREFERRED_PROTOCOL_VERSION = 4;
89252
89516
  var LEGACY_PROTOCOL_VERSION = 3;
89253
89517
  var DEFAULT_SCOPES = ["operator.admin"];
89254
- var LOG_PREFIX = "[okx-a2a:gateway]";
89518
+ var LOG_PREFIX2 = "[okx-a2a:gateway]";
89255
89519
  var GATEWAY_STARTING_RETRY_DELAYS_MS = [300, 700, 1200, 2e3, 3e3];
89256
89520
  var wsFactory = createDefaultWebSocket;
89257
89521
  var preferredGatewayProtocolVersion = PREFERRED_PROTOCOL_VERSION;
@@ -89303,6 +89567,18 @@ function createDefaultWebSocket(url) {
89303
89567
  return new Ctor(url);
89304
89568
  }
89305
89569
  var sharedClient = null;
89570
+ var streamStaleHandlerRegistered = false;
89571
+ function ensureStreamStaleHandlerRegistered() {
89572
+ if (streamStaleHandlerRegistered) {
89573
+ return;
89574
+ }
89575
+ streamStaleHandlerRegistered = true;
89576
+ getSessionBusyTracker().setStreamStaleHandler(() => {
89577
+ errorWithTimestamp(`${LOG_PREFIX2} [session-gate] event stream stale, forcing gateway reconnect`);
89578
+ clearClient();
89579
+ void isOpenClawA2aPluginAvailable();
89580
+ });
89581
+ }
89306
89582
  function resolveOpenClawGatewayConfig(env = process.env) {
89307
89583
  const synced = readSyncedGatewayConfig(env);
89308
89584
  const explicitUrl = env.OKX_A2A_OPENCLAW_GATEWAY_URL?.trim() || void 0;
@@ -89324,12 +89600,13 @@ function setOpenClawWebSocketFactoryForTests(factory) {
89324
89600
  }
89325
89601
  function clearClient() {
89326
89602
  if (sharedClient) {
89327
- errorWithTimestamp(`${LOG_PREFIX} clear client`);
89603
+ errorWithTimestamp(`${LOG_PREFIX2} clear client`);
89328
89604
  sharedClient.close();
89329
89605
  }
89330
89606
  sharedClient = null;
89331
89607
  }
89332
89608
  async function ensureClient(timeoutMs) {
89609
+ ensureStreamStaleHandlerRegistered();
89333
89610
  if (sharedClient?.isReady()) {
89334
89611
  return sharedClient;
89335
89612
  }
@@ -89353,7 +89630,7 @@ async function ensureClient(timeoutMs) {
89353
89630
  const remainingMs = Math.max(1, timeoutMs - elapsedMs);
89354
89631
  const fallbackProtocol = firstProtocol === PREFERRED_PROTOCOL_VERSION ? LEGACY_PROTOCOL_VERSION : PREFERRED_PROTOCOL_VERSION;
89355
89632
  errorWithTimestamp(
89356
- `${LOG_PREFIX} protocol fallback from=${firstProtocol} to=${fallbackProtocol} remainingMs=${remainingMs}: ${formatErrorMessage(err2)}`
89633
+ `${LOG_PREFIX2} protocol fallback from=${firstProtocol} to=${fallbackProtocol} remainingMs=${remainingMs}: ${formatErrorMessage(err2)}`
89357
89634
  );
89358
89635
  const fallbackClient = new MinimalGatewayClient(config, fallbackProtocol);
89359
89636
  sharedClient = fallbackClient;
@@ -89364,7 +89641,7 @@ async function ensureClient(timeoutMs) {
89364
89641
  }
89365
89642
  function stopOpenClawGateway() {
89366
89643
  if (!sharedClient) {
89367
- errorWithTimestamp(`${LOG_PREFIX} stop requested with no active client`);
89644
+ errorWithTimestamp(`${LOG_PREFIX2} stop requested with no active client`);
89368
89645
  return;
89369
89646
  }
89370
89647
  clearClient();
@@ -89372,12 +89649,12 @@ function stopOpenClawGateway() {
89372
89649
  async function callOpenClawGateway(opts) {
89373
89650
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
89374
89651
  const startedAt = Date.now();
89375
- errorWithTimestamp(`${LOG_PREFIX} rpc start method=${opts.method} timeoutMs=${timeoutMs}`);
89652
+ errorWithTimestamp(`${LOG_PREFIX2} rpc start method=${opts.method} timeoutMs=${timeoutMs}`);
89376
89653
  for (let attempt = 0; ; attempt += 1) {
89377
89654
  try {
89378
89655
  const client = await ensureClient(timeoutMs);
89379
89656
  const result = await client.request(opts.method, opts.params, { timeoutMs });
89380
- errorWithTimestamp(`${LOG_PREFIX} rpc ok method=${opts.method} durationMs=${Date.now() - startedAt} attempts=${attempt + 1}`);
89657
+ errorWithTimestamp(`${LOG_PREFIX2} rpc ok method=${opts.method} durationMs=${Date.now() - startedAt} attempts=${attempt + 1}`);
89381
89658
  return result;
89382
89659
  } catch (err2) {
89383
89660
  const message = err2 instanceof Error ? err2.message : String(err2);
@@ -89387,13 +89664,13 @@ async function callOpenClawGateway(opts) {
89387
89664
  }
89388
89665
  if (isGatewayStartingError(err2) && delayMs !== void 0 && Date.now() + delayMs - startedAt < timeoutMs) {
89389
89666
  errorWithTimestamp(
89390
- `${LOG_PREFIX} rpc retry method=${opts.method} attempt=${attempt + 1} delayMs=${delayMs}: ${message}`
89667
+ `${LOG_PREFIX2} rpc retry method=${opts.method} attempt=${attempt + 1} delayMs=${delayMs}: ${message}`
89391
89668
  );
89392
89669
  await sleep2(delayMs);
89393
89670
  continue;
89394
89671
  }
89395
89672
  errorWithTimestamp(
89396
- `${LOG_PREFIX} rpc failed method=${opts.method} durationMs=${Date.now() - startedAt} attempts=${attempt + 1}: ${message}`
89673
+ `${LOG_PREFIX2} rpc failed method=${opts.method} durationMs=${Date.now() - startedAt} attempts=${attempt + 1}: ${message}`
89397
89674
  );
89398
89675
  if (isGatewayStartingError(err2)) {
89399
89676
  throw Object.assign(
@@ -89502,7 +89779,7 @@ var MinimalGatewayClient = class {
89502
89779
  }
89503
89780
  async connect(timeoutMs) {
89504
89781
  errorWithTimestamp(
89505
- `${LOG_PREFIX} connect url=${this.config.url} source=${this.config.source} instanceId=${this.instanceId} protocol=${this.protocolVersion} passwordConfigured=${!!this.config.password} tokenConfigured=${!!this.config.token} scopes=${DEFAULT_SCOPES.join(",")}`
89782
+ `${LOG_PREFIX2} connect url=${this.config.url} source=${this.config.source} instanceId=${this.instanceId} protocol=${this.protocolVersion} passwordConfigured=${!!this.config.password} tokenConfigured=${!!this.config.token} scopes=${DEFAULT_SCOPES.join(",")}`
89506
89783
  );
89507
89784
  const ws = wsFactory(this.config.url);
89508
89785
  this.ws = ws;
@@ -89558,7 +89835,7 @@ var MinimalGatewayClient = class {
89558
89835
  }
89559
89836
  });
89560
89837
  });
89561
- errorWithTimestamp(`${LOG_PREFIX} hello ok instanceId=${this.instanceId} protocol=${this.protocolVersion}`);
89838
+ errorWithTimestamp(`${LOG_PREFIX2} hello ok instanceId=${this.instanceId} protocol=${this.protocolVersion}`);
89562
89839
  }
89563
89840
  request(method, params, options = {}) {
89564
89841
  if (method === "connect") {
@@ -89634,7 +89911,17 @@ var MinimalGatewayClient = class {
89634
89911
  try {
89635
89912
  frame = JSON.parse(String(raw));
89636
89913
  } catch (err2) {
89637
- errorWithTimestamp(`${LOG_PREFIX} invalid frame: ${err2 instanceof Error ? err2.message : String(err2)}`);
89914
+ errorWithTimestamp(`${LOG_PREFIX2} invalid frame: ${err2 instanceof Error ? err2.message : String(err2)}`);
89915
+ return;
89916
+ }
89917
+ if (frame.type === "event") {
89918
+ const gate = getSessionBusyTracker();
89919
+ gate.noteEvent();
89920
+ if (frame.event === "chat") {
89921
+ gate.notifyChat(frame.payload);
89922
+ } else if (frame.event === "agent") {
89923
+ gate.notifyAgent(frame.payload);
89924
+ }
89638
89925
  return;
89639
89926
  }
89640
89927
  if (frame.type !== "res") {
@@ -89656,7 +89943,7 @@ var MinimalGatewayClient = class {
89656
89943
  pending.reject(toGatewayError(frame.error));
89657
89944
  }
89658
89945
  handleClose(code2, reason) {
89659
- errorWithTimestamp(`${LOG_PREFIX} closed instanceId=${this.instanceId} code=${code2 ?? 0} reason=${reason || "no reason"}`);
89946
+ errorWithTimestamp(`${LOG_PREFIX2} closed instanceId=${this.instanceId} code=${code2 ?? 0} reason=${reason || "no reason"}`);
89660
89947
  this.ready = false;
89661
89948
  for (const [id, pending] of this.pending) {
89662
89949
  clearTimeout(pending.timer);
@@ -89680,7 +89967,7 @@ function readSyncedGatewayConfig(env) {
89680
89967
  token: synced.token
89681
89968
  };
89682
89969
  } catch (err2) {
89683
- errorWithTimestamp(`${LOG_PREFIX} failed to read synced gateway config: ${err2 instanceof Error ? err2.message : String(err2)}`);
89970
+ errorWithTimestamp(`${LOG_PREFIX2} failed to read synced gateway config: ${err2 instanceof Error ? err2.message : String(err2)}`);
89684
89971
  return null;
89685
89972
  }
89686
89973
  }
@@ -89691,7 +89978,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
89691
89978
  client: {
89692
89979
  id: "gateway-client",
89693
89980
  displayName: "okx-a2a-node",
89694
- version: "0.1.10-beta-bf67773ad1-260723190921",
89981
+ version: "0.1.10",
89695
89982
  platform: "node",
89696
89983
  mode: "backend",
89697
89984
  instanceId
@@ -89702,7 +89989,7 @@ function buildConnectParams(config, instanceId, protocolVersion) {
89702
89989
  commands: [],
89703
89990
  permissions: {},
89704
89991
  locale: Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
89705
- userAgent: `okx-a2a-node/${"0.1.10-beta-bf67773ad1-260723190921"}`,
89992
+ userAgent: `okx-a2a-node/${"0.1.10"}`,
89706
89993
  auth: {
89707
89994
  ...config.token ? { token: config.token } : {},
89708
89995
  ...config.password ? { password: config.password } : {}
@@ -90295,6 +90582,14 @@ var DEFAULT_GATEWAY_PORT2 = {
90295
90582
  callPromptUserToLatestSessions
90296
90583
  };
90297
90584
  var GATEWAY_OUTBOUND_LOG_PREFIX = "[okx-a2a:gateway-outbound]";
90585
+ var DEFAULT_SESSION_GATE_FALLBACK_MS = 3e5;
90586
+ function resolveSessionGateFallbackMs(env = process.env) {
90587
+ const raw = Number(env.OKX_A2A_SESSION_GATE_FALLBACK_MS ?? "");
90588
+ if (Number.isFinite(raw) && raw > 0) {
90589
+ return raw;
90590
+ }
90591
+ return DEFAULT_SESSION_GATE_FALLBACK_MS;
90592
+ }
90298
90593
  function resolveUserDeliveryId(input) {
90299
90594
  return input.idempotencyKey?.trim() || (0, import_node_crypto7.randomUUID)();
90300
90595
  }
@@ -90351,10 +90646,19 @@ var GatewayOutboundBehavior = class {
90351
90646
  gateway;
90352
90647
  store;
90353
90648
  sessionMetaStore;
90649
+ /** `undefined` = default (resolve the shared tracker lazily on first dispatch); `null` = disabled. */
90650
+ sessionGateOption;
90354
90651
  constructor(options = {}) {
90355
90652
  this.gateway = options.gateway ?? DEFAULT_GATEWAY_PORT2;
90356
90653
  this.store = options.store;
90357
90654
  this.sessionMetaStore = options.store;
90655
+ this.sessionGateOption = options.sessionGate;
90656
+ }
90657
+ resolveSessionGate() {
90658
+ if (this.sessionGateOption === void 0) {
90659
+ return getSessionBusyTracker();
90660
+ }
90661
+ return this.sessionGateOption;
90358
90662
  }
90359
90663
  async createSession(input) {
90360
90664
  const gatewaySessionKey = this.resolveGatewaySessionKey(input);
@@ -90407,8 +90711,19 @@ var GatewayOutboundBehavior = class {
90407
90711
  errorWithTimestamp(
90408
90712
  `${GATEWAY_OUTBOUND_LOG_PREFIX} dispatchSessionMessage sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} jobId=${input.jobId ?? "(none)"} agentId=${input.agentId ?? "(none)"} messageId=${input.messageId ?? "(none)"}`
90409
90713
  );
90714
+ const sessionGate = this.resolveSessionGate();
90715
+ const gateFallbackMs = resolveSessionGateFallbackMs();
90716
+ if (sessionGate?.isSessionBusy(gatewaySessionKey)) {
90717
+ errorWithTimestamp(
90718
+ `${GATEWAY_OUTBOUND_LOG_PREFIX} [session-gate] busy, waiting idle sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} (fallbackMs=${gateFallbackMs})`
90719
+ );
90720
+ const idleState = await sessionGate.waitForSessionIdle(gatewaySessionKey, gateFallbackMs);
90721
+ errorWithTimestamp(
90722
+ `${GATEWAY_OUTBOUND_LOG_PREFIX} [session-gate] idle wait done sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} result=${idleState}`
90723
+ );
90724
+ }
90410
90725
  try {
90411
- await this.gateway.callSessionsSend({
90726
+ const sendResult = await this.gateway.callSessionsSend({
90412
90727
  key: gatewaySessionKey,
90413
90728
  message: input.content,
90414
90729
  ...input.messageId ? { idempotencyKey: input.messageId } : {}
@@ -90423,6 +90738,16 @@ var GatewayOutboundBehavior = class {
90423
90738
  messageId: input.messageId ?? "",
90424
90739
  status: "delivered"
90425
90740
  }));
90741
+ const gateRunId = sendResult && typeof sendResult === "object" && typeof sendResult.runId === "string" ? sendResult.runId : input.messageId;
90742
+ if (sessionGate && typeof gateRunId === "string" && gateRunId) {
90743
+ errorWithTimestamp(
90744
+ `${GATEWAY_OUTBOUND_LOG_PREFIX} [session-gate] holding sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} runId=${gateRunId} until run final/error (fallbackMs=${gateFallbackMs})`
90745
+ );
90746
+ const gateState = await sessionGate.waitForRunFinal(gateRunId, gateFallbackMs);
90747
+ errorWithTimestamp(
90748
+ `${GATEWAY_OUTBOUND_LOG_PREFIX} [session-gate] released sessionKey=${input.sessionKey} gatewaySessionKey=${gatewaySessionKey} runId=${gateRunId} state=${gateState}`
90749
+ );
90750
+ }
90426
90751
  } catch (err2) {
90427
90752
  if (!this.store || !isRetryableOpenClawGatewayError(err2)) {
90428
90753
  logger.error(LogEvent.GATEWAY_REQUEST_FAILED, err2 instanceof Error ? err2 : new Error(String(err2)), gatewayOutboundExtra("sessions.send", {
@@ -94498,12 +94823,12 @@ async function runListenerWithLock(options, paths) {
94498
94823
  });
94499
94824
  }
94500
94825
  });
94501
- service.setPluginVersion("0.1.10-beta-bf67773ad1-260723190921");
94826
+ service.setPluginVersion("0.1.10");
94502
94827
  await service.init();
94503
94828
  const pluginVersionStatus = service.pluginVersionStatus;
94504
94829
  if (pluginVersionStatus.unavailable) {
94505
94830
  throw new Error(
94506
- `@okxweb3/a2a-node v${"0.1.10-beta-bf67773ad1-260723190921"} is below the required minimum v${pluginVersionStatus.minVersion}`
94831
+ `@okxweb3/a2a-node v${"0.1.10"} is below the required minimum v${pluginVersionStatus.minVersion}`
94507
94832
  );
94508
94833
  }
94509
94834
  const systemConfig = service.getSystemConfig();
@@ -94521,7 +94846,7 @@ async function runListenerWithLock(options, paths) {
94521
94846
  onchainosAgentId: "*",
94522
94847
  reason: "system-config missing sentryDsn",
94523
94848
  pluginId: "@okxweb3/a2a-node",
94524
- pluginVersion: "0.1.10-beta-bf67773ad1-260723190921"
94849
+ pluginVersion: "0.1.10"
94525
94850
  });
94526
94851
  }
94527
94852
  logWithTimestamp(
@@ -96290,7 +96615,7 @@ async function runDoctor(options = {}) {
96290
96615
  platform: options.platform ?? process.platform,
96291
96616
  env: options.env ?? process.env,
96292
96617
  target: options.target ?? resolveDoctorTarget(options.env ?? process.env),
96293
- cliVersion: options.cliVersion ?? (true ? "0.1.10-beta-bf67773ad1-260723190921" : "0.0.0"),
96618
+ cliVersion: options.cliVersion ?? (true ? "0.1.10" : "0.0.0"),
96294
96619
  fixMode: options.fix === true,
96295
96620
  nonInteractive: options.nonInteractive === true,
96296
96621
  packageChanged: false,
@@ -96526,6 +96851,7 @@ init_win_native_launcher();
96526
96851
  NATIVE_LAUNCHER_EXE_NAME,
96527
96852
  OPENCLAW_GATEWAY_ROUTE_GROUP_ID,
96528
96853
  SYSTEM_NOTIFICATION_SESSION_KEY,
96854
+ SessionBusyTracker,
96529
96855
  SessionStore,
96530
96856
  SqliteOutboundBehavior,
96531
96857
  TASK_NAME,
@@ -96591,6 +96917,7 @@ init_win_native_launcher();
96591
96917
  formatDoctorReportForHumans,
96592
96918
  getDaemonStatus,
96593
96919
  getHermesGatewayPluginStatus,
96920
+ getSessionBusyTracker,
96594
96921
  handleDoctorCommand,
96595
96922
  handleXmtpSendCommand,
96596
96923
  hasAiRuntimeMarker,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okxweb3/a2a-node",
3
- "version": "0.1.10-beta-bf67773ad1-260723190921",
3
+ "version": "0.1.10",
4
4
  "description": "Host-agnostic Node CLI for E2E encrypted agent-to-agent communication via XMTP",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -10,7 +10,7 @@
10
10
  "dist"
11
11
  ],
12
12
  "engines": {
13
- "node": ">=22.13.0"
13
+ "node": ">=22.14.0"
14
14
  },
15
15
  "scripts": {
16
16
  "preinstall": "node dist/check-node-version.js",