@senpi/runtime 3.0.135 → 3.0.139
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/dist/actions/action-events.d.ts +7 -21
- package/dist/actions/action-events.d.ts.map +1 -1
- package/dist/actions/action-events.js +26 -54
- 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 +7 -38
- 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 +15 -122
- 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 +2 -15
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/actions/event-derivation.d.ts +2 -71
- package/dist/actions/event-derivation.d.ts.map +1 -1
- package/dist/actions/event-derivation.js +8 -251
- package/dist/actions/event-derivation.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts +2 -12
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +41 -141
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.d.ts +1 -2
- package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.js +17 -62
- package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
- package/dist/actions/store/types.d.ts +2 -9
- 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 +0 -13
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js +0 -13
- 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 -4
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/constants/dsl/index.d.ts +0 -6
- package/dist/constants/dsl/index.d.ts.map +1 -1
- package/dist/constants/dsl/index.js +0 -6
- package/dist/constants/dsl/index.js.map +1 -1
- package/dist/constants/telemetry.d.ts +15 -0
- package/dist/constants/telemetry.d.ts.map +1 -0
- package/dist/constants/telemetry.js +28 -0
- package/dist/constants/telemetry.js.map +1 -0
- package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
- package/dist/dsl/engine-client/event-mapping.js +0 -14
- package/dist/dsl/engine-client/event-mapping.js.map +1 -1
- package/dist/dsl/events/build-closed-event.d.ts +1 -16
- package/dist/dsl/events/build-closed-event.d.ts.map +1 -1
- package/dist/dsl/events/build-closed-event.js +0 -6
- package/dist/dsl/events/build-closed-event.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts +0 -7
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +7 -37
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/monitor/create-monitor.d.ts +2 -6
- package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
- package/dist/dsl/monitor/create-monitor.js +9 -82
- package/dist/dsl/monitor/create-monitor.js.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.d.ts +2 -45
- package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.js +3 -52
- package/dist/dsl/monitor/dsl-failure-events.js.map +1 -1
- package/dist/dsl/monitor/process-one-position.d.ts +0 -1
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +36 -159
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts +2 -8
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +8 -20
- package/dist/dsl/plugin/index.js.map +1 -1
- package/dist/health/types.d.ts +0 -13
- package/dist/health/types.d.ts.map +1 -1
- package/dist/health/types.js +0 -12
- package/dist/health/types.js.map +1 -1
- package/dist/index.d.ts +8 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -272
- package/dist/index.js.map +1 -1
- package/dist/runtime/action-registry.d.ts.map +1 -1
- package/dist/runtime/action-registry.js +3 -4
- package/dist/runtime/action-registry.js.map +1 -1
- package/dist/runtime/domain-events.d.ts.map +1 -1
- package/dist/runtime/domain-events.js +0 -7
- package/dist/runtime/domain-events.js.map +1 -1
- package/dist/runtime/events-telemetry.d.ts +96 -0
- package/dist/runtime/events-telemetry.d.ts.map +1 -0
- package/dist/runtime/events-telemetry.js +232 -0
- package/dist/runtime/events-telemetry.js.map +1 -0
- package/dist/runtime/lifecycle-events.d.ts +3 -15
- package/dist/runtime/lifecycle-events.d.ts.map +1 -1
- package/dist/runtime/lifecycle-events.js +3 -17
- package/dist/runtime/lifecycle-events.js.map +1 -1
- package/dist/runtime/lifecycle-reaper.d.ts +6 -4
- package/dist/runtime/lifecycle-reaper.d.ts.map +1 -1
- package/dist/runtime/lifecycle-reaper.js +6 -4
- package/dist/runtime/lifecycle-reaper.js.map +1 -1
- package/dist/runtime/reconcile-run.d.ts +3 -13
- package/dist/runtime/reconcile-run.d.ts.map +1 -1
- package/dist/runtime/reconcile-run.js +3 -4
- package/dist/runtime/reconcile-run.js.map +1 -1
- package/dist/runtime/reconcile-state.d.ts +0 -8
- package/dist/runtime/reconcile-state.d.ts.map +1 -1
- package/dist/runtime/reconcile-state.js +1 -4
- package/dist/runtime/reconcile-state.js.map +1 -1
- package/dist/runtime/risk-guard.d.ts +20 -1
- package/dist/runtime/risk-guard.d.ts.map +1 -1
- package/dist/runtime/risk-guard.js +47 -6
- package/dist/runtime/risk-guard.js.map +1 -1
- package/dist/runtime/run.d.ts +7 -1
- package/dist/runtime/run.d.ts.map +1 -1
- package/dist/runtime/run.js +7 -0
- 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 +22 -2
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +74 -39
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/strategies-store.d.ts +26 -0
- package/dist/runtime/strategies-store.d.ts.map +1 -0
- package/dist/runtime/strategies-store.js +47 -0
- package/dist/runtime/strategies-store.js.map +1 -0
- package/dist/runtime/strategies-telemetry.d.ts +91 -0
- package/dist/runtime/strategies-telemetry.d.ts.map +1 -0
- package/dist/runtime/strategies-telemetry.js +128 -0
- package/dist/runtime/strategies-telemetry.js.map +1 -0
- package/dist/runtime/strategy-lifecycle.d.ts +108 -2
- package/dist/runtime/strategy-lifecycle.d.ts.map +1 -1
- package/dist/runtime/strategy-lifecycle.js +325 -4
- package/dist/runtime/strategy-lifecycle.js.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.d.ts +0 -13
- package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js +0 -1
- 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 +1 -2
- 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 +0 -1
- package/dist/runtime-api/handlers/ingest-signals.js.map +1 -1
- package/dist/runtime-api/handlers/types.d.ts +0 -5
- package/dist/runtime-api/handlers/types.d.ts.map +1 -1
- 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 +3 -9
- package/dist/runtime-api/intake/intake-events.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts +2 -0
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +25 -7
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/runtime-api/intake/process-error-post.d.ts +1 -6
- package/dist/runtime-api/intake/process-error-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-error-post.js +1 -5
- package/dist/runtime-api/intake/process-error-post.js.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.d.ts +1 -7
- package/dist/runtime-api/intake/process-signal-post.d.ts.map +1 -1
- package/dist/runtime-api/intake/process-signal-post.js +0 -5
- 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/scanner-launcher.d.ts.map +1 -1
- package/dist/runtime-api/intake/scanner-launcher.js +0 -15
- package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
- package/dist/runtime-api/intake/types.d.ts +0 -55
- package/dist/runtime-api/intake/types.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.d.ts +0 -2
- package/dist/runtime-api/routes/signals.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.js +11 -5
- package/dist/runtime-api/routes/signals.js.map +1 -1
- package/dist/runtime-api/routes/signals.schema.d.ts +0 -43
- package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.js +0 -43
- package/dist/runtime-api/routes/signals.schema.js.map +1 -1
- package/dist/runtime-api/server.d.ts +1 -7
- package/dist/runtime-api/server.d.ts.map +1 -1
- package/dist/runtime-api/server.js +0 -1
- package/dist/runtime-api/server.js.map +1 -1
- package/dist/scanners/engine/engine.d.ts +1 -22
- package/dist/scanners/engine/engine.d.ts.map +1 -1
- package/dist/scanners/engine/engine.js +11 -78
- package/dist/scanners/engine/engine.js.map +1 -1
- package/dist/scanners/engine/lifecycle.d.ts +1 -7
- package/dist/scanners/engine/lifecycle.d.ts.map +1 -1
- package/dist/scanners/events.d.ts +0 -4
- package/dist/scanners/events.d.ts.map +1 -1
- package/dist/scanners/events.js +0 -2
- package/dist/scanners/events.js.map +1 -1
- package/dist/scanners/external/scaffold/delivery.py +25 -194
- package/dist/scanners/external/scaffold/scaffold.py +31 -172
- package/dist/scanners/external/scaffold/senpi_mcp.py +8 -150
- package/dist/scanners/external-scanner-receiver.d.ts +0 -17
- package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
- package/dist/scanners/external-scanner-receiver.js +0 -8
- package/dist/scanners/external-scanner-receiver.js.map +1 -1
- package/dist/scanners/runtime-module.d.ts +0 -1
- package/dist/scanners/runtime-module.d.ts.map +1 -1
- package/dist/scanners/runtime-module.js +9 -66
- package/dist/scanners/runtime-module.js.map +1 -1
- package/dist/state/state-manager.d.ts +2 -0
- package/dist/state/state-manager.d.ts.map +1 -1
- package/dist/state/state-manager.js +8 -0
- package/dist/state/state-manager.js.map +1 -1
- package/dist/telemetry/attributes.d.ts +0 -20
- package/dist/telemetry/attributes.d.ts.map +1 -1
- package/dist/telemetry/attributes.js +0 -24
- package/dist/telemetry/attributes.js.map +1 -1
- package/dist/telemetry/event-builders.d.ts +6 -81
- package/dist/telemetry/event-builders.d.ts.map +1 -1
- package/dist/telemetry/event-builders.js +17 -141
- package/dist/telemetry/event-builders.js.map +1 -1
- package/dist/telemetry/event-store.d.ts.map +1 -1
- package/dist/telemetry/event-store.js +2 -6
- package/dist/telemetry/event-store.js.map +1 -1
- package/dist/telemetry/metrics.d.ts +2 -14
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +4 -19
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/telemetry/spans/redact-pii.d.ts.map +1 -1
- package/dist/telemetry/spans/redact-pii.js +11 -16
- package/dist/telemetry/spans/redact-pii.js.map +1 -1
- package/dist/telemetry/tracing.d.ts +0 -9
- package/dist/telemetry/tracing.d.ts.map +1 -1
- package/dist/telemetry/tracing.js +0 -36
- package/dist/telemetry/tracing.js.map +1 -1
- package/dist/types/dsl/index.d.ts +0 -48
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/types/scanner.d.ts +0 -15
- package/dist/types/scanner.d.ts.map +1 -1
- package/dist/utils/event-catalog.d.ts +0 -202
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +0 -26
- package/dist/utils/event-catalog.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -4
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- 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 +1 -1
- package/package.json +1 -1
- package/dist/actions/skipped-signal-results.d.ts +0 -25
- package/dist/actions/skipped-signal-results.d.ts.map +0 -1
- package/dist/actions/skipped-signal-results.js +0 -71
- package/dist/actions/skipped-signal-results.js.map +0 -1
- package/dist/runtime-api/intake/ingest-request.d.ts +0 -17
- package/dist/runtime-api/intake/ingest-request.d.ts.map +0 -1
- package/dist/runtime-api/intake/ingest-request.js +0 -34
- package/dist/runtime-api/intake/ingest-request.js.map +0 -1
- package/dist/runtime-api/intake/scaffold-tick-router.d.ts +0 -39
- package/dist/runtime-api/intake/scaffold-tick-router.d.ts.map +0 -1
- package/dist/runtime-api/intake/scaffold-tick-router.js +0 -122
- package/dist/runtime-api/intake/scaffold-tick-router.js.map +0 -1
- package/dist/scanners/external/scaffold/tick_facts.py +0 -130
- package/dist/scanners/external/scaffold/ulid.py +0 -58
- package/dist/telemetry/dsl-positions.d.ts +0 -111
- package/dist/telemetry/dsl-positions.d.ts.map +0 -1
- package/dist/telemetry/dsl-positions.js +0 -118
- package/dist/telemetry/dsl-positions.js.map +0 -1
- package/dist/telemetry/live-telemetry.d.ts +0 -257
- package/dist/telemetry/live-telemetry.d.ts.map +0 -1
- package/dist/telemetry/live-telemetry.js +0 -959
- package/dist/telemetry/live-telemetry.js.map +0 -1
- package/dist/telemetry/scanner-stats.d.ts +0 -143
- package/dist/telemetry/scanner-stats.d.ts.map +0 -1
- package/dist/telemetry/scanner-stats.js +0 -426
- package/dist/telemetry/scanner-stats.js.map +0 -1
- package/dist/telemetry/scanner-tick-feed.d.ts +0 -87
- package/dist/telemetry/scanner-tick-feed.d.ts.map +0 -1
- package/dist/telemetry/scanner-tick-feed.js +0 -48
- package/dist/telemetry/scanner-tick-feed.js.map +0 -1
- package/dist/telemetry/strategy-topology.d.ts +0 -99
- package/dist/telemetry/strategy-topology.d.ts.map +0 -1
- package/dist/telemetry/strategy-topology.js +0 -93
- package/dist/telemetry/strategy-topology.js.map +0 -1
- package/dist/telemetry/telemetry-publisher.d.ts +0 -83
- package/dist/telemetry/telemetry-publisher.d.ts.map +0 -1
- package/dist/telemetry/telemetry-publisher.js +0 -256
- package/dist/telemetry/telemetry-publisher.js.map +0 -1
- package/dist/telemetry/tick-frame.d.ts +0 -212
- package/dist/telemetry/tick-frame.d.ts.map +0 -1
- package/dist/telemetry/tick-frame.js +0 -421
- package/dist/telemetry/tick-frame.js.map +0 -1
- package/dist/telemetry/timeline-buffer.d.ts +0 -95
- package/dist/telemetry/timeline-buffer.d.ts.map +0 -1
- package/dist/telemetry/timeline-buffer.js +0 -237
- package/dist/telemetry/timeline-buffer.js.map +0 -1
- package/dist/utils/telemetry-required-log.d.ts +0 -60
- package/dist/utils/telemetry-required-log.d.ts.map +0 -1
- package/dist/utils/telemetry-required-log.js +0 -105
- package/dist/utils/telemetry-required-log.js.map +0 -1
- package/dist/utils/ulid.d.ts +0 -9
- package/dist/utils/ulid.d.ts.map +0 -1
- package/dist/utils/ulid.js +0 -43
- package/dist/utils/ulid.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -20,6 +20,9 @@ import { bootSkippedStatusLabel, partitionByWalletStatus, readBootWalletStatuses
|
|
|
20
20
|
import { DeployJobController } from "./deploy/job.js";
|
|
21
21
|
import { registerDeployGatewayMethods } from "./deploy/register.js";
|
|
22
22
|
import { registerUpdateGatewayMethod } from "./runtime/update-register.js";
|
|
23
|
+
import { createStrategiesPublisher } from "./runtime/strategies-telemetry.js";
|
|
24
|
+
import { buildEventsReplay, createEventsPublisher, } from "./runtime/events-telemetry.js";
|
|
25
|
+
import { strategiesStore, strategyRecordFromConfig } from "./runtime/strategies-store.js";
|
|
23
26
|
import { checkProofCoversRecipe, makeApplyToRuntime, makeWriteRegistry, runtimeChangeLock, } from "./runtime/update-apply-deps.js";
|
|
24
27
|
import { INSTALL_DEADLINE_MS, WALLET_INSTALL_IN_FLIGHT_CODE } from "./deploy/deadlines.js";
|
|
25
28
|
import { AuditQueryError } from "./runtime/audit-query.types.js";
|
|
@@ -44,7 +47,7 @@ import { resolvePackageName } from "./runtime/auto-update/resolve-package-name.j
|
|
|
44
47
|
import { runOpenClawPluginsUpdate } from "./runtime/auto-update/run-update-command.js";
|
|
45
48
|
import { registerSpanHooks } from "./telemetry/spans/index.js";
|
|
46
49
|
import { registerSessionContextSeedHook } from "./prompt/session-context-seed-hook.js";
|
|
47
|
-
import { initDefaultPiiRedactor } from "./telemetry/spans/redact-pii.js";
|
|
50
|
+
import { getDefaultPiiRedactor, initDefaultPiiRedactor } from "./telemetry/spans/redact-pii.js";
|
|
48
51
|
import { initOtelSdk, shutdownOtelSdk } from "./telemetry/otel-sdk.js";
|
|
49
52
|
import { getLogLevel, getLogLevelSource, parseLogLevel, setLogLevel, } from "./utils/logger.js";
|
|
50
53
|
import { ExternalScannerIngestError, } from "./scanners/external-scanner-receiver.js";
|
|
@@ -64,11 +67,7 @@ import { createIntakeContext } from "./runtime-api/intake/intake-context.js";
|
|
|
64
67
|
import { wireRuntime, intakeUrlFor, selectScanners } from "./runtime-api/intake/intake-mount.js";
|
|
65
68
|
import { ingestDispatchMutex, ingestDispatchKey } from "./runtime-api/intake/ingest-mutex.js";
|
|
66
69
|
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";
|
|
69
70
|
import { loadRuntimeConfig } from "./runtime/load-runtime-config.js";
|
|
70
|
-
import { createLiveTelemetry } from "./telemetry/live-telemetry.js";
|
|
71
|
-
import { resolveTickTelemetry } from "./telemetry/tick-frame.js";
|
|
72
71
|
/** Initialize all core features (DSL, risk, health) on the given event bus. */
|
|
73
72
|
export function initCore(bus) {
|
|
74
73
|
initDsl(bus);
|
|
@@ -227,16 +226,6 @@ const senpiRuntimePlugin = {
|
|
|
227
226
|
? "env SENPI_LOG_LEVEL"
|
|
228
227
|
: "default";
|
|
229
228
|
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.`);
|
|
240
229
|
const senpiCliStore = readSenpiCliStore();
|
|
241
230
|
const baseStateDir = resolveSenpiBaseStateDir({
|
|
242
231
|
store: senpiCliStore,
|
|
@@ -369,8 +358,37 @@ const senpiRuntimePlugin = {
|
|
|
369
358
|
}
|
|
370
359
|
let routeAcceptedSignalToRuntime;
|
|
371
360
|
let routeScaffoldErrorToRuntime;
|
|
372
|
-
let routeScaffoldTickToTelemetry;
|
|
373
361
|
const launchers = new Map();
|
|
362
|
+
// Captured from the `senpi.telemetry.subscribe` handler's gateway context. The plugin API
|
|
363
|
+
// offers no broadcast function outside a request handler. Used by the strategies and events loops.
|
|
364
|
+
let telemetryBroadcast;
|
|
365
|
+
const strategiesPublisher = createStrategiesPublisher({
|
|
366
|
+
store: strategiesStore.view(),
|
|
367
|
+
// Same join as the scanner system state: the launcher's supervision row for the scanner name,
|
|
368
|
+
// whose lastAliveAt is read from the liveness store under the scanner's current id.
|
|
369
|
+
lastAliveAt: (runtimeId, scannerName) => launchers
|
|
370
|
+
.get(runtimeId)
|
|
371
|
+
?.describeSupervision()
|
|
372
|
+
.find((row) => row.scannerName === scannerName)?.lastAliveAt ?? null,
|
|
373
|
+
// In-memory read from the runtime's state manager, which outlives the DSL plugin across an
|
|
374
|
+
// in-place update. Empty when the committed recipe has no DSL.
|
|
375
|
+
dslStates: (runtimeId) => runtimeHandles.get(runtimeId)?.listActiveDslStates() ?? [],
|
|
376
|
+
getBroadcast: () => telemetryBroadcast,
|
|
377
|
+
onError: (err) => pluginLogger.warn("[senpi-runtime] strategies telemetry tick failed", {
|
|
378
|
+
err: err instanceof Error ? err.message : String(err),
|
|
379
|
+
}),
|
|
380
|
+
onDslError: (runtimeId, err) => pluginLogger.warn("[senpi-runtime] strategies telemetry dsl read failed; sending dsl []", {
|
|
381
|
+
runtimeId,
|
|
382
|
+
err: err instanceof Error ? err.message : String(err),
|
|
383
|
+
}),
|
|
384
|
+
});
|
|
385
|
+
const eventsPublisher = createEventsPublisher({
|
|
386
|
+
getBroadcast: () => telemetryBroadcast,
|
|
387
|
+
warn: (message, meta) => pluginLogger.warn(message, meta),
|
|
388
|
+
onError: (err) => pluginLogger.warn("[senpi-runtime] events telemetry tick failed", {
|
|
389
|
+
err: err instanceof Error ? err.message : String(err),
|
|
390
|
+
}),
|
|
391
|
+
});
|
|
374
392
|
// Runtimes that are UP but whose external scanners never wired (mount/launch/install
|
|
375
393
|
// failure), keyed runtimeId → failed phase. Populated at the four unwired sites,
|
|
376
394
|
// cleared on a successful wire/install. senpi.listRuntimes reads it to render
|
|
@@ -397,168 +415,10 @@ const senpiRuntimePlugin = {
|
|
|
397
415
|
* engines, one margin account: the exact state `[E_WALLET_HAS_RUNTIME]` exists to prevent.
|
|
398
416
|
*/
|
|
399
417
|
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
|
-
};
|
|
424
418
|
// Dedicated logger for the runtime-api subtree — required because the
|
|
425
419
|
// PluginLogger surface (info/warn/error) lacks `debug`, while the
|
|
426
420
|
// Logger contract used by `runtime-api/` includes it.
|
|
427
421
|
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
|
-
// The DSL states, read straight off each runtime's plugin. Synchronous and in-memory — this is
|
|
514
|
-
// the same store the `senpi.listDslPositions` RPC reads — so it costs the gather nothing, and a
|
|
515
|
-
// recipe with no DSL exit block has no plugin and contributes no rows. A runtime whose read
|
|
516
|
-
// throws is skipped rather than failing the whole gather, on the same rule health follows: a
|
|
517
|
-
// strategy mid-teardown must not take the stream down.
|
|
518
|
-
const dslPositions = [];
|
|
519
|
-
for (const [runtimeId, handle] of runtimeHandles) {
|
|
520
|
-
if (!handle.dsl)
|
|
521
|
-
continue;
|
|
522
|
-
try {
|
|
523
|
-
for (const state of handle.dsl.listActiveDslStates()) {
|
|
524
|
-
dslPositions.push({ runtimeId, state });
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
catch (err) {
|
|
528
|
-
api.logger.warn("[senpi-runtime] could not read DSL states for telemetry", { runtimeId, err });
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
return {
|
|
532
|
-
strategies,
|
|
533
|
-
runningRuntimeIds: runtimeHandles,
|
|
534
|
-
unwiredRuntimeIds: unwiredRuntimes,
|
|
535
|
-
readHealth: (runtimeId) => healthByRuntime.get(runtimeId),
|
|
536
|
-
scannerState,
|
|
537
|
-
dslPositions,
|
|
538
|
-
};
|
|
539
|
-
};
|
|
540
|
-
const liveTelemetry = createLiveTelemetry({
|
|
541
|
-
collectFacts: collectTelemetryFacts,
|
|
542
|
-
tickTelemetry: config.tickTelemetry,
|
|
543
|
-
});
|
|
544
|
-
/**
|
|
545
|
-
* Wrapped gateway methods capture the broadcast handle on the FIRST request to ANY of them —
|
|
546
|
-
* not only on subscribe. `ctx.context` is the only route to the websocket push layer and it
|
|
547
|
-
* exists only inside a handler, so capturing it in the subscribe method alone would mean a box
|
|
548
|
-
* whose client never subscribes can never publish anything, silently.
|
|
549
|
-
*
|
|
550
|
-
* The wrapper covers only what is registered through it. `senpi.deploy.start`,
|
|
551
|
-
* `senpi.deploy.status` and `senpi.update` are registered straight onto the plugin api by their
|
|
552
|
-
* own registrars and never arm the handle. That is not a gap in practice: one wrapped call arms
|
|
553
|
-
* it for the life of the process, and the websocket client admits `senpi.telemetry.subscribe`,
|
|
554
|
-
* which is wrapped.
|
|
555
|
-
*/
|
|
556
|
-
const registerGatewayMethod = (name, handler, opts) => {
|
|
557
|
-
api.registerGatewayMethod(name, (ctx) => {
|
|
558
|
-
liveTelemetry.captureBroadcast(ctx, name);
|
|
559
|
-
return handler(ctx);
|
|
560
|
-
}, opts);
|
|
561
|
-
};
|
|
562
422
|
// A gateway seam failed to wire/launch a runtime's external scanner stack: the runtime is up but
|
|
563
423
|
// runs blind on internal scanners only. Two records, by design: a first-class `runtime.scanners_unwired`
|
|
564
424
|
// event (the queryable/alertable fact, by phase) and the migrated exception-form `logger.error`
|
|
@@ -666,7 +526,8 @@ const senpiRuntimePlugin = {
|
|
|
666
526
|
// ── lifecycle reaper ─────────────────────────────────────────────────────────────────────────
|
|
667
527
|
// A runtime exists iff its strategy is open. Every close path ends at the backend and none of
|
|
668
528
|
// them tells this box, so the reaper asks: once after boot, hourly, and whenever the backend
|
|
669
|
-
// rejects an order with STRATEGY_NOT_ACTIVE.
|
|
529
|
+
// rejects an order with STRATEGY_NOT_ACTIVE. Enforcing by default since the 3.0.121 observe-only
|
|
530
|
+
// validation; SENPI_REAPER_ENFORCE=false returns a box to observe-only (see
|
|
670
531
|
// runtime/lifecycle-reaper.ts for the safety rule). Constructed here so both `run()` call sites
|
|
671
532
|
// (boot and install) can hand it their not-active rejections; it does no I/O until started.
|
|
672
533
|
const reaperBootDelayOverride = resolveReaperBootDelayMs(process.env);
|
|
@@ -732,14 +593,26 @@ const senpiRuntimePlugin = {
|
|
|
732
593
|
// /signals will not be mounted and no signals will be routed.
|
|
733
594
|
if (sharedIntakeContext) {
|
|
734
595
|
const ctx = sharedIntakeContext;
|
|
735
|
-
routeAcceptedSignalToRuntime = (signal,
|
|
596
|
+
routeAcceptedSignalToRuntime = (signal, _correlationId, scannerId) => {
|
|
736
597
|
const resolved = ctx.registry.resolve(scannerId);
|
|
737
598
|
if (!resolved)
|
|
738
599
|
return;
|
|
739
600
|
const h = runtimeHandles.get(resolved.runtimeId);
|
|
740
601
|
if (!h)
|
|
741
602
|
return;
|
|
742
|
-
const request =
|
|
603
|
+
const request = {
|
|
604
|
+
scanner: resolved.scannerName,
|
|
605
|
+
signal_id: signal.signal_id, // greppable handle across intake→ingest (obs only)
|
|
606
|
+
asset: signal.asset,
|
|
607
|
+
direction: signal.direction,
|
|
608
|
+
...(signal.signal_type !== undefined && { signal_type: signal.signal_type }),
|
|
609
|
+
data: signal.data,
|
|
610
|
+
// Per-signal sizing rides at the top level; the open-position action
|
|
611
|
+
// reads request.marginPct / request.leverage directly. Without this a
|
|
612
|
+
// supervised signal's sizing is lost ("no margin source resolved").
|
|
613
|
+
...(signal.marginPct !== undefined && { marginPct: signal.marginPct }),
|
|
614
|
+
...(signal.leverage !== undefined && { leverage: signal.leverage }),
|
|
615
|
+
};
|
|
743
616
|
// Trace the sizing-relevant fields the narrowed request carries, so a
|
|
744
617
|
// field dropped at this seam is visible instead of vanishing silently.
|
|
745
618
|
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}`);
|
|
@@ -759,18 +632,15 @@ const senpiRuntimePlugin = {
|
|
|
759
632
|
// The single server-level onError closure — the `/errors` counterpart of
|
|
760
633
|
// routeAcceptedSignalToRuntime. It records a scaffold-reported tick failure into
|
|
761
634
|
// the owning runtime's scanner telemetry so a scanner that fails every tick stops
|
|
762
|
-
// rendering "healthy".
|
|
763
|
-
// `candidate_rejected` body is a per-candidate reject on an otherwise-OK tick, not
|
|
764
|
-
// a failed run.
|
|
635
|
+
// rendering "healthy".
|
|
765
636
|
//
|
|
766
|
-
// This is the SHARED implementation (scaffold-error-router.ts),
|
|
767
|
-
// buildIntakeMount's onScaffoldError
|
|
768
|
-
//
|
|
637
|
+
// This is the SHARED implementation (scaffold-error-router.ts), not a copy of
|
|
638
|
+
// buildIntakeMount's onScaffoldError. It used to be a copy, and the copy had its
|
|
639
|
+
// own hardcoded three-type filter: when `mcp_error` was added to the tick-failure
|
|
640
|
+
// list it reached the tested seam and never reached this route, so the
|
|
641
|
+
// wedged-scanner signal was green in CI and inert in production. There is now one
|
|
642
|
+
// filter, and scaffold-error-router.test.ts drives THIS closure.
|
|
769
643
|
routeScaffoldErrorToRuntime = buildScaffoldErrorRouter(ctx, runtimeHandles);
|
|
770
|
-
// The per-tick level for a supervised external scanner. Both intake routes call it, so a
|
|
771
|
-
// quiet tick and a failed tick are equally visible; it needs no runtime handle because it
|
|
772
|
-
// publishes a level rather than recording a run.
|
|
773
|
-
routeScaffoldTickToTelemetry = buildScaffoldTickRouter(ctx);
|
|
774
644
|
}
|
|
775
645
|
// One bounded read: which registered wallets the platform says are dead, so a restart does
|
|
776
646
|
// not re-scaffold runtimes on closed wallets (fail-open — see runtime/boot-skip.ts).
|
|
@@ -822,9 +692,6 @@ const senpiRuntimePlugin = {
|
|
|
822
692
|
continue;
|
|
823
693
|
const pathOrContent = entry.runtimeYamlPath ?? entry.runtimeYamlContent;
|
|
824
694
|
const entryStateDir = buildRuntimeStateDir(baseStateDir, entry.id, entry.wallet ?? "");
|
|
825
|
-
// Indexed before the boot attempt: a runtime whose start throws is installed but stopped,
|
|
826
|
-
// and the telemetry topology has to be able to say so.
|
|
827
|
-
noteTelemetryRuntime(entry.id, entry.wallet ?? "");
|
|
828
695
|
const handle = await run(pathOrContent, {
|
|
829
696
|
fromPath: hasPath,
|
|
830
697
|
stateDir: entryStateDir,
|
|
@@ -848,7 +715,6 @@ const senpiRuntimePlugin = {
|
|
|
848
715
|
if (sharedIntakeContext) {
|
|
849
716
|
try {
|
|
850
717
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
851
|
-
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
852
718
|
// Content-installed runtimes carry their recipe dir in the registry
|
|
853
719
|
// entry (runtimeYamlDir) so relative scanner paths still resolve
|
|
854
720
|
// after a restart; path-installed ones derive it from the path.
|
|
@@ -900,7 +766,6 @@ const senpiRuntimePlugin = {
|
|
|
900
766
|
// event) so listRuntimes/health don't render a bare "running".
|
|
901
767
|
try {
|
|
902
768
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
903
|
-
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
904
769
|
if (selectScanners(runtimeConfig).length > 0) {
|
|
905
770
|
unwiredRuntimes.set(entry.id, "no_intake");
|
|
906
771
|
handle.attachSupervisionSource(() => ({
|
|
@@ -925,6 +790,7 @@ const senpiRuntimePlugin = {
|
|
|
925
790
|
if (h)
|
|
926
791
|
await h.stop().catch(() => { });
|
|
927
792
|
runtimeHandles.delete(started[i]);
|
|
793
|
+
strategiesStore.delete(started[i]);
|
|
928
794
|
}
|
|
929
795
|
throw err;
|
|
930
796
|
}
|
|
@@ -959,7 +825,6 @@ const senpiRuntimePlugin = {
|
|
|
959
825
|
intakeContext: sharedIntakeContext,
|
|
960
826
|
onAccepted: routeAcceptedSignalToRuntime,
|
|
961
827
|
onError: routeScaffoldErrorToRuntime,
|
|
962
|
-
onTick: routeScaffoldTickToTelemetry,
|
|
963
828
|
});
|
|
964
829
|
apiHandle.ready();
|
|
965
830
|
}
|
|
@@ -977,6 +842,7 @@ const senpiRuntimePlugin = {
|
|
|
977
842
|
if (h)
|
|
978
843
|
await h.stop().catch(() => { });
|
|
979
844
|
runtimeHandles.delete(started[i]);
|
|
845
|
+
strategiesStore.delete(started[i]);
|
|
980
846
|
}
|
|
981
847
|
throw err;
|
|
982
848
|
}
|
|
@@ -1003,12 +869,10 @@ const senpiRuntimePlugin = {
|
|
|
1003
869
|
}
|
|
1004
870
|
}
|
|
1005
871
|
}
|
|
1006
|
-
//
|
|
1007
|
-
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1010
|
-
liveTelemetry.start();
|
|
1011
|
-
}
|
|
872
|
+
// Ticks once now, with every booted runtime in the store, then every 30 s.
|
|
873
|
+
strategiesPublisher.start();
|
|
874
|
+
// Sends the events added since the previous send, every 1 s.
|
|
875
|
+
eventsPublisher.start();
|
|
1012
876
|
// Last, once everything above is up: the reaper's post-boot pass and hourly sweep. Its
|
|
1013
877
|
// not-active escalation is also off until here, so no delete can land while the boot loop is
|
|
1014
878
|
// still wiring runtimes and launchers. Nothing here may fail start() — a throw from inside the boot loop takes every runtime down, which
|
|
@@ -1026,15 +890,12 @@ const senpiRuntimePlugin = {
|
|
|
1026
890
|
}
|
|
1027
891
|
},
|
|
1028
892
|
async stop() {
|
|
1029
|
-
// Live telemetry goes first: it owns a timer and an in-memory buffer, nothing downstream
|
|
1030
|
-
// depends on it, and stopping it here guarantees no publish tick reads a half-torn-down
|
|
1031
|
-
// runtime handle. Records emitted during the teardown below land in a buffer nobody drains,
|
|
1032
|
-
// which is the right trade for a process on its way out.
|
|
1033
|
-
liveTelemetry.stop();
|
|
1034
893
|
// Stop the reaper first so no pass starts deleting while the runtimes are torn down below,
|
|
1035
894
|
// and wait (bounded) for a pass already under way, so its delete cannot stop a handle the
|
|
1036
895
|
// teardown below is stopping too.
|
|
1037
896
|
await reaper.stop();
|
|
897
|
+
strategiesPublisher.stop();
|
|
898
|
+
eventsPublisher.stop();
|
|
1038
899
|
// Drain the HTTP server FIRST so in-flight POST /signals requests
|
|
1039
900
|
// finish before the underlying runtime they target is torn down.
|
|
1040
901
|
// `apiHandle.close()` never throws.
|
|
@@ -1061,6 +922,7 @@ const senpiRuntimePlugin = {
|
|
|
1061
922
|
await h.stop();
|
|
1062
923
|
runtimeHandles.delete(ids[i]);
|
|
1063
924
|
}
|
|
925
|
+
strategiesStore.clear();
|
|
1064
926
|
pluginLogger.info("[senpi-runtime] All runtimes stopped");
|
|
1065
927
|
// Final, process-global OTel SDK flush. Last step, after every runtime is
|
|
1066
928
|
// stopped and all spans are emitted: we disabled HyperDX's own SIGTERM
|
|
@@ -1370,10 +1232,6 @@ const senpiRuntimePlugin = {
|
|
|
1370
1232
|
onStrategyNotActive: (address) => reaper.checkWallet(address),
|
|
1371
1233
|
});
|
|
1372
1234
|
runtimeHandles.set(resolvedId, handle);
|
|
1373
|
-
if (parsedConfig)
|
|
1374
|
-
noteTelemetryRuntimeConfig(resolvedId, parsedConfig);
|
|
1375
|
-
else
|
|
1376
|
-
noteTelemetryRuntime(resolvedId, wallet);
|
|
1377
1235
|
// Registry create fact — distinct from the runtime.started boot transition (which also fires
|
|
1378
1236
|
// on a transient restart). Emitted inside emitEvent's guard so telemetry never fails the install.
|
|
1379
1237
|
emitEvent(apiLogger, () => buildRuntimeCreatedEvent({ runtimeId: resolvedId, address: wallet, recipeName: runtimeName }));
|
|
@@ -1486,7 +1344,7 @@ const senpiRuntimePlugin = {
|
|
|
1486
1344
|
}
|
|
1487
1345
|
}
|
|
1488
1346
|
/** Gateway respond accepts (ok, payload?, error?) with error as { code, message }; plugin SDK types may omit the third arg. */
|
|
1489
|
-
registerGatewayMethod("senpi.installRuntime", async (ctx) => {
|
|
1347
|
+
api.registerGatewayMethod("senpi.installRuntime", async (ctx) => {
|
|
1490
1348
|
const respond = ctx.respond;
|
|
1491
1349
|
const result = await installRuntimeCore(ctx.params);
|
|
1492
1350
|
if (result.ok)
|
|
@@ -1494,7 +1352,7 @@ const senpiRuntimePlugin = {
|
|
|
1494
1352
|
else
|
|
1495
1353
|
respond(false, undefined, result.error);
|
|
1496
1354
|
}, { scope: "operator.write" });
|
|
1497
|
-
registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
|
|
1355
|
+
api.registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
|
|
1498
1356
|
const respond = ctx.respond;
|
|
1499
1357
|
const { readRegistry } = await import("./runtime/strategy-registry.js");
|
|
1500
1358
|
const registry = await readRegistry(baseStateDir);
|
|
@@ -1637,10 +1495,8 @@ const senpiRuntimePlugin = {
|
|
|
1637
1495
|
// "running — NO ENTRY SCANNERS" forever. The map is otherwise only cleared on a
|
|
1638
1496
|
// successful wire/install, which a scanner-less reinstall never reaches.
|
|
1639
1497
|
unwiredRuntimes.delete(targetId);
|
|
1640
|
-
// Same reuse hazard: a deleted id left in the index would keep publishing a strategy that no
|
|
1641
|
-
// longer exists, and would hand a reinstall the old name and scanner list.
|
|
1642
|
-
telemetryStrategies.delete(targetId);
|
|
1643
1498
|
bootSkippedRuntimes.delete(targetId);
|
|
1499
|
+
strategiesStore.delete(targetId);
|
|
1644
1500
|
// Archive state directory before removing from registry
|
|
1645
1501
|
if (deletedEntry) {
|
|
1646
1502
|
const runtimeStateDir = buildRuntimeStateDir(baseStateDir, targetId, deletedEntry.wallet ?? "");
|
|
@@ -1670,7 +1526,7 @@ const senpiRuntimePlugin = {
|
|
|
1670
1526
|
}));
|
|
1671
1527
|
return { ok: true, payload: { id: targetId } };
|
|
1672
1528
|
}
|
|
1673
|
-
registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
|
|
1529
|
+
api.registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
|
|
1674
1530
|
const respond = ctx.respond;
|
|
1675
1531
|
const result = await deleteRuntimeCore(ctx.params);
|
|
1676
1532
|
if (result.ok)
|
|
@@ -1777,17 +1633,8 @@ const senpiRuntimePlugin = {
|
|
|
1777
1633
|
return checkProofCoversRecipe(packageDir, verifyProof(packageDir), appliedRecipe, (f) => readFileSync(f, "utf8"));
|
|
1778
1634
|
},
|
|
1779
1635
|
applyToRuntime: makeApplyToRuntime(runtimeHandles, async (runtimeId, next, recipe) => {
|
|
1780
|
-
//
|
|
1781
|
-
|
|
1782
|
-
// succeeded, so `next` is already the recipe this box runs: its display name and its
|
|
1783
|
-
// declared scanner list are the ones the topology payload must name. Leaving the index on
|
|
1784
|
-
// the previous config would publish strategy rows the new scanner stats cannot join to.
|
|
1785
|
-
//
|
|
1786
|
-
// Ahead of the early return because an apply on a box with no intake still swapped the
|
|
1787
|
-
// recipe, and ahead of the launcher because a launcher that fails is a runtime running the
|
|
1788
|
-
// new recipe with no external scanners — the degraded state a dashboard most needs to show,
|
|
1789
|
-
// and one it can only render if the declared scanners are the new ones.
|
|
1790
|
-
noteTelemetryRuntimeConfig(runtimeId, next);
|
|
1636
|
+
// Runs only after the in-place swap succeeded.
|
|
1637
|
+
strategiesStore.set(runtimeId, strategyRecordFromConfig(next));
|
|
1791
1638
|
// Restart this runtime's external scanner children against the new package.
|
|
1792
1639
|
//
|
|
1793
1640
|
// The rebuild swaps in-process components; these are separate OS processes, spawned once
|
|
@@ -1935,7 +1782,7 @@ const senpiRuntimePlugin = {
|
|
|
1935
1782
|
* producers — slated for removal in Task 5 once the migration counter
|
|
1936
1783
|
* decays to zero (see `runtime-phase-2-api-plan.md` Section 8).
|
|
1937
1784
|
*/
|
|
1938
|
-
registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
|
|
1785
|
+
api.registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
|
|
1939
1786
|
const respond = ctx.respond;
|
|
1940
1787
|
// TODO(s11): wire actual telemetry counter for
|
|
1941
1788
|
// runtime.deprecated_gateway_ingest_call (event name, properties:
|
|
@@ -1954,7 +1801,6 @@ const senpiRuntimePlugin = {
|
|
|
1954
1801
|
...(request.direction !== undefined && { direction: request.direction }),
|
|
1955
1802
|
...(request.score !== undefined && { score: request.score }),
|
|
1956
1803
|
...(request.signal_type !== undefined && { signal_type: request.signal_type }),
|
|
1957
|
-
...(request.tick_id !== undefined && { tick_id: request.tick_id }),
|
|
1958
1804
|
...(request.data !== undefined && { data: request.data }),
|
|
1959
1805
|
...(request.signals !== undefined && { signals: request.signals }),
|
|
1960
1806
|
};
|
|
@@ -2012,7 +1858,7 @@ const senpiRuntimePlugin = {
|
|
|
2012
1858
|
});
|
|
2013
1859
|
}
|
|
2014
1860
|
}, { scope: "operator.write" });
|
|
2015
|
-
registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
|
|
1861
|
+
api.registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
|
|
2016
1862
|
const respond = ctx.respond;
|
|
2017
1863
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2018
1864
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -2037,7 +1883,7 @@ const senpiRuntimePlugin = {
|
|
|
2037
1883
|
}
|
|
2038
1884
|
respond(true, { positions });
|
|
2039
1885
|
}, { scope: "operator.read" });
|
|
2040
|
-
registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
|
|
1886
|
+
api.registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
|
|
2041
1887
|
const respond = ctx.respond;
|
|
2042
1888
|
const asset = typeof ctx.params?.asset === "string" ? ctx.params.asset : undefined;
|
|
2043
1889
|
if (!asset) {
|
|
@@ -2078,7 +1924,7 @@ const senpiRuntimePlugin = {
|
|
|
2078
1924
|
}
|
|
2079
1925
|
respond(true, { positions: matches });
|
|
2080
1926
|
}, { scope: "operator.read" });
|
|
2081
|
-
registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
|
|
1927
|
+
api.registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
|
|
2082
1928
|
const respond = ctx.respond;
|
|
2083
1929
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2084
1930
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -2104,7 +1950,7 @@ const senpiRuntimePlugin = {
|
|
|
2104
1950
|
closes = closes.slice(0, limit);
|
|
2105
1951
|
respond(true, { closes });
|
|
2106
1952
|
}, { scope: "operator.read" });
|
|
2107
|
-
registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
|
|
1953
|
+
api.registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
|
|
2108
1954
|
const respond = ctx.respond;
|
|
2109
1955
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
2110
1956
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -2132,7 +1978,7 @@ const senpiRuntimePlugin = {
|
|
|
2132
1978
|
const statuses = await collectRuntimeHealthStatuses(runtimeHandles, api.logger);
|
|
2133
1979
|
respond(true, { statuses, updater });
|
|
2134
1980
|
}, { scope: "operator.read" });
|
|
2135
|
-
registerGatewayMethod("senpi.getSystemState", async (ctx) => {
|
|
1981
|
+
api.registerGatewayMethod("senpi.getSystemState", async (ctx) => {
|
|
2136
1982
|
const respond = ctx.respond;
|
|
2137
1983
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
2138
1984
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -2160,6 +2006,24 @@ const senpiRuntimePlugin = {
|
|
|
2160
2006
|
const states = await collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir);
|
|
2161
2007
|
respond(true, { states, updater });
|
|
2162
2008
|
}, { scope: "operator.read" });
|
|
2009
|
+
/**
|
|
2010
|
+
* Registered with scope `operator.read`. Optional params `afterEpoch` (string) and `afterSeq`
|
|
2011
|
+
* (non-negative integer); a cursor needs both. Replies
|
|
2012
|
+
* `{ v: 1, strategies: <last strategies payload>, events: { epoch, events, truncated } }`, where
|
|
2013
|
+
* `v` is the version of the reply. Keeps the gateway context's broadcast function for the
|
|
2014
|
+
* strategies and events loops.
|
|
2015
|
+
*/
|
|
2016
|
+
api.registerGatewayMethod("senpi.telemetry.subscribe", async (ctx) => {
|
|
2017
|
+
const respond = ctx.respond;
|
|
2018
|
+
if (typeof ctx.context?.broadcast === "function")
|
|
2019
|
+
telemetryBroadcast = ctx.context.broadcast;
|
|
2020
|
+
const { afterEpoch, afterSeq } = ctx.params ?? {};
|
|
2021
|
+
const cursor = typeof afterEpoch === "string" && Number.isSafeInteger(afterSeq) && afterSeq >= 0
|
|
2022
|
+
? { afterEpoch, afterSeq: afterSeq }
|
|
2023
|
+
: undefined;
|
|
2024
|
+
const events = await buildEventsReplay({ cursor, redactor: getDefaultPiiRedactor() });
|
|
2025
|
+
respond(true, { v: 1, strategies: strategiesPublisher.lastPayload(), events });
|
|
2026
|
+
}, { scope: "operator.read" });
|
|
2163
2027
|
/**
|
|
2164
2028
|
* Force the auto-update poll to run now instead of waiting for its timer.
|
|
2165
2029
|
*
|
|
@@ -2199,36 +2063,6 @@ const senpiRuntimePlugin = {
|
|
|
2199
2063
|
});
|
|
2200
2064
|
}
|
|
2201
2065
|
}, { scope: "operator.write" });
|
|
2202
|
-
/**
|
|
2203
|
-
* Live telemetry — the one method of the stream, called once per connect.
|
|
2204
|
-
*
|
|
2205
|
-
* It returns the current value of all three broadcast payloads plus the recent record window, so
|
|
2206
|
-
* a client joining a box that has been running for hours renders a full screen immediately
|
|
2207
|
-
* instead of waiting for the next thing to happen. `(afterEpoch, afterSeq)` resumes a client that
|
|
2208
|
-
* already holds part of this epoch's run — a cursor naming any other epoch is dropped, because a
|
|
2209
|
-
* seq from a previous run cannot address these records. `truncated` says when the records between
|
|
2210
|
-
* a cursor of this epoch and the oldest record still held are gone, which nothing in `recent`
|
|
2211
|
-
* alone can express.
|
|
2212
|
-
*
|
|
2213
|
-
* Read-only, like every other inspection method here. The scope that decides who RECEIVES the
|
|
2214
|
-
* broadcasts is a property of the client's own connection, not of this method.
|
|
2215
|
-
*/
|
|
2216
|
-
registerGatewayMethod("senpi.telemetry.subscribe", async (ctx) => {
|
|
2217
|
-
const respond = ctx.respond;
|
|
2218
|
-
try {
|
|
2219
|
-
respond(true, await liveTelemetry.snapshot({
|
|
2220
|
-
afterEpoch: ctx.params?.afterEpoch,
|
|
2221
|
-
afterSeq: ctx.params?.afterSeq,
|
|
2222
|
-
}));
|
|
2223
|
-
}
|
|
2224
|
-
catch (err) {
|
|
2225
|
-
api.logger.warn("[senpi-runtime] telemetry subscribe error", { err });
|
|
2226
|
-
respond(false, undefined, {
|
|
2227
|
-
code: "INTERNAL_ERROR",
|
|
2228
|
-
message: `Failed to build the telemetry snapshot: ${formatGatewayError(err)}`,
|
|
2229
|
-
});
|
|
2230
|
-
}
|
|
2231
|
-
}, { scope: "operator.read" });
|
|
2232
2066
|
/**
|
|
2233
2067
|
* Action layer — read-only inspection.
|
|
2234
2068
|
*
|
|
@@ -2248,7 +2082,7 @@ const senpiRuntimePlugin = {
|
|
|
2248
2082
|
* persisted action-store machinery (`src/actions/store/`) usable
|
|
2249
2083
|
* from the operator-facing CLI again.
|
|
2250
2084
|
*/
|
|
2251
|
-
registerGatewayMethod("senpi.listActions", async (ctx) => {
|
|
2085
|
+
api.registerGatewayMethod("senpi.listActions", async (ctx) => {
|
|
2252
2086
|
const respond = ctx.respond;
|
|
2253
2087
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2254
2088
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -2278,7 +2112,7 @@ const senpiRuntimePlugin = {
|
|
|
2278
2112
|
}
|
|
2279
2113
|
respond(true, { actions: merged });
|
|
2280
2114
|
}, { scope: "operator.read" });
|
|
2281
|
-
registerGatewayMethod("senpi.getActionState", async (ctx) => {
|
|
2115
|
+
api.registerGatewayMethod("senpi.getActionState", async (ctx) => {
|
|
2282
2116
|
const respond = ctx.respond;
|
|
2283
2117
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2284
2118
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -2315,7 +2149,7 @@ const senpiRuntimePlugin = {
|
|
|
2315
2149
|
});
|
|
2316
2150
|
}
|
|
2317
2151
|
}, { scope: "operator.read" });
|
|
2318
|
-
registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
|
|
2152
|
+
api.registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
|
|
2319
2153
|
const respond = ctx.respond;
|
|
2320
2154
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2321
2155
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -2383,7 +2217,7 @@ const senpiRuntimePlugin = {
|
|
|
2383
2217
|
// reachable on HTTP `/audit`. Passes through the caller-controlled subset
|
|
2384
2218
|
// of the audit_query contract; resource scoping (wallet → strategyId) is
|
|
2385
2219
|
// owned by handle.auditQuery, never the caller.
|
|
2386
|
-
registerGatewayMethod("senpi.auditQuery", async (ctx) => {
|
|
2220
|
+
api.registerGatewayMethod("senpi.auditQuery", async (ctx) => {
|
|
2387
2221
|
const respond = ctx.respond;
|
|
2388
2222
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2389
2223
|
if (!runtimeId) {
|
|
@@ -2431,7 +2265,7 @@ const senpiRuntimePlugin = {
|
|
|
2431
2265
|
// its state root private, so re-derive it from baseStateDir + the handle's
|
|
2432
2266
|
// wallet (same helper the writer's stateDir was built with); the writer
|
|
2433
2267
|
// nests entries under `<root>/<address>/risk-guard/audit.jsonl`.
|
|
2434
|
-
registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
|
|
2268
|
+
api.registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
|
|
2435
2269
|
const respond = ctx.respond;
|
|
2436
2270
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2437
2271
|
if (!runtimeId) {
|
|
@@ -2474,7 +2308,7 @@ const senpiRuntimePlugin = {
|
|
|
2474
2308
|
// Per-strategy local domain-event ring (on-disk JSONL) — the trade narrative, queryable locally.
|
|
2475
2309
|
// Same state-root re-derivation as getRiskAudit: the Runtime keeps its root private, so rebuild it
|
|
2476
2310
|
// from baseStateDir + the handle's wallet; the writer nests rows under `<root>/strategies/<address>/events/`.
|
|
2477
|
-
registerGatewayMethod("senpi.getEvents", async (ctx) => {
|
|
2311
|
+
api.registerGatewayMethod("senpi.getEvents", async (ctx) => {
|
|
2478
2312
|
const respond = ctx.respond;
|
|
2479
2313
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2480
2314
|
if (!runtimeId) {
|
|
@@ -2531,12 +2365,12 @@ const senpiRuntimePlugin = {
|
|
|
2531
2365
|
}
|
|
2532
2366
|
}, { scope: "operator.read" });
|
|
2533
2367
|
// ─── Skills manager — read + mutate ──────────────────────────────────────
|
|
2534
|
-
registerGatewayMethod("senpi.skills.status", async (ctx) => {
|
|
2368
|
+
api.registerGatewayMethod("senpi.skills.status", async (ctx) => {
|
|
2535
2369
|
const respond = ctx.respond;
|
|
2536
2370
|
const snapshot = skillsCoordinator.getSnapshot();
|
|
2537
2371
|
respond(true, { snapshot });
|
|
2538
2372
|
}, { scope: "operator.read" });
|
|
2539
|
-
registerGatewayMethod("senpi.skills.update", async (ctx) => {
|
|
2373
|
+
api.registerGatewayMethod("senpi.skills.update", async (ctx) => {
|
|
2540
2374
|
const respond = ctx.respond;
|
|
2541
2375
|
const name = typeof ctx.params?.name === "string" ? ctx.params.name.trim() : undefined;
|
|
2542
2376
|
if (!name) {
|
|
@@ -2561,7 +2395,7 @@ const senpiRuntimePlugin = {
|
|
|
2561
2395
|
});
|
|
2562
2396
|
}
|
|
2563
2397
|
}, { scope: "operator.write" });
|
|
2564
|
-
registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
|
|
2398
|
+
api.registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
|
|
2565
2399
|
const respond = ctx.respond;
|
|
2566
2400
|
try {
|
|
2567
2401
|
const results = await skillsCoordinator.updateAll();
|
|
@@ -2627,7 +2461,6 @@ function parseGatewayExternalScannerIngestParams(params) {
|
|
|
2627
2461
|
direction: params?.direction,
|
|
2628
2462
|
score: params?.score,
|
|
2629
2463
|
signal_type: params?.signal_type,
|
|
2630
|
-
tick_id: params?.tick_id,
|
|
2631
2464
|
data: params?.data,
|
|
2632
2465
|
signals: params?.signals,
|
|
2633
2466
|
},
|