@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,138 @@
1
+ // Ed25519 signature verification for OTA SKILL.md updates.
2
+ //
3
+ // Security finding C1 (docs/PRE_LAUNCH_SECURITY_HARDENING.md §2.5 + Appendix B):
4
+ // the relay is treated as a DUMB TRANSPORT. A pushed/pulled SKILL.md is applied
5
+ // only if it carries a valid signature from the operator's offline private key,
6
+ // whose PUBLIC key is pinned here in the build. A leaked ADMIN_SECRET, a
7
+ // compromised relay process, or a tampered Durable-Object value therefore
8
+ // CANNOT rewrite an agent's trading instructions — they all fail verification.
9
+ //
10
+ // Key provisioning (one-time, operator, OFFLINE):
11
+ // 1. python scripts/gen-skill-signing-key.py
12
+ // 2. paste the printed PUBLIC key (base64) into TRUSTED_SKILL_SIGNING_KEYS below
13
+ // 3. put the printed PRIVATE key into .env.deploy as SKILL_SIGNING_PRIVATE_KEY
14
+ // (gitignored — keep it offline; NEVER on the relay or any server)
15
+ // 4. rebuild + redeploy the skill (deploy-skillmd-ota.py is already wired to sign)
16
+ //
17
+ // Until a key is pinned, OTA is FAIL-CLOSED (every push rejected) while
18
+ // SKILL_OTA_REQUIRE_SIGNATURE is on (the default). Normal trading is unaffected
19
+ // — only the OTA-update path gates on this.
20
+ import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
21
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+ import { homedir } from 'node:os';
24
+ import { parseSkillVersion } from './skill-version.js';
25
+ /**
26
+ * Pinned Ed25519 PUBLIC keys (base64 of the raw 32-byte key) trusted to sign OTA
27
+ * SKILL.md updates. Multiple entries enable key rotation (accept current + next,
28
+ * retire the old after every client has updated). EMPTY until the operator pins
29
+ * one — see the header for provisioning.
30
+ */
31
+ export const TRUSTED_SKILL_SIGNING_KEYS = [
32
+ // 'Base64OfRaw32BytePublicKey=' ← paste from scripts/gen-skill-signing-key.py
33
+ ];
34
+ const OTA_STATE_PATH = join(homedir(), '.openclaw', 'workspace', '.skill-ota-state.json');
35
+ /** Lowercase hex SHA-256 of the UTF-8 content. MUST match the Python signer. */
36
+ export function sha256Hex(content) {
37
+ return createHash('sha256').update(content, 'utf8').digest('hex');
38
+ }
39
+ /** Canonical signed message — MUST match deploy-skillmd-ota.py byte-for-byte. */
40
+ export function signedMessage(version, contentSha256, signedAt) {
41
+ return `${version}\n${contentSha256}\n${signedAt}`;
42
+ }
43
+ /**
44
+ * Is OTA signature verification enforced?
45
+ *
46
+ * Default **OFF** while C1 (signed OTA) is ON HOLD — see
47
+ * docs/PRE_LAUNCH_SECURITY_HARDENING.md §2.5. Keeping it off by default makes
48
+ * this whole module DORMANT, so deploying skill/relay for UNRELATED reasons can
49
+ * never break the existing (unsigned) OTA flow — no env flag to remember.
50
+ *
51
+ * To ENABLE signed-OTA (when the agent-update mechanism is finalized): pin a
52
+ * public key in TRUSTED_SKILL_SIGNING_KEYS **and** set
53
+ * SKILL_OTA_REQUIRE_SIGNATURE=on. Both are required — a pinned key alone does
54
+ * nothing without the flag, and the flag alone fails closed (no key → reject).
55
+ */
56
+ export function signatureRequired() {
57
+ return (process.env.SKILL_OTA_REQUIRE_SIGNATURE ?? 'off').toLowerCase() === 'on';
58
+ }
59
+ function publicKeyFromRawB64(b64) {
60
+ try {
61
+ const raw = Buffer.from(b64, 'base64');
62
+ if (raw.length !== 32)
63
+ return null; // raw Ed25519 public key is exactly 32 bytes
64
+ return createPublicKey({
65
+ format: 'jwk',
66
+ key: { kty: 'OKP', crv: 'Ed25519', x: raw.toString('base64url') },
67
+ });
68
+ }
69
+ catch {
70
+ return null;
71
+ }
72
+ }
73
+ /**
74
+ * Verify a signed OTA envelope against the pinned (or supplied) trusted keys.
75
+ * Checks: required fields present, content hash matches (no tampering), the
76
+ * frontmatter version matches the signed version (no number-bump replay), and
77
+ * the Ed25519 signature verifies under some trusted key. Pure — does NOT touch
78
+ * disk or the replay clock; the caller enforces monotonic signedAt.
79
+ */
80
+ export function verifySkillSignature(env, opts) {
81
+ const keys = opts?.trustedKeys ?? TRUSTED_SKILL_SIGNING_KEYS;
82
+ if (keys.length === 0) {
83
+ return { ok: false, reason: 'no trusted signing keys configured — pin a public key in skill-signing.ts (see scripts/gen-skill-signing-key.py)' };
84
+ }
85
+ if (!env.signature || !env.signedAt || !env.contentSha256) {
86
+ return { ok: false, reason: 'missing signature/signedAt/contentSha256 (unsigned push)' };
87
+ }
88
+ if (sha256Hex(env.content) !== env.contentSha256) {
89
+ return { ok: false, reason: 'content hash mismatch (tampered content)' };
90
+ }
91
+ const frontmatterVersion = parseSkillVersion(env.content);
92
+ if (frontmatterVersion !== env.version) {
93
+ return { ok: false, reason: `version mismatch (envelope ${env.version} vs frontmatter ${frontmatterVersion ?? 'none'})` };
94
+ }
95
+ const msg = Buffer.from(signedMessage(env.version, env.contentSha256, env.signedAt), 'utf8');
96
+ let sig;
97
+ try {
98
+ sig = Buffer.from(env.signature, 'base64');
99
+ }
100
+ catch {
101
+ return { ok: false, reason: 'signature is not valid base64' };
102
+ }
103
+ for (const keyB64 of keys) {
104
+ const pub = publicKeyFromRawB64(keyB64);
105
+ if (!pub)
106
+ continue;
107
+ try {
108
+ if (cryptoVerify(null, msg, pub, sig))
109
+ return { ok: true };
110
+ }
111
+ catch {
112
+ /* malformed key/sig for this candidate — try the next */
113
+ }
114
+ }
115
+ return { ok: false, reason: 'signature does not verify under any trusted key' };
116
+ }
117
+ /** Last applied signedAt as epoch ms, 0 if none/unreadable (replay clock). */
118
+ export function readLastAppliedSignedAtMs() {
119
+ try {
120
+ const raw = readFileSync(OTA_STATE_PATH, 'utf-8');
121
+ const t = JSON.parse(raw).lastAppliedSignedAt;
122
+ const ms = t ? new Date(t).getTime() : 0;
123
+ return Number.isFinite(ms) ? ms : 0;
124
+ }
125
+ catch {
126
+ return 0;
127
+ }
128
+ }
129
+ /** Persist the signedAt of the just-applied update (monotonic replay clock). */
130
+ export function recordAppliedSignedAt(signedAt) {
131
+ try {
132
+ mkdirSync(join(homedir(), '.openclaw', 'workspace'), { recursive: true });
133
+ writeFileSync(OTA_STATE_PATH, JSON.stringify({ lastAppliedSignedAt: signedAt }), 'utf-8');
134
+ }
135
+ catch {
136
+ /* best-effort — if this fails the replay window is bounded to one process life */
137
+ }
138
+ }
@@ -0,0 +1,17 @@
1
+ /** Extract version from SKILL.md frontmatter */
2
+ export declare function parseSkillVersion(content: string): string | null;
3
+ /** Read current SKILL.md version from disk */
4
+ export declare function readLocalSkillVersion(): string | null;
5
+ /**
6
+ * Compare two semver strings.
7
+ * Returns 1 if a > b, -1 if a < b, 0 if equal.
8
+ * Non-semver inputs (or null) are treated as -Infinity so a valid semver always
9
+ * wins, which is the safe direction for "should I accept this OTA push?".
10
+ */
11
+ export declare function compareSemver(a: string | null, b: string | null): number;
12
+ /** Validate SKILL.md content is well-formed */
13
+ export declare function validateSkillContent(content: string): {
14
+ valid: boolean;
15
+ version: string | null;
16
+ error?: string;
17
+ };
@@ -0,0 +1,74 @@
1
+ // SKILL.md version parsing utility for OTA updates
2
+ import { readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ const SKILL_MD_PATH = join(homedir(), '.openclaw', 'workspace', 'SKILL.md');
6
+ /** Extract version from SKILL.md frontmatter */
7
+ export function parseSkillVersion(content) {
8
+ const match = content.match(/^---[\s\S]*?version:\s*(\S+)[\s\S]*?---/);
9
+ return match ? match[1] : null;
10
+ }
11
+ /** Read current SKILL.md version from disk */
12
+ export function readLocalSkillVersion() {
13
+ try {
14
+ const content = readFileSync(SKILL_MD_PATH, 'utf-8');
15
+ return parseSkillVersion(content);
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ // 120KB — the full SKILL.md crossed 100KB at v2.20.0 (103.7KB), which silently
22
+ // broke every OTA apply against the old 100KB cap. Keep comfortably under the
23
+ // 128KiB Cloudflare Durable-Object per-value hard limit the relay stores into.
24
+ const MAX_SKILL_CONTENT_SIZE = 120_000;
25
+ const SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
26
+ /**
27
+ * Compare two semver strings.
28
+ * Returns 1 if a > b, -1 if a < b, 0 if equal.
29
+ * Non-semver inputs (or null) are treated as -Infinity so a valid semver always
30
+ * wins, which is the safe direction for "should I accept this OTA push?".
31
+ */
32
+ export function compareSemver(a, b) {
33
+ const parse = (v) => {
34
+ if (!v || !SEMVER_REGEX.test(v))
35
+ return null;
36
+ const [maj, min, pat] = v.split('.').map(Number);
37
+ return [maj, min, pat];
38
+ };
39
+ const pa = parse(a);
40
+ const pb = parse(b);
41
+ if (!pa && !pb)
42
+ return 0;
43
+ if (!pa)
44
+ return -1;
45
+ if (!pb)
46
+ return 1;
47
+ for (let i = 0; i < 3; i++) {
48
+ if (pa[i] > pb[i])
49
+ return 1;
50
+ if (pa[i] < pb[i])
51
+ return -1;
52
+ }
53
+ return 0;
54
+ }
55
+ /** Validate SKILL.md content is well-formed */
56
+ export function validateSkillContent(content) {
57
+ if (content.length > MAX_SKILL_CONTENT_SIZE) {
58
+ return { valid: false, version: null, error: `Content too large (${content.length} bytes, max ${MAX_SKILL_CONTENT_SIZE})` };
59
+ }
60
+ if (!content.startsWith('---')) {
61
+ return { valid: false, version: null, error: 'Missing frontmatter delimiter' };
62
+ }
63
+ const version = parseSkillVersion(content);
64
+ if (!version) {
65
+ return { valid: false, version: null, error: 'No version in frontmatter' };
66
+ }
67
+ if (!SEMVER_REGEX.test(version)) {
68
+ return { valid: false, version, error: `Invalid version format: ${version} (expected semver x.y.z)` };
69
+ }
70
+ if (content.length < 100) {
71
+ return { valid: false, version, error: 'Content suspiciously short' };
72
+ }
73
+ return { valid: true, version };
74
+ }
@@ -0,0 +1,47 @@
1
+ /** Thrown by `assertNotBanned` while the gate is closed. Carries the
2
+ * remaining cooldown so callers/telemetry can reason about it. Methods
3
+ * catch this in their existing catch block — it is NOT meant to escape
4
+ * the API layer with special handling; it degrades to the method's normal
5
+ * failure contract. */
6
+ export declare class BinanceBannedError extends Error {
7
+ readonly retryAfterMs: number;
8
+ constructor(retryAfterMs: number, context: string);
9
+ }
10
+ /** Thrown by the proactive weight pacer. Subclass of BinanceBannedError so
11
+ * every `instanceof BinanceBannedError` check and every method's existing
12
+ * catch keep working unchanged — it degrades to the same null/[]/throw
13
+ * contract (treated exactly like a transient fetch failure). The distinct
14
+ * name lets journal/telemetry tell prevention apart from a real IP ban. */
15
+ export declare class BinanceWeightPacedError extends BinanceBannedError {
16
+ constructor(retryAfterMs: number, context: string);
17
+ }
18
+ /** Fast pre-flight check. Call as the first line inside a Binance call's
19
+ * try-block. Throws `BinanceBannedError` (no network) while the gate is
20
+ * open. */
21
+ export declare function assertNotBanned(context: string): void;
22
+ /** Inspect a failed call's error and arm/extend the gate on a Binance
23
+ * rate-limit signal. Non-rate-limit errors are ignored (normal transient
24
+ * handling stays in the caller). Safe to call with any error shape. */
25
+ export declare function noteBinanceError(err: unknown): void;
26
+ /** Relax the soft-429 ladder after a clean call. (A hard 418 window is NOT
27
+ * shortened by this — only Binance's stated expiry clears it.) */
28
+ export declare function noteSuccess(): void;
29
+ /** Reconcile the local weight estimate against Binance truth from a response
30
+ * header (`x-mbx-used-weight-1m`). Best-effort: callers pass whatever header
31
+ * bag they have; absent/garbage values are ignored. The local estimate
32
+ * leads (counts in-flight calls Binance hasn't tallied yet), so we take the
33
+ * MAX within the window — never let a lagging header lower our guard. A
34
+ * header far BELOW the estimate means Binance's minute rolled; re-anchor the
35
+ * window to Binance's clock so we don't shed for a stale local total. */
36
+ export declare function noteResponseWeight(headers: Record<string, string> | undefined | null): void;
37
+ /** Test/diagnostic helper — current gate state. */
38
+ export declare function getBanGateState(): {
39
+ banUntilMs: number;
40
+ remainingMs: number;
41
+ rateLimitStep: number;
42
+ weightUsed: number;
43
+ weightCeiling: number;
44
+ cosmeticCeiling: number;
45
+ };
46
+ /** Test-only reset. Not used in production code paths. */
47
+ export declare function __resetBanGateForTests(): void;