@senpi/runtime 3.0.129 → 3.0.130
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/README.md +12 -4
- package/dist/actions/action-events.d.ts +21 -7
- package/dist/actions/action-events.d.ts.map +1 -1
- package/dist/actions/action-events.js +54 -26
- package/dist/actions/action-events.js.map +1 -1
- package/dist/actions/action-runtime-module.d.ts.map +1 -1
- package/dist/actions/action-runtime-module.js +38 -7
- package/dist/actions/action-runtime-module.js.map +1 -1
- package/dist/actions/close-position/close-position.action.d.ts.map +1 -1
- package/dist/actions/close-position/close-position.action.js +122 -15
- package/dist/actions/close-position/close-position.action.js.map +1 -1
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +15 -2
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/actions/event-derivation.d.ts +71 -2
- package/dist/actions/event-derivation.d.ts.map +1 -1
- package/dist/actions/event-derivation.js +251 -8
- package/dist/actions/event-derivation.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts +12 -2
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +144 -41
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.d.ts +2 -1
- package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.js +62 -17
- package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
- package/dist/actions/skipped-signal-results.d.ts +25 -0
- package/dist/actions/skipped-signal-results.d.ts.map +1 -0
- package/dist/actions/skipped-signal-results.js +71 -0
- package/dist/actions/skipped-signal-results.js.map +1 -0
- package/dist/actions/store/types.d.ts +9 -2
- package/dist/actions/store/types.d.ts.map +1 -1
- package/dist/actions/store/types.js.map +1 -1
- package/dist/actions/types.d.ts +13 -0
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js +13 -0
- package/dist/actions/types.js.map +1 -1
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +7 -6
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +4 -11
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
- package/dist/dsl/engine-client/event-mapping.js +14 -0
- package/dist/dsl/engine-client/event-mapping.js.map +1 -1
- package/dist/dsl/events/build-closed-event.d.ts +16 -1
- package/dist/dsl/events/build-closed-event.d.ts.map +1 -1
- package/dist/dsl/events/build-closed-event.js +6 -0
- package/dist/dsl/events/build-closed-event.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts +7 -0
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +37 -7
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/monitor/create-monitor.d.ts +6 -2
- package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
- package/dist/dsl/monitor/create-monitor.js +15 -9
- package/dist/dsl/monitor/create-monitor.js.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.d.ts +2 -0
- package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.js +2 -1
- package/dist/dsl/monitor/dsl-failure-events.js.map +1 -1
- package/dist/dsl/monitor/process-one-position.d.ts +1 -0
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +59 -25
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts +8 -2
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +8 -6
- package/dist/dsl/plugin/index.js.map +1 -1
- package/dist/health/types.d.ts +13 -0
- package/dist/health/types.d.ts.map +1 -1
- package/dist/health/types.js +12 -0
- package/dist/health/types.js.map +1 -1
- package/dist/index.d.ts +27 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +251 -39
- package/dist/index.js.map +1 -1
- package/dist/runtime/action-registry.d.ts.map +1 -1
- package/dist/runtime/action-registry.js +4 -3
- package/dist/runtime/action-registry.js.map +1 -1
- package/dist/runtime/auto-update/coordinator.d.ts +13 -6
- package/dist/runtime/auto-update/coordinator.d.ts.map +1 -1
- package/dist/runtime/auto-update/coordinator.js +86 -18
- package/dist/runtime/auto-update/coordinator.js.map +1 -1
- package/dist/runtime/auto-update/run-update-command.d.ts.map +1 -1
- package/dist/runtime/auto-update/run-update-command.js +4 -3
- package/dist/runtime/auto-update/run-update-command.js.map +1 -1
- package/dist/runtime/auto-update/types.d.ts +11 -5
- package/dist/runtime/auto-update/types.d.ts.map +1 -1
- package/dist/runtime/auto-update/types.js +11 -0
- package/dist/runtime/auto-update/types.js.map +1 -1
- package/dist/runtime/domain-events.d.ts.map +1 -1
- package/dist/runtime/domain-events.js +7 -0
- package/dist/runtime/domain-events.js.map +1 -1
- package/dist/runtime/lifecycle-events.d.ts +9 -1
- package/dist/runtime/lifecycle-events.d.ts.map +1 -1
- package/dist/runtime/lifecycle-events.js +11 -2
- package/dist/runtime/lifecycle-events.js.map +1 -1
- package/dist/runtime/reconcile-run.d.ts +13 -3
- package/dist/runtime/reconcile-run.d.ts.map +1 -1
- package/dist/runtime/reconcile-run.js +4 -3
- package/dist/runtime/reconcile-run.js.map +1 -1
- package/dist/runtime/reconcile-state.d.ts +8 -0
- package/dist/runtime/reconcile-state.d.ts.map +1 -1
- package/dist/runtime/reconcile-state.js +4 -1
- package/dist/runtime/reconcile-state.js.map +1 -1
- package/dist/runtime/run.d.ts +1 -0
- package/dist/runtime/run.d.ts.map +1 -1
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/runtime-schema.js +3 -3
- package/dist/runtime/runtime-schema.js.map +1 -1
- package/dist/runtime/runtime.d.ts +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +27 -3
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.d.ts +13 -0
- package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js +1 -0
- package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
- package/dist/runtime/validate-dsl-position-tracking.d.ts.map +1 -1
- package/dist/runtime/validate-dsl-position-tracking.js +2 -1
- package/dist/runtime/validate-dsl-position-tracking.js.map +1 -1
- package/dist/runtime-api/handlers/ingest-signals.d.ts.map +1 -1
- package/dist/runtime-api/handlers/ingest-signals.js +1 -0
- package/dist/runtime-api/handlers/ingest-signals.js.map +1 -1
- package/dist/runtime-api/handlers/types.d.ts +5 -0
- package/dist/runtime-api/handlers/types.d.ts.map +1 -1
- package/dist/runtime-api/intake/ingest-request.d.ts +17 -0
- package/dist/runtime-api/intake/ingest-request.d.ts.map +1 -0
- package/dist/runtime-api/intake/ingest-request.js +34 -0
- package/dist/runtime-api/intake/ingest-request.js.map +1 -0
- package/dist/runtime-api/intake/intake-events.d.ts +1 -1
- package/dist/runtime-api/intake/intake-events.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-events.js +9 -3
- package/dist/runtime-api/intake/intake-events.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +4 -19
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/runtime-api/intake/process-error-post.d.ts +6 -1
- package/dist/runtime-api/intake/process-error-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-error-post.js +5 -1
- package/dist/runtime-api/intake/process-error-post.js.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts +7 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.js +5 -0
- package/dist/runtime-api/intake/process-signal-post.js.map +1 -1
- package/dist/runtime-api/intake/scaffold-error-router.js.map +1 -1
- package/dist/runtime-api/intake/scaffold-tick-router.d.ts +39 -0
- package/dist/runtime-api/intake/scaffold-tick-router.d.ts.map +1 -0
- package/dist/runtime-api/intake/scaffold-tick-router.js +122 -0
- package/dist/runtime-api/intake/scaffold-tick-router.js.map +1 -0
- package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.js +15 -0
- package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
- package/dist/runtime-api/intake/types.d.ts +55 -0
- package/dist/runtime-api/intake/types.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.d.ts +2 -0
- package/dist/runtime-api/routes/signals.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.js +5 -1
- package/dist/runtime-api/routes/signals.js.map +1 -1
- package/dist/runtime-api/routes/signals.schema.d.ts +43 -0
- package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.js +43 -0
- package/dist/runtime-api/routes/signals.schema.js.map +1 -1
- package/dist/runtime-api/server.d.ts +7 -1
- package/dist/runtime-api/server.d.ts.map +1 -1
- package/dist/runtime-api/server.js +1 -0
- package/dist/runtime-api/server.js.map +1 -1
- package/dist/scanners/engine/engine.d.ts +22 -1
- package/dist/scanners/engine/engine.d.ts.map +1 -1
- package/dist/scanners/engine/engine.js +78 -11
- package/dist/scanners/engine/engine.js.map +1 -1
- package/dist/scanners/engine/lifecycle.d.ts +7 -1
- package/dist/scanners/engine/lifecycle.d.ts.map +1 -1
- package/dist/scanners/events.d.ts +4 -0
- package/dist/scanners/events.d.ts.map +1 -1
- package/dist/scanners/events.js +2 -0
- package/dist/scanners/events.js.map +1 -1
- package/dist/scanners/external/scaffold/delivery.py +194 -25
- package/dist/scanners/external/scaffold/scaffold.py +172 -31
- package/dist/scanners/external/scaffold/senpi_mcp.py +150 -8
- package/dist/scanners/external/scaffold/tick_facts.py +130 -0
- package/dist/scanners/external/scaffold/ulid.py +58 -0
- package/dist/scanners/external-scanner-receiver.d.ts +17 -0
- package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
- package/dist/scanners/external-scanner-receiver.js +8 -0
- package/dist/scanners/external-scanner-receiver.js.map +1 -1
- package/dist/scanners/runtime-module.d.ts +1 -0
- package/dist/scanners/runtime-module.d.ts.map +1 -1
- package/dist/scanners/runtime-module.js +66 -9
- package/dist/scanners/runtime-module.js.map +1 -1
- package/dist/telemetry/attributes.d.ts +20 -0
- package/dist/telemetry/attributes.d.ts.map +1 -1
- package/dist/telemetry/attributes.js +24 -0
- package/dist/telemetry/attributes.js.map +1 -1
- package/dist/telemetry/event-builders.d.ts +81 -6
- package/dist/telemetry/event-builders.d.ts.map +1 -1
- package/dist/telemetry/event-builders.js +141 -17
- package/dist/telemetry/event-builders.js.map +1 -1
- package/dist/telemetry/event-store.d.ts +6 -0
- package/dist/telemetry/event-store.d.ts.map +1 -1
- package/dist/telemetry/event-store.js +12 -8
- package/dist/telemetry/event-store.js.map +1 -1
- package/dist/telemetry/live-telemetry.d.ts +238 -0
- package/dist/telemetry/live-telemetry.d.ts.map +1 -0
- package/dist/telemetry/live-telemetry.js +853 -0
- package/dist/telemetry/live-telemetry.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +14 -2
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +19 -4
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/telemetry/scanner-stats.d.ts +143 -0
- package/dist/telemetry/scanner-stats.d.ts.map +1 -0
- package/dist/telemetry/scanner-stats.js +426 -0
- package/dist/telemetry/scanner-stats.js.map +1 -0
- package/dist/telemetry/scanner-tick-feed.d.ts +87 -0
- package/dist/telemetry/scanner-tick-feed.d.ts.map +1 -0
- package/dist/telemetry/scanner-tick-feed.js +48 -0
- package/dist/telemetry/scanner-tick-feed.js.map +1 -0
- package/dist/telemetry/spans/redact-pii.d.ts.map +1 -1
- package/dist/telemetry/spans/redact-pii.js +18 -2
- package/dist/telemetry/spans/redact-pii.js.map +1 -1
- package/dist/telemetry/strategy-topology.d.ts +99 -0
- package/dist/telemetry/strategy-topology.d.ts.map +1 -0
- package/dist/telemetry/strategy-topology.js +93 -0
- package/dist/telemetry/strategy-topology.js.map +1 -0
- package/dist/telemetry/telemetry-publisher.d.ts +76 -0
- package/dist/telemetry/telemetry-publisher.d.ts.map +1 -0
- package/dist/telemetry/telemetry-publisher.js +247 -0
- package/dist/telemetry/telemetry-publisher.js.map +1 -0
- package/dist/telemetry/tick-frame.d.ts +212 -0
- package/dist/telemetry/tick-frame.d.ts.map +1 -0
- package/dist/telemetry/tick-frame.js +421 -0
- package/dist/telemetry/tick-frame.js.map +1 -0
- package/dist/telemetry/timeline-buffer.d.ts +95 -0
- package/dist/telemetry/timeline-buffer.d.ts.map +1 -0
- package/dist/telemetry/timeline-buffer.js +237 -0
- package/dist/telemetry/timeline-buffer.js.map +1 -0
- package/dist/telemetry/tracing.d.ts +9 -0
- package/dist/telemetry/tracing.d.ts.map +1 -1
- package/dist/telemetry/tracing.js +36 -0
- package/dist/telemetry/tracing.js.map +1 -1
- package/dist/types/dsl/index.d.ts +11 -0
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/types/scanner.d.ts +15 -0
- package/dist/types/scanner.d.ts.map +1 -1
- package/dist/utils/event-catalog.d.ts +166 -0
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +18 -0
- package/dist/utils/event-catalog.js.map +1 -1
- package/dist/utils/logger.d.ts +4 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/telemetry-required-log.d.ts +60 -0
- package/dist/utils/telemetry-required-log.d.ts.map +1 -0
- package/dist/utils/telemetry-required-log.js +105 -0
- package/dist/utils/telemetry-required-log.js.map +1 -0
- package/dist/utils/ulid.d.ts +9 -0
- package/dist/utils/ulid.d.ts.map +1 -0
- package/dist/utils/ulid.js +43 -0
- package/dist/utils/ulid.js.map +1 -0
- package/dist/validate/recipe-checks.d.ts.map +1 -1
- package/dist/validate/recipe-checks.js +2 -2
- package/dist/validate/recipe-checks.js.map +1 -1
- package/openclaw.plugin.json +8 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -64,7 +64,11 @@ import { createIntakeContext } from "./runtime-api/intake/intake-context.js";
|
|
|
64
64
|
import { wireRuntime, intakeUrlFor, selectScanners } from "./runtime-api/intake/intake-mount.js";
|
|
65
65
|
import { ingestDispatchMutex, ingestDispatchKey } from "./runtime-api/intake/ingest-mutex.js";
|
|
66
66
|
import { buildScaffoldErrorRouter } from "./runtime-api/intake/scaffold-error-router.js";
|
|
67
|
+
import { buildScaffoldTickRouter } from "./runtime-api/intake/scaffold-tick-router.js";
|
|
68
|
+
import { toIngestRequest } from "./runtime-api/intake/ingest-request.js";
|
|
67
69
|
import { loadRuntimeConfig } from "./runtime/load-runtime-config.js";
|
|
70
|
+
import { createLiveTelemetry } from "./telemetry/live-telemetry.js";
|
|
71
|
+
import { resolveTickTelemetry } from "./telemetry/tick-frame.js";
|
|
68
72
|
/** Initialize all core features (DSL, risk, health) on the given event bus. */
|
|
69
73
|
export function initCore(bus) {
|
|
70
74
|
initDsl(bus);
|
|
@@ -223,6 +227,16 @@ const senpiRuntimePlugin = {
|
|
|
223
227
|
? "env SENPI_LOG_LEVEL"
|
|
224
228
|
: "default";
|
|
225
229
|
pluginLogger.info(`[senpi-runtime] log level: ${startupLogLevel} (source: ${logLevelSourceLabel}). Set plugin config "logLevel" or env SENPI_LOG_LEVEL to debug|info|warn|error to override.`);
|
|
230
|
+
// Same shape as the log-level line above, and for the same reason: a box publishing less than
|
|
231
|
+
// an operator expects must say so at boot rather than be diagnosed from an empty screen. No env
|
|
232
|
+
// var is named because there is none — see `tickTelemetry` on SenpiRuntimePluginConfig.
|
|
233
|
+
const tickTelemetry = resolveTickTelemetry(config.tickTelemetry);
|
|
234
|
+
if (tickTelemetry.invalid !== undefined) {
|
|
235
|
+
pluginLogger.warn(`[senpi-runtime] Ignoring invalid plugin config ${tickTelemetry.invalid} (expected off|summary|verbose); keeping mcp=${tickTelemetry.settings.mcp} state=${tickTelemetry.settings.state}.`);
|
|
236
|
+
}
|
|
237
|
+
pluginLogger.info(tickTelemetry.settings.enabled
|
|
238
|
+
? `[senpi-runtime] tick telemetry: mcp=${tickTelemetry.settings.mcp} state=${tickTelemetry.settings.state} (source: ${config.tickTelemetry === undefined ? "default" : "plugin config tickTelemetry"}). Set plugin config "tickTelemetry" to off|summary|verbose, or {mcp,state}, to override.`
|
|
239
|
+
: `[senpi-runtime] tick telemetry: off (source: plugin config tickTelemetry). No per-tick frame is published.`);
|
|
226
240
|
const senpiCliStore = readSenpiCliStore();
|
|
227
241
|
const baseStateDir = resolveSenpiBaseStateDir({
|
|
228
242
|
store: senpiCliStore,
|
|
@@ -355,6 +369,7 @@ const senpiRuntimePlugin = {
|
|
|
355
369
|
}
|
|
356
370
|
let routeAcceptedSignalToRuntime;
|
|
357
371
|
let routeScaffoldErrorToRuntime;
|
|
372
|
+
let routeScaffoldTickToTelemetry;
|
|
358
373
|
const launchers = new Map();
|
|
359
374
|
// Runtimes that are UP but whose external scanners never wired (mount/launch/install
|
|
360
375
|
// failure), keyed runtimeId → failed phase. Populated at the four unwired sites,
|
|
@@ -382,10 +397,149 @@ const senpiRuntimePlugin = {
|
|
|
382
397
|
* engines, one margin account: the exact state `[E_WALLET_HAS_RUNTIME]` exists to prevent.
|
|
383
398
|
*/
|
|
384
399
|
const installsInFlight = new Map();
|
|
400
|
+
// ── Live telemetry ────────────────────────────────────────────────
|
|
401
|
+
// Every runtime this box knows about, running or not, keyed runtimeId. The topology payload has
|
|
402
|
+
// to name a stopped strategy too, so this is populated from the registry rather than from
|
|
403
|
+
// `runtimeHandles` (which holds only the live ones) and is the source of the two facts the
|
|
404
|
+
// registry entry does not carry: the strategy's display name and its declared scanners. Both come
|
|
405
|
+
// from the parsed YAML — `name` is the same `config.name` that `RuntimeHealthStatus.runtimeName`
|
|
406
|
+
// is built from, so a runtime whose config never parsed falls back to its id rather than to a
|
|
407
|
+
// guess, and its scanner list stays empty rather than inventing rows.
|
|
408
|
+
const telemetryStrategies = new Map();
|
|
409
|
+
const noteTelemetryRuntime = (runtimeId, address) => {
|
|
410
|
+
const existing = telemetryStrategies.get(runtimeId);
|
|
411
|
+
telemetryStrategies.set(runtimeId, {
|
|
412
|
+
address,
|
|
413
|
+
name: existing?.name ?? runtimeId,
|
|
414
|
+
scanners: existing?.scanners ?? [],
|
|
415
|
+
});
|
|
416
|
+
};
|
|
417
|
+
const noteTelemetryRuntimeConfig = (runtimeId, config) => {
|
|
418
|
+
telemetryStrategies.set(runtimeId, {
|
|
419
|
+
address: config.strategy?.wallet ?? telemetryStrategies.get(runtimeId)?.address ?? "",
|
|
420
|
+
name: config.name,
|
|
421
|
+
scanners: config.scanners ?? [],
|
|
422
|
+
});
|
|
423
|
+
};
|
|
385
424
|
// Dedicated logger for the runtime-api subtree — required because the
|
|
386
425
|
// PluginLogger surface (info/warn/error) lacks `debug`, while the
|
|
387
426
|
// Logger contract used by `runtime-api/` includes it.
|
|
388
427
|
const apiLogger = createLogger("[senpi-core][api]");
|
|
428
|
+
/**
|
|
429
|
+
* One gather per publish tick. Health and system state are read together because the two payloads
|
|
430
|
+
* need both: the topology reads health and `runtimeName`, the stats read the per-scanner rows,
|
|
431
|
+
* and `restartCount` / `degradedReason` live only on the health rows while every other scanner
|
|
432
|
+
* field lives only on the system-state rows — so they are joined here on `(runtimeId, scannerId)`.
|
|
433
|
+
*/
|
|
434
|
+
const collectTelemetryFacts = async () => {
|
|
435
|
+
const [statuses, states] = await Promise.all([
|
|
436
|
+
collectRuntimeHealthStatuses(runtimeHandles, api.logger),
|
|
437
|
+
collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir),
|
|
438
|
+
]);
|
|
439
|
+
const healthByRuntime = new Map();
|
|
440
|
+
for (const status of statuses) {
|
|
441
|
+
if (status.runtimeId !== undefined)
|
|
442
|
+
healthByRuntime.set(status.runtimeId, status);
|
|
443
|
+
}
|
|
444
|
+
const scannerState = [];
|
|
445
|
+
// Registration facts per (runtimeId, scannerId), so the topology can report a scanner's live
|
|
446
|
+
// schedule rather than only what its YAML declared.
|
|
447
|
+
const registrations = new Map();
|
|
448
|
+
for (const state of states) {
|
|
449
|
+
const runtimeId = state.runtimeId;
|
|
450
|
+
const healthRows = runtimeId === undefined
|
|
451
|
+
? []
|
|
452
|
+
: healthByRuntime.get(runtimeId)?.components.scanners.scanners ?? [];
|
|
453
|
+
for (const row of state.components.scanners.state.scanners) {
|
|
454
|
+
const supervision = healthRows.find((r) => r.scannerId === row.scannerId);
|
|
455
|
+
const snapshot = {
|
|
456
|
+
address: row.address,
|
|
457
|
+
scannerId: row.scannerId,
|
|
458
|
+
health: row.health,
|
|
459
|
+
degradedReason: supervision?.degradedReason ?? null,
|
|
460
|
+
enabled: row.enabled,
|
|
461
|
+
inFlightSince: row.inFlightSince,
|
|
462
|
+
lastRunStartedAt: row.lastRunStartedAt,
|
|
463
|
+
lastRunFinishedAt: row.lastRunFinishedAt,
|
|
464
|
+
lastRunStatus: row.lastRunStatus,
|
|
465
|
+
lastRunReason: row.lastRunReason,
|
|
466
|
+
nextRunAt: row.nextRunAt,
|
|
467
|
+
lastAliveAt: row.lastAliveAt,
|
|
468
|
+
// No supervision row means the restart count is unknown, not zero. A zero here would
|
|
469
|
+
// read as "this scanner has never restarted" on a scanner nobody is supervising.
|
|
470
|
+
restartCount: supervision?.restartCount ?? null,
|
|
471
|
+
};
|
|
472
|
+
scannerState.push(snapshot);
|
|
473
|
+
if (runtimeId !== undefined) {
|
|
474
|
+
registrations.set(`${runtimeId}::${row.scannerId}`, {
|
|
475
|
+
...snapshot,
|
|
476
|
+
scheduleMode: row.scheduleMode,
|
|
477
|
+
configVersion: row.configVersion,
|
|
478
|
+
intervalSeconds: row.intervalSeconds,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const strategies = [];
|
|
484
|
+
for (const [runtimeId, entry] of telemetryStrategies) {
|
|
485
|
+
const health = healthByRuntime.get(runtimeId);
|
|
486
|
+
const scanners = entry.scanners.map((declared) => {
|
|
487
|
+
const live = registrations.get(`${runtimeId}::${declared.name}`);
|
|
488
|
+
return live === undefined
|
|
489
|
+
? { entry: declared }
|
|
490
|
+
: {
|
|
491
|
+
entry: declared,
|
|
492
|
+
registration: {
|
|
493
|
+
enabled: live.enabled,
|
|
494
|
+
scheduleMode: live.scheduleMode,
|
|
495
|
+
// The engine's own cadence, which is what the scanner actually polls on — a
|
|
496
|
+
// recipe may omit it and take the scanner definition's default. An external
|
|
497
|
+
// scanner registers zero because it is POSTed to rather than polled, and only
|
|
498
|
+
// the recipe knows the cadence its scaffold was told to tick at.
|
|
499
|
+
intervalSeconds: live.intervalSeconds > 0 ? live.intervalSeconds : declared.interval_seconds ?? null,
|
|
500
|
+
configVersion: live.configVersion,
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
});
|
|
504
|
+
strategies.push({
|
|
505
|
+
runtimeId,
|
|
506
|
+
address: entry.address,
|
|
507
|
+
// `runtimeName` is built from the same `config.name`; the indexed value is used because it
|
|
508
|
+
// is available for a stopped runtime too, where there is no health status to read.
|
|
509
|
+
name: health?.runtimeName ?? entry.name,
|
|
510
|
+
scanners,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
strategies,
|
|
515
|
+
runningRuntimeIds: runtimeHandles,
|
|
516
|
+
unwiredRuntimeIds: unwiredRuntimes,
|
|
517
|
+
readHealth: (runtimeId) => healthByRuntime.get(runtimeId),
|
|
518
|
+
scannerState,
|
|
519
|
+
};
|
|
520
|
+
};
|
|
521
|
+
const liveTelemetry = createLiveTelemetry({
|
|
522
|
+
collectFacts: collectTelemetryFacts,
|
|
523
|
+
tickTelemetry: config.tickTelemetry,
|
|
524
|
+
});
|
|
525
|
+
/**
|
|
526
|
+
* Wrapped gateway methods capture the broadcast handle on the FIRST request to ANY of them —
|
|
527
|
+
* not only on subscribe. `ctx.context` is the only route to the websocket push layer and it
|
|
528
|
+
* exists only inside a handler, so capturing it in the subscribe method alone would mean a box
|
|
529
|
+
* whose client never subscribes can never publish anything, silently.
|
|
530
|
+
*
|
|
531
|
+
* The wrapper covers only what is registered through it. `senpi.deploy.start`,
|
|
532
|
+
* `senpi.deploy.status` and `senpi.update` are registered straight onto the plugin api by their
|
|
533
|
+
* own registrars and never arm the handle. That is not a gap in practice: one wrapped call arms
|
|
534
|
+
* it for the life of the process, and the websocket client admits `senpi.telemetry.subscribe`,
|
|
535
|
+
* which is wrapped.
|
|
536
|
+
*/
|
|
537
|
+
const registerGatewayMethod = (name, handler, opts) => {
|
|
538
|
+
api.registerGatewayMethod(name, (ctx) => {
|
|
539
|
+
liveTelemetry.captureBroadcast(ctx, name);
|
|
540
|
+
return handler(ctx);
|
|
541
|
+
}, opts);
|
|
542
|
+
};
|
|
389
543
|
// A gateway seam failed to wire/launch a runtime's external scanner stack: the runtime is up but
|
|
390
544
|
// runs blind on internal scanners only. Two records, by design: a first-class `runtime.scanners_unwired`
|
|
391
545
|
// event (the queryable/alertable fact, by phase) and the migrated exception-form `logger.error`
|
|
@@ -559,26 +713,14 @@ const senpiRuntimePlugin = {
|
|
|
559
713
|
// /signals will not be mounted and no signals will be routed.
|
|
560
714
|
if (sharedIntakeContext) {
|
|
561
715
|
const ctx = sharedIntakeContext;
|
|
562
|
-
routeAcceptedSignalToRuntime = (signal,
|
|
716
|
+
routeAcceptedSignalToRuntime = (signal, correlationId, scannerId) => {
|
|
563
717
|
const resolved = ctx.registry.resolve(scannerId);
|
|
564
718
|
if (!resolved)
|
|
565
719
|
return;
|
|
566
720
|
const h = runtimeHandles.get(resolved.runtimeId);
|
|
567
721
|
if (!h)
|
|
568
722
|
return;
|
|
569
|
-
const request =
|
|
570
|
-
scanner: resolved.scannerName,
|
|
571
|
-
signal_id: signal.signal_id, // greppable handle across intake→ingest (obs only)
|
|
572
|
-
asset: signal.asset,
|
|
573
|
-
direction: signal.direction,
|
|
574
|
-
...(signal.signal_type !== undefined && { signal_type: signal.signal_type }),
|
|
575
|
-
data: signal.data,
|
|
576
|
-
// Per-signal sizing rides at the top level; the open-position action
|
|
577
|
-
// reads request.marginPct / request.leverage directly. Without this a
|
|
578
|
-
// supervised signal's sizing is lost ("no margin source resolved").
|
|
579
|
-
...(signal.marginPct !== undefined && { marginPct: signal.marginPct }),
|
|
580
|
-
...(signal.leverage !== undefined && { leverage: signal.leverage }),
|
|
581
|
-
};
|
|
723
|
+
const request = toIngestRequest(signal, correlationId, resolved.scannerName);
|
|
582
724
|
// Trace the sizing-relevant fields the narrowed request carries, so a
|
|
583
725
|
// field dropped at this seam is visible instead of vanishing silently.
|
|
584
726
|
apiLogger.debug(`[intake] ingest req signal_id=${signal.signal_id} scanner=${resolved.scannerName} asset=${signal.asset} direction=${signal.direction} marginPct=${signal.marginPct} leverage=${signal.leverage}`);
|
|
@@ -598,15 +740,18 @@ const senpiRuntimePlugin = {
|
|
|
598
740
|
// The single server-level onError closure — the `/errors` counterpart of
|
|
599
741
|
// routeAcceptedSignalToRuntime. It records a scaffold-reported tick failure into
|
|
600
742
|
// the owning runtime's scanner telemetry so a scanner that fails every tick stops
|
|
601
|
-
// rendering "healthy".
|
|
743
|
+
// rendering "healthy". Filter: only tick-level failure types count — a
|
|
744
|
+
// `candidate_rejected` body is a per-candidate reject on an otherwise-OK tick, not
|
|
745
|
+
// a failed run.
|
|
602
746
|
//
|
|
603
|
-
// This is the SHARED implementation (scaffold-error-router.ts),
|
|
604
|
-
// buildIntakeMount's onScaffoldError
|
|
605
|
-
//
|
|
606
|
-
// list it reached the tested seam and never reached this route, so the
|
|
607
|
-
// wedged-scanner signal was green in CI and inert in production. There is now one
|
|
608
|
-
// filter, and scaffold-error-router.test.ts drives THIS closure.
|
|
747
|
+
// This is the SHARED implementation (scaffold-error-router.ts), so this route and
|
|
748
|
+
// buildIntakeMount's onScaffoldError apply one filter, and scaffold-error-router.test.ts
|
|
749
|
+
// drives THIS closure.
|
|
609
750
|
routeScaffoldErrorToRuntime = buildScaffoldErrorRouter(ctx, runtimeHandles);
|
|
751
|
+
// The per-tick level for a supervised external scanner. Both intake routes call it, so a
|
|
752
|
+
// quiet tick and a failed tick are equally visible; it needs no runtime handle because it
|
|
753
|
+
// publishes a level rather than recording a run.
|
|
754
|
+
routeScaffoldTickToTelemetry = buildScaffoldTickRouter(ctx);
|
|
610
755
|
}
|
|
611
756
|
// One bounded read: which registered wallets the platform says are dead, so a restart does
|
|
612
757
|
// not re-scaffold runtimes on closed wallets (fail-open — see runtime/boot-skip.ts).
|
|
@@ -658,6 +803,9 @@ const senpiRuntimePlugin = {
|
|
|
658
803
|
continue;
|
|
659
804
|
const pathOrContent = entry.runtimeYamlPath ?? entry.runtimeYamlContent;
|
|
660
805
|
const entryStateDir = buildRuntimeStateDir(baseStateDir, entry.id, entry.wallet ?? "");
|
|
806
|
+
// Indexed before the boot attempt: a runtime whose start throws is installed but stopped,
|
|
807
|
+
// and the telemetry topology has to be able to say so.
|
|
808
|
+
noteTelemetryRuntime(entry.id, entry.wallet ?? "");
|
|
661
809
|
const handle = await run(pathOrContent, {
|
|
662
810
|
fromPath: hasPath,
|
|
663
811
|
stateDir: entryStateDir,
|
|
@@ -681,6 +829,7 @@ const senpiRuntimePlugin = {
|
|
|
681
829
|
if (sharedIntakeContext) {
|
|
682
830
|
try {
|
|
683
831
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
832
|
+
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
684
833
|
// Content-installed runtimes carry their recipe dir in the registry
|
|
685
834
|
// entry (runtimeYamlDir) so relative scanner paths still resolve
|
|
686
835
|
// after a restart; path-installed ones derive it from the path.
|
|
@@ -732,6 +881,7 @@ const senpiRuntimePlugin = {
|
|
|
732
881
|
// event) so listRuntimes/health don't render a bare "running".
|
|
733
882
|
try {
|
|
734
883
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
884
|
+
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
735
885
|
if (selectScanners(runtimeConfig).length > 0) {
|
|
736
886
|
unwiredRuntimes.set(entry.id, "no_intake");
|
|
737
887
|
handle.attachSupervisionSource(() => ({
|
|
@@ -790,6 +940,7 @@ const senpiRuntimePlugin = {
|
|
|
790
940
|
intakeContext: sharedIntakeContext,
|
|
791
941
|
onAccepted: routeAcceptedSignalToRuntime,
|
|
792
942
|
onError: routeScaffoldErrorToRuntime,
|
|
943
|
+
onTick: routeScaffoldTickToTelemetry,
|
|
793
944
|
});
|
|
794
945
|
apiHandle.ready();
|
|
795
946
|
}
|
|
@@ -833,6 +984,12 @@ const senpiRuntimePlugin = {
|
|
|
833
984
|
}
|
|
834
985
|
}
|
|
835
986
|
}
|
|
987
|
+
// Publish ticks start only once the runtimes are up, so the first frames describe a real
|
|
988
|
+
// box. Gated by the same switch the skills coordinator uses: unit tests boot `register()`
|
|
989
|
+
// and must not acquire background timers. The subscribe method still answers without them.
|
|
990
|
+
if (process.env.SENPI_RUNTIME_API_DISABLED !== "true") {
|
|
991
|
+
liveTelemetry.start();
|
|
992
|
+
}
|
|
836
993
|
// Last, once everything above is up: the reaper's post-boot pass and hourly sweep. Its
|
|
837
994
|
// not-active escalation is also off until here, so no delete can land while the boot loop is
|
|
838
995
|
// still wiring runtimes and launchers. Nothing here may fail start() — a throw from inside the boot loop takes every runtime down, which
|
|
@@ -850,6 +1007,11 @@ const senpiRuntimePlugin = {
|
|
|
850
1007
|
}
|
|
851
1008
|
},
|
|
852
1009
|
async stop() {
|
|
1010
|
+
// Live telemetry goes first: it owns a timer and an in-memory buffer, nothing downstream
|
|
1011
|
+
// depends on it, and stopping it here guarantees no publish tick reads a half-torn-down
|
|
1012
|
+
// runtime handle. Records emitted during the teardown below land in a buffer nobody drains,
|
|
1013
|
+
// which is the right trade for a process on its way out.
|
|
1014
|
+
liveTelemetry.stop();
|
|
853
1015
|
// Stop the reaper first so no pass starts deleting while the runtimes are torn down below,
|
|
854
1016
|
// and wait (bounded) for a pass already under way, so its delete cannot stop a handle the
|
|
855
1017
|
// teardown below is stopping too.
|
|
@@ -1189,6 +1351,10 @@ const senpiRuntimePlugin = {
|
|
|
1189
1351
|
onStrategyNotActive: (address) => reaper.checkWallet(address),
|
|
1190
1352
|
});
|
|
1191
1353
|
runtimeHandles.set(resolvedId, handle);
|
|
1354
|
+
if (parsedConfig)
|
|
1355
|
+
noteTelemetryRuntimeConfig(resolvedId, parsedConfig);
|
|
1356
|
+
else
|
|
1357
|
+
noteTelemetryRuntime(resolvedId, wallet);
|
|
1192
1358
|
// Registry create fact — distinct from the runtime.started boot transition (which also fires
|
|
1193
1359
|
// on a transient restart). Emitted inside emitEvent's guard so telemetry never fails the install.
|
|
1194
1360
|
emitEvent(apiLogger, () => buildRuntimeCreatedEvent({ runtimeId: resolvedId, address: wallet, recipeName: runtimeName }));
|
|
@@ -1301,7 +1467,7 @@ const senpiRuntimePlugin = {
|
|
|
1301
1467
|
}
|
|
1302
1468
|
}
|
|
1303
1469
|
/** Gateway respond accepts (ok, payload?, error?) with error as { code, message }; plugin SDK types may omit the third arg. */
|
|
1304
|
-
|
|
1470
|
+
registerGatewayMethod("senpi.installRuntime", async (ctx) => {
|
|
1305
1471
|
const respond = ctx.respond;
|
|
1306
1472
|
const result = await installRuntimeCore(ctx.params);
|
|
1307
1473
|
if (result.ok)
|
|
@@ -1309,7 +1475,7 @@ const senpiRuntimePlugin = {
|
|
|
1309
1475
|
else
|
|
1310
1476
|
respond(false, undefined, result.error);
|
|
1311
1477
|
}, { scope: "operator.write" });
|
|
1312
|
-
|
|
1478
|
+
registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
|
|
1313
1479
|
const respond = ctx.respond;
|
|
1314
1480
|
const { readRegistry } = await import("./runtime/strategy-registry.js");
|
|
1315
1481
|
const registry = await readRegistry(baseStateDir);
|
|
@@ -1452,6 +1618,9 @@ const senpiRuntimePlugin = {
|
|
|
1452
1618
|
// "running — NO ENTRY SCANNERS" forever. The map is otherwise only cleared on a
|
|
1453
1619
|
// successful wire/install, which a scanner-less reinstall never reaches.
|
|
1454
1620
|
unwiredRuntimes.delete(targetId);
|
|
1621
|
+
// Same reuse hazard: a deleted id left in the index would keep publishing a strategy that no
|
|
1622
|
+
// longer exists, and would hand a reinstall the old name and scanner list.
|
|
1623
|
+
telemetryStrategies.delete(targetId);
|
|
1455
1624
|
bootSkippedRuntimes.delete(targetId);
|
|
1456
1625
|
// Archive state directory before removing from registry
|
|
1457
1626
|
if (deletedEntry) {
|
|
@@ -1482,7 +1651,7 @@ const senpiRuntimePlugin = {
|
|
|
1482
1651
|
}));
|
|
1483
1652
|
return { ok: true, payload: { id: targetId } };
|
|
1484
1653
|
}
|
|
1485
|
-
|
|
1654
|
+
registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
|
|
1486
1655
|
const respond = ctx.respond;
|
|
1487
1656
|
const result = await deleteRuntimeCore(ctx.params);
|
|
1488
1657
|
if (result.ok)
|
|
@@ -1589,6 +1758,17 @@ const senpiRuntimePlugin = {
|
|
|
1589
1758
|
return checkProofCoversRecipe(packageDir, verifyProof(packageDir), appliedRecipe, (f) => readFileSync(f, "utf8"));
|
|
1590
1759
|
},
|
|
1591
1760
|
applyToRuntime: makeApplyToRuntime(runtimeHandles, async (runtimeId, next, recipe) => {
|
|
1761
|
+
// Re-index the topology facts FIRST, ahead of every early return and every failure path
|
|
1762
|
+
// below. `makeApplyToRuntime` only reaches this callback once the in-process swap has
|
|
1763
|
+
// succeeded, so `next` is already the recipe this box runs: its display name and its
|
|
1764
|
+
// declared scanner list are the ones the topology payload must name. Leaving the index on
|
|
1765
|
+
// the previous config would publish strategy rows the new scanner stats cannot join to.
|
|
1766
|
+
//
|
|
1767
|
+
// Ahead of the early return because an apply on a box with no intake still swapped the
|
|
1768
|
+
// recipe, and ahead of the launcher because a launcher that fails is a runtime running the
|
|
1769
|
+
// new recipe with no external scanners — the degraded state a dashboard most needs to show,
|
|
1770
|
+
// and one it can only render if the declared scanners are the new ones.
|
|
1771
|
+
noteTelemetryRuntimeConfig(runtimeId, next);
|
|
1592
1772
|
// Restart this runtime's external scanner children against the new package.
|
|
1593
1773
|
//
|
|
1594
1774
|
// The rebuild swaps in-process components; these are separate OS processes, spawned once
|
|
@@ -1736,7 +1916,7 @@ const senpiRuntimePlugin = {
|
|
|
1736
1916
|
* producers — slated for removal in Task 5 once the migration counter
|
|
1737
1917
|
* decays to zero (see `runtime-phase-2-api-plan.md` Section 8).
|
|
1738
1918
|
*/
|
|
1739
|
-
|
|
1919
|
+
registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
|
|
1740
1920
|
const respond = ctx.respond;
|
|
1741
1921
|
// TODO(s11): wire actual telemetry counter for
|
|
1742
1922
|
// runtime.deprecated_gateway_ingest_call (event name, properties:
|
|
@@ -1755,6 +1935,7 @@ const senpiRuntimePlugin = {
|
|
|
1755
1935
|
...(request.direction !== undefined && { direction: request.direction }),
|
|
1756
1936
|
...(request.score !== undefined && { score: request.score }),
|
|
1757
1937
|
...(request.signal_type !== undefined && { signal_type: request.signal_type }),
|
|
1938
|
+
...(request.tick_id !== undefined && { tick_id: request.tick_id }),
|
|
1758
1939
|
...(request.data !== undefined && { data: request.data }),
|
|
1759
1940
|
...(request.signals !== undefined && { signals: request.signals }),
|
|
1760
1941
|
};
|
|
@@ -1812,7 +1993,7 @@ const senpiRuntimePlugin = {
|
|
|
1812
1993
|
});
|
|
1813
1994
|
}
|
|
1814
1995
|
}, { scope: "operator.write" });
|
|
1815
|
-
|
|
1996
|
+
registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
|
|
1816
1997
|
const respond = ctx.respond;
|
|
1817
1998
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1818
1999
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1837,7 +2018,7 @@ const senpiRuntimePlugin = {
|
|
|
1837
2018
|
}
|
|
1838
2019
|
respond(true, { positions });
|
|
1839
2020
|
}, { scope: "operator.read" });
|
|
1840
|
-
|
|
2021
|
+
registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
|
|
1841
2022
|
const respond = ctx.respond;
|
|
1842
2023
|
const asset = typeof ctx.params?.asset === "string" ? ctx.params.asset : undefined;
|
|
1843
2024
|
if (!asset) {
|
|
@@ -1878,7 +2059,7 @@ const senpiRuntimePlugin = {
|
|
|
1878
2059
|
}
|
|
1879
2060
|
respond(true, { positions: matches });
|
|
1880
2061
|
}, { scope: "operator.read" });
|
|
1881
|
-
|
|
2062
|
+
registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
|
|
1882
2063
|
const respond = ctx.respond;
|
|
1883
2064
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1884
2065
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1904,7 +2085,7 @@ const senpiRuntimePlugin = {
|
|
|
1904
2085
|
closes = closes.slice(0, limit);
|
|
1905
2086
|
respond(true, { closes });
|
|
1906
2087
|
}, { scope: "operator.read" });
|
|
1907
|
-
|
|
2088
|
+
registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
|
|
1908
2089
|
const respond = ctx.respond;
|
|
1909
2090
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
1910
2091
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -1932,7 +2113,7 @@ const senpiRuntimePlugin = {
|
|
|
1932
2113
|
const statuses = await collectRuntimeHealthStatuses(runtimeHandles, api.logger);
|
|
1933
2114
|
respond(true, { statuses, updater });
|
|
1934
2115
|
}, { scope: "operator.read" });
|
|
1935
|
-
|
|
2116
|
+
registerGatewayMethod("senpi.getSystemState", async (ctx) => {
|
|
1936
2117
|
const respond = ctx.respond;
|
|
1937
2118
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
1938
2119
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -1960,6 +2141,36 @@ const senpiRuntimePlugin = {
|
|
|
1960
2141
|
const states = await collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir);
|
|
1961
2142
|
respond(true, { states, updater });
|
|
1962
2143
|
}, { scope: "operator.read" });
|
|
2144
|
+
/**
|
|
2145
|
+
* Live telemetry — the one method of the stream, called once per connect.
|
|
2146
|
+
*
|
|
2147
|
+
* It returns the current value of all three broadcast payloads plus the recent record window, so
|
|
2148
|
+
* a client joining a box that has been running for hours renders a full screen immediately
|
|
2149
|
+
* instead of waiting for the next thing to happen. `(afterEpoch, afterSeq)` resumes a client that
|
|
2150
|
+
* already holds part of this epoch's run — a cursor naming any other epoch is dropped, because a
|
|
2151
|
+
* seq from a previous run cannot address these records. `truncated` says when the records between
|
|
2152
|
+
* a cursor of this epoch and the oldest record still held are gone, which nothing in `recent`
|
|
2153
|
+
* alone can express.
|
|
2154
|
+
*
|
|
2155
|
+
* Read-only, like every other inspection method here. The scope that decides who RECEIVES the
|
|
2156
|
+
* broadcasts is a property of the client's own connection, not of this method.
|
|
2157
|
+
*/
|
|
2158
|
+
registerGatewayMethod("senpi.telemetry.subscribe", async (ctx) => {
|
|
2159
|
+
const respond = ctx.respond;
|
|
2160
|
+
try {
|
|
2161
|
+
respond(true, await liveTelemetry.snapshot({
|
|
2162
|
+
afterEpoch: ctx.params?.afterEpoch,
|
|
2163
|
+
afterSeq: ctx.params?.afterSeq,
|
|
2164
|
+
}));
|
|
2165
|
+
}
|
|
2166
|
+
catch (err) {
|
|
2167
|
+
api.logger.warn("[senpi-runtime] telemetry subscribe error", { err });
|
|
2168
|
+
respond(false, undefined, {
|
|
2169
|
+
code: "INTERNAL_ERROR",
|
|
2170
|
+
message: `Failed to build the telemetry snapshot: ${formatGatewayError(err)}`,
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
}, { scope: "operator.read" });
|
|
1963
2174
|
/**
|
|
1964
2175
|
* Force the auto-update poll to run now instead of waiting for its timer.
|
|
1965
2176
|
*
|
|
@@ -2018,7 +2229,7 @@ const senpiRuntimePlugin = {
|
|
|
2018
2229
|
* persisted action-store machinery (`src/actions/store/`) usable
|
|
2019
2230
|
* from the operator-facing CLI again.
|
|
2020
2231
|
*/
|
|
2021
|
-
|
|
2232
|
+
registerGatewayMethod("senpi.listActions", async (ctx) => {
|
|
2022
2233
|
const respond = ctx.respond;
|
|
2023
2234
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2024
2235
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -2048,7 +2259,7 @@ const senpiRuntimePlugin = {
|
|
|
2048
2259
|
}
|
|
2049
2260
|
respond(true, { actions: merged });
|
|
2050
2261
|
}, { scope: "operator.read" });
|
|
2051
|
-
|
|
2262
|
+
registerGatewayMethod("senpi.getActionState", async (ctx) => {
|
|
2052
2263
|
const respond = ctx.respond;
|
|
2053
2264
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2054
2265
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -2085,7 +2296,7 @@ const senpiRuntimePlugin = {
|
|
|
2085
2296
|
});
|
|
2086
2297
|
}
|
|
2087
2298
|
}, { scope: "operator.read" });
|
|
2088
|
-
|
|
2299
|
+
registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
|
|
2089
2300
|
const respond = ctx.respond;
|
|
2090
2301
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2091
2302
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -2153,7 +2364,7 @@ const senpiRuntimePlugin = {
|
|
|
2153
2364
|
// reachable on HTTP `/audit`. Passes through the caller-controlled subset
|
|
2154
2365
|
// of the audit_query contract; resource scoping (wallet → strategyId) is
|
|
2155
2366
|
// owned by handle.auditQuery, never the caller.
|
|
2156
|
-
|
|
2367
|
+
registerGatewayMethod("senpi.auditQuery", async (ctx) => {
|
|
2157
2368
|
const respond = ctx.respond;
|
|
2158
2369
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2159
2370
|
if (!runtimeId) {
|
|
@@ -2201,7 +2412,7 @@ const senpiRuntimePlugin = {
|
|
|
2201
2412
|
// its state root private, so re-derive it from baseStateDir + the handle's
|
|
2202
2413
|
// wallet (same helper the writer's stateDir was built with); the writer
|
|
2203
2414
|
// nests entries under `<root>/<address>/risk-guard/audit.jsonl`.
|
|
2204
|
-
|
|
2415
|
+
registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
|
|
2205
2416
|
const respond = ctx.respond;
|
|
2206
2417
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2207
2418
|
if (!runtimeId) {
|
|
@@ -2244,7 +2455,7 @@ const senpiRuntimePlugin = {
|
|
|
2244
2455
|
// Per-strategy local domain-event ring (on-disk JSONL) — the trade narrative, queryable locally.
|
|
2245
2456
|
// Same state-root re-derivation as getRiskAudit: the Runtime keeps its root private, so rebuild it
|
|
2246
2457
|
// from baseStateDir + the handle's wallet; the writer nests rows under `<root>/strategies/<address>/events/`.
|
|
2247
|
-
|
|
2458
|
+
registerGatewayMethod("senpi.getEvents", async (ctx) => {
|
|
2248
2459
|
const respond = ctx.respond;
|
|
2249
2460
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2250
2461
|
if (!runtimeId) {
|
|
@@ -2301,12 +2512,12 @@ const senpiRuntimePlugin = {
|
|
|
2301
2512
|
}
|
|
2302
2513
|
}, { scope: "operator.read" });
|
|
2303
2514
|
// ─── Skills manager — read + mutate ──────────────────────────────────────
|
|
2304
|
-
|
|
2515
|
+
registerGatewayMethod("senpi.skills.status", async (ctx) => {
|
|
2305
2516
|
const respond = ctx.respond;
|
|
2306
2517
|
const snapshot = skillsCoordinator.getSnapshot();
|
|
2307
2518
|
respond(true, { snapshot });
|
|
2308
2519
|
}, { scope: "operator.read" });
|
|
2309
|
-
|
|
2520
|
+
registerGatewayMethod("senpi.skills.update", async (ctx) => {
|
|
2310
2521
|
const respond = ctx.respond;
|
|
2311
2522
|
const name = typeof ctx.params?.name === "string" ? ctx.params.name.trim() : undefined;
|
|
2312
2523
|
if (!name) {
|
|
@@ -2331,7 +2542,7 @@ const senpiRuntimePlugin = {
|
|
|
2331
2542
|
});
|
|
2332
2543
|
}
|
|
2333
2544
|
}, { scope: "operator.write" });
|
|
2334
|
-
|
|
2545
|
+
registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
|
|
2335
2546
|
const respond = ctx.respond;
|
|
2336
2547
|
try {
|
|
2337
2548
|
const results = await skillsCoordinator.updateAll();
|
|
@@ -2397,6 +2608,7 @@ function parseGatewayExternalScannerIngestParams(params) {
|
|
|
2397
2608
|
direction: params?.direction,
|
|
2398
2609
|
score: params?.score,
|
|
2399
2610
|
signal_type: params?.signal_type,
|
|
2611
|
+
tick_id: params?.tick_id,
|
|
2400
2612
|
data: params?.data,
|
|
2401
2613
|
signals: params?.signals,
|
|
2402
2614
|
},
|