@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,18 @@
1
+ import type { CcxtTicker, CcxtOHLCV } from '../types.js';
2
+ import type { OrderBookDepth } from '../simulator/types.js';
3
+ export declare class BinancePublicApi {
4
+ private exchange;
5
+ constructor();
6
+ /** Fetch raw CCXT ticker (includes funding rate, OI, info). Returns null on error. */
7
+ fetchTickerRaw(symbol: string): Promise<Record<string, any> | null>;
8
+ /** Fetch current ticker for a symbol. Returns null on error. */
9
+ fetchTicker(symbol: string): Promise<CcxtTicker | null>;
10
+ /** Fetch funding rate for a perpetual futures symbol. Returns null on error. */
11
+ fetchFundingRate(symbol: string): Promise<Record<string, any> | null>;
12
+ /** Fetch open interest for a perpetual futures symbol. Returns null on error. */
13
+ fetchOpenInterest(symbol: string): Promise<Record<string, any> | null>;
14
+ /** Fetch order book depth. Returns null on error. */
15
+ fetchOrderBook(symbol: string, limit?: number): Promise<OrderBookDepth | null>;
16
+ /** Fetch OHLCV candles. Returns null on error. */
17
+ fetchOHLCV(symbol: string, timeframe?: string, limit?: number): Promise<CcxtOHLCV[] | null>;
18
+ }
@@ -0,0 +1,147 @@
1
+ // Thin wrapper around CCXT for Binance public API endpoints.
2
+ // No API keys required — only uses public market data.
3
+ import { createRequire } from 'node:module';
4
+ import { logger } from '../logger.js';
5
+ import { assertNotBanned, noteBinanceError, noteSuccess } from './binance-ban-gate.js';
6
+ const TAG = 'binance-public';
7
+ // Load ccxt via CJS require — OpenClaw's ESM loader gives wrong module shape
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ let ccxtCjs;
10
+ try {
11
+ const _require = createRequire(import.meta.url);
12
+ ccxtCjs = _require('ccxt');
13
+ }
14
+ catch {
15
+ // Fallback: try global require (available in OpenClaw's loader context)
16
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
17
+ ccxtCjs = require('ccxt');
18
+ }
19
+ export class BinancePublicApi {
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ exchange;
22
+ constructor() {
23
+ const BinanceClass = ccxtCjs.binance ?? ccxtCjs.default?.binance;
24
+ if (!BinanceClass) {
25
+ throw new Error('CCXT binance class not found — check ccxt version');
26
+ }
27
+ this.exchange = new BinanceClass({
28
+ enableRateLimit: true,
29
+ });
30
+ logger.info(TAG, 'Binance public API initialized (no auth)');
31
+ }
32
+ /** Fetch raw CCXT ticker (includes funding rate, OI, info). Returns null on error. */
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ async fetchTickerRaw(symbol) {
35
+ try {
36
+ assertNotBanned('fetchTickerRaw');
37
+ const r = await this.exchange.fetchTicker(symbol);
38
+ noteSuccess();
39
+ return r;
40
+ }
41
+ catch (err) {
42
+ noteBinanceError(err);
43
+ logger.error(TAG, `fetchTickerRaw(${symbol}) failed: ${err instanceof Error ? err.message : String(err)}`);
44
+ return null;
45
+ }
46
+ }
47
+ /** Fetch current ticker for a symbol. Returns null on error. */
48
+ async fetchTicker(symbol) {
49
+ try {
50
+ assertNotBanned('fetchTicker');
51
+ const raw = await this.exchange.fetchTicker(symbol);
52
+ noteSuccess();
53
+ return {
54
+ symbol: raw.symbol,
55
+ last: raw.last ?? 0,
56
+ bid: raw.bid ?? 0,
57
+ ask: raw.ask ?? 0,
58
+ baseVolume: raw.baseVolume ?? 0,
59
+ quoteVolume: raw.quoteVolume ?? 0,
60
+ change: raw.change ?? 0,
61
+ percentage: raw.percentage ?? 0,
62
+ timestamp: raw.timestamp ?? Date.now(),
63
+ datetime: raw.datetime ?? new Date().toISOString(),
64
+ };
65
+ }
66
+ catch (err) {
67
+ noteBinanceError(err);
68
+ logger.error(TAG, `fetchTicker(${symbol}) failed: ${err instanceof Error ? err.message : String(err)}`);
69
+ return null;
70
+ }
71
+ }
72
+ /** Fetch funding rate for a perpetual futures symbol. Returns null on error. */
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ async fetchFundingRate(symbol) {
75
+ try {
76
+ assertNotBanned('fetchFundingRate');
77
+ const r = await this.exchange.fetchFundingRate(symbol);
78
+ noteSuccess();
79
+ return r;
80
+ }
81
+ catch (err) {
82
+ noteBinanceError(err);
83
+ logger.error(TAG, `fetchFundingRate(${symbol}) failed: ${err instanceof Error ? err.message : String(err)}`);
84
+ return null;
85
+ }
86
+ }
87
+ /** Fetch open interest for a perpetual futures symbol. Returns null on error. */
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ async fetchOpenInterest(symbol) {
90
+ try {
91
+ assertNotBanned('fetchOpenInterest');
92
+ const r = await this.exchange.fetchOpenInterest(symbol);
93
+ noteSuccess();
94
+ return r;
95
+ }
96
+ catch (err) {
97
+ noteBinanceError(err);
98
+ logger.error(TAG, `fetchOpenInterest(${symbol}) failed: ${err instanceof Error ? err.message : String(err)}`);
99
+ return null;
100
+ }
101
+ }
102
+ /** Fetch order book depth. Returns null on error. */
103
+ async fetchOrderBook(symbol, limit = 20) {
104
+ try {
105
+ assertNotBanned('fetchOrderBook');
106
+ const raw = await this.exchange.fetchOrderBook(symbol, limit);
107
+ noteSuccess();
108
+ return {
109
+ bids: (raw.bids ?? []).map((l) => [l[0], l[1]]),
110
+ asks: (raw.asks ?? []).map((l) => [l[0], l[1]]),
111
+ timestamp: raw.timestamp ?? Date.now(),
112
+ };
113
+ }
114
+ catch (err) {
115
+ noteBinanceError(err);
116
+ logger.error(TAG, `fetchOrderBook(${symbol}) failed: ${err instanceof Error ? err.message : String(err)}`);
117
+ return null;
118
+ }
119
+ }
120
+ /** Fetch OHLCV candles. Returns null on error. */
121
+ async fetchOHLCV(symbol, timeframe = '1h', limit = 100) {
122
+ try {
123
+ assertNotBanned('fetchOHLCV');
124
+ const raw = await this.exchange.fetchOHLCV(symbol, timeframe, undefined, limit);
125
+ noteSuccess();
126
+ // Validate each candle has 6 numeric values
127
+ const valid = [];
128
+ for (const candle of raw) {
129
+ if (candle.length >= 6 &&
130
+ typeof candle[0] === 'number' &&
131
+ typeof candle[1] === 'number' &&
132
+ typeof candle[2] === 'number' &&
133
+ typeof candle[3] === 'number' &&
134
+ typeof candle[4] === 'number' &&
135
+ typeof candle[5] === 'number') {
136
+ valid.push(candle);
137
+ }
138
+ }
139
+ return valid;
140
+ }
141
+ catch (err) {
142
+ noteBinanceError(err);
143
+ logger.error(TAG, `fetchOHLCV(${symbol}, ${timeframe}) failed: ${err instanceof Error ? err.message : String(err)}`);
144
+ return null;
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,55 @@
1
+ /** Central operational-gate values. Each gate is validated against its own enum
2
+ * PLUGIN-SIDE (§5b: central can never express a value the plugin doesn't
3
+ * already consider safe); an invalid field is dropped — field-level fallback,
4
+ * never whole-payload rejection. Gates are added one at a time
5
+ * (TOOL_DISTRIBUTION_ARCHITECTURE.md §11 step 3): `exitGate` (slice 2) →
6
+ * `positionReviewMode` (slice 3, the Position Decision Journal
7
+ * heartbeat-mandate + superset gate, file key `positionReview.mode`). Both
8
+ * ride the same four-stage `off → shadow → observe → enforce` ladder. */
9
+ export interface AgentGates {
10
+ exitGate?: 'off' | 'shadow' | 'observe' | 'enforce';
11
+ positionReviewMode?: 'off' | 'shadow' | 'observe' | 'enforce';
12
+ }
13
+ /** The slice of /api/internal/config the plugin consumes today. `limits`
14
+ * arrives in the payload but is deliberately not modeled yet. */
15
+ export interface AgentConfig {
16
+ version: number;
17
+ tools: {
18
+ disabled: string[];
19
+ };
20
+ gates: AgentGates;
21
+ }
22
+ /** Hardcoded safe default — identical to pre-central behavior (nothing
23
+ * disabled, no central gate values → local file / defaults rule). */
24
+ export declare function defaultAgentConfig(): AgentConfig;
25
+ /**
26
+ * Validate an untrusted payload (network response OR cache file) into an
27
+ * AgentConfig. Returns null when the payload as a whole is malformed —
28
+ * partial application is never allowed (§5b: central cannot express a value
29
+ * the plugin doesn't already consider safe).
30
+ */
31
+ export declare function validateAgentConfig(raw: unknown): AgentConfig | null;
32
+ /** Version-monotonic acceptance (basic rollback/replay protection): a fetched
33
+ * config older than what we already applied is rejected. Equal versions are
34
+ * acceptable (idempotent re-apply is a no-op for the gate). */
35
+ export declare function isAcceptableVersion(fetched: AgentConfig, current: AgentConfig | null): boolean;
36
+ export interface FetchAgentConfigOptions {
37
+ apiBaseUrl: string;
38
+ token: string;
39
+ /** Injectable for unit tests + observability shims. */
40
+ fetchImpl?: typeof fetch;
41
+ timeoutMs?: number;
42
+ }
43
+ /**
44
+ * Fetch the central config. Returns a VALIDATED AgentConfig, or null on any
45
+ * failure (network, non-200, non-JSON, invalid shape). Never throws.
46
+ */
47
+ export declare function fetchAgentConfig(opts: FetchAgentConfigOptions): Promise<AgentConfig | null>;
48
+ export declare function defaultCachePath(): string;
49
+ /** Read + validate the cached config. Missing/corrupt/invalid → null (the
50
+ * caller falls through to safe defaults). Never throws. */
51
+ export declare function readCachedConfig(path?: string): AgentConfig | null;
52
+ /** Atomically persist the last-known-good config (tmp + rename, 0o600 — the
53
+ * plugin-config-io convention). Failures are logged, never thrown: cache
54
+ * persistence is an optimization, not a correctness requirement. */
55
+ export declare function writeCachedConfig(config: AgentConfig, path?: string): void;
@@ -0,0 +1,145 @@
1
+ // Central per-user agent config — fetch + validate + last-known-good cache.
2
+ //
3
+ // Slice 1 of docs/TOOL_DISTRIBUTION_ARCHITECTURE.md §5: the plugin PULLS
4
+ // `GET /api/internal/config` (webapp, rc_ token → tenant) and applies the
5
+ // tool-enablement OFF-list through the ToolGate. The critical contract (§5b):
6
+ // only a successful, authenticated, VALIDATED, version-acceptable response
7
+ // ever changes behavior. A network error, 5xx, 401, garbage payload, or
8
+ // corrupt cache file can NEVER change what the agent does — the caller keeps
9
+ // the last-known-good config, or the hardcoded safe default (nothing
10
+ // disabled).
11
+ //
12
+ // The cache file lives next to plugin-config.json (`~/.reefclaw/
13
+ // agent-config-cache.json`) and follows the same atomic write convention
14
+ // (tmp + rename, mode 0o600 — see plugin-config-io.ts). It is validated on
15
+ // read with the same validator as the network path: a poisoned cache is as
16
+ // untrusted as a poisoned response.
17
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
18
+ import { dirname, join } from 'node:path';
19
+ import { homedir } from 'node:os';
20
+ import { logger, formatError } from '../logger.js';
21
+ const TAG = 'agent-config';
22
+ /** Shared four-stage ladder for the gates migrated so far. Kept as one set
23
+ * because exitGate + positionReviewMode use identical values; a future gate
24
+ * with a different enum gets its own constant. */
25
+ const MODE_LADDER_VALUES = new Set(['off', 'shadow', 'observe', 'enforce']);
26
+ /** Hardcoded safe default — identical to pre-central behavior (nothing
27
+ * disabled, no central gate values → local file / defaults rule). */
28
+ export function defaultAgentConfig() {
29
+ return { version: 0, tools: { disabled: [] }, gates: {} };
30
+ }
31
+ /** Tool names must look like tool names. Anything else is dropped — central
32
+ * can never smuggle a weird value into the gate's set. */
33
+ const TOOL_NAME_RE = /^[a-z0-9_]{1,64}$/;
34
+ /**
35
+ * Validate an untrusted payload (network response OR cache file) into an
36
+ * AgentConfig. Returns null when the payload as a whole is malformed —
37
+ * partial application is never allowed (§5b: central cannot express a value
38
+ * the plugin doesn't already consider safe).
39
+ */
40
+ export function validateAgentConfig(raw) {
41
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
42
+ return null;
43
+ const obj = raw;
44
+ const version = obj.version;
45
+ if (typeof version !== 'number' || !Number.isFinite(version) || version < 0)
46
+ return null;
47
+ const tools = obj.tools;
48
+ if (!tools || typeof tools !== 'object' || Array.isArray(tools))
49
+ return null;
50
+ const disabledRaw = tools.disabled;
51
+ if (!Array.isArray(disabledRaw))
52
+ return null;
53
+ const disabled = disabledRaw.filter((v) => typeof v === 'string' && TOOL_NAME_RE.test(v));
54
+ return { version, tools: { disabled }, gates: validateGates(obj.gates) };
55
+ }
56
+ /** Per-field gate validation (§5b): a structurally-absent/garbage `gates`
57
+ * becomes `{}` (no central values → local config rules); each known gate is
58
+ * kept only when its value is in that gate's enum. Unknown keys are dropped. */
59
+ function validateGates(raw) {
60
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
61
+ return {};
62
+ const obj = raw;
63
+ const gates = {};
64
+ const exitGate = obj.exitGate;
65
+ if (typeof exitGate === 'string' && MODE_LADDER_VALUES.has(exitGate)) {
66
+ gates.exitGate = exitGate;
67
+ }
68
+ const positionReviewMode = obj.positionReviewMode;
69
+ if (typeof positionReviewMode === 'string' && MODE_LADDER_VALUES.has(positionReviewMode)) {
70
+ gates.positionReviewMode = positionReviewMode;
71
+ }
72
+ return gates;
73
+ }
74
+ /** Version-monotonic acceptance (basic rollback/replay protection): a fetched
75
+ * config older than what we already applied is rejected. Equal versions are
76
+ * acceptable (idempotent re-apply is a no-op for the gate). */
77
+ export function isAcceptableVersion(fetched, current) {
78
+ if (!current)
79
+ return true;
80
+ return fetched.version >= current.version;
81
+ }
82
+ /**
83
+ * Fetch the central config. Returns a VALIDATED AgentConfig, or null on any
84
+ * failure (network, non-200, non-JSON, invalid shape). Never throws.
85
+ */
86
+ export async function fetchAgentConfig(opts) {
87
+ const { apiBaseUrl, token, fetchImpl = fetch, timeoutMs = 10_000 } = opts;
88
+ const url = `${apiBaseUrl.replace(/\/+$/, '')}/api/internal/config`;
89
+ try {
90
+ const res = await fetchImpl(url, {
91
+ headers: { authorization: `Bearer ${token}` },
92
+ signal: AbortSignal.timeout(timeoutMs),
93
+ });
94
+ if (!res.ok) {
95
+ logger.debug(TAG, `config fetch non-200: ${res.status}`);
96
+ return null;
97
+ }
98
+ const body = (await res.json());
99
+ const validated = validateAgentConfig(body);
100
+ if (!validated)
101
+ logger.warn(TAG, 'config fetch returned an invalid payload — ignored');
102
+ return validated;
103
+ }
104
+ catch (err) {
105
+ logger.debug(TAG, `config fetch failed: ${formatError(err)}`);
106
+ return null;
107
+ }
108
+ }
109
+ // ---- last-known-good cache file ---------------------------------------------
110
+ export function defaultCachePath() {
111
+ return join(homedir(), '.reefclaw', 'agent-config-cache.json');
112
+ }
113
+ /** Read + validate the cached config. Missing/corrupt/invalid → null (the
114
+ * caller falls through to safe defaults). Never throws. */
115
+ export function readCachedConfig(path = defaultCachePath()) {
116
+ try {
117
+ if (!existsSync(path))
118
+ return null;
119
+ const raw = readFileSync(path, 'utf-8');
120
+ return validateAgentConfig(JSON.parse(raw));
121
+ }
122
+ catch (err) {
123
+ logger.warn(TAG, `config cache unreadable (${formatError(err)}) — using safe defaults`);
124
+ return null;
125
+ }
126
+ }
127
+ /** Atomically persist the last-known-good config (tmp + rename, 0o600 — the
128
+ * plugin-config-io convention). Failures are logged, never thrown: cache
129
+ * persistence is an optimization, not a correctness requirement. */
130
+ export function writeCachedConfig(config, path = defaultCachePath()) {
131
+ try {
132
+ const dir = dirname(path);
133
+ if (!existsSync(dir))
134
+ mkdirSync(dir, { recursive: true });
135
+ const tmpPath = `${path}.tmp`;
136
+ writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', {
137
+ encoding: 'utf-8',
138
+ mode: 0o600,
139
+ });
140
+ renameSync(tmpPath, path);
141
+ }
142
+ catch (err) {
143
+ logger.warn(TAG, `config cache write failed: ${formatError(err)}`);
144
+ }
145
+ }
@@ -0,0 +1,25 @@
1
+ import { type AgentConfig, type AgentGates } from './agent-config-client.js';
2
+ import type { ToolGate } from './tool-gate.js';
3
+ export declare function resolvePollIntervalMs(): number;
4
+ export interface AgentConfigPollerOptions {
5
+ gate: ToolGate;
6
+ /** Central operational-gates consumer (slice 2 — exitGate). Optional so the
7
+ * tool-allowlist path stands alone; wired from index.ts with the module
8
+ * singleton `gateStore`. */
9
+ gateStore?: {
10
+ apply(gates: AgentGates): void;
11
+ };
12
+ apiBaseUrl: string;
13
+ token: string;
14
+ fetchImpl?: typeof fetch;
15
+ intervalMs?: number;
16
+ cachePath?: string;
17
+ }
18
+ export interface AgentConfigPollerHandle {
19
+ stop(): void;
20
+ /** Exposed for tests/diagnostics — the currently applied config. */
21
+ getCurrent(): AgentConfig;
22
+ }
23
+ /** Test-only — allows the singleton guard to be reset between unit tests. */
24
+ export declare function __resetPollerSingleton(): void;
25
+ export declare function startAgentConfigPoller(opts: AgentConfigPollerOptions): AgentConfigPollerHandle;
@@ -0,0 +1,100 @@
1
+ // Agent-config poller — boots the ToolGate from central config and keeps it
2
+ // fresh (docs/TOOL_DISTRIBUTION_ARCHITECTURE.md §5b).
3
+ //
4
+ // Boot: fetch → valid + version-acceptable → apply + write cache
5
+ // fetch fails → last-known-good cache file → apply
6
+ // nothing → hardcoded safe default (nothing disabled)
7
+ // Runtime: re-poll every RC_AGENT_CONFIG_POLL_MS (default 60s). Every failed
8
+ // poll is a keep-last-known-good no-op. Version-lower responses are
9
+ // rejected (rollback guard). The interval is unref()'d so it never
10
+ // holds the process open at shutdown.
11
+ //
12
+ // No token → the poller does not start at all: the gate stays empty (all
13
+ // tools on), byte-identical to pre-feature behavior. A module-level singleton
14
+ // guard mirrors `pluginInitialised` in index.ts — OpenClaw calls register()
15
+ // multiple times per process and we must never spawn a second timer.
16
+ import { defaultAgentConfig, fetchAgentConfig, isAcceptableVersion, readCachedConfig, writeCachedConfig, } from './agent-config-client.js';
17
+ import { logger } from '../logger.js';
18
+ const TAG = 'agent-config';
19
+ const DEFAULT_POLL_MS = 60_000;
20
+ const MIN_POLL_MS = 5_000;
21
+ export function resolvePollIntervalMs() {
22
+ const raw = Number(process.env.RC_AGENT_CONFIG_POLL_MS);
23
+ if (!Number.isFinite(raw) || raw <= 0)
24
+ return DEFAULT_POLL_MS;
25
+ return Math.max(MIN_POLL_MS, raw);
26
+ }
27
+ let pollerStarted = false;
28
+ /** Test-only — allows the singleton guard to be reset between unit tests. */
29
+ export function __resetPollerSingleton() {
30
+ pollerStarted = false;
31
+ }
32
+ export function startAgentConfigPoller(opts) {
33
+ const noop = {
34
+ stop: () => { },
35
+ getCurrent: () => defaultAgentConfig(),
36
+ };
37
+ if (pollerStarted) {
38
+ logger.warn(TAG, 'poller already started — ignoring duplicate start');
39
+ return noop;
40
+ }
41
+ if (!opts.token || !opts.token.trim()) {
42
+ logger.info(TAG, 'no connection token — central tool config disabled, all tools enabled (safe default)');
43
+ return noop;
44
+ }
45
+ pollerStarted = true;
46
+ const intervalMs = opts.intervalMs ?? resolvePollIntervalMs();
47
+ let current = defaultAgentConfig();
48
+ let loggedFetchFailure = false;
49
+ const applyIfAcceptable = (fetched, source) => {
50
+ if (!isAcceptableVersion(fetched, current)) {
51
+ logger.warn(TAG, `rejected ${source} config v${fetched.version} < applied v${current.version} (rollback guard)`);
52
+ return false;
53
+ }
54
+ current = fetched;
55
+ opts.gate.apply(fetched);
56
+ opts.gateStore?.apply(fetched.gates);
57
+ return true;
58
+ };
59
+ const poll = async (isBoot) => {
60
+ const fetched = await fetchAgentConfig({
61
+ apiBaseUrl: opts.apiBaseUrl,
62
+ token: opts.token,
63
+ fetchImpl: opts.fetchImpl,
64
+ });
65
+ if (fetched) {
66
+ loggedFetchFailure = false;
67
+ if (applyIfAcceptable(fetched, 'network')) {
68
+ writeCachedConfig(fetched, opts.cachePath);
69
+ }
70
+ return;
71
+ }
72
+ if (isBoot) {
73
+ // Network unavailable at boot — fall back to the last-known-good cache.
74
+ const cached = readCachedConfig(opts.cachePath);
75
+ if (cached) {
76
+ logger.info(TAG, `boot fetch failed — applying last-known-good cache v${cached.version}`);
77
+ applyIfAcceptable(cached, 'cache');
78
+ }
79
+ else {
80
+ logger.info(TAG, 'boot fetch failed, no cache — safe defaults (all tools enabled)');
81
+ }
82
+ return;
83
+ }
84
+ // Runtime failure: keep last-known-good silently (log once per outage).
85
+ if (!loggedFetchFailure) {
86
+ loggedFetchFailure = true;
87
+ logger.warn(TAG, `config poll failed — keeping last-known-good v${current.version}`);
88
+ }
89
+ };
90
+ void poll(true);
91
+ const timer = setInterval(() => {
92
+ void poll(false);
93
+ }, intervalMs);
94
+ timer.unref();
95
+ logger.info(TAG, `poller started (interval ${Math.round(intervalMs / 1000)}s)`);
96
+ return {
97
+ stop: () => clearInterval(timer),
98
+ getCurrent: () => current,
99
+ };
100
+ }
@@ -0,0 +1,22 @@
1
+ import { type PluginConfigFile } from './plugin-config-io.js';
2
+ export type BracketMode = 'off' | 'observe' | 'enforce';
3
+ /** Read bracket mode from a config object. Invalid values fall back to 'off'. */
4
+ export declare function getBracketMode(config?: PluginConfigFile, remoteOverride?: BracketMode): BracketMode;
5
+ /** Convenience: load from disk and return the effective mode. */
6
+ export declare function loadBracketMode(): BracketMode;
7
+ /** Is the feature enabled at all (observe or enforce)? */
8
+ export declare function bracketsEnabled(mode: BracketMode): boolean;
9
+ /** Should the stop-watcher still run alongside brackets? Only 'enforce' turns it off for live. */
10
+ export declare function stopWatcherActiveInLive(mode: BracketMode): boolean;
11
+ /** Per-side requirement flags. Default both true — operator can untick
12
+ * individually from the Trading Parameters panel to accept the documented
13
+ * risk. Read alongside the bracket mode so a single config read covers
14
+ * everything the pre-trade gate needs. */
15
+ export interface BracketRequirements {
16
+ requireStopLoss: boolean;
17
+ requireTakeProfit: boolean;
18
+ }
19
+ export declare function getBracketRequirements(config?: PluginConfigFile): BracketRequirements;
20
+ /** Convenience loader. Returns defaults on a missing/corrupt file — same
21
+ * policy as loadBracketMode: don't gate trading on a corrupt flag read. */
22
+ export declare function loadBracketRequirements(): BracketRequirements;
@@ -0,0 +1,58 @@
1
+ // Feature flag reader for the bracket-orders system.
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 `brackets.mode` field.
8
+ // 3. Default: 'off' (legacy stop-watcher remains authoritative).
9
+ import { readPluginConfig } from './plugin-config-io.js';
10
+ const VALID = new Set(['off', 'observe', 'enforce']);
11
+ /** Read bracket mode from a config object. Invalid values fall back to 'off'. */
12
+ export function getBracketMode(config, remoteOverride) {
13
+ if (remoteOverride && VALID.has(remoteOverride))
14
+ return remoteOverride;
15
+ const raw = config?.brackets?.mode;
16
+ if (typeof raw === 'string' && VALID.has(raw)) {
17
+ return raw;
18
+ }
19
+ return 'off';
20
+ }
21
+ /** Convenience: load from disk and return the effective mode. */
22
+ export function loadBracketMode() {
23
+ try {
24
+ return getBracketMode(readPluginConfig());
25
+ }
26
+ catch {
27
+ // If plugin-config.json is malformed, we deliberately default to 'off'
28
+ // rather than throw — other subsystems will alert; trading should not
29
+ // be gated on bracket-mode reading a corrupt file.
30
+ return 'off';
31
+ }
32
+ }
33
+ /** Is the feature enabled at all (observe or enforce)? */
34
+ export function bracketsEnabled(mode) {
35
+ return mode !== 'off';
36
+ }
37
+ /** Should the stop-watcher still run alongside brackets? Only 'enforce' turns it off for live. */
38
+ export function stopWatcherActiveInLive(mode) {
39
+ return mode !== 'enforce';
40
+ }
41
+ export function getBracketRequirements(config) {
42
+ const b = config?.brackets;
43
+ return {
44
+ // Explicit `false` turns off; anything else (undefined, true, junk) stays on.
45
+ requireStopLoss: b?.requireStopLoss !== false,
46
+ requireTakeProfit: b?.requireTakeProfit !== false,
47
+ };
48
+ }
49
+ /** Convenience loader. Returns defaults on a missing/corrupt file — same
50
+ * policy as loadBracketMode: don't gate trading on a corrupt flag read. */
51
+ export function loadBracketRequirements() {
52
+ try {
53
+ return getBracketRequirements(readPluginConfig());
54
+ }
55
+ catch {
56
+ return { requireStopLoss: true, requireTakeProfit: true };
57
+ }
58
+ }
@@ -0,0 +1,18 @@
1
+ import type { AgentGates } from './agent-config-client.js';
2
+ declare class GateStore {
3
+ private gates;
4
+ /** Apply a validated gates object (poller path). Logs only on change. */
5
+ apply(gates: AgentGates): void;
6
+ /** The central exitGate mode, or null when central has no value (or the
7
+ * kill-switch is on) — null tells the reader to fall back to the file. */
8
+ getExitGate(): AgentGates['exitGate'] | null;
9
+ /** The central positionReview.mode, or null when central has no value (or the
10
+ * kill-switch is on) — null tells the reader to fall back to the file. */
11
+ getPositionReviewMode(): AgentGates['positionReviewMode'] | null;
12
+ /** Test-only. */
13
+ __reset(): void;
14
+ }
15
+ /** Module singleton — one store per plugin process, shared by the poller
16
+ * (writer) and the config readers (consumers). */
17
+ export declare const gateStore: GateStore;
18
+ export {};
@@ -0,0 +1,61 @@
1
+ // GateStore — the plugin-side holder for centrally-delivered operational-gate
2
+ // values (TOOL_DISTRIBUTION_ARCHITECTURE.md §11 step 3; slice 2 = exitGate,
3
+ // slice 3 = positionReviewMode).
4
+ //
5
+ // The agent-config poller applies each validated config's `gates` here; the
6
+ // per-feature config readers (position-review-config.ts loadExitGateMode +
7
+ // loadPositionReviewMode) consult the store FIRST and fall back to the local
8
+ // plugin-config.json when no central value is present:
9
+ //
10
+ // central (this store) → ~/.reefclaw/plugin-config.json → hardcoded default
11
+ //
12
+ // Values in the store have already passed the plugin-side enum validation in
13
+ // agent-config-client.ts (validateGates) — central can never deliver a value
14
+ // outside a gate's enum (§5b). A config-server outage never lands here either:
15
+ // the poller keeps last-known-good, and with no applied config the store is
16
+ // empty → the local file rules, byte-identical to pre-central behavior.
17
+ //
18
+ // Kill-switch: RC_CENTRAL_GATES=off makes the store report no central values
19
+ // (local file rules again) without a redeploy — same convention as
20
+ // RC_TOOL_GATE for the tool allowlist.
21
+ import { logger } from '../logger.js';
22
+ const TAG = 'gate-store';
23
+ function centralGatesEnabled() {
24
+ return (process.env.RC_CENTRAL_GATES ?? '').toLowerCase() !== 'off';
25
+ }
26
+ const UNSET = '(unset — local config rules)';
27
+ class GateStore {
28
+ gates = {};
29
+ /** Apply a validated gates object (poller path). Logs only on change. */
30
+ apply(gates) {
31
+ const next = gates ?? {};
32
+ const changed = next.exitGate !== this.gates.exitGate ||
33
+ next.positionReviewMode !== this.gates.positionReviewMode;
34
+ this.gates = { ...next };
35
+ if (changed) {
36
+ logger.info(TAG, `applied central gates: exitGate=${next.exitGate ?? UNSET} ` +
37
+ `positionReviewMode=${next.positionReviewMode ?? UNSET}`);
38
+ }
39
+ }
40
+ /** The central exitGate mode, or null when central has no value (or the
41
+ * kill-switch is on) — null tells the reader to fall back to the file. */
42
+ getExitGate() {
43
+ if (!centralGatesEnabled())
44
+ return null;
45
+ return this.gates.exitGate ?? null;
46
+ }
47
+ /** The central positionReview.mode, or null when central has no value (or the
48
+ * kill-switch is on) — null tells the reader to fall back to the file. */
49
+ getPositionReviewMode() {
50
+ if (!centralGatesEnabled())
51
+ return null;
52
+ return this.gates.positionReviewMode ?? null;
53
+ }
54
+ /** Test-only. */
55
+ __reset() {
56
+ this.gates = {};
57
+ }
58
+ }
59
+ /** Module singleton — one store per plugin process, shared by the poller
60
+ * (writer) and the config readers (consumers). */
61
+ export const gateStore = new GateStore();