@senpi/runtime 3.0.0-dev.runtime-phase-2.20260623210537 → 3.0.0-dev.runtime-phase-2.20260625112547

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/README.md +10 -9
  2. package/dist/actions/action-runtime-module.d.ts.map +1 -1
  3. package/dist/actions/action-runtime-module.js +1 -1
  4. package/dist/actions/action-runtime-module.js.map +1 -1
  5. package/dist/actions/decision-engine.d.ts.map +1 -1
  6. package/dist/actions/decision-engine.js +3 -0
  7. package/dist/actions/decision-engine.js.map +1 -1
  8. package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
  9. package/dist/actions/open-position/open-position.action.js +54 -14
  10. package/dist/actions/open-position/open-position.action.js.map +1 -1
  11. package/dist/actions/types.d.ts +10 -2
  12. package/dist/actions/types.d.ts.map +1 -1
  13. package/dist/actions/types.js +8 -0
  14. package/dist/actions/types.js.map +1 -1
  15. package/dist/cli/guide-commands.d.ts.map +1 -1
  16. package/dist/cli/guide-commands.js +80 -74
  17. package/dist/cli/guide-commands.js.map +1 -1
  18. package/dist/cli/senpi-commands.d.ts.map +1 -1
  19. package/dist/cli/senpi-commands.js +0 -108
  20. package/dist/cli/senpi-commands.js.map +1 -1
  21. package/dist/constants/dsl/index.d.ts +2 -0
  22. package/dist/constants/dsl/index.d.ts.map +1 -1
  23. package/dist/constants/dsl/index.js +2 -0
  24. package/dist/constants/dsl/index.js.map +1 -1
  25. package/dist/dsl/config/parse-tiers.d.ts.map +1 -1
  26. package/dist/dsl/config/parse-tiers.js +6 -2
  27. package/dist/dsl/config/parse-tiers.js.map +1 -1
  28. package/dist/dsl/engine-client/config-mapping.d.ts +7 -0
  29. package/dist/dsl/engine-client/config-mapping.d.ts.map +1 -1
  30. package/dist/dsl/engine-client/config-mapping.js +16 -2
  31. package/dist/dsl/engine-client/config-mapping.js.map +1 -1
  32. package/dist/dsl/events/handlers.d.ts +18 -2
  33. package/dist/dsl/events/handlers.d.ts.map +1 -1
  34. package/dist/dsl/events/handlers.js +302 -213
  35. package/dist/dsl/events/handlers.js.map +1 -1
  36. package/dist/dsl/monitor/create-monitor.d.ts +15 -0
  37. package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
  38. package/dist/dsl/monitor/create-monitor.js +46 -9
  39. package/dist/dsl/monitor/create-monitor.js.map +1 -1
  40. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  41. package/dist/dsl/monitor/process-one-position.js +138 -77
  42. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  43. package/dist/dsl/plugin/index.d.ts +16 -1
  44. package/dist/dsl/plugin/index.d.ts.map +1 -1
  45. package/dist/dsl/plugin/index.js +35 -4
  46. package/dist/dsl/plugin/index.js.map +1 -1
  47. package/dist/dsl/plugin/options.d.ts +18 -0
  48. package/dist/dsl/plugin/options.d.ts.map +1 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +4 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/runtime/create-scanner-module.d.ts +4 -3
  53. package/dist/runtime/create-scanner-module.d.ts.map +1 -1
  54. package/dist/runtime/create-scanner-module.js +14 -5
  55. package/dist/runtime/create-scanner-module.js.map +1 -1
  56. package/dist/runtime/create-scanner-providers-for-run.d.ts.map +1 -1
  57. package/dist/runtime/create-scanner-providers-for-run.js +12 -1
  58. package/dist/runtime/create-scanner-providers-for-run.js.map +1 -1
  59. package/dist/runtime/load-runtime-config.js +1 -1
  60. package/dist/runtime/load-runtime-config.js.map +1 -1
  61. package/dist/runtime/map-strategies-registration.js +7 -8
  62. package/dist/runtime/map-strategies-registration.js.map +1 -1
  63. package/dist/runtime/map-strategies.d.ts.map +1 -1
  64. package/dist/runtime/map-strategies.js +12 -3
  65. package/dist/runtime/map-strategies.js.map +1 -1
  66. package/dist/runtime/reconcile-decide.d.ts +73 -0
  67. package/dist/runtime/reconcile-decide.d.ts.map +1 -0
  68. package/dist/runtime/reconcile-decide.js +186 -0
  69. package/dist/runtime/reconcile-decide.js.map +1 -0
  70. package/dist/runtime/reconcile-run.d.ts +62 -0
  71. package/dist/runtime/reconcile-run.d.ts.map +1 -0
  72. package/dist/runtime/reconcile-run.js +173 -0
  73. package/dist/runtime/reconcile-run.js.map +1 -0
  74. package/dist/runtime/reconcile-state.d.ts +44 -0
  75. package/dist/runtime/reconcile-state.d.ts.map +1 -1
  76. package/dist/runtime/reconcile-state.js +80 -44
  77. package/dist/runtime/reconcile-state.js.map +1 -1
  78. package/dist/runtime/risk-guard.d.ts.map +1 -1
  79. package/dist/runtime/risk-guard.js +17 -12
  80. package/dist/runtime/risk-guard.js.map +1 -1
  81. package/dist/runtime/runtime-schema.d.ts +2912 -1052
  82. package/dist/runtime/runtime-schema.d.ts.map +1 -1
  83. package/dist/runtime/runtime-schema.js +136 -42
  84. package/dist/runtime/runtime-schema.js.map +1 -1
  85. package/dist/runtime/runtime.d.ts +26 -0
  86. package/dist/runtime/runtime.d.ts.map +1 -1
  87. package/dist/runtime/runtime.js +254 -8
  88. package/dist/runtime/runtime.js.map +1 -1
  89. package/dist/runtime/scanner-registry.d.ts +1 -1
  90. package/dist/runtime/scanner-registry.d.ts.map +1 -1
  91. package/dist/runtime/scanner-registry.js +3 -12
  92. package/dist/runtime/scanner-registry.js.map +1 -1
  93. package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
  94. package/dist/runtime/supervisor/long-running-supervisor.js +1 -1
  95. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  96. package/dist/runtime/supervisor/orphan-sweep.js +1 -1
  97. package/dist/runtime/supervisor/orphan-sweep.js.map +1 -1
  98. package/dist/runtime/supervisor/supervisor-utils.d.ts +1 -1
  99. package/dist/runtime/supervisor/supervisor-utils.d.ts.map +1 -1
  100. package/dist/runtime/supervisor/supervisor-utils.js +1 -1
  101. package/dist/runtime/supervisor/supervisor-utils.js.map +1 -1
  102. package/dist/runtime-api/intake/acceptance-record.d.ts +10 -0
  103. package/dist/runtime-api/intake/acceptance-record.d.ts.map +1 -1
  104. package/dist/runtime-api/intake/acceptance-record.js +28 -0
  105. package/dist/runtime-api/intake/acceptance-record.js.map +1 -1
  106. package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
  107. package/dist/runtime-api/intake/intake-mount.js +28 -36
  108. package/dist/runtime-api/intake/intake-mount.js.map +1 -1
  109. package/dist/runtime-api/intake/process-signal-post.d.ts +20 -1
  110. package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
  111. package/dist/runtime-api/intake/process-signal-post.js +57 -6
  112. package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
  113. package/dist/runtime-api/intake/scanner-launcher.d.ts +18 -4
  114. package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
  115. package/dist/runtime-api/intake/scanner-launcher.js +24 -3
  116. package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
  117. package/dist/runtime-api/intake/types.d.ts +16 -0
  118. package/dist/runtime-api/intake/types.d.ts.map +1 -1
  119. package/dist/runtime-api/routes/signals.schema.d.ts +4 -0
  120. package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
  121. package/dist/runtime-api/routes/signals.schema.js +12 -0
  122. package/dist/runtime-api/routes/signals.schema.js.map +1 -1
  123. package/dist/scanners/external/scaffold/__main__.py +65 -24
  124. package/dist/scanners/external/scaffold/ctx.py +78 -0
  125. package/dist/scanners/external/scaffold/delivery.py +56 -22
  126. package/dist/scanners/external/scaffold/entrypoint_loader.py +42 -48
  127. package/dist/scanners/external/scaffold/journal.py +13 -2
  128. package/dist/scanners/external/scaffold/requirements.txt +11 -0
  129. package/dist/scanners/external/scaffold/scaffold.py +159 -128
  130. package/dist/scanners/external/scaffold/senpi_mcp.py +250 -0
  131. package/dist/scanners/external/scaffold/signal_envelope.py +177 -34
  132. package/dist/scanners/external/scaffold/state_manager.py +175 -0
  133. package/dist/scanners/external-scanner-receiver.d.ts +6 -0
  134. package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
  135. package/dist/scanners/external-scanner-receiver.js +14 -1
  136. package/dist/scanners/external-scanner-receiver.js.map +1 -1
  137. package/dist/scripts/run-dsl-standalone-live.d.ts +1 -1
  138. package/dist/scripts/run-dsl-standalone-live.js +5 -3
  139. package/dist/scripts/run-dsl-standalone-live.js.map +1 -1
  140. package/dist/senpi/client.d.ts +37 -4
  141. package/dist/senpi/client.d.ts.map +1 -1
  142. package/dist/senpi/client.js +101 -24
  143. package/dist/senpi/client.js.map +1 -1
  144. package/dist/state/state-manager.d.ts +17 -0
  145. package/dist/state/state-manager.d.ts.map +1 -1
  146. package/dist/state/state-manager.js +50 -4
  147. package/dist/state/state-manager.js.map +1 -1
  148. package/dist/strategy/strategy-state.d.ts +9 -1
  149. package/dist/strategy/strategy-state.d.ts.map +1 -1
  150. package/dist/strategy/strategy-state.js +62 -9
  151. package/dist/strategy/strategy-state.js.map +1 -1
  152. package/dist/tracking/constants.d.ts.map +1 -1
  153. package/dist/tracking/constants.js +2 -0
  154. package/dist/tracking/constants.js.map +1 -1
  155. package/dist/types/dsl/index.d.ts +1 -1
  156. package/dist/types/dsl/index.d.ts.map +1 -1
  157. package/dist/types/scanner.d.ts +7 -0
  158. package/dist/types/scanner.d.ts.map +1 -1
  159. package/dist/types/strategy.d.ts +3 -1
  160. package/dist/types/strategy.d.ts.map +1 -1
  161. package/dist/utils/keyed-mutex.d.ts +37 -0
  162. package/dist/utils/keyed-mutex.d.ts.map +1 -0
  163. package/dist/utils/keyed-mutex.js +77 -0
  164. package/dist/utils/keyed-mutex.js.map +1 -0
  165. package/examples/strategies/README.md +17 -12
  166. package/examples/strategies/iguana/README.md +135 -0
  167. package/examples/strategies/iguana/recipe.yaml +137 -0
  168. package/examples/strategies/iguana/scanners/scan.py +243 -0
  169. package/examples/strategies/iguana/scanners/scoring.py +152 -0
  170. package/examples/strategies/raptor/README.md +156 -0
  171. package/examples/strategies/raptor/recipe.yaml +154 -0
  172. package/examples/strategies/raptor/scanners/scan.py +349 -0
  173. package/examples/strategies/raptor/scanners/scoring.py +452 -0
  174. package/examples/strategies/spider/README.md +89 -0
  175. package/examples/strategies/spider/scalp/recipe.yaml +129 -0
  176. package/examples/strategies/spider/scalp/scanners/scan.py +219 -0
  177. package/examples/strategies/spider/scalp/scanners/scoring.py +179 -0
  178. package/examples/strategies/spider/swing/recipe.yaml +137 -0
  179. package/examples/strategies/spider/swing/scanners/scan.py +334 -0
  180. package/examples/strategies/spider/swing/scanners/scoring.py +184 -0
  181. package/examples/strategies/turbine/README.md +166 -0
  182. package/examples/strategies/turbine/recipe-runners.yaml +123 -0
  183. package/examples/strategies/turbine/recipe-volume.yaml +129 -0
  184. package/examples/strategies/turbine/scanners/scan.py +283 -0
  185. package/examples/strategies/turbine/scanners/scoring.py +190 -0
  186. package/package.json +1 -1
  187. package/dist/scanners/external/liquidation-watchdog/README.md +0 -107
  188. package/dist/scanners/external/liquidation-watchdog/liquidation-watchdog.yaml +0 -48
  189. package/dist/scanners/external/liquidation-watchdog/producer.mjs +0 -336
  190. package/dist/scanners/external/momentum/README.md +0 -148
  191. package/dist/scanners/external/momentum/momentum.yaml +0 -116
  192. package/dist/scanners/external/momentum/producer.mjs +0 -251
  193. package/dist/scanners/external/shared/ingest.mjs +0 -112
  194. package/examples/README.md +0 -89
  195. package/examples/scanners-consumer-quickstart.mjs +0 -134
  196. package/examples/scanners-emerging-movers-v4.mjs +0 -287
  197. package/examples/scanners-liquidation-watchdog.mjs +0 -341
  198. package/examples/scanners-live-mcp.mjs +0 -208
  199. package/examples/scanners-multi-strategy.mjs +0 -165
  200. package/examples/scanners-position-tracker-e2e.mjs +0 -225
  201. package/examples/scanners-scheduled-events.mjs +0 -96
  202. package/examples/scanners-senpi-provider-client.mjs +0 -182
  203. package/examples/scanners-single-strategy.mjs +0 -198
  204. package/examples/strategies/dsl-showcase.yaml +0 -68
  205. package/examples/strategies/dsl.yaml +0 -51
  206. package/examples/strategies/fox-minimal-loosened.yaml +0 -240
  207. package/examples/strategies/fox-minimal.yaml +0 -242
  208. package/examples/strategies/minimal.yaml +0 -46
  209. package/examples/strategies/momentum/how-it-works.md +0 -48
  210. package/examples/strategies/momentum/momentum-guarded.yaml +0 -161
  211. package/examples/strategies/risk-showcase.yaml +0 -93
  212. package/examples/strategies/viper.yaml +0 -67
  213. package/examples/strategies/wolf.yaml +0 -72
  214. package/examples/yamls/sample-with-dsl-and-emerging-movers.yaml +0 -123
@@ -39,7 +39,7 @@ Scanner types
39
39
  emerging_movers
40
40
  Watches the Senpi smart-money leaderboard for rank and contribution
41
41
  movements. Stateful — compares each scan to stored history.
42
- Typical interval: 20s–60s
42
+ Typical interval_seconds: 20–60
43
43
 
44
44
  Signal types (priority order):
45
45
  FIRST_JUMP (score 0.95) 10+ rank jump from deep, first appearance
@@ -64,7 +64,7 @@ emerging_movers
64
64
  momentum
65
65
  Emits directional breakout signals based on candle price movement.
66
66
  Works best paired with the prescreener scanner.
67
- Typical interval: 1m
67
+ Typical interval_seconds: 60
68
68
 
69
69
  Signal types: MOMENTUM_BREAKOUT (LONG or SHORT)
70
70
 
@@ -82,7 +82,7 @@ prescreener
82
82
  Infrastructure scanner. Ranks liquid instruments by a composite
83
83
  liquidity/momentum/OI score and publishes a shared artifact consumed
84
84
  by momentum and oi_tracker. Add this scanner before those in your YAML.
85
- Typical interval: 5m
85
+ Typical interval_seconds: 300
86
86
 
87
87
  Signal types: none (artifact producer only)
88
88
 
@@ -97,7 +97,7 @@ prescreener
97
97
  oi_tracker
98
98
  Infrastructure scanner. Samples open interest per instrument and writes
99
99
  an oi-history artifact. Used by downstream scanners that need OI trend data.
100
- Typical interval: 1m
100
+ Typical interval_seconds: 60
101
101
 
102
102
  Signal types: none (artifact producer only)
103
103
 
@@ -110,7 +110,7 @@ oi_tracker
110
110
  position_tracker
111
111
  Wallet position monitor: compares clearing-house snapshots and emits lifecycle deltas.
112
112
  Use with action POSITION_TRACKER to feed the DSL exit engine.
113
- Typical interval: 10s–60s
113
+ Typical interval_seconds: 10–60
114
114
 
115
115
  Type aliases (same scanner): position-tracker
116
116
 
@@ -124,29 +124,28 @@ position_tracker
124
124
 
125
125
  ──────────────────────────────────────────────────────────────────────────────
126
126
 
127
- external_scanner
128
- Push-driven scanner configured in the runtime YAML and fed through the
129
- gateway RPC \`senpi.ingestExternalScannerData\`.
130
- It is not scheduled and does not accept \`interval\`.
131
-
132
- Typical uses:
133
- signal-only Ingest external signals into the normal action pipeline
134
- context-only Retain external context for depends_on and prompts
135
- mixed-output Commit one signal and refresh retained context together
136
-
137
- YAML-only fields:
138
- outputs.signals boolean Whether ingests emit signals
139
- outputs.context boolean Whether ingests retain context
140
- retention string last_only | rolling_window
141
- retention_max_runs number Optional history cap when rolling_window
142
- config.fields object Declared external payload schema
127
+ external_scanner (v2 supervised)
128
+ A scan(inputs, ctx) module the runtime supervises. The runtime spawns a
129
+ scaffold child, calls scan() every interval_seconds, and delivers the
130
+ returned list[dict] of signals to intake over loopback. No push, no
131
+ ingest CLI, no producer process.
132
+
133
+ YAML fields:
134
+ path string Required scan module directory
135
+ entrypoint string Required module exporting scan(inputs, ctx)
136
+ interval_seconds number Opt (30) tick cadence (integer seconds)
137
+ timeout_seconds number Opt per-tick budget (default = interval_seconds)
138
+ default_signal_validity_seconds number Required fallback signal TTL (seconds)
139
+ state_history_max_count number Opt (0) ctx.state bound; 0 = history disabled
140
+ inputs object Opt author knobs → scan(inputs, ctx)
141
+ signal_data_schema object Required schema of each signal's data{}
142
+
143
+ scan() returns a plain list[dict]; state via ctx.state; MCP via
144
+ ctx.senpi_mcp. Rejected by load: interval, depends_on, outputs, config,
145
+ params, blocked_assets.
143
146
 
144
147
  Prompt/context access stays namespaced:
145
148
  {{signal_<scanner_name>}}
146
- {{context_<scanner_name>}}
147
-
148
- Gateway ingest example:
149
- openclaw gateway call senpi.ingestExternalScannerData --params '{ ... }'
150
149
  `.trim();
151
150
  const ACTIONS_TEXT = `
152
151
  Action types
@@ -157,12 +156,14 @@ OPEN_POSITION
157
156
 
158
157
  Required YAML fields:
159
158
  action_type OPEN_POSITION
160
- decision_mode llm | no_decision
159
+ decision_mode llm | rule | none
161
160
  scanners list of scanner names that feed this action
162
161
  min_confidence 0–10; signals below this score are skipped
162
+ (when omitted the effective floor is 1, not 0)
163
163
 
164
164
  Optional fields:
165
- decision_model Claude model id (required when decision_mode: llm)
165
+ decision_model Claude model id (recommended when decision_mode: llm;
166
+ falls back to the host default if omitted)
166
167
  context list of context fields injected into the prompt
167
168
  currently supported: signal
168
169
  decision_prompt system prompt template; use {{signal}} for signal JSON
@@ -184,8 +185,14 @@ Decision modes:
184
185
  }
185
186
  Trades are skipped when execute=false or confidence < min_confidence.
186
187
 
187
- no_decision Opens a position for every qualifying signal immediately.
188
- No LLM call. Use with tight DSL stops to manage risk.
188
+ rule Deterministic; no LLM call. Executes for every qualifying
189
+ signal (any signal carrying an asset) and forwards all of them
190
+ to the action. confidence = round(highest signal score × 10),
191
+ clamped to 1–10. Use with tight DSL stops to manage risk.
192
+
193
+ none No-op decision: logs the decision step but opens NOTHING
194
+ (execute=false). Use for watch-only recipes — e.g.
195
+ position_tracker + DSL applying stops to existing positions.
189
196
  `.trim();
190
197
  const DSL_TEXT = `
191
198
  DSL exit engine — two-phase trailing stop-loss (DSL v2)
@@ -200,8 +207,9 @@ tier (no phase2_trigger_tier). Optional hard_timeout, weak_peak_cut, and
200
207
  dead_weight_cut live on the preset (not nested under phase1). When any of these
201
208
  has enabled: true, interval_in_minutes is required (finite number > 0);
202
209
  weak_peak_cut also requires min_value (finite ROE % > 0). hard_timeout
203
- applies only in phase 1: entering phase 2 before the interval elapses cancels it;
204
- same tick as first tier (phase 2) skips hard_timeout in favor of tier advance.
210
+ fires in either phase once interval_in_minutes elapses since entry (the timer
211
+ starts at entry and never resets); the only exception is the single tick that
212
+ first advances into phase 2, where tier advance wins that tick.
205
213
  dead_weight_cut: stagnation clock resets every tick while current ROE > 0 (mark vs entry);
206
214
  after interval_in_minutes with current ROE ≤ 0 on the tick, close with dead_weight_cut.
207
215
 
@@ -308,32 +316,35 @@ strategy
308
316
  enabled boolean No Default true
309
317
 
310
318
  scanners[]
311
- name string Yes Unique name referenced in actions.scanners
312
- type string Yes emerging_movers | momentum | prescreener | oi_tracker | position_tracker | external_scanner
313
- interval string Yes* e.g. "30s", "2m", "1h" (* forbidden for external_scanner)
314
- blocked_assets array No
315
- outputs object No Required for external_scanner: { signals, context }
316
- retention string No external_scanner only: last_only | rolling_window
317
- retention_max_runs number No external_scanner rolling window cap
318
- config object No Scanner-specific config (see: senpi guide scanners)
319
- advanced object No
320
-
321
- external_scanner config.fields
322
- fields.<name>.type string Yes string | number | boolean | object | array
323
- fields.<name>.required boolean No Default false
324
-
325
- External scanner ingests arrive through:
326
- openclaw gateway call senpi.ingestExternalScannerData --params '{ ... }'
319
+ name string Yes Unique name referenced in actions.scanners
320
+ type string Yes emerging_movers | momentum | prescreener | oi_tracker | position_tracker | external_scanner
321
+ interval_seconds number No Built-in scanners only; integer seconds (v2.0.0+). Rejected: legacy interval duration string.
322
+ blocked_assets array No Built-in scanners only
323
+ config object No Built-in scanner config (see: senpi guide scanners)
324
+ advanced object No
325
+
326
+ external_scanner (v2 supervised) — distinct field set:
327
+ path string Yes scan module directory
328
+ entrypoint string Yes module exporting scan(inputs, ctx)
329
+ interval_seconds number No tick cadence (integer seconds; default 30)
330
+ timeout_seconds number No per-tick budget (default = interval_seconds)
331
+ default_signal_validity_seconds number Yes fallback signal TTL (seconds)
332
+ state_history_max_count number No ctx.state bound; 0/unset = history disabled
333
+ inputs object No author knobs → scan(inputs, ctx)
334
+ signal_data_schema object Yes per-data-key { type, required? }
335
+ type: string | number | boolean | object | array
336
+ external_scanner rejects: interval, depends_on, outputs, config, params, blocked_assets
337
+
327
338
  Use namespaced prompt keys only:
328
339
  {{signal_<scanner_name>}} and {{context_<scanner_name>}}
329
340
 
330
341
  actions[]
331
342
  name string Yes
332
343
  action_type string Yes OPEN_POSITION
333
- decision_mode string Yes llm | no_decision
334
- decision_model string No Required when decision_mode: llm
344
+ decision_mode string No llm | rule | none (default none)
345
+ decision_model string No Recommended when decision_mode: llm
335
346
  scanners string[] No Scanner names this action listens to
336
- min_confidence number No 0–10
347
+ min_confidence number No 0–10 (omitted ⇒ effective floor 1)
337
348
  context string[] No [signal]
338
349
  decision_prompt string No Prompt template with {{signal}}
339
350
  params object No
@@ -356,23 +367,33 @@ exit
356
367
  phase1.enabled and phase2.enabled cannot both be false. Each tier row:
357
368
  lock_hw_pct [0,100]. Unknown keys under exit / presets / phase1 / phase2 / tiers throw
358
369
  DslConfigValidationError (UNKNOWN_DSL_CONFIG_KEY) at load. DSL parse also
359
- enforces strictly increasing trigger_pct per tier list, bounds on
360
- interval/max_loss/trigger/lock_hw, and integer breach counts; see dsl-reference
370
+ enforces strictly increasing trigger_pct per tier list (trigger_pct itself only
371
+ needs to be > 0 and finite — no upper cap, since ROE can exceed 100% under
372
+ leverage), bounds on interval/max_loss/lock_hw, and integer breach counts; see
373
+ dsl-reference
361
374
  Config validation. See: senpi guide dsl
362
375
 
363
376
  risk
364
- data_retention_hours number No 1–168; hours to retain audit logs (default: 48)
377
+ data_retention_seconds number No integer 3600–604800; seconds to retain audit logs
365
378
  guard_rails object No Guardrail thresholds (see below)
366
379
 
367
380
  risk.guard_rails
368
381
  daily_loss_limit_pct number No Max daily loss as % of account value at day open. Gate CLOSED when exceeded. Uses Hyperliquid trading PnL (excludes deposits/withdrawals).
369
382
  max_entries_per_day number No Opt-in: max trade entries per day; omit to skip gate 5. Counts open+closed positions opened today via MCP (MCP: Unix seconds).
370
383
  bypass_max_entries_per_day_on_profit boolean No At cap: keep gate 5 OPEN if today_snapshot.pnl.delta_since_open > 0 (default: false). Reuses strategy_get_pnl_and_account_value_history with gates 1–2 or one lazy fetch at cap only.
371
- max_consecutive_losses number No Opt-in with cooldown_minutes: gate 3; omit both to skip. Stateless: derived from trade history each tick.
372
- cooldown_minutes number No Opt-in with max_consecutive_losses: cooldown from last loss close (minutes)
384
+ max_consecutive_losses number No Opt-in with cooldown_seconds: gate 3; omit both to skip. Stateless: derived from trade history each tick.
385
+ cooldown_seconds number No Min 60; opt-in with max_consecutive_losses: cooldown from last loss close (seconds)
373
386
  drawdown_halt_pct number No 0–100; gate CLOSED when PnL drawdown from peak exceeds this %. PnL-based (immune to deposits/withdrawals).
374
387
  drawdown_reset_on_day_rollover boolean No Reset PnL peak at UTC midnight (default: false; peak carries from prior ~24h)
375
- per_asset_cooldown_minutes number No Min 5; gate COOLDOWN for a specific asset when closed within this many minutes. Checked via MCP per entry attempt.
388
+ per_asset_cooldown_seconds number No Min 300; gate COOLDOWN for a specific asset when closed within this many seconds. Checked via MCP per entry attempt.
389
+
390
+ telemetry
391
+ enabled boolean No Default true. Set false to disable OTEL event recording.
392
+ url string No Ingestion URL override (else SENPI_TRACKING_URL env, else built-in default).
393
+ Empty after env resolution degrades to default — never blocks boot.
394
+ flush_interval_seconds number No Integer seconds between telemetry flushes.
395
+ Rejected: legacy flush_interval_ms (renamed in v2.0.0).
396
+ A missing telemetry block defaults to enabled=true — existing recipes need no change.
376
397
 
377
398
  Environment substitution: \${VAR} and \${VAR:-default} resolved at load time.
378
399
  `.trim();
@@ -392,7 +413,7 @@ strategy:
392
413
  scanners:
393
414
  - name: emerging_movers
394
415
  type: emerging_movers
395
- interval: 60s
416
+ interval_seconds: 60
396
417
  config:
397
418
  topN: 20
398
419
  minRankJump: 5
@@ -401,7 +422,7 @@ scanners:
401
422
  actions:
402
423
  - name: log_signals
403
424
  action_type: OPEN_POSITION
404
- decision_mode: no_decision
425
+ decision_mode: none
405
426
  scanners: [emerging_movers]
406
427
  min_confidence: 0
407
428
 
@@ -421,7 +442,7 @@ export const CHEATSHEET_TEXT = `
421
442
  Senpi Plugin Commands Cheatsheet
422
443
  ================================
423
444
 
424
- All CLI commands and gateway methods exposed by @senpi-ai/runtime.
445
+ Core CLI commands and gateway methods exposed by @senpi-ai/runtime.
425
446
 
426
447
  CLI Commands
427
448
  ------------
@@ -481,13 +502,6 @@ senpi action — Action layer (execution history, decisions)
481
502
  -l, --limit <n> Max rows
482
503
  --json Raw JSON output
483
504
 
484
- senpi external-scanner — Ingest external scanner data (pure-JSON output)
485
- external-scanner ingest Call senpi.ingestExternalScannerData and emit JSON
486
- --address <wallet> Strategy wallet address (required)
487
- --scanner <name> Configured external_scanner name (required)
488
- --payload <json> Inline JSON payload (asset/direction/score/signal_type/data/signals)
489
- --payload-path <file> Read JSON payload from file (alternative to --payload)
490
-
491
505
  senpi guide — In-shell reference
492
506
  guide Overview and quick commands
493
507
  guide scanners Scanner types and config fields
@@ -546,14 +560,6 @@ senpi.getActionHistory
546
560
  outcomeFilter? ("with_decision" | "executed") — with_decision: decision != null; executed: decision.execute === true
547
561
  Success: { history: ActionExecutionRecord[] }
548
562
 
549
- senpi.ingestExternalScannerData
550
- Params: address (string, required), scanner (string, required), plus either
551
- single-ingest fields { asset?, direction?, score?, signal_type?, data }
552
- or a batch signals array [{ asset, direction?, score?, signal_type?, data }]
553
- Success: { accepted, address, scanner, timestamp, signalCount, contextUpdated }
554
- Errors: INVALID_REQUEST, NOT_FOUND, UNAVAILABLE
555
- Notes: Context-producing scanners accept single-item ingests only in v1.
556
-
557
563
  Full cheatsheet with sample responses: docs/plugin-commands-cheatsheet.md
558
564
  `.trim();
559
565
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"guide-commands.js","sourceRoot":"","sources":["../../src/cli/guide-commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"guide-commands.js","sourceRoot":"","sources":["../../src/cli/guide-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,aAAa,GACjB,0EAA0E,CAAC;AAE7E,+EAA+E;AAE/E,MAAM,aAAa,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC;qBACtB,OAAO;;;;;;;;;;;;;;;;;;;CAmB3B,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkHrB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CpB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0FhB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+GnB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCpB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2H9B,CAAC,IAAI,EAAE,CAAC;AAYT;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAuB,EACvB,OAAe;IAEf,MAAM,KAAK,GAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAsB;SACvD,WAAW,CAAC,0CAA0C,CAAC,CAAC;IAE3D,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE;QAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,UAAU,CAAsB;SAC5C,WAAW,CAAC,iCAAiC,CAAC;SAC9C,MAAM,CAAC,GAAG,EAAE;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,SAAS,CAAsB;SAC3C,WAAW,CAAC,iCAAiC,CAAC;SAC9C,MAAM,CAAC,GAAG,EAAE;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,KAAK,CAAsB;SACvC,WAAW,CAAC,6DAA6D,CAAC;SAC1E,MAAM,CAAC,GAAG,EAAE;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,UAAU,CAAsB;SAC5C,WAAW,CAAC,uCAAuC,CAAC;SACpD,MAAM,CAAC,GAAG,EAAE;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAsB;SAC1C,WAAW,CAAC,kCAAkC,CAAC;SAC/C,MAAM,CAAC,GAAG,EAAE;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,SAAS,CAAsB;SAC3C,WAAW,CAAC,kCAAkC,CAAC;SAC/C,MAAM,CAAC,GAAG,EAAE;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,OAAO,KAAK,aAAa,IAAI,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"senpi-commands.d.ts","sourceRoot":"","sources":["../../src/cli/senpi-commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,gBAAgB,GAAG,OAAO,CAAA;CAAE,GAChF,IAAI,CAopCN;AAoMD,kEAAkE;AAClE,UAAU,gBAAgB;IACxB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACvD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC/D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC9D,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACtE,MAAM,CACJ,EAAE,EACE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GACzD,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GACzE,gBAAgB,CAAC;CACrB"}
1
+ {"version":3,"file":"senpi-commands.d.ts","sourceRoot":"","sources":["../../src/cli/senpi-commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkBH;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,gBAAgB,GAAG,OAAO,CAAA;CAAE,GAChF,IAAI,CA8+BN;AAoMD,kEAAkE;AAClE,UAAU,gBAAgB;IACxB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACvD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC/D,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAC9D,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACtE,MAAM,CACJ,EAAE,EACE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GACzD,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,GACzE,gBAAgB,CAAC;CACrB"}
@@ -822,114 +822,6 @@ export function registerSenpiCli(program) {
822
822
  console.log(`${r.name}: ${status}`);
823
823
  }
824
824
  });
825
- const externalScanner = senpi.command("external-scanner");
826
- externalScanner.description("External scanner ingest (pure-JSON output for producers)");
827
- externalScanner
828
- .command("ingest")
829
- .description("Ingest external scanner data into a running runtime (calls senpi.ingestExternalScannerData). Always emits JSON.")
830
- .option("--address <wallet>", "Strategy wallet address the runtime is bound to")
831
- .option("--scanner <name>", "Configured external_scanner name in the runtime YAML")
832
- .option("--payload <json>", "Inline JSON payload with ingest fields (asset/direction/score/signal_type/data/signals)")
833
- .option("--payload-path <file>", "Path to a JSON file containing the ingest payload (alternative to --payload)")
834
- .action(async (opts) => {
835
- const address = typeof opts.address === "string" ? opts.address.trim() : "";
836
- const scanner = typeof opts.scanner === "string" ? opts.scanner.trim() : "";
837
- if (!address) {
838
- emitCliJsonError("CLI_INVALID_ARGS", "--address is required (strategy wallet address of a running runtime).");
839
- return;
840
- }
841
- if (!scanner) {
842
- emitCliJsonError("CLI_INVALID_ARGS", "--scanner is required (configured external_scanner name).");
843
- return;
844
- }
845
- const hasPayload = typeof opts.payload === "string" && opts.payload.length > 0;
846
- const hasPayloadPath = typeof opts.payloadPath === "string" && opts.payloadPath.length > 0;
847
- if (hasPayload && hasPayloadPath) {
848
- emitCliJsonError("CLI_INVALID_ARGS", "Provide either --payload or --payload-path, not both.");
849
- return;
850
- }
851
- let payloadText;
852
- if (hasPayload) {
853
- payloadText = opts.payload;
854
- }
855
- else if (hasPayloadPath) {
856
- try {
857
- payloadText = readFileSync(path.resolve(process.cwd(), opts.payloadPath), "utf8");
858
- }
859
- catch (err) {
860
- const msg = err instanceof Error ? err.message : String(err);
861
- emitCliJsonError("CLI_READ_FAILED", `Failed to read --payload-path: ${msg}`);
862
- return;
863
- }
864
- }
865
- let payloadObj = {};
866
- if (payloadText !== undefined) {
867
- try {
868
- const parsed = JSON.parse(payloadText);
869
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
870
- emitCliJsonError("CLI_INVALID_JSON", "Payload must be a JSON object.");
871
- return;
872
- }
873
- payloadObj = parsed;
874
- }
875
- catch (err) {
876
- const msg = err instanceof Error ? err.message : String(err);
877
- emitCliJsonError("CLI_INVALID_JSON", `Payload is not valid JSON: ${msg}`);
878
- return;
879
- }
880
- }
881
- const params = {
882
- ...payloadObj,
883
- address,
884
- scanner,
885
- };
886
- const result = spawnSync(process.argv[0], [
887
- process.argv[1],
888
- "gateway",
889
- "call",
890
- "senpi.ingestExternalScannerData",
891
- "--json",
892
- "--params",
893
- JSON.stringify(params),
894
- ], {
895
- stdio: ["inherit", "pipe", "pipe"],
896
- cwd: process.cwd(),
897
- encoding: "utf8",
898
- });
899
- const stdout = typeof result.stdout === "string" ? result.stdout : "";
900
- let data = null;
901
- if (stdout.length > 0) {
902
- try {
903
- data = JSON.parse(stdout);
904
- }
905
- catch {
906
- data = null;
907
- }
908
- }
909
- if (!data) {
910
- const stderr = typeof result.stderr === "string" ? result.stderr.trim() : "";
911
- emitCliJsonError("CLI_GATEWAY_UNAVAILABLE", stderr.length > 0
912
- ? `Gateway call failed: ${stderr}`
913
- : "Gateway call failed with no parseable response. Is the gateway running? (openclaw gateway run)");
914
- return;
915
- }
916
- if (data.ok === false) {
917
- const err = (data.error ?? {});
918
- process.stdout.write(JSON.stringify({
919
- ok: false,
920
- error: {
921
- code: typeof err.code === "string" ? err.code : "UNAVAILABLE",
922
- message: typeof err.message === "string"
923
- ? err.message
924
- : "External scanner ingest failed.",
925
- },
926
- }) + "\n");
927
- process.exitCode = 1;
928
- return;
929
- }
930
- const payload = (data.payload ?? data);
931
- process.stdout.write(JSON.stringify({ ok: true, result: payload }) + "\n");
932
- });
933
825
  registerGuideCommands(senpi, VERSION);
934
826
  }
935
827
  function emitCliJsonError(code, message) {