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