@vtxmacro/cli 2026.9.54 → 2026.9.56

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.
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
16
16
  // agent-cli-release.json
17
17
  var agent_cli_release_default = {
18
18
  package_name: "@vtxmacro/cli",
19
- package_version: "2026.9.54",
19
+ package_version: "2026.9.56",
20
20
  codex_package_name: "@openai/codex",
21
21
  codex_version: "0.155.0",
22
22
  copilot_sdk_package_name: "@github/copilot-sdk",
@@ -296,6 +296,28 @@ var runtime_policy_generated_default = {
296
296
  apiWallets: 20
297
297
  },
298
298
  exchange_action_batch_size: 40
299
+ },
300
+ public_recovery: {
301
+ fallback_initial_seconds: 2,
302
+ fallback_max_seconds: 60,
303
+ permit_lease_seconds: 60,
304
+ recovery_initial_concurrency: 1,
305
+ recovery_max_concurrency: 8,
306
+ recovery_successes_per_step: 4,
307
+ recovery_min_spacing_ms: 100,
308
+ queue_poll_ms: 50,
309
+ recovery_queue_wait_seconds: 10,
310
+ recovery_quiet_seconds: 30
311
+ },
312
+ http: {
313
+ exchange: {
314
+ request_timeout_seconds: 10,
315
+ pool_timeout_seconds: 10
316
+ },
317
+ public_critical: {
318
+ request_timeout_seconds: 10,
319
+ pool_timeout_seconds: 5
320
+ }
299
321
  }
300
322
  }
301
323
  }
@@ -179,6 +179,28 @@ var runtime_policy_generated_default = {
179
179
  apiWallets: 20
180
180
  },
181
181
  exchange_action_batch_size: 40
182
+ },
183
+ public_recovery: {
184
+ fallback_initial_seconds: 2,
185
+ fallback_max_seconds: 60,
186
+ permit_lease_seconds: 60,
187
+ recovery_initial_concurrency: 1,
188
+ recovery_max_concurrency: 8,
189
+ recovery_successes_per_step: 4,
190
+ recovery_min_spacing_ms: 100,
191
+ queue_poll_ms: 50,
192
+ recovery_queue_wait_seconds: 10,
193
+ recovery_quiet_seconds: 30
194
+ },
195
+ http: {
196
+ exchange: {
197
+ request_timeout_seconds: 10,
198
+ pool_timeout_seconds: 10
199
+ },
200
+ public_critical: {
201
+ request_timeout_seconds: 10,
202
+ pool_timeout_seconds: 5
203
+ }
182
204
  }
183
205
  }
184
206
  }
package/bin/vtx.js CHANGED
@@ -77,7 +77,7 @@ var init_agent_cli_release = __esm({
77
77
  "agent-cli-release.json"() {
78
78
  agent_cli_release_default = {
79
79
  package_name: "@vtxmacro/cli",
80
- package_version: "2026.9.54",
80
+ package_version: "2026.9.56",
81
81
  codex_package_name: "@openai/codex",
82
82
  codex_version: "0.155.0",
83
83
  copilot_sdk_package_name: "@github/copilot-sdk",
@@ -20637,6 +20637,28 @@ var init_runtime_policy_generated = __esm({
20637
20637
  apiWallets: 20
20638
20638
  },
20639
20639
  exchange_action_batch_size: 40
20640
+ },
20641
+ public_recovery: {
20642
+ fallback_initial_seconds: 2,
20643
+ fallback_max_seconds: 60,
20644
+ permit_lease_seconds: 60,
20645
+ recovery_initial_concurrency: 1,
20646
+ recovery_max_concurrency: 8,
20647
+ recovery_successes_per_step: 4,
20648
+ recovery_min_spacing_ms: 100,
20649
+ queue_poll_ms: 50,
20650
+ recovery_queue_wait_seconds: 10,
20651
+ recovery_quiet_seconds: 30
20652
+ },
20653
+ http: {
20654
+ exchange: {
20655
+ request_timeout_seconds: 10,
20656
+ pool_timeout_seconds: 10
20657
+ },
20658
+ public_critical: {
20659
+ request_timeout_seconds: 10,
20660
+ pool_timeout_seconds: 5
20661
+ }
20640
20662
  }
20641
20663
  }
20642
20664
  }
@@ -21519,7 +21541,8 @@ var init_mcp_client = __esm({
21519
21541
  this.#initialized = true;
21520
21542
  }
21521
21543
  async callTool(name, argumentsValue, options = {}) {
21522
- const timing = name === "inference.job.claim" && options.onClaimTiming ? new ClaimTiming() : void 0;
21544
+ const timingObserver = name === "inference.job.claim" ? options.onClaimTiming : name === "inference.host.heartbeat" ? options.onHostHeartbeatTiming : void 0;
21545
+ const timing = timingObserver ? new ClaimTiming() : void 0;
21523
21546
  let outcome = "failure";
21524
21547
  try {
21525
21548
  if (!this.#initialized) {
@@ -21598,7 +21621,7 @@ var init_mcp_client = __esm({
21598
21621
  if (timing) {
21599
21622
  const receipt = Object.freeze({ ...timing.finish(outcome), transport_route: this.#transport.route });
21600
21623
  try {
21601
- void Promise.resolve(options.onClaimTiming?.(receipt)).catch(() => void 0);
21624
+ void Promise.resolve(timingObserver?.(receipt)).catch(() => void 0);
21602
21625
  } catch {
21603
21626
  }
21604
21627
  }
@@ -32615,6 +32638,39 @@ var init_runner = __esm({
32615
32638
  };
32616
32639
  const hostHeartbeatAbort = new AbortController();
32617
32640
  stopHostHeartbeatLoop = () => hostHeartbeatAbort.abort();
32641
+ const backgroundHostHeartbeat = async (advertisementGeneration, allowOfflineProjection, deadlineAtMs) => {
32642
+ const startedAt = performance.now();
32643
+ let clientTiming;
32644
+ try {
32645
+ await hostHeartbeat("healthy", {
32646
+ signal: hostHeartbeatAbort.signal,
32647
+ // A liveness request must not inherit the general MCP timeout,
32648
+ // which can exceed the server's host-staleness window. Generation
32649
+ // recovery shares this deadline with its first attempt.
32650
+ timeoutMs: settings.hostHeartbeatMs,
32651
+ deadlineAtMs,
32652
+ onHostHeartbeatTiming: (timing) => {
32653
+ clientTiming = timing;
32654
+ }
32655
+ }, false, allowOfflineProjection, 0, advertisementGeneration);
32656
+ } catch (error48) {
32657
+ if (!hostHeartbeatAbort.signal.aborted && !drainRequested) {
32658
+ emitDiagnostic("host_heartbeat_request_failed", {
32659
+ elapsed_ms: Math.max(0, Math.round(performance.now() - startedAt)),
32660
+ request_timeout_ms: settings.hostHeartbeatMs,
32661
+ advertisement_generation: advertisementGeneration,
32662
+ failure_code: safeFailureCode(
32663
+ error48 instanceof ExternalInferenceMcpError ? error48.code : "",
32664
+ "heartbeat_failed"
32665
+ ),
32666
+ http_status_code: error48 instanceof ExternalInferenceMcpError ? error48.httpStatusCode : null,
32667
+ retry_after_ms: error48 instanceof ExternalInferenceMcpError ? error48.retryAfterMs : null,
32668
+ ...clientTiming ? { client_timing: clientTiming } : {}
32669
+ });
32670
+ }
32671
+ throw error48;
32672
+ }
32673
+ };
32618
32674
  let nextHostHeartbeatAt = now() + settings.hostHeartbeatMs;
32619
32675
  hostHeartbeatLoop = (async () => {
32620
32676
  while (!drainRequested && !hostHeartbeatAbort.signal.aborted) {
@@ -32643,19 +32699,16 @@ var init_runner = __esm({
32643
32699
  }
32644
32700
  }
32645
32701
  if (drainRequested || hostHeartbeatAbort.signal.aborted) return;
32646
- const heartbeatAttemptedAt = now();
32702
+ const heartbeatDeadlineAtMs = Date.now() + settings.hostHeartbeatMs;
32647
32703
  const attemptedAdvertisementGeneration = receipt.advertisement_generation;
32648
32704
  try {
32649
- await hostHeartbeat(
32650
- "healthy",
32651
- { signal: hostHeartbeatAbort.signal },
32652
- false,
32705
+ await backgroundHostHeartbeat(
32706
+ attemptedAdvertisementGeneration,
32653
32707
  // The server evaluates advertisement expiry when it receives
32654
32708
  // the heartbeat. A request sent just before expiry can therefore
32655
32709
  // truthfully return offline; the advertisement loop owns repair.
32656
32710
  true,
32657
- 0,
32658
- attemptedAdvertisementGeneration
32711
+ heartbeatDeadlineAtMs
32659
32712
  );
32660
32713
  } catch (error48) {
32661
32714
  if (hostHeartbeatAbort.signal.aborted || drainRequested) return;
@@ -32664,13 +32717,10 @@ var init_runner = __esm({
32664
32717
  const recoveryAdvertisementGeneration = receipt.advertisement_generation > attemptedAdvertisementGeneration ? receipt.advertisement_generation : errorCode === "generation_stale" && pendingAdvertisement && pendingAdvertisement.advertisement_generation === attemptedAdvertisementGeneration + 1 ? pendingAdvertisement.advertisement_generation : null;
32665
32718
  if (errorCode === "generation_stale" && recoveryAdvertisementGeneration !== null) {
32666
32719
  try {
32667
- await hostHeartbeat(
32668
- "healthy",
32669
- { signal: hostHeartbeatAbort.signal },
32670
- false,
32720
+ await backgroundHostHeartbeat(
32721
+ recoveryAdvertisementGeneration,
32671
32722
  false,
32672
- 0,
32673
- recoveryAdvertisementGeneration
32723
+ heartbeatDeadlineAtMs
32674
32724
  );
32675
32725
  if (receipt.advertisement_generation < recoveryAdvertisementGeneration) {
32676
32726
  await mutateReceipt((current) => current.advertisement_generation >= recoveryAdvertisementGeneration ? current : {
@@ -42647,6 +42697,28 @@ var init_runtime_policy_generated2 = __esm({
42647
42697
  apiWallets: 20
42648
42698
  },
42649
42699
  exchange_action_batch_size: 40
42700
+ },
42701
+ public_recovery: {
42702
+ fallback_initial_seconds: 2,
42703
+ fallback_max_seconds: 60,
42704
+ permit_lease_seconds: 60,
42705
+ recovery_initial_concurrency: 1,
42706
+ recovery_max_concurrency: 8,
42707
+ recovery_successes_per_step: 4,
42708
+ recovery_min_spacing_ms: 100,
42709
+ queue_poll_ms: 50,
42710
+ recovery_queue_wait_seconds: 10,
42711
+ recovery_quiet_seconds: 30
42712
+ },
42713
+ http: {
42714
+ exchange: {
42715
+ request_timeout_seconds: 10,
42716
+ pool_timeout_seconds: 10
42717
+ },
42718
+ public_critical: {
42719
+ request_timeout_seconds: 10,
42720
+ pool_timeout_seconds: 5
42721
+ }
42650
42722
  }
42651
42723
  }
42652
42724
  }
@@ -48588,7 +48660,7 @@ var init_runtime_redaction = __esm({
48588
48660
  });
48589
48661
 
48590
48662
  // lib/runtime/hyperliquid-public-read.ts
48591
- var policy, weights, budgets, unpersistedBudgets, cache, pending2, PREFIX, abortError2, checkAbort, wait, storage, loadBudget, saveBudget, withBudgetLock, HyperliquidPublicReadError, cooldownError, admit, cooldown, cacheTtlMs, eligibleCache, reserveHyperliquidPublicReads, releaseHyperliquidPublicReadReservation, admitHyperliquidExchangeRequest, recordHyperliquidExchangeResponse, readHyperliquidPublicInfo;
48663
+ var policy, weights, recoveryPolicy, budgets, unpersistedBudgets, cache, pending2, PREFIX, abortError2, checkAbort, wait, storage, loadBudget, saveBudget, withBudgetLock, HyperliquidPublicReadError, getHyperliquidPublicReadBudget, cooldownError, recoveryWaitError, fallbackDelayMs, admit, cooldown, finishHyperliquidRecoveryRequest, assertHyperliquidRecoveryDispatch, cacheTtlMs, eligibleCache, reserveHyperliquidPublicReads, releaseHyperliquidPublicReadReservation, admitHyperliquidExchangeRequest, recordHyperliquidExchangeResponse, readHyperliquidPublicInfo;
48592
48664
  var init_hyperliquid_public_read = __esm({
48593
48665
  "lib/runtime/hyperliquid-public-read.ts"() {
48594
48666
  "use strict";
@@ -48599,6 +48671,7 @@ var init_hyperliquid_public_read = __esm({
48599
48671
  init_runtime_policy_generated();
48600
48672
  policy = runtime_policy_generated_default.exchange.hyperliquid.client_public_read_budget;
48601
48673
  weights = runtime_policy_generated_default.exchange.hyperliquid.read_budget.weights;
48674
+ recoveryPolicy = runtime_policy_generated_default.exchange.hyperliquid.public_recovery;
48602
48675
  budgets = /* @__PURE__ */ new Map();
48603
48676
  unpersistedBudgets = /* @__PURE__ */ new Set();
48604
48677
  cache = /* @__PURE__ */ new Map();
@@ -48656,7 +48729,15 @@ var init_hyperliquid_public_read = __esm({
48656
48729
  withBudgetLock = async (key, signal, work) => {
48657
48730
  checkAbort(signal);
48658
48731
  if (typeof navigator !== "undefined" && navigator.locks?.request) {
48659
- return navigator.locks.request(key, { ...signal ? { signal } : {} }, work);
48732
+ const result2 = await navigator.locks.request(key, { ...signal ? { signal } : {} }, () => {
48733
+ try {
48734
+ return { ok: true, value: work() };
48735
+ } catch (error48) {
48736
+ return { ok: false, error: error48 };
48737
+ }
48738
+ });
48739
+ if (!result2.ok) throw result2.error;
48740
+ return result2.value;
48660
48741
  }
48661
48742
  return work();
48662
48743
  };
@@ -48673,6 +48754,15 @@ var init_hyperliquid_public_read = __esm({
48673
48754
  this.name = "HyperliquidPublicReadError";
48674
48755
  }
48675
48756
  };
48757
+ getHyperliquidPublicReadBudget = (endpoint) => {
48758
+ const budget = loadBudget(`${PREFIX}${endpoint.replace(/\/$/, "")}`);
48759
+ return {
48760
+ usedWeight: budget.reservations.filter((r) => r.at > Date.now() - policy.window_seconds * 1e3).reduce((sum, r) => sum + r.weight, 0),
48761
+ maxWeight: policy.max_weight,
48762
+ windowSeconds: policy.window_seconds,
48763
+ cooldownUntilMs: budget.cooldownUntil
48764
+ };
48765
+ };
48676
48766
  cooldownError = (requestType, until) => new HyperliquidPublicReadError(
48677
48767
  "public_cooldown_active",
48678
48768
  "Hyperliquid public request cooldown is active.",
@@ -48680,48 +48770,129 @@ var init_hyperliquid_public_read = __esm({
48680
48770
  void 0,
48681
48771
  Math.max(0, until - Date.now())
48682
48772
  );
48683
- admit = async (apiUrl, requestType, signal, bundleWeight, bundleId, deadlineMs) => {
48773
+ recoveryWaitError = (requestType) => new HyperliquidPublicReadError(
48774
+ "public_recovery_wait",
48775
+ "Hyperliquid provider recovery could not dispatch before the request deadline.",
48776
+ requestType
48777
+ );
48778
+ fallbackDelayMs = (failures) => Math.min(
48779
+ recoveryPolicy.fallback_max_seconds * 1e3,
48780
+ recoveryPolicy.fallback_initial_seconds * 1e3 * 2 ** Math.min(30, Math.max(0, failures - 1)) * (0.5 + Math.random())
48781
+ );
48782
+ admit = async (apiUrl, requestType, signal, weightOverride, deadlineMs) => {
48684
48783
  const key = `${PREFIX}${apiUrl}`;
48685
- const weight = bundleWeight ?? weights[requestType] ?? policy.default_request_weight;
48686
- if (!(weight > 0 && weight <= policy.max_weight)) throw new HyperliquidPublicReadError("public_budget_exhausted", "Hyperliquid public request exceeds the available allowance.", requestType, void 0, void 0, weight, void 0, policy.max_weight);
48687
- for (; ; ) {
48688
- checkAbort(signal);
48689
- const { delay: delay2, usedWeight } = await withBudgetLock(key, signal, () => {
48690
- const now = Date.now();
48691
- const budget = loadBudget(key);
48692
- budget.reservations = budget.reservations.filter((r) => r.at > now - policy.window_seconds * 1e3);
48693
- if (budget.cooldownUntil > now) throw cooldownError(requestType, budget.cooldownUntil);
48694
- let used = budget.reservations.reduce((sum, r) => sum + r.weight, 0);
48695
- const usedWeight2 = used;
48696
- if (used + weight <= policy.max_weight) {
48697
- budget.reservations.push({ at: now, weight, ...bundleId ? { id: bundleId } : {} });
48784
+ const weight = weightOverride ?? weights[requestType] ?? policy.default_request_weight;
48785
+ const ticket = crypto.randomUUID();
48786
+ const deadline = Math.min(deadlineMs ?? Infinity, Date.now() + recoveryPolicy.recovery_queue_wait_seconds * 1e3);
48787
+ let admitted = false;
48788
+ try {
48789
+ for (; ; ) {
48790
+ checkAbort(signal);
48791
+ if (deadlineMs != null && Date.now() >= deadlineMs) throw recoveryWaitError(requestType);
48792
+ const result2 = await withBudgetLock(key, signal, () => {
48793
+ const now = Date.now();
48794
+ const budget = loadBudget(key);
48795
+ if (budget.cooldownUntil > now) throw cooldownError(requestType, budget.cooldownUntil);
48796
+ const recovery = budget.recovery;
48797
+ let permit = null;
48798
+ if (recovery) {
48799
+ recovery.active = recovery.active.filter((entry) => entry.expiresAt > now);
48800
+ recovery.queue = recovery.queue.filter((entry) => entry.expiresAt > now);
48801
+ if (now >= deadline) throw recoveryWaitError(requestType);
48802
+ if (!recovery.queue.some((entry) => entry.id === ticket)) recovery.queue.push({ id: ticket, expiresAt: deadline });
48803
+ if (recovery.queue[0]?.id !== ticket || recovery.active.length >= recovery.concurrency || recovery.nextAt > now) {
48804
+ saveBudget(key, budget);
48805
+ return { ready: false, permit: null };
48806
+ }
48807
+ recovery.queue.shift();
48808
+ const expiresAt = now + recoveryPolicy.permit_lease_seconds * 1e3;
48809
+ recovery.active.push({ id: ticket, expiresAt });
48810
+ recovery.nextAt = now + recoveryPolicy.recovery_min_spacing_ms;
48811
+ permit = { endpoint: apiUrl, id: ticket, generation: recovery.generation, expiresAt };
48812
+ }
48813
+ budget.reservations = budget.reservations.filter((entry) => entry.at > now - policy.window_seconds * 1e3);
48814
+ budget.reservations.push({ at: now, weight });
48698
48815
  saveBudget(key, budget);
48699
- return { delay: 0, usedWeight: usedWeight2 };
48816
+ return { ready: true, permit };
48817
+ });
48818
+ if (result2.ready) {
48819
+ admitted = true;
48820
+ return result2.permit;
48700
48821
  }
48701
- for (const reservation of budget.reservations) {
48702
- used -= reservation.weight;
48703
- if (used + weight <= policy.max_weight) return { delay: Math.max(1, reservation.at + policy.window_seconds * 1e3 - now), usedWeight: usedWeight2 };
48822
+ await wait(Math.min(recoveryPolicy.queue_poll_ms, Math.max(1, deadline - Date.now())), signal);
48823
+ }
48824
+ } finally {
48825
+ if (!admitted) await withBudgetLock(key, void 0, () => {
48826
+ const budget = loadBudget(key);
48827
+ if (budget.recovery) {
48828
+ budget.recovery.queue = budget.recovery.queue.filter((entry) => entry.id !== ticket);
48829
+ saveBudget(key, budget);
48704
48830
  }
48705
- throw new Error("Invalid Hyperliquid public read budget.");
48706
48831
  });
48707
- if (!delay2) return;
48708
- if (bundleWeight != null || deadlineMs != null && Date.now() + delay2 >= deadlineMs) {
48709
- throw new HyperliquidPublicReadError("public_budget_exhausted", "Hyperliquid local public request allowance is exhausted.", requestType, void 0, delay2, weight, usedWeight, policy.max_weight);
48710
- }
48711
- await wait(delay2, signal);
48712
48832
  }
48713
48833
  };
48714
48834
  cooldown = async (apiUrl, retryAfter) => {
48715
48835
  const key = `${PREFIX}${apiUrl}`;
48716
48836
  await withBudgetLock(key, void 0, () => {
48717
48837
  const now = Date.now();
48718
- const seconds = retryAfter == null ? NaN : Number(retryAfter);
48719
- const providerUntil = Number.isFinite(seconds) ? now + seconds * 1e3 : Date.parse(retryAfter ?? "");
48720
48838
  const budget = loadBudget(key);
48721
- budget.cooldownUntil = Math.max(budget.cooldownUntil, now + policy.cooldown_seconds * 1e3, Number.isFinite(providerUntil) ? providerUntil : 0);
48839
+ if (!budget.recovery && now - (budget.lastFailure ?? 0) >= recoveryPolicy.recovery_quiet_seconds * 1e3) budget.failures = 0;
48840
+ budget.failures = (budget.failures ?? 0) + 1;
48841
+ const trimmed = retryAfter?.trim() ?? "";
48842
+ const seconds = trimmed !== "" ? Number(trimmed) : NaN;
48843
+ const providerUntil = Number.isFinite(seconds) && seconds >= 0 ? now + seconds * 1e3 : trimmed && !Number.isFinite(seconds) ? Date.parse(trimmed) : NaN;
48844
+ const until = Number.isFinite(providerUntil) ? Math.max(now, Number(providerUntil)) : now + fallbackDelayMs(budget.failures);
48845
+ budget.cooldownUntil = Math.max(budget.cooldownUntil, until);
48846
+ budget.lastFailure = now;
48847
+ budget.recovery = {
48848
+ generation: crypto.randomUUID(),
48849
+ concurrency: recoveryPolicy.recovery_initial_concurrency,
48850
+ successes: 0,
48851
+ nextAt: budget.cooldownUntil,
48852
+ queue: budget.recovery?.queue ?? [],
48853
+ active: []
48854
+ };
48722
48855
  saveBudget(key, budget);
48723
48856
  });
48724
48857
  };
48858
+ finishHyperliquidRecoveryRequest = async (permit, outcome) => {
48859
+ if (!permit) return;
48860
+ const key = `${PREFIX}${permit.endpoint}`;
48861
+ await withBudgetLock(key, void 0, () => {
48862
+ const budget = loadBudget(key);
48863
+ const recovery = budget.recovery;
48864
+ if (!recovery || recovery.generation !== permit.generation) return;
48865
+ const now = Date.now();
48866
+ const member = recovery.active.find((entry) => entry.id === permit.id);
48867
+ recovery.active = recovery.active.filter((entry) => entry.id !== permit.id);
48868
+ if (member && member.expiresAt > now) {
48869
+ if (outcome === "success") {
48870
+ recovery.successes += 1;
48871
+ if (recovery.successes >= recoveryPolicy.recovery_successes_per_step) {
48872
+ if (recovery.concurrency >= recoveryPolicy.recovery_max_concurrency) {
48873
+ if (recovery.queue.length === 0 && recovery.active.length === 0) delete budget.recovery;
48874
+ } else {
48875
+ recovery.concurrency = Math.min(recoveryPolicy.recovery_max_concurrency, recovery.concurrency * 2);
48876
+ recovery.successes = 0;
48877
+ }
48878
+ }
48879
+ } else if (outcome === "failure") {
48880
+ recovery.successes = 0;
48881
+ recovery.concurrency = recoveryPolicy.recovery_initial_concurrency;
48882
+ recovery.nextAt = Math.max(recovery.nextAt, now + fallbackDelayMs(budget.failures ?? 1));
48883
+ }
48884
+ }
48885
+ saveBudget(key, budget);
48886
+ });
48887
+ };
48888
+ assertHyperliquidRecoveryDispatch = (endpoint, permit, requestType) => {
48889
+ const budget = loadBudget(`${PREFIX}${endpoint.replace(/\/$/, "")}`);
48890
+ if (budget.cooldownUntil > Date.now()) throw cooldownError(requestType, budget.cooldownUntil);
48891
+ if (permit && (permit.expiresAt <= Date.now() || budget.recovery && (budget.recovery.generation !== permit.generation || !budget.recovery.active.some((entry) => entry.id === permit.id && entry.expiresAt > Date.now())))) {
48892
+ throw recoveryWaitError(requestType);
48893
+ }
48894
+ if (!permit && budget.recovery) throw recoveryWaitError(requestType);
48895
+ };
48725
48896
  cacheTtlMs = (body, options) => {
48726
48897
  const requestType = String(body.type);
48727
48898
  return (["meta", "spotMeta", "perpDexs"].includes(requestType) ? policy.metadata_ttl_seconds : requestType === "userFees" ? policy.user_fees_ttl_seconds : options.cacheAccountMode && ["userAbstraction", "userDexAbstraction"].includes(requestType) ? policy.account_mode_ttl_seconds : 0) * 1e3;
@@ -48733,47 +48904,26 @@ var init_hyperliquid_public_read = __esm({
48733
48904
  return ttl > 0 && existing && Date.now() >= existing.fetchedAtMs && horizon - existing.fetchedAtMs < ttl ? existing : void 0;
48734
48905
  };
48735
48906
  reserveHyperliquidPublicReads = async (endpoint, bodies, signal, options = {}) => {
48907
+ checkAbort(signal);
48736
48908
  const apiUrl = endpoint.replace(/\/$/, "");
48737
48909
  const cached2 = /* @__PURE__ */ new Map();
48738
- const remaining = /* @__PURE__ */ new Map();
48739
- let weight = 0;
48740
48910
  for (const body of bodies) {
48741
- const key = JSON.stringify(body);
48742
48911
  const hit = eligibleCache(apiUrl, body, options);
48743
- if (hit) {
48744
- cached2.set(key, hit);
48745
- continue;
48746
- }
48747
- remaining.set(key, (remaining.get(key) ?? 0) + 1);
48748
- weight += weights[String(body.type)] ?? policy.default_request_weight;
48912
+ if (hit) cached2.set(JSON.stringify(body), hit);
48749
48913
  }
48750
- const id2 = crypto.randomUUID();
48751
- const admittedAtMs = Date.now();
48752
- if (weight) await admit(apiUrl, "bundle", signal, weight, id2, options.cacheValidUntilMs);
48753
- return { id: id2, closed: false, endpoint: apiUrl, expiresAtMs: admittedAtMs + policy.window_seconds * 1e3, remaining, cached: cached2, cacheValidUntilMs: options.cacheValidUntilMs };
48914
+ return { closed: false, endpoint: apiUrl, cached: cached2, cacheValidUntilMs: options.cacheValidUntilMs };
48754
48915
  };
48755
48916
  releaseHyperliquidPublicReadReservation = async (reservation) => {
48756
48917
  reservation.closed = true;
48757
- const key = `${PREFIX}${reservation.endpoint}`;
48758
- await withBudgetLock(key, void 0, () => {
48759
- const budget = loadBudget(key);
48760
- const row = budget.reservations.find((entry) => entry.id === reservation.id);
48761
- if (row) {
48762
- const unused = [...reservation.remaining].reduce((sum, [body, count4]) => sum + (weights[String(JSON.parse(body).type)] ?? policy.default_request_weight) * count4, 0);
48763
- row.weight = Math.max(0, row.weight - unused);
48764
- budget.reservations = budget.reservations.filter((entry) => entry.weight > 0);
48765
- saveBudget(key, budget);
48766
- }
48767
- reservation.remaining.clear();
48768
- });
48769
48918
  };
48770
48919
  admitHyperliquidExchangeRequest = async (endpoint, action, signal) => {
48771
48920
  const batchLength = Math.max(0, ...[action.orders, action.cancels, action.modifies].filter(Array.isArray).map((value) => value.length));
48772
48921
  const weight = weights.exchange + Math.floor(batchLength / runtime_policy_generated_default.exchange.hyperliquid.read_budget.exchange_action_batch_size);
48773
- await admit(endpoint.replace(/\/$/, ""), "exchange", signal, weight);
48922
+ return admit(endpoint.replace(/\/$/, ""), "exchange", signal, weight);
48774
48923
  };
48775
- recordHyperliquidExchangeResponse = async (endpoint, response) => {
48924
+ recordHyperliquidExchangeResponse = async (endpoint, response, permit) => {
48776
48925
  if (response.status === 429) await cooldown(endpoint.replace(/\/$/, ""), response.headers.get("Retry-After"));
48926
+ await finishHyperliquidRecoveryRequest(permit, response.status < 400 ? "success" : "failure");
48777
48927
  };
48778
48928
  readHyperliquidPublicInfo = async (endpoint, body, options = {}) => {
48779
48929
  const apiUrl = endpoint.replace(/\/$/, "");
@@ -48782,71 +48932,101 @@ var init_hyperliquid_public_read = __esm({
48782
48932
  const ttlMs = cacheTtlMs(body, cacheOptions);
48783
48933
  const key = `${apiUrl}:${JSON.stringify(body)}`;
48784
48934
  checkAbort(options.signal);
48785
- if (ttlMs) {
48786
- for (; ; ) {
48787
- const existing = options.reservation?.cached.get(JSON.stringify(body)) ?? eligibleCache(apiUrl, body, cacheOptions);
48788
- if (existing) return existing;
48789
- const running = pending2.get(key);
48790
- if (!running) break;
48791
- await new Promise((resolve13, reject) => {
48792
- const aborted2 = () => {
48793
- options.signal?.removeEventListener("abort", aborted2);
48794
- reject(abortError2(options.signal));
48795
- };
48796
- options.signal?.addEventListener("abort", aborted2, { once: true });
48797
- running.then(() => {
48798
- options.signal?.removeEventListener("abort", aborted2);
48799
- resolve13();
48800
- });
48801
- });
48802
- checkAbort(options.signal);
48803
- }
48804
- }
48935
+ const transportPolicy = runtime_policy_generated_default.exchange.hyperliquid.http.public_critical;
48936
+ const deadline = Math.min(
48937
+ Date.now() + (transportPolicy.request_timeout_seconds + transportPolicy.pool_timeout_seconds) * 1e3,
48938
+ options.deadlineMs ?? Infinity,
48939
+ options.reservation?.cacheValidUntilMs ?? Infinity
48940
+ );
48941
+ const controller = new AbortController();
48942
+ const signal = controller.signal;
48943
+ const callerAborted = () => controller.abort(abortError2(options.signal));
48944
+ options.signal?.addEventListener("abort", callerAborted, { once: true });
48945
+ let timer;
48946
+ const armDeadline = (until) => {
48947
+ clearTimeout(timer);
48948
+ const timeout = () => controller.abort(new DOMException("Hyperliquid public request timed out.", "TimeoutError"));
48949
+ if (until <= Date.now()) timeout();
48950
+ else timer = setTimeout(timeout, until - Date.now());
48951
+ };
48952
+ armDeadline(deadline);
48953
+ let permit = null;
48954
+ let outcome = "cancelled";
48805
48955
  let settled;
48806
- if (ttlMs) pending2.set(key, new Promise((resolve13) => {
48807
- settled = resolve13;
48808
- }));
48956
+ let acquiring = false;
48809
48957
  try {
48810
- const reservedKey = JSON.stringify(body);
48811
- const remaining = options.reservation?.remaining.get(reservedKey) ?? 0;
48812
- if (options.reservation?.endpoint === apiUrl && remaining > 0) {
48813
- if (Date.now() >= options.reservation.expiresAtMs) throw new HyperliquidPublicReadError("public_budget_exhausted", "Hyperliquid public read reservation expired.", requestType);
48814
- await withBudgetLock(`${PREFIX}${apiUrl}`, options.signal, () => {
48815
- if (options.reservation.closed) throw new HyperliquidPublicReadError("public_budget_exhausted", "Hyperliquid public read bundle is closed.", requestType);
48816
- if ((options.reservation.remaining.get(reservedKey) ?? 0) <= 0) throw new HyperliquidPublicReadError("public_budget_exhausted", "Hyperliquid request is outside its admitted bundle.", requestType);
48817
- const budget = loadBudget(`${PREFIX}${apiUrl}`);
48818
- const row = budget.reservations.find((entry) => entry.id === options.reservation.id);
48819
- if (!row || Date.now() >= options.reservation.expiresAtMs) throw new HyperliquidPublicReadError("public_budget_exhausted", "Hyperliquid public read reservation expired.", requestType);
48820
- row.at = Date.now();
48821
- budget.reservations.sort((a, b2) => a.at - b2.at);
48822
- options.reservation.remaining.set(reservedKey, (options.reservation.remaining.get(reservedKey) ?? 0) - 1);
48823
- saveBudget(`${PREFIX}${apiUrl}`, budget);
48958
+ checkAbort(signal);
48959
+ if (ttlMs) {
48960
+ for (; ; ) {
48961
+ const existing = options.reservation?.cached.get(JSON.stringify(body)) ?? eligibleCache(apiUrl, body, cacheOptions);
48962
+ if (existing) return existing;
48963
+ const running = pending2.get(key);
48964
+ if (!running) break;
48965
+ await new Promise((resolve13, reject) => {
48966
+ const aborted2 = () => {
48967
+ signal.removeEventListener("abort", aborted2);
48968
+ reject(abortError2(signal));
48969
+ };
48970
+ signal.addEventListener("abort", aborted2, { once: true });
48971
+ running.then(() => {
48972
+ signal.removeEventListener("abort", aborted2);
48973
+ resolve13();
48974
+ });
48975
+ });
48976
+ checkAbort(signal);
48977
+ }
48978
+ pending2.set(key, new Promise((resolve13) => {
48979
+ settled = resolve13;
48980
+ }));
48981
+ }
48982
+ if (options.reservation?.closed) throw new DOMException("Hyperliquid account read was cancelled.", "AbortError");
48983
+ acquiring = true;
48984
+ permit = await admit(apiUrl, requestType, signal, void 0, deadline);
48985
+ acquiring = false;
48986
+ armDeadline(Math.min(deadline, permit?.expiresAt ?? Infinity));
48987
+ checkAbort(signal);
48988
+ if (options.reservation?.closed) throw new DOMException("Hyperliquid account read was cancelled.", "AbortError");
48989
+ assertHyperliquidRecoveryDispatch(apiUrl, permit, requestType);
48990
+ outcome = "failure";
48991
+ let transportAborted;
48992
+ let response;
48993
+ let payload;
48994
+ try {
48995
+ const aborted2 = new Promise((_resolve, reject) => {
48996
+ transportAborted = () => reject(abortError2(signal));
48997
+ signal.addEventListener("abort", transportAborted, { once: true });
48824
48998
  });
48825
- } else if (options.reservation) throw new HyperliquidPublicReadError("public_budget_exhausted", "Hyperliquid request is outside its admitted bundle.", requestType);
48826
- else await admit(apiUrl, requestType, options.signal, void 0, void 0, options.deadlineMs);
48827
- checkAbort(options.signal);
48828
- if (options.reservation?.closed) throw new HyperliquidPublicReadError("public_budget_exhausted", "Hyperliquid public read bundle is closed.", requestType);
48829
- const cooldownUntil = loadBudget(`${PREFIX}${apiUrl}`).cooldownUntil;
48830
- if (cooldownUntil > Date.now()) throw cooldownError(requestType, cooldownUntil);
48831
- const response = await fetch(`${apiUrl}/info`, {
48832
- method: "POST",
48833
- headers: { "Content-Type": "application/json" },
48834
- body: JSON.stringify(body),
48835
- signal: options.signal
48836
- });
48837
- if (response.status === 429) await cooldown(apiUrl, response.headers.get("Retry-After"));
48838
- const payload = await response.json().catch((error48) => {
48839
- if (error48 instanceof SyntaxError) return {};
48840
- throw error48;
48841
- });
48999
+ const transport = (async () => {
49000
+ const received = await fetch(`${apiUrl}/info`, {
49001
+ method: "POST",
49002
+ headers: { "Content-Type": "application/json" },
49003
+ body: JSON.stringify(body),
49004
+ signal
49005
+ });
49006
+ checkAbort(signal);
49007
+ if (received.status === 429) {
49008
+ await cooldown(apiUrl, received.headers.get("Retry-After"));
49009
+ throw new HyperliquidPublicReadError("public_rate_limited", "Hyperliquid public API returned HTTP 429.", requestType, 429);
49010
+ }
49011
+ const receivedPayload = await received.json().catch((error48) => {
49012
+ if (error48 instanceof SyntaxError) return {};
49013
+ throw error48;
49014
+ });
49015
+ checkAbort(signal);
49016
+ return { response: received, payload: receivedPayload };
49017
+ })();
49018
+ ({ response, payload } = await Promise.race([transport, aborted2]));
49019
+ } finally {
49020
+ if (transportAborted) signal.removeEventListener("abort", transportAborted);
49021
+ }
48842
49022
  if (!response.ok) {
48843
49023
  const detail = sanitizeRuntimeDiagnosticValue(typeof payload?.response === "string" ? payload.response : typeof payload?.detail === "string" ? payload.detail : "");
48844
- if (response.status === 429) throw new HyperliquidPublicReadError("public_rate_limited", "Hyperliquid public API returned HTTP 429.", requestType, 429);
48845
49024
  throw Object.assign(new Error(`Hyperliquid info request failed (${response.status}) request_type=${requestType}.${detail ? ` ${detail}` : ""}`), {
48846
49025
  status: response.status,
48847
49026
  requestType
48848
49027
  });
48849
49028
  }
49029
+ outcome = "success";
48850
49030
  const result2 = { payload, fetchedAtMs: Date.now() };
48851
49031
  if (ttlMs) {
48852
49032
  cache.set(key, result2);
@@ -48854,14 +49034,26 @@ var init_hyperliquid_public_read = __esm({
48854
49034
  }
48855
49035
  return result2;
48856
49036
  } catch (error48) {
48857
- if (options.signal?.aborted || error48 instanceof HyperliquidPublicReadError) throw error48;
49037
+ if (options.signal?.aborted) {
49038
+ outcome = "cancelled";
49039
+ throw abortError2(options.signal);
49040
+ }
49041
+ if (acquiring && signal.aborted && signal.reason?.name === "TimeoutError") throw recoveryWaitError(requestType);
49042
+ if (error48 instanceof HyperliquidPublicReadError) throw error48;
48858
49043
  if (error48 instanceof TypeError) throw new HyperliquidPublicReadError("public_network_error", "Hyperliquid public request network failure.", requestType);
48859
49044
  if (error48 instanceof Error && (error48.name === "TimeoutError" || error48.name === "AbortError")) throw new HyperliquidPublicReadError("public_timeout", "Hyperliquid public request timed out.", requestType);
48860
49045
  throw error48;
48861
49046
  } finally {
48862
- if (ttlMs) {
48863
- pending2.delete(key);
48864
- settled?.();
49047
+ clearTimeout(timer);
49048
+ options.signal?.removeEventListener("abort", callerAborted);
49049
+ controller.abort();
49050
+ try {
49051
+ await finishHyperliquidRecoveryRequest(permit, outcome);
49052
+ } finally {
49053
+ if (settled) {
49054
+ pending2.delete(key);
49055
+ settled();
49056
+ }
48865
49057
  }
48866
49058
  }
48867
49059
  };
@@ -49839,6 +50031,8 @@ var init_network_debug = __esm({
49839
50031
  kind: input.kind,
49840
50032
  label: input.label,
49841
50033
  transport: "browser",
50034
+ providerTransport: input.kind === "provider" ? input.providerTransport ?? "direct" : null,
50035
+ actualRelayPath: input.kind === "provider" && input.providerTransport === "server_relay" ? "/llm/native-provider-relay" : null,
49842
50036
  method: (input.method || "POST").toUpperCase(),
49843
50037
  targetProtocol,
49844
50038
  targetHost,
@@ -49957,7 +50151,7 @@ var init_network_debug = __esm({
49957
50151
  });
49958
50152
 
49959
50153
  // lib/runtime/hyperliquid-account-state-adapter.ts
49960
- var unsupportedUserActiveAssetCache, MAX_UNSUPPORTED_USER_ACTIVE_ASSET_CACHE_KEYS, UNSUPPORTED_USER_ACTIVE_ASSET_STORAGE_KEY, USER_FILLS_CACHE_STORAGE_PREFIX, ACCOUNT_STATE_CACHE_STORAGE_PREFIX, INFO_RATE_LIMIT_COOLDOWN_STORAGE_PREFIX, INFO_RATE_LIMIT_COOLDOWN_MIN_MS, INFO_RATE_LIMIT_COOLDOWN_MAX_MS, browserUserFillsCache, browserUserFillsInFlight, HYPERLIQUID_USER_FILLS_PROVIDER_PAGE_CAP, browserAccountStateCache, browserAccountStateInFlight, browserInfoRateLimitCooldowns, UNSUPPORTED_USER_ACTIVE_ASSET_DETAIL_PATTERNS, unsupportedUserActiveAssetCacheHydrated, hydrateUnsupportedUserActiveAssetCache, persistUnsupportedUserActiveAssetCache, toPositiveFinite, toFinite, firstFiniteNumber, parseFundingUsdcFromInfoPosition, buildActiveAssetCacheKey, buildUserFillsCacheKey, buildUserFillsStorageKey, buildAccountStateCacheKey, buildAccountStateStorageKey, normalizeInfoRateLimitApiUrl, normalizeInfoRequestType, buildInfoRateLimitCooldownKey, buildInfoRateLimitCooldownStorageKey, readInfoRateLimitCooldownUntilMs, hasActiveInfoRateLimitCooldown, writeInfoRateLimitCooldown, extractInfoRequestTypeFromRateLimitError, recordInfoRateLimitCooldownFromError, requireNonNegativeConfigSeconds, requireAccountStateCacheConfig, requireUserFillsCacheConfig, normalizeFillSymbol, buildSymbolFilter, filterFillsBySymbols, readBrowserUserFillsCache, writeBrowserUserFillsCache, cloneBrowserAccountStateResult, withAccountStateEvidence, readBrowserAccountStateCache, writeBrowserAccountStateCache, isRateLimitError, createAbortError, waitForSharedBrowserRequest, waitForSharedBrowserUserFills, getBrowserActiveAssetTypeCandidates, normalizeHyperliquidErrorDetail, isUnsupportedUserActiveAssetError, rememberUnsupportedUserActiveAssetKey, parseInfoPositions, requireFinitePromptMetric, requirePromptClearinghouseState, requirePromptSpotState, normalizePerpDexName, listConfiguredPerpDexs, listPerpDexsForAccountState, prefixHip3PositionCoins, aggregateClearinghouseResponses, hasCompletePerpDexAccountMetrics, normalizeBrowserFillToTradeHistory, isPerpFill, fetchHyperliquidInfoPayload, requireHyperliquidApiUrl, requireWalletAddress, parseInfoPositionSnapshot, fetchBrowserAccountState, fetchBrowserAccountStateUncached, fetchBrowserUserFills, fetchBrowserPositionSnapshot;
50154
+ var unsupportedUserActiveAssetCache, MAX_UNSUPPORTED_USER_ACTIVE_ASSET_CACHE_KEYS, UNSUPPORTED_USER_ACTIVE_ASSET_STORAGE_KEY, USER_FILLS_CACHE_STORAGE_PREFIX, ACCOUNT_STATE_CACHE_STORAGE_PREFIX, browserUserFillsCache, browserUserFillsInFlight, HYPERLIQUID_USER_FILLS_PROVIDER_PAGE_CAP, browserAccountStateCache, browserAccountStateInFlight, UNSUPPORTED_USER_ACTIVE_ASSET_DETAIL_PATTERNS, unsupportedUserActiveAssetCacheHydrated, hydrateUnsupportedUserActiveAssetCache, persistUnsupportedUserActiveAssetCache, toPositiveFinite, toFinite, firstFiniteNumber, parseFundingUsdcFromInfoPosition, buildActiveAssetCacheKey, buildUserFillsCacheKey, buildUserFillsStorageKey, buildAccountStateCacheKey, buildAccountStateStorageKey, hasActiveInfoRateLimitCooldown, requireNonNegativeConfigSeconds, requireAccountStateCacheConfig, requireUserFillsCacheConfig, normalizeFillSymbol, buildSymbolFilter, filterFillsBySymbols, readBrowserUserFillsCache, writeBrowserUserFillsCache, cloneBrowserAccountStateResult, withAccountStateEvidence, readBrowserAccountStateCache, writeBrowserAccountStateCache, isRateLimitError, createAbortError, waitForSharedBrowserRequest, waitForSharedBrowserUserFills, getBrowserActiveAssetTypeCandidates, normalizeHyperliquidErrorDetail, isUnsupportedUserActiveAssetError, rememberUnsupportedUserActiveAssetKey, parseInfoPositions, requireFinitePromptMetric, requirePromptClearinghouseState, requirePromptSpotState, normalizePerpDexName, listConfiguredPerpDexs, listPerpDexsForAccountState, prefixHip3PositionCoins, aggregateClearinghouseResponses, hasCompletePerpDexAccountMetrics, normalizeBrowserFillToTradeHistory, isPerpFill, awaitAccountReads, fetchHyperliquidInfoPayload, requireHyperliquidApiUrl, requireWalletAddress, parseInfoPositionSnapshot, fetchBrowserAccountState, fetchBrowserAccountStateUncached, fetchBrowserUserFills, fetchBrowserPositionSnapshot;
49961
50155
  var init_hyperliquid_account_state_adapter = __esm({
49962
50156
  "lib/runtime/hyperliquid-account-state-adapter.ts"() {
49963
50157
  "use strict";
@@ -49978,15 +50172,11 @@ var init_hyperliquid_account_state_adapter = __esm({
49978
50172
  UNSUPPORTED_USER_ACTIVE_ASSET_STORAGE_KEY = "vtx:hyperliquid:unsupported-user-active-asset:v1";
49979
50173
  USER_FILLS_CACHE_STORAGE_PREFIX = "vtx:hyperliquid:user-fills:v1:";
49980
50174
  ACCOUNT_STATE_CACHE_STORAGE_PREFIX = "vtx:hyperliquid:account-state:v1:";
49981
- INFO_RATE_LIMIT_COOLDOWN_STORAGE_PREFIX = "vtx:hyperliquid:info-rate-limit-cooldown:v1:";
49982
- INFO_RATE_LIMIT_COOLDOWN_MIN_MS = 5e3;
49983
- INFO_RATE_LIMIT_COOLDOWN_MAX_MS = 6e4;
49984
50175
  browserUserFillsCache = /* @__PURE__ */ new Map();
49985
50176
  browserUserFillsInFlight = /* @__PURE__ */ new Map();
49986
50177
  HYPERLIQUID_USER_FILLS_PROVIDER_PAGE_CAP = 2e3;
49987
50178
  browserAccountStateCache = /* @__PURE__ */ new Map();
49988
50179
  browserAccountStateInFlight = /* @__PURE__ */ new Map();
49989
- browserInfoRateLimitCooldowns = /* @__PURE__ */ new Map();
49990
50180
  UNSUPPORTED_USER_ACTIVE_ASSET_DETAIL_PATTERNS = [
49991
50181
  /unsupported/i,
49992
50182
  /not\s+supported/i,
@@ -50104,93 +50294,7 @@ var init_hyperliquid_account_state_adapter = __esm({
50104
50294
  buildAccountStateStorageKey = (cacheKey) => {
50105
50295
  return `${ACCOUNT_STATE_CACHE_STORAGE_PREFIX}${cacheKey}`;
50106
50296
  };
50107
- normalizeInfoRateLimitApiUrl = (apiUrl) => {
50108
- return String(apiUrl || "").trim().replace(/\/$/, "").toLowerCase();
50109
- };
50110
- normalizeInfoRequestType = (requestType) => {
50111
- return String(requestType || "").trim() || "info";
50112
- };
50113
- buildInfoRateLimitCooldownKey = (apiUrl, requestType) => {
50114
- return [
50115
- normalizeInfoRateLimitApiUrl(apiUrl),
50116
- normalizeInfoRequestType(requestType)
50117
- ].join("::");
50118
- };
50119
- buildInfoRateLimitCooldownStorageKey = (cooldownKey) => {
50120
- return `${INFO_RATE_LIMIT_COOLDOWN_STORAGE_PREFIX}${encodeURIComponent(cooldownKey)}`;
50121
- };
50122
- readInfoRateLimitCooldownUntilMs = (apiUrl, requestType, nowMs = Date.now()) => {
50123
- const cooldownKey = buildInfoRateLimitCooldownKey(apiUrl, requestType);
50124
- const memoryValue = browserInfoRateLimitCooldowns.get(cooldownKey);
50125
- if (Number.isFinite(memoryValue)) {
50126
- if (Number(memoryValue) > nowMs) {
50127
- return Number(memoryValue);
50128
- }
50129
- browserInfoRateLimitCooldowns.delete(cooldownKey);
50130
- }
50131
- if (typeof window === "undefined" || !window.localStorage) {
50132
- return 0;
50133
- }
50134
- try {
50135
- const storageKey = buildInfoRateLimitCooldownStorageKey(cooldownKey);
50136
- const rawValue = window.localStorage.getItem(storageKey);
50137
- if (!rawValue) {
50138
- return 0;
50139
- }
50140
- const parsed = JSON.parse(rawValue);
50141
- const untilMs = Number(parsed?.untilMs);
50142
- if (!Number.isFinite(untilMs) || untilMs <= nowMs) {
50143
- window.localStorage.removeItem(storageKey);
50144
- return 0;
50145
- }
50146
- browserInfoRateLimitCooldowns.set(cooldownKey, untilMs);
50147
- return untilMs;
50148
- } catch {
50149
- return 0;
50150
- }
50151
- };
50152
- hasActiveInfoRateLimitCooldown = (apiUrl, requestTypes, nowMs = Date.now()) => requestTypes.some((requestType) => readInfoRateLimitCooldownUntilMs(apiUrl, requestType, nowMs) > nowMs);
50153
- writeInfoRateLimitCooldown = (apiUrl, requestType, staleIf429MaxAgeMs, nowMs = Date.now()) => {
50154
- if (!Number.isFinite(staleIf429MaxAgeMs) || staleIf429MaxAgeMs <= 0) {
50155
- return;
50156
- }
50157
- const cooldownKey = buildInfoRateLimitCooldownKey(apiUrl, requestType);
50158
- const cooldownMs = Math.max(
50159
- INFO_RATE_LIMIT_COOLDOWN_MIN_MS,
50160
- Math.min(INFO_RATE_LIMIT_COOLDOWN_MAX_MS, staleIf429MaxAgeMs)
50161
- );
50162
- const untilMs = nowMs + cooldownMs;
50163
- browserInfoRateLimitCooldowns.set(cooldownKey, untilMs);
50164
- if (typeof window === "undefined" || !window.localStorage) {
50165
- return;
50166
- }
50167
- try {
50168
- window.localStorage.setItem(
50169
- buildInfoRateLimitCooldownStorageKey(cooldownKey),
50170
- JSON.stringify({ untilMs })
50171
- );
50172
- } catch {
50173
- }
50174
- };
50175
- extractInfoRequestTypeFromRateLimitError = (error48) => {
50176
- const explicitType = normalizeInfoRequestType(error48?.requestType);
50177
- if (explicitType !== "info") {
50178
- return explicitType;
50179
- }
50180
- const detail = normalizeHyperliquidErrorDetail(error48);
50181
- const match = /\brequest_type=([A-Za-z0-9_:-]+)/.exec(detail);
50182
- return match?.[1] ? normalizeInfoRequestType(match[1]) : null;
50183
- };
50184
- recordInfoRateLimitCooldownFromError = (apiUrl, error48, staleIf429MaxAgeMs, nowMs = Date.now()) => {
50185
- if (!isRateLimitError(error48)) {
50186
- return;
50187
- }
50188
- const requestType = extractInfoRequestTypeFromRateLimitError(error48);
50189
- if (!requestType) {
50190
- return;
50191
- }
50192
- writeInfoRateLimitCooldown(apiUrl, requestType, staleIf429MaxAgeMs, nowMs);
50193
- };
50297
+ hasActiveInfoRateLimitCooldown = (apiUrl, _requestTypes, nowMs = Date.now()) => getHyperliquidPublicReadBudget(apiUrl).cooldownUntilMs > nowMs;
50194
50298
  requireNonNegativeConfigSeconds = (value, path) => {
50195
50299
  const numeric2 = Number(value);
50196
50300
  if (!Number.isFinite(numeric2) || numeric2 < 0) {
@@ -50714,6 +50818,19 @@ var init_hyperliquid_account_state_adapter = __esm({
50714
50818
  }
50715
50819
  return true;
50716
50820
  };
50821
+ awaitAccountReads = async (reads) => {
50822
+ try {
50823
+ return await Promise.all(reads);
50824
+ } catch (error48) {
50825
+ if (error48 instanceof HyperliquidPublicReadError && error48.code === "public_cooldown_active") {
50826
+ const settled = await Promise.allSettled(reads);
50827
+ for (const result2 of settled) {
50828
+ if (result2.status === "rejected" && result2.reason instanceof HyperliquidPublicReadError && result2.reason.code === "public_rate_limited") throw result2.reason;
50829
+ }
50830
+ }
50831
+ throw error48;
50832
+ }
50833
+ };
50717
50834
  fetchHyperliquidInfoPayload = async (apiUrl, body, signal, reservation) => {
50718
50835
  const trace = startRuntimeNetworkDebugTrace({ kind: "exchange", label: `hyperliquid_info:${String(body.type)}`, method: "POST", url: `${apiUrl.replace(/\/$/, "")}/info` });
50719
50836
  try {
@@ -50837,7 +50954,6 @@ var init_hyperliquid_account_state_adapter = __esm({
50837
50954
  "Browser account state request aborted."
50838
50955
  ));
50839
50956
  } catch (error48) {
50840
- recordInfoRateLimitCooldownFromError(apiUrl, error48, staleIf429MaxAgeMs);
50841
50957
  const staleCache = readBrowserAccountStateCache(cacheKey);
50842
50958
  if (!input.forceRefresh && isRateLimitError(error48) && staleCache && Date.now() - staleCache.fetchedAtMs <= staleIf429MaxAgeMs) {
50843
50959
  return withAccountStateEvidence(staleCache.result, "stale_if_429", staleCache.fetchedAtMs, staleIf429MaxAgeMs);
@@ -50897,10 +51013,10 @@ var init_hyperliquid_account_state_adapter = __esm({
50897
51013
  capturedAt: new Date(capturedAtMs).toISOString()
50898
51014
  };
50899
51015
  })() : Promise.resolve(null);
50900
- await Promise.all([userFeesPromise, exactAccountModePromise]);
51016
+ await awaitAccountReads([userFeesPromise, exactAccountModePromise]);
50901
51017
  const metadataByDex = /* @__PURE__ */ new Map();
50902
51018
  if (input.includeEffectiveTakerRate === true) {
50903
- await Promise.all(["", ...dexNames].map(async (dex) => {
51019
+ await awaitAccountReads(["", ...dexNames].map(async (dex) => {
50904
51020
  try {
50905
51021
  metadataByDex.set(dex, await fetchHyperliquidInfoPayload(apiUrl, { type: "meta", ...dex ? { dex } : {} }, input.signal, reservation));
50906
51022
  } catch (error48) {
@@ -50919,7 +51035,7 @@ var init_hyperliquid_account_state_adapter = __esm({
50919
51035
  if (input.includeEffectiveTakerRate === true) {
50920
51036
  requirePromptClearinghouseState(defaultResponse, "default");
50921
51037
  }
50922
- const dexResponses = await Promise.all(
51038
+ const dexResponses = await awaitAccountReads(
50923
51039
  dexNames.map(async (dexName) => {
50924
51040
  const response = await fetchHyperliquidInfoPayload(
50925
51041
  apiUrl,
@@ -50964,7 +51080,6 @@ var init_hyperliquid_account_state_adapter = __esm({
50964
51080
  reservation
50965
51081
  );
50966
51082
  } catch (error48) {
50967
- recordInfoRateLimitCooldownFromError(apiUrl, error48, staleIf429MaxAgeMs);
50968
51083
  if (input.forceRefresh || input.includeEffectiveTakerRate === true) {
50969
51084
  throw error48;
50970
51085
  }
@@ -50985,7 +51100,6 @@ var init_hyperliquid_account_state_adapter = __esm({
50985
51100
  return { payload, type: typeName };
50986
51101
  } catch (error48) {
50987
51102
  lastError = error48;
50988
- recordInfoRateLimitCooldownFromError(apiUrl, error48, staleIf429MaxAgeMs);
50989
51103
  if (isRateLimitError(error48)) throw error48;
50990
51104
  if (typeName === "userActiveAssetData" && isUnsupportedUserActiveAssetError(error48)) {
50991
51105
  rememberUnsupportedUserActiveAssetKey(activeAssetCacheKey);
@@ -50994,7 +51108,7 @@ var init_hyperliquid_account_state_adapter = __esm({
50994
51108
  }
50995
51109
  throw lastError instanceof Error ? lastError : new Error("Failed to fetch browser active asset data.");
50996
51110
  })();
50997
- const [perpsResult, spotResult, activeAssetResult, userFeesResult, exactAccountMode] = await Promise.all([
51111
+ const [perpsResult, spotResult, activeAssetResult, userFeesResult, exactAccountMode] = await awaitAccountReads([
50998
51112
  perpsPromise,
50999
51113
  spotPromise,
51000
51114
  activeAssetPromise,
@@ -51020,7 +51134,7 @@ var init_hyperliquid_account_state_adapter = __esm({
51020
51134
  ...parseInfoPositions(perpsResponse).filter((position) => Number(position.size) !== 0).map((position) => position.symbol)
51021
51135
  ].map(normalizeHyperliquidMarketSymbol).filter(Boolean))].sort();
51022
51136
  const feeDexes = [...new Set(feeSymbols.map(getHyperliquidMarketDex))];
51023
- const metadata = await Promise.all(feeDexes.map(async (dex) => {
51137
+ const metadata = await awaitAccountReads(feeDexes.map(async (dex) => {
51024
51138
  try {
51025
51139
  const payload = metadataByDex.get(dex) ?? null;
51026
51140
  return [dex, payload];
@@ -51215,7 +51329,6 @@ var init_hyperliquid_account_state_adapter = __esm({
51215
51329
  providerRowCount = liveResult.providerRowCount;
51216
51330
  fetchedAtMs = liveResult.fetchedAtMs;
51217
51331
  } catch (error48) {
51218
- recordInfoRateLimitCooldownFromError(apiUrl, error48, staleIf429MaxAgeMs);
51219
51332
  const staleCache = readBrowserUserFillsCache(cacheKey);
51220
51333
  if (isRateLimitError(error48) && !input.forceRefresh) {
51221
51334
  evidenceSource = "stale_if_429";
@@ -83814,6 +83927,7 @@ var init_hyperliquid_client = __esm({
83814
83927
  init_define_VTX_EXO_POLICY();
83815
83928
  init_define_VTX_GROK_POLICY();
83816
83929
  init_define_VTX_PI_MODEL_POLICY();
83930
+ init_runtime_policy_generated();
83817
83931
  init_hyperliquid_public_read();
83818
83932
  init_api2();
83819
83933
  init_abort();
@@ -85484,6 +85598,7 @@ var init_hyperliquid_client = __esm({
85484
85598
  const serializedPayload = JSON.stringify(input.payload);
85485
85599
  const durableMutation = input.durableMutation;
85486
85600
  let exchangeDispatched = false;
85601
+ let recoveryPermit = null;
85487
85602
  let settlementAttempted = false;
85488
85603
  assertFreshPreparedExecutionContext(input.preparedContext);
85489
85604
  const captureTimeoutMs = Number(
@@ -85640,8 +85755,9 @@ var init_hyperliquid_client = __esm({
85640
85755
  }
85641
85756
  }
85642
85757
  try {
85643
- await admitHyperliquidExchangeRequest(apiUrl, input.payload.action, input.signal);
85758
+ recoveryPermit = await admitHyperliquidExchangeRequest(apiUrl, input.payload.action, input.signal);
85644
85759
  assertFreshPreparedExecutionContext(input.preparedContext);
85760
+ assertHyperliquidRecoveryDispatch(apiUrl, recoveryPermit, "exchange");
85645
85761
  } catch (preparedContextError) {
85646
85762
  await settleDurableMutation("rejected");
85647
85763
  if (isAbortLikeError(preparedContextError) || preparedContextError instanceof HyperliquidPublicReadError) throw preparedContextError;
@@ -85669,16 +85785,44 @@ var init_hyperliquid_client = __esm({
85669
85785
  }
85670
85786
  }
85671
85787
  exchangeDispatched = true;
85672
- const response = await fetch(requestUrl, {
85673
- method: "POST",
85674
- headers: {
85675
- "Content-Type": "application/json"
85676
- },
85677
- body: serializedPayload
85678
- });
85679
- responseStatus = response.status;
85680
- await recordHyperliquidExchangeResponse(apiUrl, response);
85681
- const payload = await response.json().catch(() => ({}));
85788
+ const transportController = new AbortController();
85789
+ const exchangeTimeout = runtime_policy_generated_default.exchange.hyperliquid.http.exchange;
85790
+ const transportTimeoutMs = Math.min(
85791
+ (exchangeTimeout.request_timeout_seconds + exchangeTimeout.pool_timeout_seconds) * 1e3,
85792
+ recoveryPermit ? Math.max(0, recoveryPermit.expiresAt - Date.now()) : Infinity
85793
+ );
85794
+ let transportTimer;
85795
+ let response;
85796
+ let payload;
85797
+ try {
85798
+ const transport = (async () => {
85799
+ const received = await fetch(requestUrl, {
85800
+ method: "POST",
85801
+ headers: { "Content-Type": "application/json" },
85802
+ body: serializedPayload,
85803
+ signal: transportController.signal
85804
+ });
85805
+ if (transportController.signal.aborted) throw transportController.signal.reason;
85806
+ responseStatus = received.status;
85807
+ await recordHyperliquidExchangeResponse(apiUrl, received);
85808
+ const body = await received.json().catch(() => ({}));
85809
+ if (transportController.signal.aborted) throw transportController.signal.reason;
85810
+ return { response: received, payload: body };
85811
+ })();
85812
+ const timeout = new Promise((_resolve, reject) => {
85813
+ transportTimer = globalThis.setTimeout(() => {
85814
+ const error48 = new DOMException("Hyperliquid exchange transport timed out.", "TimeoutError");
85815
+ transportController.abort(error48);
85816
+ reject(error48);
85817
+ }, transportTimeoutMs);
85818
+ });
85819
+ ({ response, payload } = await Promise.race([transport, timeout]));
85820
+ } finally {
85821
+ globalThis.clearTimeout(transportTimer);
85822
+ transportController.abort();
85823
+ }
85824
+ await finishHyperliquidRecoveryRequest(recoveryPermit, response.status < 400 ? "success" : "failure").catch(() => void 0);
85825
+ recoveryPermit = null;
85682
85826
  if (!response.ok) {
85683
85827
  await settleDurableMutation("transport_ambiguous");
85684
85828
  throw new RuntimeExecutionError(
@@ -85743,6 +85887,8 @@ var init_hyperliquid_client = __esm({
85743
85887
  );
85744
85888
  }
85745
85889
  throw error48;
85890
+ } finally {
85891
+ await finishHyperliquidRecoveryRequest(recoveryPermit, exchangeDispatched ? "failure" : "cancelled").catch(() => void 0);
85746
85892
  }
85747
85893
  });
85748
85894
  } catch (error48) {
@@ -92451,13 +92597,14 @@ var init_constraint_contract = __esm({
92451
92597
  });
92452
92598
 
92453
92599
  // lib/runtime/provider-observability.ts
92454
- var record4, identifier, count3, normalizeProviderDiagnostics;
92600
+ var MAX_DIAGNOSTIC_LIST_ITEMS, record4, identifier, count3, normalizeProviderDiagnostics;
92455
92601
  var init_provider_observability = __esm({
92456
92602
  "lib/runtime/provider-observability.ts"() {
92457
92603
  "use strict";
92458
92604
  init_define_VTX_EXO_POLICY();
92459
92605
  init_define_VTX_GROK_POLICY();
92460
92606
  init_define_VTX_PI_MODEL_POLICY();
92607
+ MAX_DIAGNOSTIC_LIST_ITEMS = 16;
92461
92608
  record4 = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
92462
92609
  identifier = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:/-]{0,255}$/.test(v) ? v : void 0;
92463
92610
  count3 = (v) => {
@@ -92479,7 +92626,7 @@ var init_provider_observability = __esm({
92479
92626
  result2.quota_project_source = "provider_quota_subject";
92480
92627
  }
92481
92628
  if (Array.isArray(raw.quota_violations)) {
92482
- const violations = raw.quota_violations.slice(0, 16).map((item) => {
92629
+ const violations = raw.quota_violations.slice(0, MAX_DIAGNOSTIC_LIST_ITEMS).map((item) => {
92483
92630
  const v = record4(item);
92484
92631
  const out = {};
92485
92632
  const metric = identifier(v.metric), id2 = identifier(v.id), value2 = count3(v.value);
@@ -92493,6 +92640,11 @@ var init_provider_observability = __esm({
92493
92640
  }).filter((v) => Object.keys(v).length > 0);
92494
92641
  if (violations.length) result2.quota_violations = violations;
92495
92642
  }
92643
+ const outputValidation = record4(raw.output_validation);
92644
+ if (Array.isArray(outputValidation.expected_symbols)) {
92645
+ const symbols = outputValidation.expected_symbols.slice(0, MAX_DIAGNOSTIC_LIST_ITEMS).map(identifier).filter((symbol2) => symbol2 !== void 0);
92646
+ if (symbols.length) result2.output_validation = { expected_symbols: [...new Set(symbols)] };
92647
+ }
92496
92648
  return result2;
92497
92649
  };
92498
92650
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtxmacro/cli",
3
- "version": "2026.9.54",
3
+ "version": "2026.9.56",
4
4
  "description": "VTX Macro CLI, MCP server, and durable external inference host.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",