@reefclaw/openclaw-plugin 0.1.0

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 (371) hide show
  1. package/.gitignore +8 -0
  2. package/audit/mode-transition-audit.d.ts +11 -0
  3. package/audit/mode-transition-audit.js +29 -0
  4. package/balance-utils.d.ts +36 -0
  5. package/balance-utils.js +98 -0
  6. package/bridge/bridge.d.ts +109 -0
  7. package/bridge/bridge.js +1036 -0
  8. package/bridge/config.d.ts +43 -0
  9. package/bridge/config.js +176 -0
  10. package/bridge/connector.d.ts +52 -0
  11. package/bridge/connector.js +293 -0
  12. package/bridge/event-replay-buffer.d.ts +43 -0
  13. package/bridge/event-replay-buffer.js +109 -0
  14. package/bridge/gateway/event-parser.d.ts +209 -0
  15. package/bridge/gateway/event-parser.js +793 -0
  16. package/bridge/gateway/gateway-config.d.ts +39 -0
  17. package/bridge/gateway/gateway-config.js +86 -0
  18. package/bridge/gateway/gateway-http-client.d.ts +50 -0
  19. package/bridge/gateway/gateway-http-client.js +165 -0
  20. package/bridge/gateway/gateway-ws-client.d.ts +116 -0
  21. package/bridge/gateway/gateway-ws-client.js +417 -0
  22. package/bridge/gateway/poller.d.ts +146 -0
  23. package/bridge/gateway/poller.js +505 -0
  24. package/bridge/gateway/tool-discovery.d.ts +25 -0
  25. package/bridge/gateway/tool-discovery.js +198 -0
  26. package/bridge/index.d.ts +2 -0
  27. package/bridge/index.js +253 -0
  28. package/bridge/logger.d.ts +2 -0
  29. package/bridge/logger.js +2 -0
  30. package/bridge/provider.d.ts +156 -0
  31. package/bridge/provider.js +2 -0
  32. package/bridge/providers/emergency-commands.d.ts +54 -0
  33. package/bridge/providers/emergency-commands.js +235 -0
  34. package/bridge/providers/gateway.d.ts +322 -0
  35. package/bridge/providers/gateway.js +2302 -0
  36. package/bridge/providers/mock.d.ts +37 -0
  37. package/bridge/providers/mock.js +385 -0
  38. package/bridge/providers/onboarding-commands.d.ts +83 -0
  39. package/bridge/providers/onboarding-commands.js +213 -0
  40. package/bridge/providers/risk-calculator.d.ts +96 -0
  41. package/bridge/providers/risk-calculator.js +369 -0
  42. package/bridge/setup.d.ts +32 -0
  43. package/bridge/setup.js +226 -0
  44. package/bridge/types.d.ts +584 -0
  45. package/bridge/types.js +50 -0
  46. package/bridge/utils/reconnect.d.ts +6 -0
  47. package/bridge/utils/reconnect.js +6 -0
  48. package/bridge/utils/skill-signing.d.ts +51 -0
  49. package/bridge/utils/skill-signing.js +138 -0
  50. package/bridge/utils/skill-version.d.ts +17 -0
  51. package/bridge/utils/skill-version.js +74 -0
  52. package/ccxt/binance-ban-gate.d.ts +47 -0
  53. package/ccxt/binance-ban-gate.js +409 -0
  54. package/ccxt/binance-private.d.ts +325 -0
  55. package/ccxt/binance-private.js +1415 -0
  56. package/ccxt/binance-public.d.ts +18 -0
  57. package/ccxt/binance-public.js +147 -0
  58. package/config/agent-config-client.d.ts +55 -0
  59. package/config/agent-config-client.js +145 -0
  60. package/config/agent-config-poller.d.ts +25 -0
  61. package/config/agent-config-poller.js +100 -0
  62. package/config/brackets-config.d.ts +22 -0
  63. package/config/brackets-config.js +58 -0
  64. package/config/gate-store.d.ts +18 -0
  65. package/config/gate-store.js +61 -0
  66. package/config/plugin-config-io.d.ts +181 -0
  67. package/config/plugin-config-io.js +84 -0
  68. package/config/position-review-config.d.ts +35 -0
  69. package/config/position-review-config.js +105 -0
  70. package/config/tool-gate.d.ts +53 -0
  71. package/config/tool-gate.js +125 -0
  72. package/config/user-data-stream-config.d.ts +85 -0
  73. package/config/user-data-stream-config.js +224 -0
  74. package/connector-supervisor.d.ts +36 -0
  75. package/connector-supervisor.js +149 -0
  76. package/exchange-adapter.d.ts +49 -0
  77. package/exchange-adapter.js +4 -0
  78. package/index.d.ts +30 -0
  79. package/index.js +2030 -0
  80. package/ingest/pending-entry-metadata.d.ts +52 -0
  81. package/ingest/pending-entry-metadata.js +182 -0
  82. package/ingest/position-auto-capture.d.ts +98 -0
  83. package/ingest/position-auto-capture.js +394 -0
  84. package/ingest/position-decisions-client.d.ts +318 -0
  85. package/ingest/position-decisions-client.js +296 -0
  86. package/ingest/reconcile-db-vs-exchange.d.ts +13 -0
  87. package/ingest/reconcile-db-vs-exchange.js +114 -0
  88. package/ingest/reconciler-cleanup.d.ts +37 -0
  89. package/ingest/reconciler-cleanup.js +147 -0
  90. package/ingest/rest-gap-filler.d.ts +191 -0
  91. package/ingest/rest-gap-filler.js +565 -0
  92. package/ingest/touched-symbols-store.d.ts +25 -0
  93. package/ingest/touched-symbols-store.js +96 -0
  94. package/ingest/trade-store-client.d.ts +40 -0
  95. package/ingest/trade-store-client.js +125 -0
  96. package/ingest/ws-ingest.d.ts +43 -0
  97. package/ingest/ws-ingest.js +126 -0
  98. package/learning/setup-family.d.ts +21 -0
  99. package/learning/setup-family.js +103 -0
  100. package/lifecycle/install-signal-handlers.d.ts +33 -0
  101. package/lifecycle/install-signal-handlers.js +112 -0
  102. package/lifecycle/shutdown-coordinator.d.ts +43 -0
  103. package/lifecycle/shutdown-coordinator.js +131 -0
  104. package/live/bracket-id.d.ts +18 -0
  105. package/live/bracket-id.js +81 -0
  106. package/live/bracket-ledger.d.ts +54 -0
  107. package/live/bracket-ledger.js +267 -0
  108. package/live/bracket-manager.d.ts +82 -0
  109. package/live/bracket-manager.js +478 -0
  110. package/live/bracket-params.d.ts +22 -0
  111. package/live/bracket-params.js +124 -0
  112. package/live/bracket-reconciler.d.ts +95 -0
  113. package/live/bracket-reconciler.js +573 -0
  114. package/live/bracket-types.d.ts +102 -0
  115. package/live/bracket-types.js +8 -0
  116. package/live/deposit-tracker.d.ts +62 -0
  117. package/live/deposit-tracker.js +97 -0
  118. package/live/emergency-controls.d.ts +32 -0
  119. package/live/emergency-controls.js +226 -0
  120. package/live/exchange-errors.d.ts +12 -0
  121. package/live/exchange-errors.js +130 -0
  122. package/live/exchange-info-cache.d.ts +35 -0
  123. package/live/exchange-info-cache.js +119 -0
  124. package/live/fact-subscriber.d.ts +78 -0
  125. package/live/fact-subscriber.js +182 -0
  126. package/live/intent-journal.d.ts +42 -0
  127. package/live/intent-journal.js +122 -0
  128. package/live/listen-key-manager.d.ts +70 -0
  129. package/live/listen-key-manager.js +169 -0
  130. package/live/live-adapter.d.ts +264 -0
  131. package/live/live-adapter.js +1665 -0
  132. package/live/live-balance-enricher.d.ts +32 -0
  133. package/live/live-balance-enricher.js +104 -0
  134. package/live/live-bracket-api.d.ts +13 -0
  135. package/live/live-bracket-api.js +20 -0
  136. package/live/live-state-store.d.ts +194 -0
  137. package/live/live-state-store.js +450 -0
  138. package/live/local-signal-service.d.ts +57 -0
  139. package/live/local-signal-service.js +146 -0
  140. package/live/local-strategy-evaluator.d.ts +62 -0
  141. package/live/local-strategy-evaluator.js +127 -0
  142. package/live/microstructure-assembler.d.ts +54 -0
  143. package/live/microstructure-assembler.js +148 -0
  144. package/live/order-poller.d.ts +29 -0
  145. package/live/order-poller.js +125 -0
  146. package/live/position-state-store.d.ts +83 -0
  147. package/live/position-state-store.js +237 -0
  148. package/live/proposal-decision-listener.d.ts +64 -0
  149. package/live/proposal-decision-listener.js +288 -0
  150. package/live/proposal-manager.d.ts +76 -0
  151. package/live/proposal-manager.js +140 -0
  152. package/live/rate-limiter.d.ts +47 -0
  153. package/live/rate-limiter.js +159 -0
  154. package/live/reconciler.d.ts +39 -0
  155. package/live/reconciler.js +175 -0
  156. package/live/setup-buckets.d.ts +7 -0
  157. package/live/setup-buckets.js +33 -0
  158. package/live/slippage-tracker.d.ts +45 -0
  159. package/live/slippage-tracker.js +78 -0
  160. package/live/stop-watcher.d.ts +34 -0
  161. package/live/stop-watcher.js +158 -0
  162. package/live/user-data-active-probe.d.ts +54 -0
  163. package/live/user-data-active-probe.js +180 -0
  164. package/live/user-data-stream-controller.d.ts +200 -0
  165. package/live/user-data-stream-controller.js +579 -0
  166. package/live/user-data-stream-ws.d.ts +22 -0
  167. package/live/user-data-stream-ws.js +63 -0
  168. package/live/user-data-stream.d.ts +243 -0
  169. package/live/user-data-stream.js +704 -0
  170. package/logger.d.ts +2 -0
  171. package/logger.js +2 -0
  172. package/mfe.d.ts +21 -0
  173. package/mfe.js +68 -0
  174. package/onboarding/mode-ladder.d.ts +1 -0
  175. package/onboarding/mode-ladder.js +3 -0
  176. package/onboarding/runtime.d.ts +71 -0
  177. package/onboarding/runtime.js +153 -0
  178. package/openclaw.plugin.json +94 -0
  179. package/package.json +27 -0
  180. package/paper-adapter.d.ts +24 -0
  181. package/paper-adapter.js +91 -0
  182. package/persistence/state-manager.d.ts +42 -0
  183. package/persistence/state-manager.js +164 -0
  184. package/pinned-plan.d.ts +9 -0
  185. package/pinned-plan.js +23 -0
  186. package/risk/pre-trade-check.d.ts +38 -0
  187. package/risk/pre-trade-check.js +345 -0
  188. package/risk/pre-trade-types.d.ts +60 -0
  189. package/risk/pre-trade-types.js +3 -0
  190. package/scripts/assemble.mjs +114 -0
  191. package/shadow/shadow-tracker.d.ts +36 -0
  192. package/shadow/shadow-tracker.js +151 -0
  193. package/shadow/types.d.ts +42 -0
  194. package/shadow/types.js +20 -0
  195. package/shared/indicators-extended.d.ts +52 -0
  196. package/shared/indicators-extended.js +291 -0
  197. package/shared/indicators.d.ts +15 -0
  198. package/shared/indicators.js +114 -0
  199. package/signals/conditions/registry.d.ts +16 -0
  200. package/signals/conditions/registry.js +1274 -0
  201. package/signals/conditions/types.d.ts +1 -0
  202. package/signals/conditions/types.js +4 -0
  203. package/signals/direction-rules.d.ts +3 -0
  204. package/signals/direction-rules.js +24 -0
  205. package/signals/entry-rules.d.ts +6 -0
  206. package/signals/entry-rules.js +33 -0
  207. package/signals/serialize-context.d.ts +4 -0
  208. package/signals/serialize-context.js +39 -0
  209. package/signals/stop-rules.d.ts +3 -0
  210. package/signals/stop-rules.js +48 -0
  211. package/signals/strategy-adapter.d.ts +14 -0
  212. package/signals/strategy-adapter.js +122 -0
  213. package/signals/types.d.ts +1 -0
  214. package/signals/types.js +8 -0
  215. package/simulator/exchange-simulator.d.ts +93 -0
  216. package/simulator/exchange-simulator.js +684 -0
  217. package/simulator/fill-engine.d.ts +53 -0
  218. package/simulator/fill-engine.js +276 -0
  219. package/simulator/paper-market-feed.d.ts +26 -0
  220. package/simulator/paper-market-feed.js +104 -0
  221. package/simulator/realistic-fills.d.ts +59 -0
  222. package/simulator/realistic-fills.js +175 -0
  223. package/simulator/types.d.ts +219 -0
  224. package/simulator/types.js +43 -0
  225. package/skills/reefclaw/SKILL.md +97 -0
  226. package/strategy/builtin-strategies.d.ts +2 -0
  227. package/strategy/builtin-strategies.js +109 -0
  228. package/strategy/condition-registry.d.ts +3 -0
  229. package/strategy/condition-registry.js +153 -0
  230. package/strategy/evaluator.d.ts +67 -0
  231. package/strategy/evaluator.js +93 -0
  232. package/tools/assessment-validation.d.ts +118 -0
  233. package/tools/assessment-validation.js +415 -0
  234. package/tools/attach-brackets.d.ts +34 -0
  235. package/tools/attach-brackets.js +363 -0
  236. package/tools/audit-bracket-protection.d.ts +49 -0
  237. package/tools/audit-bracket-protection.js +527 -0
  238. package/tools/cancel-all-orders.d.ts +7 -0
  239. package/tools/cancel-all-orders.js +5 -0
  240. package/tools/cancel-order.d.ts +10 -0
  241. package/tools/cancel-order.js +14 -0
  242. package/tools/check-position-health.d.ts +46 -0
  243. package/tools/check-position-health.js +194 -0
  244. package/tools/clear-exchange-credentials.d.ts +24 -0
  245. package/tools/clear-exchange-credentials.js +70 -0
  246. package/tools/close-position.d.ts +22 -0
  247. package/tools/close-position.js +449 -0
  248. package/tools/create-order.d.ts +54 -0
  249. package/tools/create-order.js +338 -0
  250. package/tools/exit-gate.d.ts +58 -0
  251. package/tools/exit-gate.js +162 -0
  252. package/tools/fetch-balance.d.ts +5 -0
  253. package/tools/fetch-balance.js +4 -0
  254. package/tools/fetch-ohlcv.d.ts +11 -0
  255. package/tools/fetch-ohlcv.js +8 -0
  256. package/tools/fetch-open-orders.d.ts +7 -0
  257. package/tools/fetch-open-orders.js +4 -0
  258. package/tools/fetch-positions.d.ts +7 -0
  259. package/tools/fetch-positions.js +4 -0
  260. package/tools/fetch-ticker.d.ts +11 -0
  261. package/tools/fetch-ticker.js +5 -0
  262. package/tools/get-agent-profile.d.ts +4 -0
  263. package/tools/get-agent-profile.js +6 -0
  264. package/tools/get-analytics.d.ts +6 -0
  265. package/tools/get-analytics.js +7 -0
  266. package/tools/get-backtest.d.ts +12 -0
  267. package/tools/get-backtest.js +91 -0
  268. package/tools/get-basis.d.ts +7 -0
  269. package/tools/get-basis.js +7 -0
  270. package/tools/get-bracket-config.d.ts +11 -0
  271. package/tools/get-bracket-config.js +24 -0
  272. package/tools/get-cascade-risk.d.ts +7 -0
  273. package/tools/get-cascade-risk.js +8 -0
  274. package/tools/get-crypto-metrics.d.ts +18 -0
  275. package/tools/get-crypto-metrics.js +45 -0
  276. package/tools/get-cvd.d.ts +6 -0
  277. package/tools/get-cvd.js +6 -0
  278. package/tools/get-divergences.d.ts +6 -0
  279. package/tools/get-divergences.js +6 -0
  280. package/tools/get-funding-context.d.ts +6 -0
  281. package/tools/get-funding-context.js +16 -0
  282. package/tools/get-liquidation-levels.d.ts +7 -0
  283. package/tools/get-liquidation-levels.js +7 -0
  284. package/tools/get-liquidation-pulse.d.ts +9 -0
  285. package/tools/get-liquidation-pulse.js +22 -0
  286. package/tools/get-market-breadth.d.ts +6 -0
  287. package/tools/get-market-breadth.js +8 -0
  288. package/tools/get-market-intel.d.ts +19 -0
  289. package/tools/get-market-intel.js +116 -0
  290. package/tools/get-market-structure.d.ts +47 -0
  291. package/tools/get-market-structure.js +198 -0
  292. package/tools/get-my-mined-patterns.d.ts +20 -0
  293. package/tools/get-my-mined-patterns.js +61 -0
  294. package/tools/get-my-proposed-learnings.d.ts +20 -0
  295. package/tools/get-my-proposed-learnings.js +55 -0
  296. package/tools/get-my-recent-reviews.d.ts +22 -0
  297. package/tools/get-my-recent-reviews.js +66 -0
  298. package/tools/get-orderbook.d.ts +21 -0
  299. package/tools/get-orderbook.js +32 -0
  300. package/tools/get-pattern-scan.d.ts +7 -0
  301. package/tools/get-pattern-scan.js +8 -0
  302. package/tools/get-regime.d.ts +6 -0
  303. package/tools/get-regime.js +7 -0
  304. package/tools/get-relevant-learnings.d.ts +21 -0
  305. package/tools/get-relevant-learnings.js +65 -0
  306. package/tools/get-resting-liquidity.d.ts +6 -0
  307. package/tools/get-resting-liquidity.js +11 -0
  308. package/tools/get-risk-scenario.d.ts +29 -0
  309. package/tools/get-risk-scenario.js +47 -0
  310. package/tools/get-risk-summary.d.ts +51 -0
  311. package/tools/get-risk-summary.js +118 -0
  312. package/tools/get-sentiment.d.ts +4 -0
  313. package/tools/get-sentiment.js +6 -0
  314. package/tools/get-session-review.d.ts +7 -0
  315. package/tools/get-session-review.js +8 -0
  316. package/tools/get-setup-detail.d.ts +7 -0
  317. package/tools/get-setup-detail.js +303 -0
  318. package/tools/get-signals.d.ts +15 -0
  319. package/tools/get-signals.js +54 -0
  320. package/tools/get-sizing.d.ts +6 -0
  321. package/tools/get-sizing.js +6 -0
  322. package/tools/get-trade-feedback.d.ts +7 -0
  323. package/tools/get-trade-feedback.js +8 -0
  324. package/tools/get-trade-flow.d.ts +7 -0
  325. package/tools/get-trade-flow.js +7 -0
  326. package/tools/get-volume-analysis.d.ts +21 -0
  327. package/tools/get-volume-analysis.js +74 -0
  328. package/tools/get-volume-profile.d.ts +7 -0
  329. package/tools/get-volume-profile.js +7 -0
  330. package/tools/helpers.d.ts +25 -0
  331. package/tools/helpers.js +38 -0
  332. package/tools/intel-api.d.ts +14 -0
  333. package/tools/intel-api.js +52 -0
  334. package/tools/intel-cache.d.ts +25 -0
  335. package/tools/intel-cache.js +133 -0
  336. package/tools/list-strategies.d.ts +7 -0
  337. package/tools/list-strategies.js +6 -0
  338. package/tools/modify-stop.d.ts +17 -0
  339. package/tools/modify-stop.js +81 -0
  340. package/tools/modify-target.d.ts +17 -0
  341. package/tools/modify-target.js +71 -0
  342. package/tools/propose-learning.d.ts +22 -0
  343. package/tools/propose-learning.js +65 -0
  344. package/tools/query-review-outcomes.d.ts +30 -0
  345. package/tools/query-review-outcomes.js +64 -0
  346. package/tools/query-trades.d.ts +21 -0
  347. package/tools/query-trades.js +37 -0
  348. package/tools/record-position-reviews.d.ts +38 -0
  349. package/tools/record-position-reviews.js +147 -0
  350. package/tools/save-strategy.d.ts +16 -0
  351. package/tools/save-strategy.js +46 -0
  352. package/tools/scan-pairs.d.ts +18 -0
  353. package/tools/scan-pairs.js +220 -0
  354. package/tools/score-setup.d.ts +31 -0
  355. package/tools/score-setup.js +268 -0
  356. package/tools/set-bracket-requirement.d.ts +18 -0
  357. package/tools/set-bracket-requirement.js +81 -0
  358. package/tools/set-exchange-credentials.d.ts +25 -0
  359. package/tools/set-exchange-credentials.js +80 -0
  360. package/tools/set-trading-mode.d.ts +26 -0
  361. package/tools/set-trading-mode.js +135 -0
  362. package/tools/test-exchange-credentials.d.ts +16 -0
  363. package/tools/test-exchange-credentials.js +100 -0
  364. package/tools/toggle-strategy.d.ts +8 -0
  365. package/tools/toggle-strategy.js +8 -0
  366. package/trading-params-cache.d.ts +26 -0
  367. package/trading-params-cache.js +52 -0
  368. package/types.d.ts +110 -0
  369. package/types.js +7 -0
  370. package/util/plugin-paths.d.ts +3 -0
  371. package/util/plugin-paths.js +15 -0
@@ -0,0 +1,505 @@
1
+ // Polling orchestrator for the OpenClaw gateway.
2
+ // Interval-based HTTP polling for market data, positions, balance, and open orders.
3
+ // Polls are scheduled per-tool with independent intervals and in-flight guards.
4
+ import { logger } from '../logger.js';
5
+ import { mapCcxtTicker, mapCcxtBalance, mapCcxtOrder } from './event-parser.js';
6
+ const TAG = 'poller';
7
+ // ---- Polling intervals (ms) ----
8
+ // 1s was redundant: price/mark already streams to the dashboard via the
9
+ // WS subscription at 250ms (see CLAUDE.md "Rendering Throttle"). A 1s REST
10
+ // ticker poll added constant per-IP weight pressure and amplified retries
11
+ // under the pacer for zero UI benefit. 5s is operationally invisible.
12
+ // Exported so tests advance timers relative to the cadence, not a literal.
13
+ export const TICKER_INTERVAL_MS = 5_000;
14
+ // 2026-05-15: 5s → 15s. Chart candles only need eventual consistency — the
15
+ // live price/last-candle tip moves via the 250ms WS mark/ticker path, not
16
+ // this REST backfill. 15s is operationally invisible and cuts this poll's
17
+ // per-IP klines weight 3×. Exported so tests track the cadence.
18
+ export const OHLCV_INTERVAL_MS = 15_000;
19
+ // 2026-05-15 EFFICIENCY_QUICK_WINS Change 2: 3s → 10s. Position QUANTITY
20
+ // only changes when we trade (heartbeats every 10 min); live P&L keeps
21
+ // ticking via the mark-price path, and brackets are exchange-enforced. 10s
22
+ // is operationally imperceptible and saves ~70 weight/min (100→30). The
23
+ // plan's config-gating refinement is deliberately deferred — the constant
24
+ // delivers the full saving; piping a new flag through the plugin→skill
25
+ // startup handshake is extra live-system surface area for no extra weight.
26
+ export const POSITIONS_INTERVAL_MS = 10_000;
27
+ const BALANCE_INTERVAL_MS = 10_000;
28
+ const OPEN_ORDERS_INTERVAL_MS = 3_000;
29
+ // 2026-05-15: 30s → 60s. get_market_structure fetches SIX timeframes of
30
+ // OHLCV per poll (the heaviest single skill poll). Multi-timeframe trend
31
+ // structure does not meaningfully change in 30s; 60s keeps the Agent-State
32
+ // panel current while halving this poll's klines weight. Exported for tests.
33
+ export const MARKET_STRUCTURE_INTERVAL_MS = 60_000;
34
+ const CRYPTO_METRICS_INTERVAL_MS = 60_000;
35
+ const VOLUME_ANALYSIS_INTERVAL_MS = 15_000;
36
+ // ---- Poller class ----
37
+ /** 429 exponential-backoff schedule (ms). After each consecutive 429 we
38
+ * advance one step; a clean poll resets to step 0. Capped so we never go
39
+ * fully silent — at the cap we still probe once every 2 min, which is
40
+ * enough to notice the limit clearing without re-triggering it. */
41
+ const RATE_LIMIT_BACKOFF_MS = [5_000, 15_000, 45_000, 90_000, 120_000];
42
+ /** Extra slack added on top of Binance's stated `banned until` timestamp
43
+ * before we resume — clock skew + a margin so the very first post-ban poll
44
+ * doesn't land on the boundary and immediately re-ban. */
45
+ const BAN_RESUME_SLACK_MS = 8_000;
46
+ /** True when an error message carries a Binance rate-limit / IP-ban signal
47
+ * (418 ban, or 429 / -1003 "too many requests" soft limit). Poll methods that
48
+ * wrap their own try/catch MUST rethrow these so guardedExec's catch can arm
49
+ * the process-wide circuit-breaker (applyRateLimitBackoff). Swallowing them
50
+ * internally as "supplementary/non-fatal" silently defeats the ban gate: a
51
+ * fixed-cadence poller keeps hammering a banned IP, and every request during a
52
+ * 418 EXTENDS the ban (see docs/CLAUDE/binance-ratelimit.md). Kept in sync
53
+ * with the classification branches in applyRateLimitBackoff. */
54
+ export function isRateLimitShapedError(msg) {
55
+ return (msg.includes('418') ||
56
+ /banned until/i.test(msg) ||
57
+ msg.includes('429') ||
58
+ msg.includes('-1003') ||
59
+ /too many requests/i.test(msg));
60
+ }
61
+ export class Poller {
62
+ http;
63
+ toolMap;
64
+ symbol;
65
+ callbacks;
66
+ quoteCurrency;
67
+ running = false;
68
+ paused = false;
69
+ timers = [];
70
+ /** Prevents concurrent polls of the same type (if HTTP is slow) */
71
+ inflight = new Set();
72
+ /** Process-wide circuit-breaker. While `Date.now() < cooldownUntilMs`,
73
+ * `guardedExec` skips EVERY poll regardless of type. Set when any poll
74
+ * observes a Binance 418 (IP ban) or 429 (rate-limit) error.
75
+ *
76
+ * Why this exists: pre-2026-05-15 the poller had no ban awareness — on a
77
+ * 418 every one of ~8 pollers (ticker 1s, positions/orders 3s, …) kept
78
+ * firing on its original cadence straight through the ban, and since
79
+ * Binance EXTENDS an IP ban every time you hit it during the ban, the
80
+ * system never recovered on its own (self-perpetuating lockout that
81
+ * zeroed the dashboard + blinded the agent for the whole window). The
82
+ * breaker makes the whole poller go quiet until Binance's stated ban
83
+ * expiry (+slack), or an exponential window for soft 429s. */
84
+ cooldownUntilMs = 0;
85
+ /** Index into RATE_LIMIT_BACKOFF_MS for consecutive soft 429s. Reset to
86
+ * -1 (→ step 0 on first hit) after any clean poll. */
87
+ rateLimitStep = -1;
88
+ /** Throttles the "in cooldown, skipping" log to once per cooldown window
89
+ * so a 2-min ban doesn't emit hundreds of identical skip lines. */
90
+ cooldownLogged = false;
91
+ /** Per-tool poll-attribution for the current 60s window (observability
92
+ * only). The skill is a SEPARATE process from the plugin but shares the
93
+ * Binance per-IP weight budget; the plugin's ban-gate weight summary
94
+ * cannot see skill polls. This is the skill-side mirror so the IP load
95
+ * split is provable, not guessed. */
96
+ pollWindowStart = 0;
97
+ pollCountByTool = new Map();
98
+ constructor(http, toolMap, symbol, callbacks, quoteCurrency = 'USDT') {
99
+ this.http = http;
100
+ this.toolMap = toolMap;
101
+ this.symbol = symbol;
102
+ this.callbacks = callbacks;
103
+ this.quoteCurrency = quoteCurrency;
104
+ }
105
+ /**
106
+ * Start all poll loops. Each tool is polled on its own interval.
107
+ * Tools not present in the ToolMap are silently skipped.
108
+ * Each poll fires immediately on start, then repeats on interval.
109
+ */
110
+ start() {
111
+ if (this.running) {
112
+ logger.warn(TAG, 'Poller already running');
113
+ return;
114
+ }
115
+ this.running = true;
116
+ this.paused = false;
117
+ this.cooldownUntilMs = 0;
118
+ this.rateLimitStep = -1;
119
+ this.cooldownLogged = false;
120
+ logger.info(TAG, `Starting poller for ${this.symbol}`);
121
+ this.scheduleIfAvailable('fetch_ticker', TICKER_INTERVAL_MS, () => this.pollTicker());
122
+ this.scheduleIfAvailable('fetch_ohlcv', OHLCV_INTERVAL_MS, () => this.pollOhlcv());
123
+ this.scheduleIfAvailable('fetch_positions', POSITIONS_INTERVAL_MS, () => this.pollPositions());
124
+ this.scheduleIfAvailable('fetch_balance', BALANCE_INTERVAL_MS, () => this.pollBalance());
125
+ this.scheduleIfAvailable('fetch_open_orders', OPEN_ORDERS_INTERVAL_MS, () => this.pollOpenOrders());
126
+ this.scheduleIfAvailable('get_market_structure', MARKET_STRUCTURE_INTERVAL_MS, () => this.pollMarketStructure());
127
+ // Crypto metrics: funding rate + open interest via dedicated get_crypto_metrics tool
128
+ // Delayed start (5s) to avoid interfering with initial polls
129
+ if (this.toolMap.get_crypto_metrics) {
130
+ const cryptoDelay = setTimeout(() => {
131
+ if (!this.running)
132
+ return;
133
+ void this.guardedExec('crypto_metrics', () => this.pollCryptoMetrics());
134
+ const cryptoTimer = setInterval(() => void this.guardedExec('crypto_metrics', () => this.pollCryptoMetrics()), CRYPTO_METRICS_INTERVAL_MS);
135
+ this.timers.push(cryptoTimer);
136
+ }, 5_000);
137
+ this.timers.push(cryptoDelay);
138
+ }
139
+ // Volume analysis: computed from OHLCV data (uses fetch_ohlcv tool — no new tool needed)
140
+ // Delayed start (5s) to let initial OHLCV data arrive
141
+ if (this.toolMap.fetch_ohlcv) {
142
+ const volumeDelay = setTimeout(() => {
143
+ if (!this.running)
144
+ return;
145
+ void this.guardedExec('volume_analysis', () => this.pollVolumeAnalysis());
146
+ const volumeTimer = setInterval(() => void this.guardedExec('volume_analysis', () => this.pollVolumeAnalysis()), VOLUME_ANALYSIS_INTERVAL_MS);
147
+ this.timers.push(volumeTimer);
148
+ }, 5_000);
149
+ this.timers.push(volumeDelay);
150
+ }
151
+ }
152
+ /** Stop all poll loops and clean up timers. */
153
+ stop() {
154
+ if (!this.running)
155
+ return;
156
+ logger.info(TAG, 'Stopping poller');
157
+ this.running = false;
158
+ this.paused = false;
159
+ for (const timer of this.timers) {
160
+ clearInterval(timer);
161
+ }
162
+ this.timers = [];
163
+ this.inflight.clear();
164
+ }
165
+ /** Pause polling (timers keep ticking but polls are skipped). */
166
+ pause() {
167
+ if (!this.running || this.paused)
168
+ return;
169
+ logger.info(TAG, 'Pausing poller');
170
+ this.paused = true;
171
+ }
172
+ /** Resume polling after a pause. */
173
+ resume() {
174
+ if (!this.running || !this.paused)
175
+ return;
176
+ logger.info(TAG, 'Resuming poller');
177
+ this.paused = false;
178
+ }
179
+ // ---- Scheduling ----
180
+ /**
181
+ * Schedule a poll loop for a logical tool, only if it was discovered.
182
+ * Fires immediately, then repeats on interval.
183
+ */
184
+ scheduleIfAvailable(logical, intervalMs, fn) {
185
+ if (!this.toolMap[logical]) {
186
+ logger.info(TAG, `Skipping ${logical} poll — tool not discovered`);
187
+ return;
188
+ }
189
+ // Fire immediately on start
190
+ void this.guardedExec(logical, fn);
191
+ const timer = setInterval(() => void this.guardedExec(logical, fn), intervalMs);
192
+ this.timers.push(timer);
193
+ }
194
+ /**
195
+ * Execute a poll function with error handling and in-flight guard.
196
+ * - Skipped when paused or stopped (checked before AND after acquiring inflight slot)
197
+ * - Skipped if a previous poll of the same type is still in-flight
198
+ * - Errors are logged and swallowed (never crash the provider)
199
+ */
200
+ async guardedExec(label, fn) {
201
+ if (this.paused || !this.running)
202
+ return;
203
+ // Process-wide circuit-breaker. While in cooldown, EVERY poll is
204
+ // skipped — the whole point is to stop hitting Binance so an IP ban
205
+ // can actually expire instead of being perpetually re-triggered.
206
+ const now = Date.now();
207
+ if (now < this.cooldownUntilMs) {
208
+ if (!this.cooldownLogged) {
209
+ const secs = Math.ceil((this.cooldownUntilMs - now) / 1000);
210
+ logger.warn(TAG, `Rate-limit cooldown active — ALL polls paused for ~${secs}s (Binance 418/429 backoff)`);
211
+ this.cooldownLogged = true;
212
+ }
213
+ return;
214
+ }
215
+ if (this.cooldownLogged) {
216
+ // We just crossed out of a cooldown window.
217
+ logger.info(TAG, 'Rate-limit cooldown cleared — resuming polls');
218
+ this.cooldownLogged = false;
219
+ }
220
+ if (this.inflight.has(label)) {
221
+ logger.debug(TAG, `Skipping ${label} — previous poll still in-flight`);
222
+ return;
223
+ }
224
+ this.inflight.add(label);
225
+ try {
226
+ // Re-check after acquiring slot — defensive against stop() during await
227
+ if (this.paused || !this.running)
228
+ return;
229
+ // Per-tool poll attribution (60s window). Counts polls that actually
230
+ // execute (past cooldown + in-flight guards) — the real Binance-bound
231
+ // call rate from this process.
232
+ if (this.pollWindowStart === 0)
233
+ this.pollWindowStart = now;
234
+ if (now - this.pollWindowStart >= 60_000) {
235
+ if (this.pollCountByTool.size > 0) {
236
+ const top = [...this.pollCountByTool.entries()]
237
+ .sort((a, b) => b[1] - a[1])
238
+ .map(([k, v]) => `${k}=${v}`)
239
+ .join(' ');
240
+ logger.info(TAG, `poll window summary (60s, skill process): ${top}`);
241
+ }
242
+ this.pollWindowStart = now;
243
+ this.pollCountByTool.clear();
244
+ }
245
+ this.pollCountByTool.set(label, (this.pollCountByTool.get(label) ?? 0) + 1);
246
+ await fn();
247
+ // Clean poll — relax the soft-429 backoff ladder.
248
+ this.rateLimitStep = -1;
249
+ }
250
+ catch (err) {
251
+ const msg = err instanceof Error ? err.message : String(err);
252
+ this.applyRateLimitBackoff(label, msg);
253
+ logger.warn(TAG, `Poll ${label} failed: ${msg}`);
254
+ }
255
+ finally {
256
+ this.inflight.delete(label);
257
+ }
258
+ }
259
+ /** Inspect a failed poll's error for Binance rate-limit signals and arm
260
+ * the process-wide circuit-breaker accordingly.
261
+ *
262
+ * - **418** (IP ban): Binance returns `banned until <epoch-ms>` in the
263
+ * message. We respect that exact timestamp + slack — guessing a
264
+ * shorter window would just re-ban and extend it.
265
+ * - **429 / -1003 "Too many requests"** (soft rate-limit, no ban yet):
266
+ * exponential backoff so we stop climbing toward a 418.
267
+ *
268
+ * Any non-rate-limit error is left alone (normal transient handling). */
269
+ applyRateLimitBackoff(label, msg) {
270
+ // 418 — hard IP ban with an explicit expiry Binance tells us.
271
+ if (msg.includes('418') || /banned until/i.test(msg)) {
272
+ const m = /banned until (\d{10,})/i.exec(msg);
273
+ const bannedUntil = m ? Number(m[1]) : 0;
274
+ const target = (Number.isFinite(bannedUntil) && bannedUntil > Date.now()
275
+ ? bannedUntil
276
+ : Date.now() + RATE_LIMIT_BACKOFF_MS[RATE_LIMIT_BACKOFF_MS.length - 1])
277
+ + BAN_RESUME_SLACK_MS;
278
+ if (target > this.cooldownUntilMs) {
279
+ this.cooldownUntilMs = target;
280
+ this.cooldownLogged = false; // force a fresh "paused for ~Ns" line
281
+ const secs = Math.ceil((target - Date.now()) / 1000);
282
+ logger.error(TAG, `Binance 418 IP BAN detected on ${label} — circuit-breaker engaged, ALL polls paused ~${secs}s (until Binance-stated expiry +slack)`);
283
+ }
284
+ return;
285
+ }
286
+ // 429 / -1003 — soft rate-limit. Exponential backoff, no ban yet.
287
+ if (msg.includes('429') || msg.includes('-1003') || /too many requests/i.test(msg)) {
288
+ this.rateLimitStep = Math.min(this.rateLimitStep + 1, RATE_LIMIT_BACKOFF_MS.length - 1);
289
+ const window = RATE_LIMIT_BACKOFF_MS[this.rateLimitStep];
290
+ const target = Date.now() + window;
291
+ if (target > this.cooldownUntilMs) {
292
+ this.cooldownUntilMs = target;
293
+ this.cooldownLogged = false;
294
+ logger.warn(TAG, `Binance 429 on ${label} — circuit-breaker backoff step ${this.rateLimitStep}, ALL polls paused ${window / 1000}s`);
295
+ }
296
+ }
297
+ }
298
+ // ---- Individual poll methods ----
299
+ async pollTicker() {
300
+ const tool = this.toolMap.fetch_ticker;
301
+ const result = await this.http.invoke(tool, { symbol: this.symbol });
302
+ const ticker = mapCcxtTicker(result.data);
303
+ if (ticker) {
304
+ this.callbacks.onTicker(ticker);
305
+ }
306
+ }
307
+ async pollOhlcv() {
308
+ const tool = this.toolMap.fetch_ohlcv;
309
+ const result = await this.http.invoke(tool, {
310
+ symbol: this.symbol,
311
+ timeframe: '1m',
312
+ limit: 2, // Last 2 candles — newest may still be forming
313
+ });
314
+ const rows = result.data;
315
+ if (!Array.isArray(rows) || rows.length === 0)
316
+ return;
317
+ // Emit the most recent candle
318
+ const row = rows[rows.length - 1];
319
+ if (!Array.isArray(row) || row.length < 6)
320
+ return;
321
+ const [ts, open, high, low, close, volume] = row;
322
+ // Validate all values are numbers — CCXT can return null for sparse candles
323
+ if (typeof ts !== 'number' ||
324
+ typeof open !== 'number' ||
325
+ typeof high !== 'number' ||
326
+ typeof low !== 'number' ||
327
+ typeof close !== 'number' ||
328
+ typeof volume !== 'number') {
329
+ logger.warn(TAG, `Invalid OHLCV values for ${this.symbol}, skipping`);
330
+ return;
331
+ }
332
+ this.callbacks.onCandle({
333
+ symbol: this.symbol,
334
+ candle: {
335
+ time: Math.floor(ts / 1000), // CCXT returns ms, LWC expects seconds
336
+ open,
337
+ high,
338
+ low,
339
+ close,
340
+ volume,
341
+ },
342
+ timestamp: new Date(ts).toISOString(),
343
+ });
344
+ }
345
+ async pollPositions() {
346
+ const tool = this.toolMap.fetch_positions;
347
+ // Fetch ALL positions (no symbol filter) so multi-symbol trades are visible
348
+ const result = await this.http.invoke(tool, {});
349
+ const positions = Array.isArray(result.data) ? result.data : [];
350
+ this.callbacks.onPositions(positions);
351
+ }
352
+ async pollBalance() {
353
+ const tool = this.toolMap.fetch_balance;
354
+ const result = await this.http.invoke(tool, {});
355
+ const balance = mapCcxtBalance(result.data, this.quoteCurrency);
356
+ this.callbacks.onBalance(balance);
357
+ }
358
+ async pollOpenOrders() {
359
+ const tool = this.toolMap.fetch_open_orders;
360
+ const result = await this.http.invoke(tool, { symbol: this.symbol });
361
+ const orders = [];
362
+ if (Array.isArray(result.data)) {
363
+ for (const ccxt of result.data) {
364
+ const mapped = mapCcxtOrder(ccxt);
365
+ if (mapped)
366
+ orders.push(mapped);
367
+ }
368
+ }
369
+ this.callbacks.onOpenOrders(orders);
370
+ }
371
+ async pollMarketStructure() {
372
+ const tool = this.toolMap.get_market_structure;
373
+ logger.info(TAG, `Polling market structure (tool=${tool}, symbol=${this.symbol})`);
374
+ const result = await this.http.invoke(tool, {
375
+ symbol: this.symbol,
376
+ }, { timeoutMs: 30_000 }); // Higher timeout — fetches 6 TFs of OHLCV
377
+ const data = result.data;
378
+ if (data && typeof data === 'object' && 'timeframes' in data && Array.isArray(data.timeframes)) {
379
+ logger.info(TAG, `Market structure: ${data.timeframes.length} timeframes, alignment=${data.alignment?.summary ?? 'unknown'}`);
380
+ this.callbacks.onMarketStructure?.(data);
381
+ }
382
+ else {
383
+ logger.warn(TAG, `Market structure: unexpected response shape: ${JSON.stringify(data).slice(0, 200)}`);
384
+ }
385
+ }
386
+ // ---- Feature B: Crypto metrics (funding rate + open interest) ----
387
+ /**
388
+ * Fetch funding rate and open interest via the dedicated get_crypto_metrics tool.
389
+ * The plugin tool handles spot→perp symbol conversion and raw CCXT ticker parsing.
390
+ */
391
+ async pollCryptoMetrics() {
392
+ const tool = this.toolMap.get_crypto_metrics;
393
+ try {
394
+ const result = await this.http.invoke(tool, { symbol: this.symbol });
395
+ const data = result.data;
396
+ if (!data || typeof data !== 'object')
397
+ return;
398
+ if (data.error) {
399
+ logger.debug(TAG, `Crypto metrics tool error: ${data.error}`);
400
+ return;
401
+ }
402
+ // Only emit if we have at least one useful field
403
+ if (data.fundingRate !== null || data.openInterest !== null) {
404
+ const metrics = {
405
+ symbol: this.symbol,
406
+ fundingRate: data.fundingRate,
407
+ fundingRateAnnualized: data.fundingRateAnnualized,
408
+ nextFundingTime: data.nextFundingTime,
409
+ openInterest: data.openInterest,
410
+ openInterestUsd: data.openInterestUsd,
411
+ timestamp: data.timestamp,
412
+ };
413
+ logger.info(TAG, `Crypto metrics: FR=${data.fundingRate}, OI=${data.openInterest}, OI_USD=${data.openInterestUsd}`);
414
+ this.callbacks.onCryptoMetrics?.(metrics);
415
+ }
416
+ else {
417
+ logger.debug(TAG, 'Crypto metrics: no funding rate or OI returned by tool');
418
+ }
419
+ }
420
+ catch (err) {
421
+ const msg = err instanceof Error ? err.message : String(err);
422
+ // Rate-limit / IP-ban errors MUST propagate to guardedExec so the
423
+ // process-wide circuit-breaker arms — swallowing them here would let this
424
+ // fixed-cadence poll keep hitting a banned IP and extend the 418 ban.
425
+ if (isRateLimitShapedError(msg))
426
+ throw err;
427
+ // Otherwise non-fatal — crypto metrics are supplementary.
428
+ logger.debug(TAG, `Crypto metrics poll failed: ${msg}`);
429
+ }
430
+ }
431
+ // ---- Feature A: Volume / thrust analysis ----
432
+ /**
433
+ * Fetch 25 bars of 5m OHLCV and compute volume analysis metrics:
434
+ * - Volume expansion ratio (current vs 20-bar avg)
435
+ * - Close position (where close sits in bar's range, 0=low, 1=high)
436
+ * - Directional thrust (volume-weighted close position, -1 to +1)
437
+ * - Buy volume estimate (proxy using close position)
438
+ */
439
+ async pollVolumeAnalysis() {
440
+ const tool = this.toolMap.fetch_ohlcv;
441
+ try {
442
+ const result = await this.http.invoke(tool, {
443
+ symbol: this.symbol,
444
+ timeframe: '5m',
445
+ limit: 25, // 20 for avg + a few extra for robustness
446
+ });
447
+ const rows = result.data;
448
+ if (!Array.isArray(rows) || rows.length < 5)
449
+ return;
450
+ // Validate and filter valid rows
451
+ const valid = rows.filter((r) => Array.isArray(r) && r.length >= 6 &&
452
+ typeof r[0] === 'number' && typeof r[1] === 'number' &&
453
+ typeof r[2] === 'number' && typeof r[3] === 'number' &&
454
+ typeof r[4] === 'number' && typeof r[5] === 'number' &&
455
+ r[5] > 0 // Volume > 0
456
+ );
457
+ if (valid.length < 3)
458
+ return;
459
+ // Latest bar
460
+ const latest = valid[valid.length - 1];
461
+ const [, , latestHigh, latestLow, latestClose, latestVolume] = latest;
462
+ // Compute 20-bar average volume (excluding latest bar)
463
+ const lookback = valid.slice(Math.max(0, valid.length - 21), valid.length - 1);
464
+ const avgVolume = lookback.length > 0
465
+ ? lookback.reduce((sum, r) => sum + r[5], 0) / lookback.length
466
+ : latestVolume;
467
+ // Volume expansion ratio
468
+ const volumeExpansion = avgVolume > 0 ? latestVolume / avgVolume : 1;
469
+ // Close position: where close sits in the bar's range (0 = at low, 1 = at high)
470
+ const range = latestHigh - latestLow;
471
+ const closePosition = range > 0 ? (latestClose - latestLow) / range : 0.5;
472
+ // Directional thrust: combines volume expansion with close position
473
+ // -1 = high volume bearish (close at low), +1 = high volume bullish (close at high)
474
+ const dirMultiplier = (closePosition - 0.5) * 2; // -1 to +1
475
+ const cappedExpansion = Math.min(volumeExpansion, 5); // Cap at 5x to avoid outlier distortion
476
+ const directionalThrust = dirMultiplier * Math.min(1, cappedExpansion / 2);
477
+ // Buy volume estimate (proxy): close position as buy % approximation
478
+ const buyVolEstimate = closePosition;
479
+ const analysis = {
480
+ symbol: this.symbol,
481
+ timeframe: '5m',
482
+ volumeExpansion: +volumeExpansion.toFixed(2),
483
+ closePosition: +closePosition.toFixed(3),
484
+ directionalThrust: +directionalThrust.toFixed(3),
485
+ buyVolEstimate: +buyVolEstimate.toFixed(3),
486
+ avgVolume: +avgVolume.toFixed(2),
487
+ currentVolume: latestVolume,
488
+ barsAnalyzed: valid.length,
489
+ timestamp: new Date().toISOString(),
490
+ };
491
+ logger.debug(TAG, `Volume analysis: expansion=${analysis.volumeExpansion}x, close=${analysis.closePosition}, thrust=${analysis.directionalThrust}`);
492
+ this.callbacks.onVolumeAnalysis?.(analysis);
493
+ }
494
+ catch (err) {
495
+ const msg = err instanceof Error ? err.message : String(err);
496
+ // Rate-limit / IP-ban errors MUST propagate to guardedExec so the
497
+ // process-wide circuit-breaker arms — swallowing them here would let this
498
+ // fixed-cadence poll keep hitting a banned IP and extend the 418 ban.
499
+ if (isRateLimitShapedError(msg))
500
+ throw err;
501
+ // Otherwise non-fatal — volume analysis is supplementary.
502
+ logger.debug(TAG, `Volume analysis poll failed: ${msg}`);
503
+ }
504
+ }
505
+ }
@@ -0,0 +1,25 @@
1
+ import { GatewayHttpClient } from './gateway-http-client.js';
2
+ /**
3
+ * Logical tool names used by ReefClaw internally.
4
+ * Each maps to one or more candidate actual tool names on the gateway.
5
+ */
6
+ export type LogicalTool = 'fetch_ticker' | 'fetch_balance' | 'fetch_ohlcv' | 'fetch_positions' | 'fetch_open_orders' | 'cancel_all_orders' | 'cancel_order' | 'close_position' | 'create_order' | 'get_market_structure' | 'get_crypto_metrics' | 'get_market_intel' | 'set_trading_mode' | 'set_exchange_credentials' | 'test_exchange_credentials' | 'clear_exchange_credentials' | 'get_bracket_config' | 'set_bracket_requirement';
7
+ /** Map of logical tool names to their discovered actual tool names on the gateway */
8
+ export type ToolMap = Partial<Record<LogicalTool, string>>;
9
+ export interface DiscoveryResult {
10
+ /** Map of logical names → actual gateway tool names */
11
+ toolMap: ToolMap;
12
+ /** Logical names of tools that were found */
13
+ found: LogicalTool[];
14
+ /** Logical names of required tools that are missing */
15
+ missingRequired: LogicalTool[];
16
+ /** Logical names of optional tools that are missing */
17
+ missingOptional: LogicalTool[];
18
+ }
19
+ /**
20
+ * Discover all available trading tools on the gateway.
21
+ *
22
+ * Probes required tools first, then optional tools in parallel.
23
+ * Fails fast if no required tools are found.
24
+ */
25
+ export declare function discoverTools(http: GatewayHttpClient): Promise<DiscoveryResult>;