@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,85 @@
1
+ import { type PluginConfigFile } from './plugin-config-io.js';
2
+ export type UserDataStreamMode = 'off' | 'shadow' | 'observe' | 'enforce';
3
+ /** Production-safe defaults. Override via plugin-config for runbook use only. */
4
+ export interface UserDataStreamTunables {
5
+ /** How often to call PUT /fapi/v1/listenKey to keep the stream alive.
6
+ * Binance kills the key after 60 min of no keepalive; 25 min gives a 2x
7
+ * safety margin under jitter and transient failures. */
8
+ listenKeyRefreshMs: number;
9
+ /** Initial backoff delay after a WS disconnect. Doubles on each attempt
10
+ * up to reconnectCapMs. ±20% jitter applied to avoid thundering herd. */
11
+ reconnectBaseMs: number;
12
+ /** Cap on the exponential backoff. Keeps us trying indefinitely but not
13
+ * more than once/minute after repeated failures. */
14
+ reconnectCapMs: number;
15
+ /** If no WS frame of any kind (message, ping, or pong) arrives within this
16
+ * window, force a reconnect. Binance documents server pings every 3 min on
17
+ * the user-data stream; a 6-min default catches a truly dead socket while
18
+ * remaining quiet on calm accounts that get only the periodic pings. */
19
+ staleEventWatchdogMs: number;
20
+ /** How often to REST-refresh algo (STOP_MARKET / TAKE_PROFIT_MARKET / bracket)
21
+ * orders into the store. Binance's user-data WS does NOT emit
22
+ * ORDER_TRADE_UPDATE for algo orders until they trigger, so the store has no
23
+ * other way to observe algo-order add/cancel events. Without this loop, the
24
+ * 60s truth-check reports MAJOR order drift every cycle any time brackets
25
+ * are touched. Set to 0 to disable (falls back to snapshot-only). */
26
+ algoRefreshMs: number;
27
+ /** @deprecated since 2026-04-25 (ping-aware watchdog refactor). Field kept
28
+ * for backward compatibility with existing plugin-config.json files; the
29
+ * runtime no longer consults it. Empirically 99% of its fires were
30
+ * false positives because the 90 s default sat below Binance's 180 s
31
+ * server-ping interval and the watchdog only counted message frames as
32
+ * liveness. The single `staleEventWatchdogMs` (6 min default, ping-aware)
33
+ * is now sufficient; the active REST probe handles per-position freshness. */
34
+ activeStaleEventWatchdogMs: number;
35
+ /** How often the active REST probe checks for fills the WS missed.
36
+ * Iterates open positions, calls fetchMyTrades(since=lastEventAt), and
37
+ * forces a reconnect if any trade newer than lastEventAt is found.
38
+ * Set to 0 to disable. Cost: ~5 weight per open position per probe. */
39
+ activeProbeIntervalMs: number;
40
+ }
41
+ export declare const DEFAULT_TUNABLES: UserDataStreamTunables;
42
+ /** Read user-data-stream mode from a config object. Invalid values fall back to 'off'. */
43
+ export declare function getUserDataStreamMode(config?: PluginConfigFile, remoteOverride?: UserDataStreamMode): UserDataStreamMode;
44
+ /** Convenience: load from disk and return the effective mode. */
45
+ export declare function loadUserDataStreamMode(): UserDataStreamMode;
46
+ /** Is the WS stream enabled at all (anything other than 'off')? */
47
+ export declare function userDataStreamEnabled(mode: UserDataStreamMode): boolean;
48
+ export type UserDataStreamDbWrite = 'off' | 'on';
49
+ /** Read the dbWrite flag. Orthogonal to `mode`: `mode` controls the read
50
+ * path (WS vs REST authoritative for getPositions etc.); `dbWrite` controls
51
+ * whether WS-observed fills are POSTed to the webapp's audit-trail store.
52
+ * Both must be enabled together for WS rows to land in `trades`. */
53
+ export declare function getUserDataStreamDbWrite(config?: PluginConfigFile): UserDataStreamDbWrite;
54
+ export declare function loadUserDataStreamDbWrite(): UserDataStreamDbWrite;
55
+ /** Webapp ingest base URL (POST target). Defaults to production reefclaw.com. */
56
+ export declare function getUserDataStreamIngestBaseUrl(config?: PluginConfigFile): string;
57
+ /** Resolve the webapp-ingest credential (the per-user `rc_*` token).
58
+ *
59
+ * Prefers `connectionToken` in plugin-config.json — the same `rc_*` token
60
+ * already stored there for the intel-service client, so there's one fewer
61
+ * place to update on rotation than the legacy `WEBAPP_INGEST_TOKEN` systemd
62
+ * env var. Falls back to that env var so existing deployments that haven't
63
+ * moved the token into plugin-config.json keep working unchanged.
64
+ *
65
+ * Both ingest paths (position-decisions journal + WS audit-trail) use this. */
66
+ export declare function resolveIngestToken(config?: PluginConfigFile): string;
67
+ /** Is the WS authoritative for reads (observe or enforce)? In shadow it's
68
+ * only observed — REST remains the source of truth for getPositions etc. */
69
+ export declare function userDataStreamAuthoritative(mode: UserDataStreamMode): boolean;
70
+ /** Should REST polling cadence be reduced? True in observe/enforce where
71
+ * WS is carrying the hot-path signal and REST is only a truth-check. */
72
+ export declare function restPollingReduced(mode: UserDataStreamMode): boolean;
73
+ /** Read tunables, falling back to DEFAULT_TUNABLES for any missing field.
74
+ * Clamps are defense-in-depth against a truncated or nonsensical config. */
75
+ export declare function getUserDataStreamTunables(config?: PluginConfigFile): UserDataStreamTunables;
76
+ export declare function loadUserDataStreamTunables(): UserDataStreamTunables;
77
+ /** Validate a proposed transition. Returns `{ allowed: false, reason }` when
78
+ * the move is blocked so the deploy script can refuse. Forward transitions
79
+ * must progress through stages; reverse transitions (to any earlier stage,
80
+ * including 'off') are always permitted as a safety-net rollback. */
81
+ export interface TransitionCheck {
82
+ allowed: boolean;
83
+ reason?: string;
84
+ }
85
+ export declare function checkTransition(from: UserDataStreamMode, to: UserDataStreamMode): TransitionCheck;
@@ -0,0 +1,224 @@
1
+ // Feature flag reader for the Binance user-data WebSocket stream.
2
+ //
3
+ // Ordering of sources (first hit wins):
4
+ // 1. Remote override from intelligence (set via admin dashboard OTA) — stored
5
+ // in memory on the plugin after push; not persisted to plugin-config.json.
6
+ // Wired in Phase 2; for Phase 1 this is always undefined.
7
+ // 2. Local plugin-config.json `userDataStream.mode` field.
8
+ // 3. Default: 'off' (legacy REST polling remains authoritative).
9
+ //
10
+ // Rollout stages:
11
+ // off — WS not started. REST polling authoritative.
12
+ // shadow — DEPRECATED 2026-05-06. WS runs alongside REST; diffs logged every
13
+ // 60s; REST authoritative. Was the soak stage during the original
14
+ // rollout; retained as a runtime mode for backward compatibility
15
+ // with existing plugin-config files but no longer reachable as a
16
+ // forward transition target. New deployments go off → observe.
17
+ // observe — WS authoritative for reads; REST drops to 30-60s truth-check.
18
+ // enforce — Full cutover. Skill-side pollers disabled; REST at 60s only.
19
+ //
20
+ // Transitions must progress forward only (no skipping). Deploy script enforces.
21
+ // Reverse transitions to any earlier stage (including 'shadow' and 'off') are
22
+ // always permitted as a safety-net rollback.
23
+ import { readPluginConfig } from './plugin-config-io.js';
24
+ const VALID = new Set(['off', 'shadow', 'observe', 'enforce']);
25
+ /** Allowed forward transitions. Reverse transitions are always permitted so
26
+ * the operator can fall back to REST if WS misbehaves.
27
+ *
28
+ * 'shadow' is deprecated as a forward target (2026-05-06): nothing transitions
29
+ * TO shadow anymore. Configs that already have 'shadow' continue to work; the
30
+ * shadow → observe escape path is preserved so an operator stuck on a
31
+ * shadow-configured plugin can still ladder up. */
32
+ const FORWARD_TRANSITIONS = {
33
+ off: new Set(['observe']),
34
+ shadow: new Set(['observe']),
35
+ observe: new Set(['enforce']),
36
+ enforce: new Set(),
37
+ };
38
+ export const DEFAULT_TUNABLES = {
39
+ listenKeyRefreshMs: 25 * 60_000, // 25 min
40
+ reconnectBaseMs: 1_000, // 1 s
41
+ reconnectCapMs: 60_000, // 60 s
42
+ // Ping-aware watchdog (refactored 2026-04-25). The watchdog now compares
43
+ // against `lastFrameAt` — any incoming WS frame including server pings —
44
+ // not `lastEventAt` (message frames only). Binance documents server pings
45
+ // every 3 min on the user-data stream:
46
+ // "The websocket server will send a ping frame every 3 minutes."
47
+ // "If the websocket server does not receive a pong frame back from the
48
+ // connection within a 10 minute period, the connection will be disconnected."
49
+ // https://developers.binance.com/docs/derivatives/usds-margined-futures/websocket-market-streams
50
+ // 6 min comfortably exceeds the 180 s ping interval and stays well under
51
+ // Binance's own 10-min no-pong cutoff. The previous 90 s active threshold
52
+ // false-fired ~200×/day on idle accounts; the active REST probe (60 s
53
+ // fetchMyTrades per open-position symbol) is the correct mechanism for
54
+ // catching missed fills, and remains in place.
55
+ staleEventWatchdogMs: 6 * 60_000, // 6 min
56
+ // 60 s. NOT a "cheap" call: the algo refresh is an UNSCOPED fetchOpenOrders
57
+ // = 80 weight (40 regular + 40 algo, doc-verified developers.binance.com
58
+ // 2026-05-15). At 15 s that was 4/min × 80 = ~320 weight/min — measured on
59
+ // prod (binance-ban-gate weight-window summary) as the dominant idle
60
+ // consumer after the audit-scope fix, pinning the pacer ceiling and
61
+ // starving the agent's heartbeat reads. ALGO_UPDATE (5a16f05) is now the
62
+ // AUTHORITATIVE bracket-lifecycle push (terminal events arrive immediately,
63
+ // not on this poll) and the 60 s bracket-reconciler unscoped sweep
64
+ // cross-checks, so this periodic full-algo-view refresh only needs to keep
65
+ // the dashboard's open-orders panel eventually-consistent — 60 s is
66
+ // operationally invisible. Still clamped 5 s–5 min + plugin-config
67
+ // overridable for per-VPS tuning.
68
+ algoRefreshMs: 60_000,
69
+ // @deprecated — kept for backward compatibility with existing plugin-config
70
+ // files. Runtime no longer consults this value (see UserDataStreamController
71
+ // .buildStreamOptions). 90 s default retained so an old config file doesn't
72
+ // drift silently on field validation.
73
+ activeStaleEventWatchdogMs: 90_000,
74
+ // Active probe (added 2026-04-25). 60 s tick polls fetchMyTrades for every
75
+ // open-position symbol; if any trade timestamp exceeds lastEventAt, the WS
76
+ // missed a fill and we force-reconnect. Belt + suspenders alongside the
77
+ // adaptive watchdog. ~5 weight per open position per probe; with 5 positions
78
+ // = 25 weight/min, well under the 2400/min budget.
79
+ activeProbeIntervalMs: 60_000,
80
+ };
81
+ /** Read user-data-stream mode from a config object. Invalid values fall back to 'off'. */
82
+ export function getUserDataStreamMode(config, remoteOverride) {
83
+ if (remoteOverride && VALID.has(remoteOverride))
84
+ return remoteOverride;
85
+ const raw = config?.userDataStream?.mode;
86
+ if (typeof raw === 'string' && VALID.has(raw)) {
87
+ return raw;
88
+ }
89
+ return 'off';
90
+ }
91
+ /** Convenience: load from disk and return the effective mode. */
92
+ export function loadUserDataStreamMode() {
93
+ try {
94
+ return getUserDataStreamMode(readPluginConfig());
95
+ }
96
+ catch {
97
+ // Matches loadBracketMode policy: don't gate trading on a corrupt flag read.
98
+ return 'off';
99
+ }
100
+ }
101
+ /** Is the WS stream enabled at all (anything other than 'off')? */
102
+ export function userDataStreamEnabled(mode) {
103
+ return mode !== 'off';
104
+ }
105
+ /** Read the dbWrite flag. Orthogonal to `mode`: `mode` controls the read
106
+ * path (WS vs REST authoritative for getPositions etc.); `dbWrite` controls
107
+ * whether WS-observed fills are POSTed to the webapp's audit-trail store.
108
+ * Both must be enabled together for WS rows to land in `trades`. */
109
+ export function getUserDataStreamDbWrite(config) {
110
+ const raw = config?.userDataStream?.dbWrite;
111
+ return raw === 'on' ? 'on' : 'off';
112
+ }
113
+ export function loadUserDataStreamDbWrite() {
114
+ try {
115
+ return getUserDataStreamDbWrite(readPluginConfig());
116
+ }
117
+ catch {
118
+ return 'off';
119
+ }
120
+ }
121
+ /** Webapp ingest base URL (POST target). Defaults to production reefclaw.com. */
122
+ export function getUserDataStreamIngestBaseUrl(config) {
123
+ const raw = config?.userDataStream?.ingestBaseUrl;
124
+ if (typeof raw === 'string' && /^https?:\/\//.test(raw))
125
+ return raw;
126
+ return 'https://www.reefclaw.com';
127
+ }
128
+ /** Resolve the webapp-ingest credential (the per-user `rc_*` token).
129
+ *
130
+ * Prefers `connectionToken` in plugin-config.json — the same `rc_*` token
131
+ * already stored there for the intel-service client, so there's one fewer
132
+ * place to update on rotation than the legacy `WEBAPP_INGEST_TOKEN` systemd
133
+ * env var. Falls back to that env var so existing deployments that haven't
134
+ * moved the token into plugin-config.json keep working unchanged.
135
+ *
136
+ * Both ingest paths (position-decisions journal + WS audit-trail) use this. */
137
+ export function resolveIngestToken(config) {
138
+ const fromConfig = config?.connectionToken;
139
+ if (typeof fromConfig === 'string' && fromConfig.trim())
140
+ return fromConfig.trim();
141
+ return process.env.WEBAPP_INGEST_TOKEN ?? '';
142
+ }
143
+ /** Is the WS authoritative for reads (observe or enforce)? In shadow it's
144
+ * only observed — REST remains the source of truth for getPositions etc. */
145
+ export function userDataStreamAuthoritative(mode) {
146
+ return mode === 'observe' || mode === 'enforce';
147
+ }
148
+ /** Should REST polling cadence be reduced? True in observe/enforce where
149
+ * WS is carrying the hot-path signal and REST is only a truth-check. */
150
+ export function restPollingReduced(mode) {
151
+ return mode === 'observe' || mode === 'enforce';
152
+ }
153
+ /** Read tunables, falling back to DEFAULT_TUNABLES for any missing field.
154
+ * Clamps are defense-in-depth against a truncated or nonsensical config. */
155
+ export function getUserDataStreamTunables(config) {
156
+ const u = config?.userDataStream;
157
+ const clamp = (v, fallback, min, max) => {
158
+ if (typeof v !== 'number' || !Number.isFinite(v) || v < min || v > max)
159
+ return fallback;
160
+ return v;
161
+ };
162
+ return {
163
+ // listenKey expires at 60 min; refresh must be below that with margin.
164
+ listenKeyRefreshMs: clamp(u?.listenKeyRefreshMs, DEFAULT_TUNABLES.listenKeyRefreshMs, 60_000, 55 * 60_000),
165
+ reconnectBaseMs: clamp(u?.reconnectBaseMs, DEFAULT_TUNABLES.reconnectBaseMs, 100, 10_000),
166
+ reconnectCapMs: clamp(u?.reconnectCapMs, DEFAULT_TUNABLES.reconnectCapMs, 5_000, 300_000),
167
+ // Lower bound 4 min — any tighter and Binance's documented 3-min ping
168
+ // interval can fail to update lastFrameAt (jitter, network blips) and
169
+ // trigger false reconnects. Upper bound 60 min — beyond that we no longer
170
+ // catch a dead socket before Binance's own 10-min idle timeout would.
171
+ staleEventWatchdogMs: clamp(u?.staleEventWatchdogMs, DEFAULT_TUNABLES.staleEventWatchdogMs, 4 * 60_000, 60 * 60_000),
172
+ // 0 = disabled. Otherwise clamp to a reasonable range: 5s minimum (so we
173
+ // don't hammer REST), 5 min maximum (beyond that the drift window gets
174
+ // long enough to forcedResync the whole store anyway).
175
+ algoRefreshMs: clampOrZero(u?.algoRefreshMs, DEFAULT_TUNABLES.algoRefreshMs, 5_000, 5 * 60_000),
176
+ // @deprecated — read for backward compat with existing plugin-config files
177
+ // but the runtime no longer wires this into the watchdog. Clamp range left
178
+ // intact so a bad value in an old config doesn't cause a validation failure
179
+ // when re-saving the file.
180
+ activeStaleEventWatchdogMs: clamp(u?.activeStaleEventWatchdogMs, DEFAULT_TUNABLES.activeStaleEventWatchdogMs, 30_000, 5 * 60_000),
181
+ // 0 = disabled. 10s minimum to keep weight pressure sensible; 10 min
182
+ // maximum because a probe that runs less often than the stale watchdog
183
+ // adds no value.
184
+ activeProbeIntervalMs: clampOrZero(u?.activeProbeIntervalMs, DEFAULT_TUNABLES.activeProbeIntervalMs, 10_000, 10 * 60_000),
185
+ };
186
+ }
187
+ function clampOrZero(v, fallback, min, max) {
188
+ if (v === 0)
189
+ return 0;
190
+ if (typeof v !== 'number' || !Number.isFinite(v) || v < min || v > max)
191
+ return fallback;
192
+ return v;
193
+ }
194
+ export function loadUserDataStreamTunables() {
195
+ try {
196
+ return getUserDataStreamTunables(readPluginConfig());
197
+ }
198
+ catch {
199
+ return { ...DEFAULT_TUNABLES };
200
+ }
201
+ }
202
+ export function checkTransition(from, to) {
203
+ if (!VALID.has(from))
204
+ return { allowed: false, reason: `invalid source mode: ${from}` };
205
+ if (!VALID.has(to))
206
+ return { allowed: false, reason: `invalid target mode: ${to}` };
207
+ if (from === to)
208
+ return { allowed: true };
209
+ const stageOrder = ['off', 'shadow', 'observe', 'enforce'];
210
+ const fromIdx = stageOrder.indexOf(from);
211
+ const toIdx = stageOrder.indexOf(to);
212
+ // Reverse transitions always allowed (rollback).
213
+ if (toIdx < fromIdx)
214
+ return { allowed: true };
215
+ // Forward transition — must go through next stage only.
216
+ const allowed = FORWARD_TRANSITIONS[from];
217
+ if (!allowed.has(to)) {
218
+ return {
219
+ allowed: false,
220
+ reason: `forward transition ${from} → ${to} skips stages (next allowed: ${[...allowed].join(', ') || '(none)'})`,
221
+ };
222
+ }
223
+ return { allowed: true };
224
+ }
@@ -0,0 +1,36 @@
1
+ import { spawn } from 'node:child_process';
2
+ /** Bridge bundled INSIDE the plugin package (the npm-channel distribution
3
+ * `@reefclaw/openclaw-plugin` ships the connector at <pluginRoot>/bridge).
4
+ * Compiled connector-supervisor.js sits at the plugin dist root, so the
5
+ * bundled bridge is a sibling directory. */
6
+ export declare function bundledBridgeDir(): string;
7
+ /** Where the connector lives, in preference order: bundled-in-package first
8
+ * (npm-channel install — self-contained), then the npx installer's
9
+ * placement. Returns null when neither exists. */
10
+ export declare function resolveBridgeDir(): string | null;
11
+ /** True when the plugin should supervise the connector even WITHOUT an
12
+ * explicit connectorSupervisor='on' in plugin-config: the connector is
13
+ * bundled inside this plugin package, which only the npm-channel
14
+ * distribution does. Prod's linked plugin dir and the npx installer's
15
+ * ~/.reefclaw/plugin have no bridge/ subdir, so they stay opt-in — prod
16
+ * keeps running the bridge under systemd and must never double-connect. */
17
+ export declare function hasBundledBridge(): boolean;
18
+ export interface ConnectorSupervisorOptions {
19
+ /** Directory holding the placed bridge (default ~/.reefclaw/bridge). */
20
+ bridgeDir?: string;
21
+ /** Injectable spawn for tests. */
22
+ spawnFn?: typeof spawn;
23
+ }
24
+ export interface ConnectorSupervisorHandle {
25
+ stop(): void;
26
+ /** Test/debug introspection. */
27
+ isRunning(): boolean;
28
+ }
29
+ /**
30
+ * Start supervising the connector. Idempotent per process — repeat calls
31
+ * (OpenClaw can invoke register() more than once) return the existing handle.
32
+ * Returns null when the bridge isn't placed on disk (installer not run).
33
+ */
34
+ export declare function startConnectorSupervisor(opts?: ConnectorSupervisorOptions): ConnectorSupervisorHandle | null;
35
+ /** Test-only: reset the module singleton. */
36
+ export declare function resetConnectorSupervisorForTest(): void;
@@ -0,0 +1,149 @@
1
+ // Plugin-supervised ReefClaw connector (frictionless-onboarding phase 1).
2
+ //
3
+ // The plugin runs persistently inside the OpenClaw gateway process, so it can
4
+ // supervise the relay connector (the bridge) as a child process — making
5
+ // OpenClaw itself the process manager. This removes every host-level concern
6
+ // from onboarding: no systemd, no terminal, works inside containers, and the
7
+ // connector's lifetime is correctly tied to the gateway's (gateway down ⇒
8
+ // nothing to bridge anyway — the plugin holding the exchange keys lives in
9
+ // the same process).
10
+ //
11
+ // SAFETY: default OFF. Prod runs the bridge as `reefclaw-skill.service`
12
+ // (systemd) — a supervisor turned on there would double-connect the relay
13
+ // room. Only the fresh-install path (the npx installer) writes
14
+ // `connectorSupervisor: 'on'` into ~/.reefclaw/plugin-config.json.
15
+ // Kill-switch: RC_CONNECTOR_SUPERVISOR=off beats config.
16
+ import { spawn } from 'node:child_process';
17
+ import { existsSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { join, dirname } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { logger } from './logger.js';
22
+ const TAG = 'connector-supervisor';
23
+ /** Bridge bundled INSIDE the plugin package (the npm-channel distribution
24
+ * `@reefclaw/openclaw-plugin` ships the connector at <pluginRoot>/bridge).
25
+ * Compiled connector-supervisor.js sits at the plugin dist root, so the
26
+ * bundled bridge is a sibling directory. */
27
+ export function bundledBridgeDir() {
28
+ return join(dirname(fileURLToPath(import.meta.url)), 'bridge');
29
+ }
30
+ /** Where the connector lives, in preference order: bundled-in-package first
31
+ * (npm-channel install — self-contained), then the npx installer's
32
+ * placement. Returns null when neither exists. */
33
+ export function resolveBridgeDir() {
34
+ for (const dir of [bundledBridgeDir(), join(homedir(), '.reefclaw', 'bridge')]) {
35
+ if (existsSync(join(dir, 'index.js')))
36
+ return dir;
37
+ }
38
+ return null;
39
+ }
40
+ /** True when the plugin should supervise the connector even WITHOUT an
41
+ * explicit connectorSupervisor='on' in plugin-config: the connector is
42
+ * bundled inside this plugin package, which only the npm-channel
43
+ * distribution does. Prod's linked plugin dir and the npx installer's
44
+ * ~/.reefclaw/plugin have no bridge/ subdir, so they stay opt-in — prod
45
+ * keeps running the bridge under systemd and must never double-connect. */
46
+ export function hasBundledBridge() {
47
+ return existsSync(join(bundledBridgeDir(), 'index.js'));
48
+ }
49
+ const MIN_BACKOFF_MS = 5_000;
50
+ const MAX_BACKOFF_MS = 60_000;
51
+ /** A child that survives this long resets the backoff (it was healthy). */
52
+ const STABLE_RESET_MS = 5 * 60_000;
53
+ let singleton = null;
54
+ /**
55
+ * Start supervising the connector. Idempotent per process — repeat calls
56
+ * (OpenClaw can invoke register() more than once) return the existing handle.
57
+ * Returns null when the bridge isn't placed on disk (installer not run).
58
+ */
59
+ export function startConnectorSupervisor(opts = {}) {
60
+ if (singleton)
61
+ return singleton;
62
+ const bridgeDir = opts.bridgeDir ?? resolveBridgeDir();
63
+ if (!bridgeDir) {
64
+ logger.warn(TAG, 'connector not found (no bundled bridge/ and no ~/.reefclaw/bridge) — supervisor idle');
65
+ return null;
66
+ }
67
+ const indexJs = join(bridgeDir, 'index.js');
68
+ if (!existsSync(indexJs)) {
69
+ logger.warn(TAG, `connector not found at ${indexJs} — supervisor idle`);
70
+ return null;
71
+ }
72
+ const spawnFn = opts.spawnFn ?? spawn;
73
+ let child = null;
74
+ let stopped = false;
75
+ let backoffMs = MIN_BACKOFF_MS;
76
+ let restartTimer = null;
77
+ const launch = () => {
78
+ if (stopped)
79
+ return;
80
+ const startedAt = Date.now();
81
+ // NOT detached: the connector must die with the gateway (a gateway
82
+ // restart resurrects both, and an orphan bridge can never linger).
83
+ child = spawnFn(process.execPath, [indexJs, '--provider', 'gateway', '--log-level', 'info'], {
84
+ cwd: bridgeDir,
85
+ stdio: ['ignore', 'pipe', 'pipe'],
86
+ });
87
+ logger.info(TAG, `connector started (pid ${child.pid})`);
88
+ const forward = (stream, level) => {
89
+ stream?.on('data', (chunk) => {
90
+ for (const line of chunk.toString().split('\n')) {
91
+ if (line.trim())
92
+ logger[level](TAG, `[connector] ${line}`);
93
+ }
94
+ });
95
+ };
96
+ forward(child.stdout, 'info');
97
+ forward(child.stderr, 'warn');
98
+ child.on('exit', (code, signal) => {
99
+ child = null;
100
+ if (stopped)
101
+ return;
102
+ const aliveMs = Date.now() - startedAt;
103
+ if (aliveMs >= STABLE_RESET_MS)
104
+ backoffMs = MIN_BACKOFF_MS;
105
+ // Exit is EXPECTED pre-token (the connector exits until the connect
106
+ // message is saved) — restart with backoff, exactly like systemd's
107
+ // Restart=always did.
108
+ logger.info(TAG, `connector exited (code=${code ?? 'null'} signal=${signal ?? 'null'} after ${Math.round(aliveMs / 1000)}s) — restarting in ${backoffMs / 1000}s`);
109
+ restartTimer = setTimeout(launch, backoffMs);
110
+ restartTimer.unref?.();
111
+ backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);
112
+ });
113
+ child.on('error', (err) => {
114
+ logger.error(TAG, `connector spawn failed: ${err.message}`);
115
+ child = null;
116
+ if (stopped)
117
+ return;
118
+ restartTimer = setTimeout(launch, backoffMs);
119
+ restartTimer.unref?.();
120
+ backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);
121
+ });
122
+ };
123
+ launch();
124
+ const handle = {
125
+ stop() {
126
+ stopped = true;
127
+ if (restartTimer)
128
+ clearTimeout(restartTimer);
129
+ if (child) {
130
+ try {
131
+ child.kill('SIGTERM');
132
+ }
133
+ catch {
134
+ /* already gone */
135
+ }
136
+ }
137
+ singleton = null;
138
+ },
139
+ isRunning() {
140
+ return child !== null;
141
+ },
142
+ };
143
+ singleton = handle;
144
+ return handle;
145
+ }
146
+ /** Test-only: reset the module singleton. */
147
+ export function resetConnectorSupervisorForTest() {
148
+ singleton = null;
149
+ }
@@ -0,0 +1,49 @@
1
+ import type { CcxtOrder, CcxtBalance, CcxtPosition, TradingMode } from './types.js';
2
+ import type { PositionMetadata, CloseReason } from './simulator/types.js';
3
+ /** Adapter readiness state machine: INIT_PENDING → READY | DEGRADED | BLOCKED */
4
+ export type AdapterReadiness = 'INIT_PENDING' | 'READY' | 'DEGRADED' | 'BLOCKED';
5
+ /** Options for order submission. */
6
+ export interface OrderOptions {
7
+ clientOrderId?: string;
8
+ reduceOnly?: boolean;
9
+ positionSide?: 'LONG' | 'SHORT' | 'BOTH';
10
+ emergency?: boolean;
11
+ }
12
+ /**
13
+ * Common exchange adapter interface.
14
+ * Implemented by PaperAdapter (wrapping ExchangeSimulator) and LiveAdapter (wrapping BinancePrivateApi).
15
+ *
16
+ * Trade tools call adapter methods instead of directly referencing the simulator.
17
+ * Emergency controls (cancel, close) have NO readiness gate — they ALWAYS work.
18
+ * Only create_order is blocked when readiness !== 'READY'.
19
+ */
20
+ export interface IExchangeAdapter {
21
+ createOrder(symbol: string, side: 'buy' | 'sell', type: 'market' | 'limit', amount: number, price?: number, metadata?: PositionMetadata, options?: OrderOptions): Promise<CcxtOrder>;
22
+ cancelOrder(orderId: string, symbol?: string): Promise<CcxtOrder>;
23
+ cancelAllOrders(symbol?: string): Promise<CcxtOrder[]>;
24
+ /** Close a position at market.
25
+ * @param closeReason Tags the resulting Trade record with who initiated the close.
26
+ * Paper mode persists it on the closing Trade.metadata.closeReason so the agent
27
+ * can see on its next heartbeat that e.g. a stop_watcher auto-closed the trade.
28
+ * Live mode currently ignores this (metadata not tracked in live path). */
29
+ closePosition(symbol: string, closeReason?: CloseReason): Promise<CcxtOrder>;
30
+ getBalance(): Promise<CcxtBalance>;
31
+ getPositions(symbol?: string): Promise<CcxtPosition[]>;
32
+ /**
33
+ * Like getPositions(), but returns `null` (not `[]`) when the underlying
34
+ * exchange fetch FAILED (429 / weight-paced / transient) — i.e. position
35
+ * state is UNKNOWN, not "flat". Decision/destructive paths (flatten, audit,
36
+ * portfolio snapshot, attach_brackets, reconciler) MUST use this and treat
37
+ * `null` as "do not act on a phantom-empty account". getPositions() keeps
38
+ * the `?? []` display contract for the 20+ KPI/display callers
39
+ * (KPI-must-equal-Binance invariant). Paper adapters never fail a fetch, so
40
+ * this is equivalent to getPositions() there.
41
+ */
42
+ getPositionsOrNull(symbol?: string): Promise<CcxtPosition[] | null>;
43
+ getOpenOrders(symbol?: string): Promise<CcxtOrder[]>;
44
+ fetchOrder(orderId: string, symbol?: string): Promise<CcxtOrder | null>;
45
+ getLastPrice(symbol: string): Promise<number | null>;
46
+ readonly readiness: AdapterReadiness;
47
+ readonly mode: TradingMode;
48
+ readonly isLive: boolean;
49
+ }
@@ -0,0 +1,4 @@
1
+ // Exchange adapter interface — common abstraction over ExchangeSimulator (paper) and BinancePrivateApi (live).
2
+ // Both PaperAdapter and LiveAdapter implement this interface.
3
+ // All methods return Promise for uniform call sites — paper adapter wraps sync results in Promise.resolve().
4
+ export {};
package/index.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ interface PluginApi {
2
+ registerTool: (tool: any, opts?: {
3
+ names?: string[];
4
+ optional?: boolean;
5
+ }) => void;
6
+ config?: any;
7
+ [key: string]: any;
8
+ }
9
+ declare const paperTradingPlugin: {
10
+ id: string;
11
+ name: string;
12
+ description: string;
13
+ configSchema: {
14
+ type: "object";
15
+ properties: {
16
+ startingBalance: {
17
+ type: "number";
18
+ default: number;
19
+ description: string;
20
+ };
21
+ symbol: {
22
+ type: "string";
23
+ default: string;
24
+ description: string;
25
+ };
26
+ };
27
+ };
28
+ register(api: PluginApi): void;
29
+ };
30
+ export default paperTradingPlugin;