@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,112 @@
1
+ // Wires SIGTERM / SIGINT to the ShutdownCoordinator.
2
+ //
3
+ // Lives in its own file (separate from index.ts) so the compiled dist/index.js
4
+ // — which reads env vars for the ingest-token / user-id — does not also contain
5
+ // the network-send call site. OpenClaw's plugin-install heuristic flags the
6
+ // combination as possible credential harvesting (false positive, but worth
7
+ // avoiding by keeping each compiled file single-purpose).
8
+ //
9
+ // All env-var-bearing context is passed in by the caller via the typed
10
+ // argument shape, NOT read from process.env here.
11
+ import { LiveAdapter } from '../live/live-adapter.js';
12
+ import { logger, formatError } from '../logger.js';
13
+ import { initiateShutdown } from './shutdown-coordinator.js';
14
+ const TAG = 'shutdown';
15
+ /** The phase-2 drain body. Exported for direct unit-testing of the teardown
16
+ * ordering + the StateManager flush wiring (the signal handler itself can't
17
+ * be tested without process.exit). Every step is individually guarded so one
18
+ * failing teardown never aborts the rest. */
19
+ export async function performShutdownDrain(deps) {
20
+ // Stop the approval-mode listener BEFORE the adapter shuts down.
21
+ // The listener's tick may call createOrderTool which needs the
22
+ // adapter; once stop() resolves, no new tick will fire.
23
+ if (deps.proposalListener) {
24
+ try {
25
+ await deps.proposalListener.stop();
26
+ }
27
+ catch (err) {
28
+ logger.warn(TAG, `proposalListener stop failed: ${formatError(err)}`);
29
+ }
30
+ }
31
+ // Flush any pending audit-trail POSTs.
32
+ if (deps.tradeStore) {
33
+ try {
34
+ await deps.tradeStore.drain();
35
+ }
36
+ catch (err) {
37
+ logger.warn(TAG, `tradeStore drain failed: ${formatError(err)}`);
38
+ }
39
+ }
40
+ // Stop background loops + close WS + DELETE listenKey.
41
+ const live = deps.runtime.adapter;
42
+ if (live instanceof LiveAdapter) {
43
+ try {
44
+ live.shutdown();
45
+ }
46
+ catch (err) {
47
+ logger.warn(TAG, `LiveAdapter.shutdown failed: ${formatError(err)}`);
48
+ }
49
+ }
50
+ // Stop the watcher last so it doesn't fire a stop after the
51
+ // adapter is gone.
52
+ const watcher = deps.runtime.stopWatcher;
53
+ if (watcher) {
54
+ try {
55
+ watcher.stop();
56
+ }
57
+ catch (err) {
58
+ logger.warn(TAG, `stopWatcher.stop failed: ${formatError(err)}`);
59
+ }
60
+ }
61
+ // Stop the paper market feed so its interval doesn't outlive the process.
62
+ const marketFeed = deps.runtime.marketFeed;
63
+ if (marketFeed) {
64
+ try {
65
+ marketFeed.stop();
66
+ }
67
+ catch (err) {
68
+ logger.warn(TAG, `marketFeed.stop failed: ${formatError(err)}`);
69
+ }
70
+ }
71
+ // Flush the paper StateManager AFTER all mutation sources are stopped, so
72
+ // the 1s debounce window doesn't drop the final fills/closes/PnL on restart.
73
+ // flush() clears the timer and awaits the atomic save; completes in ms
74
+ // inside the drain budget. Without this, every restart silently lost up to
75
+ // ~1s of paper state (a just-closed position would resurrect on boot).
76
+ if (deps.stateManager) {
77
+ try {
78
+ await deps.stateManager.flush();
79
+ }
80
+ catch (err) {
81
+ logger.warn(TAG, `stateManager flush failed: ${formatError(err)}`);
82
+ }
83
+ }
84
+ }
85
+ /** Register graceful-shutdown handlers. Idempotent — repeat signals during
86
+ * the drain are logged + ignored. After the coordinator resolves we call
87
+ * process.exit(0) so systemd doesn't escalate to SIGKILL. */
88
+ export function installSignalHandlers(deps) {
89
+ let invoked = false;
90
+ const onSignal = (signal) => {
91
+ if (invoked) {
92
+ logger.info(TAG, `Received ${signal} during shutdown — ignoring`);
93
+ return;
94
+ }
95
+ invoked = true;
96
+ logger.info(TAG, `Received ${signal} — initiating graceful shutdown`);
97
+ void initiateShutdown({
98
+ reason: signal,
99
+ callbacks: {
100
+ onDrainComplete: () => performShutdownDrain(deps),
101
+ },
102
+ }).then(() => {
103
+ logger.info(TAG, 'Shutdown drain complete — exit(0)');
104
+ process.exit(0);
105
+ }).catch((err) => {
106
+ logger.error(TAG, `Shutdown threw: ${formatError(err)} — exit(1)`);
107
+ process.exit(1);
108
+ });
109
+ };
110
+ process.on('SIGTERM', onSignal);
111
+ process.on('SIGINT', onSignal);
112
+ }
@@ -0,0 +1,43 @@
1
+ export declare class ShutdownInProgressError extends Error {
2
+ constructor(opName: string);
3
+ }
4
+ interface InFlightOp {
5
+ name: string;
6
+ startedAt: number;
7
+ }
8
+ /** True once SIGTERM/SIGINT has been received. New risky ops should be refused. */
9
+ export declare function isShuttingDown(): boolean;
10
+ /** Throw ShutdownInProgressError if shutdown has begun. Use as a guard at the
11
+ * top of risky entry points (new bracket attach, modify_stop, new order). */
12
+ export declare function assertNotShuttingDown(opName: string): void;
13
+ /** Register an in-flight operation. The returned function MUST be called when
14
+ * the op ends. Prefer `trackOp` for typical async cases. */
15
+ export declare function registerOp(name: string): () => void;
16
+ /** Convenience: register + auto-deregister around a promise. */
17
+ export declare function trackOp<T>(name: string, fn: () => Promise<T>): Promise<T>;
18
+ /** Snapshot of currently in-flight ops. Mainly for diagnostics + tests. */
19
+ export declare function getInFlight(): InFlightOp[];
20
+ export interface ShutdownCallbacks {
21
+ /** Run after in-flight ops drain (or phase-1 deadline hits). Last chance to
22
+ * flush the trade ingest queue and tear down adapters. Errors are logged
23
+ * + swallowed. */
24
+ onDrainComplete?: () => Promise<void>;
25
+ }
26
+ export interface ShutdownOptions {
27
+ reason: string;
28
+ /** Phase 1 budget — wait for tracked ops to settle. Default 3000 ms. */
29
+ inFlightDeadlineMs?: number;
30
+ /** Phase 2 budget — onDrainComplete callback. Default 2000 ms. */
31
+ drainCallbackDeadlineMs?: number;
32
+ /** Hard wall-clock deadline across both phases. Default 5000 ms. */
33
+ totalDeadlineMs?: number;
34
+ callbacks?: ShutdownCallbacks;
35
+ }
36
+ /** Run the drain sequence. Idempotent — second call returns the first call's
37
+ * promise. Resolves when both phases are complete or the wall-clock deadline
38
+ * is hit (whichever is first). The signal handler should `process.exit` after
39
+ * this resolves. */
40
+ export declare function initiateShutdown(opts: ShutdownOptions): Promise<void>;
41
+ /** Test seam — reset module-level state. Do NOT call from production. */
42
+ export declare function __resetForTesting(): void;
43
+ export {};
@@ -0,0 +1,131 @@
1
+ // Process-wide graceful shutdown coordinator.
2
+ //
3
+ // Two responsibilities:
4
+ // 1. Track in-flight risky operations (bracket attach/modify, new entries)
5
+ // so a SIGTERM-driven shutdown can wait for them before exiting.
6
+ // 2. Block NEW risky operations from starting once shutdown begins, while
7
+ // still allowing risk-reducing ops (closePosition, reduceOnly) through.
8
+ //
9
+ // Why a global module instead of a constructor-injected service:
10
+ // - Many files own the at-risk operations (BracketManager, LiveAdapter).
11
+ // Threading a coordinator through every constructor is mechanical noise
12
+ // and the caller graph is fixed for this process's lifetime.
13
+ // - Process-wide state is a fine match for a singleton — one shutdown event
14
+ // per process, ever.
15
+ // - Tests reset the singleton via `__resetForTesting()`.
16
+ import { logger } from '../logger.js';
17
+ const TAG = 'shutdown-coordinator';
18
+ export class ShutdownInProgressError extends Error {
19
+ constructor(opName) {
20
+ super(`shutting_down: refused to start ${opName}`);
21
+ this.name = 'ShutdownInProgressError';
22
+ }
23
+ }
24
+ let _shuttingDown = false;
25
+ const _inFlight = new Map();
26
+ /** True once SIGTERM/SIGINT has been received. New risky ops should be refused. */
27
+ export function isShuttingDown() {
28
+ return _shuttingDown;
29
+ }
30
+ /** Throw ShutdownInProgressError if shutdown has begun. Use as a guard at the
31
+ * top of risky entry points (new bracket attach, modify_stop, new order). */
32
+ export function assertNotShuttingDown(opName) {
33
+ if (_shuttingDown) {
34
+ throw new ShutdownInProgressError(opName);
35
+ }
36
+ }
37
+ /** Register an in-flight operation. The returned function MUST be called when
38
+ * the op ends. Prefer `trackOp` for typical async cases. */
39
+ export function registerOp(name) {
40
+ const handle = Symbol(name);
41
+ _inFlight.set(handle, { name, startedAt: Date.now() });
42
+ return () => { _inFlight.delete(handle); };
43
+ }
44
+ /** Convenience: register + auto-deregister around a promise. */
45
+ export async function trackOp(name, fn) {
46
+ const done = registerOp(name);
47
+ try {
48
+ return await fn();
49
+ }
50
+ finally {
51
+ done();
52
+ }
53
+ }
54
+ /** Snapshot of currently in-flight ops. Mainly for diagnostics + tests. */
55
+ export function getInFlight() {
56
+ return Array.from(_inFlight.values()).map(o => ({ ...o }));
57
+ }
58
+ /** Run the drain sequence. Idempotent — second call returns the first call's
59
+ * promise. Resolves when both phases are complete or the wall-clock deadline
60
+ * is hit (whichever is first). The signal handler should `process.exit` after
61
+ * this resolves. */
62
+ export function initiateShutdown(opts) {
63
+ if (_shuttingDownPromise)
64
+ return _shuttingDownPromise;
65
+ _shuttingDown = true;
66
+ _shuttingDownPromise = runShutdown(opts);
67
+ return _shuttingDownPromise;
68
+ }
69
+ let _shuttingDownPromise = null;
70
+ async function runShutdown(opts) {
71
+ // Phase-1 default sized for the worst-case bracket retry loop:
72
+ // 3 attempts × ~500ms API + 1s + 2s backoff = ~4.5s. 6s leaves margin.
73
+ // Phase-2 default covers TradeStoreClient.drain (network-bounded) + listen
74
+ // key teardown (one DELETE).
75
+ // Total wall-clock 10s sits well inside systemd's default TimeoutStopSec.
76
+ const inFlightDeadline = opts.inFlightDeadlineMs ?? 6_000;
77
+ const callbackDeadline = opts.drainCallbackDeadlineMs ?? 3_000;
78
+ const totalDeadline = opts.totalDeadlineMs ?? 10_000;
79
+ const t0 = Date.now();
80
+ const remaining = () => Math.max(0, totalDeadline - (Date.now() - t0));
81
+ const opsAtStart = Array.from(_inFlight.values()).map(o => o.name);
82
+ logger.info(TAG, `Shutdown initiated (reason=${opts.reason}). In-flight ops: ${opsAtStart.length}` +
83
+ (opsAtStart.length ? ` [${opsAtStart.join(', ')}]` : ''));
84
+ // Phase 1: wait for in-flight ops to drain.
85
+ const phase1Budget = Math.min(inFlightDeadline, remaining());
86
+ await waitForInFlightDrain(phase1Budget);
87
+ if (_inFlight.size > 0) {
88
+ const stuckOps = Array.from(_inFlight.values()).map(o => o.name);
89
+ logger.warn(TAG, `Phase 1 timed out after ${phase1Budget}ms — ${_inFlight.size} op(s) still in flight: [${stuckOps.join(', ')}]`);
90
+ }
91
+ else if (opsAtStart.length > 0) {
92
+ logger.info(TAG, `Phase 1 complete: in-flight ops drained in ${Date.now() - t0}ms`);
93
+ }
94
+ // Phase 2: callback drain (trade-store + adapter).
95
+ if (opts.callbacks?.onDrainComplete) {
96
+ const phase2Budget = Math.min(callbackDeadline, remaining());
97
+ if (phase2Budget <= 0) {
98
+ logger.warn(TAG, 'Phase 2 skipped — total deadline exhausted by phase 1');
99
+ }
100
+ else {
101
+ try {
102
+ await Promise.race([
103
+ opts.callbacks.onDrainComplete(),
104
+ new Promise(resolve => setTimeout(resolve, phase2Budget)),
105
+ ]);
106
+ logger.info(TAG, `Phase 2 complete (total elapsed ${Date.now() - t0}ms)`);
107
+ }
108
+ catch (err) {
109
+ const msg = err instanceof Error ? err.message : String(err);
110
+ logger.warn(TAG, `Phase 2 callback threw: ${msg}`);
111
+ }
112
+ }
113
+ }
114
+ logger.info(TAG, `Shutdown coordinator finished in ${Date.now() - t0}ms`);
115
+ }
116
+ async function waitForInFlightDrain(budgetMs) {
117
+ if (_inFlight.size === 0)
118
+ return;
119
+ const start = Date.now();
120
+ // Poll-based; in-flight ops settle through their own promise chains and
121
+ // unregister themselves. Cheaper than wiring an emitter just for this.
122
+ while (_inFlight.size > 0 && Date.now() - start < budgetMs) {
123
+ await new Promise(r => setTimeout(r, 50));
124
+ }
125
+ }
126
+ /** Test seam — reset module-level state. Do NOT call from production. */
127
+ export function __resetForTesting() {
128
+ _shuttingDown = false;
129
+ _shuttingDownPromise = null;
130
+ _inFlight.clear();
131
+ }
@@ -0,0 +1,18 @@
1
+ import type { BracketId, BracketRole } from './bracket-types.js';
2
+ /** Generate a fresh 16-char hex bracketId. */
3
+ export declare function generateBracketId(): BracketId;
4
+ /** Build a clientOrderId for a given bracket + role. Emits the CURRENT
5
+ * (`bkt…`) scheme only — never the legacy `rc-` scheme. */
6
+ export declare function buildBracketCid(bracketId: BracketId, role: BracketRole): string;
7
+ /** Parse a clientOrderId. Recognises BOTH the current `bkt…` scheme and
8
+ * the legacy `rc-…` scheme (so existing ledger rows / in-flight legs and
9
+ * the cancelAllOrders bracket-preserve filter keep working through the
10
+ * transition). Returns null for non-bracket orders (so this can be called
11
+ * on every order Binance returns during reconciliation). */
12
+ export declare function parseBracketCid(cid: string): {
13
+ bracketId: BracketId;
14
+ role: BracketRole;
15
+ } | null;
16
+ /** Cheap type-guard for "is this a reefclaw-managed bracket clientOrderId"
17
+ * — true for both the current and legacy schemes. */
18
+ export declare function isBracketCid(cid: string): boolean;
@@ -0,0 +1,81 @@
1
+ // Deterministic clientOrderId scheme for bracket orders.
2
+ //
3
+ // CURRENT format: bkt<16hex><role> (20 chars, well under Binance's 36)
4
+ // - "bkt" fixed prefix (no dashes — does NOT look like a tagged pair)
5
+ // - <16hex> shared bracketId linking entry + SL + TP of the same trade
6
+ // - <role> 'e' (entry), 's' (stop-loss), 't' (take-profit)
7
+ //
8
+ // LEGACY format (still PARSED, never EMITTED): rc-<16hex>-<role>
9
+ //
10
+ // ROOT-CAUSE FIX 2026-05-16. Controlled prod reproduction proved the
11
+ // recurring ~20-45s bracket cancellation is keyed to the `rc-` cid
12
+ // pattern: a byte-identical-shape NON-rc closePosition pair survived
13
+ // 30+ min on the SAME symbol/window where every `rc-<16hex>-{s,t}` pair
14
+ // died in ~42s, `reason=none`, server-side — with the 8-surface raw-ccxt
15
+ // WIDE-CANCEL-TRACE silent, the skill ccxt-less, the agent issuing zero
16
+ // tool/exec calls, and OpenClaw having no native exchange. i.e. an
17
+ // actor with Binance API access, OFF this VPS and outside our software,
18
+ // cancels orders whose clientOrderId matches the `rc-` bracket pattern.
19
+ // Renaming the scheme makes our protective brackets invisible to it —
20
+ // exactly why every `ctp-`/`opguard-`/`mctest-` test order survived.
21
+ //
22
+ // Backward compat is load-bearing: in-flight brackets + the persisted
23
+ // bracket-ledger.json carry LEGACY `rc-*` cids. parseBracketCid /
24
+ // isBracketCid MUST keep recognising them so the reconciler still tracks
25
+ // them and — critically — `cancelAllOrders`'s bracket-preserve filter
26
+ // never strips a legacy leg off a live position during the transition.
27
+ // Only buildBracketCid changes (emits the new scheme); legacy ages out.
28
+ //
29
+ // Parseability is load-bearing: on restart, the reconciler fetches open
30
+ // orders from Binance, parses each clientOrderId, and reassembles brackets
31
+ // from the (bracketId, role) pairs. No reliance on local state to find them.
32
+ //
33
+ // Binance USDⓂ-Futures newClientOrderId charset is documented as
34
+ // ^[\.A-Z\:/a-z0-9_-]{1,36}$ — `bkt[0-9a-f]{16}[est]` (20 chars, lowercase
35
+ // alphanumeric) is well within it. Verified vs developers.binance.com
36
+ // USDⓂ New-Order clientOrderId filter, 2026-05-16.
37
+ import { randomBytes } from 'node:crypto';
38
+ // Emitted scheme (no `rc-`, no dashes).
39
+ const CID_REGEX = /^bkt([0-9a-f]{16})([est])$/;
40
+ // Legacy scheme — still parsed for backward compat, never emitted again.
41
+ const LEGACY_CID_REGEX = /^rc-([0-9a-f]{16})-([est])$/;
42
+ const ROLE_TO_TAG = {
43
+ entry: 'e',
44
+ stop: 's',
45
+ target: 't',
46
+ };
47
+ const TAG_TO_ROLE = {
48
+ e: 'entry',
49
+ s: 'stop',
50
+ t: 'target',
51
+ };
52
+ /** Generate a fresh 16-char hex bracketId. */
53
+ export function generateBracketId() {
54
+ return randomBytes(8).toString('hex');
55
+ }
56
+ /** Build a clientOrderId for a given bracket + role. Emits the CURRENT
57
+ * (`bkt…`) scheme only — never the legacy `rc-` scheme. */
58
+ export function buildBracketCid(bracketId, role) {
59
+ if (!/^[0-9a-f]{16}$/.test(bracketId)) {
60
+ throw new Error(`Invalid bracketId: ${bracketId} — expected 16 hex chars`);
61
+ }
62
+ return `bkt${bracketId}${ROLE_TO_TAG[role]}`;
63
+ }
64
+ /** Parse a clientOrderId. Recognises BOTH the current `bkt…` scheme and
65
+ * the legacy `rc-…` scheme (so existing ledger rows / in-flight legs and
66
+ * the cancelAllOrders bracket-preserve filter keep working through the
67
+ * transition). Returns null for non-bracket orders (so this can be called
68
+ * on every order Binance returns during reconciliation). */
69
+ export function parseBracketCid(cid) {
70
+ const match = CID_REGEX.exec(cid) ?? LEGACY_CID_REGEX.exec(cid);
71
+ if (!match)
72
+ return null;
73
+ const bracketId = match[1];
74
+ const tag = match[2];
75
+ return { bracketId, role: TAG_TO_ROLE[tag] };
76
+ }
77
+ /** Cheap type-guard for "is this a reefclaw-managed bracket clientOrderId"
78
+ * — true for both the current and legacy schemes. */
79
+ export function isBracketCid(cid) {
80
+ return CID_REGEX.test(cid) || LEGACY_CID_REGEX.test(cid);
81
+ }
@@ -0,0 +1,54 @@
1
+ import type { BracketId, BracketLedgerEntry, BracketState } from './bracket-types.js';
2
+ /** Canonical form for a bracket-ledger symbol key.
3
+ *
4
+ * CCXT returns Binance Futures symbols as `BTC/USDT:USDT` (the `:USDT`
5
+ * suffix is the settlement asset); the skill + agent's tool args use the
6
+ * un-suffixed `BTC/USDT`. Historically we keyed the ledger by whatever the
7
+ * caller passed in — which produced doubled rows per symbol and caused the
8
+ * 2026-04-21 ADA audit-vs-attach deadlock (9 of 11 symbols had both keys;
9
+ * see `memory/feedback_symbol_normalize_boundary.md`). This helper picks a
10
+ * single canonical form (un-suffixed) and is applied at every public
11
+ * BracketLedger entry point.
12
+ *
13
+ * Matches `user-data-stream-controller.ts` and `webapp/src/lib/symbols.ts`
14
+ * `normalizeSymbol`, so the bracket path lines up with the WS store + UI. */
15
+ export declare function normalizeBracketSymbol(s: string): string;
16
+ /** Test seam — lets tests override the clock for deterministic timestamps. */
17
+ export type NowFn = () => string;
18
+ export declare class BracketLedger {
19
+ private readonly filePath;
20
+ private readonly dir;
21
+ private readonly now;
22
+ private entries;
23
+ constructor(pluginId?: string, opts?: {
24
+ now?: NowFn;
25
+ basePath?: string;
26
+ });
27
+ /** Insert or replace the entry for a symbol. Persists synchronously.
28
+ * Symbol is normalized; the stored entry carries the canonical form so
29
+ * later readers see it regardless of whether the caller passed suffixed
30
+ * or un-suffixed input. */
31
+ upsert(entry: Omit<BracketLedgerEntry, 'createdAt' | 'updatedAt'> & {
32
+ createdAt?: string;
33
+ updatedAt?: string;
34
+ }): BracketLedgerEntry;
35
+ /** Mutate the state of an existing entry. Throws if entry not found. */
36
+ markState(symbol: string, nextState: BracketState, extras?: Partial<Pick<BracketLedgerEntry, 'attachStartedAt' | 'attachCompletedAt' | 'attachLatencyMs' | 'closedAt' | 'closeReason' | 'lastError' | 'slCid' | 'tpCid' | 'qty'>>): BracketLedgerEntry;
37
+ getBySymbol(symbol: string): BracketLedgerEntry | undefined;
38
+ getByBracketId(bracketId: BracketId): BracketLedgerEntry | undefined;
39
+ /** All active (non-terminal) entries. Used by reconciler + telemetry. */
40
+ getActive(): BracketLedgerEntry[];
41
+ /** Every entry in the ledger, including terminal ones (kept for audit). */
42
+ getAll(): BracketLedgerEntry[];
43
+ /** Remove an entry — typically only called for very old terminal entries
44
+ * during periodic cleanup. Does NOT persist unless mutated. */
45
+ remove(symbol: string): boolean;
46
+ /** Number of entries currently in the ledger. */
47
+ size(): number;
48
+ /** Test seam — full ledger file path. */
49
+ getFilePath(): string;
50
+ private loadOrInit;
51
+ private persist;
52
+ private validateFile;
53
+ private validateEntry;
54
+ }