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