@senpi/runtime 3.0.118 → 3.0.122
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 -17
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +56 -162
- 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 +16 -11
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/constants/dsl/index.d.ts +17 -6
- package/dist/constants/dsl/index.d.ts.map +1 -1
- package/dist/constants/dsl/index.js +17 -6
- package/dist/constants/dsl/index.js.map +1 -1
- package/dist/dsl/engine/floors.d.ts +25 -1
- package/dist/dsl/engine/floors.d.ts.map +1 -1
- package/dist/dsl/engine/floors.js +38 -6
- package/dist/dsl/engine/floors.js.map +1 -1
- package/dist/dsl/engine/index.d.ts +1 -1
- package/dist/dsl/engine/index.d.ts.map +1 -1
- package/dist/dsl/engine/index.js +1 -1
- package/dist/dsl/engine/index.js.map +1 -1
- package/dist/dsl/engine/round.d.ts +22 -2
- package/dist/dsl/engine/round.d.ts.map +1 -1
- package/dist/dsl/engine/round.js +78 -5
- package/dist/dsl/engine/round.js.map +1 -1
- package/dist/dsl/engine/tick.d.ts.map +1 -1
- package/dist/dsl/engine/tick.js +31 -16
- package/dist/dsl/engine/tick.js.map +1 -1
- 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/events/initial-state.d.ts +1 -1
- package/dist/dsl/events/initial-state.d.ts.map +1 -1
- package/dist/dsl/events/initial-state.js +4 -7
- package/dist/dsl/events/initial-state.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 -15
- package/dist/dsl/monitor/create-monitor.js.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.d.ts +0 -2
- package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.js +1 -2
- package/dist/dsl/monitor/dsl-failure-events.js.map +1 -1
- package/dist/dsl/monitor/exchange-sync.d.ts +8 -3
- package/dist/dsl/monitor/exchange-sync.d.ts.map +1 -1
- package/dist/dsl/monitor/exchange-sync.js +16 -4
- package/dist/dsl/monitor/exchange-sync.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 +113 -62
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts +10 -12
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +14 -12
- 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 +4 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +206 -257
- 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/boot-skip.d.ts +73 -0
- package/dist/runtime/boot-skip.d.ts.map +1 -0
- package/dist/runtime/boot-skip.js +113 -0
- package/dist/runtime/boot-skip.js.map +1 -0
- package/dist/runtime/create-runtime-context.d.ts +2 -0
- package/dist/runtime/create-runtime-context.d.ts.map +1 -1
- package/dist/runtime/create-runtime-context.js +1 -0
- package/dist/runtime/create-runtime-context.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/lifecycle-events.d.ts +43 -10
- package/dist/runtime/lifecycle-events.d.ts.map +1 -1
- package/dist/runtime/lifecycle-events.js +83 -12
- package/dist/runtime/lifecycle-events.js.map +1 -1
- package/dist/runtime/lifecycle-reaper.d.ts +202 -0
- package/dist/runtime/lifecycle-reaper.d.ts.map +1 -0
- package/dist/runtime/lifecycle-reaper.js +390 -0
- package/dist/runtime/lifecycle-reaper.js.map +1 -0
- 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/run.d.ts +5 -1
- package/dist/runtime/run.d.ts.map +1 -1
- package/dist/runtime/run.js +1 -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 +2 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +4 -27
- package/dist/runtime/runtime.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/update-apply-deps.d.ts +23 -2
- package/dist/runtime/update-apply-deps.d.ts.map +1 -1
- package/dist/runtime/update-apply-deps.js +11 -5
- package/dist/runtime/update-apply-deps.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.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +19 -4
- 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 +1 -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/skills-manager/manifest.d.ts.map +1 -1
- package/dist/skills-manager/manifest.js +1 -0
- package/dist/skills-manager/manifest.js.map +1 -1
- package/dist/skills-manager/skills-manifest.json +1 -0
- 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 +0 -6
- package/dist/telemetry/event-store.d.ts.map +1 -1
- package/dist/telemetry/event-store.js +8 -12
- 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 +2 -18
- 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 +1 -12
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/types/runtime.d.ts +8 -0
- package/dist/types/runtime.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 +91 -166
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +13 -18
- package/dist/utils/event-catalog.js.map +1 -1
- package/dist/utils/keyed-mutex.d.ts +3 -2
- package/dist/utils/keyed-mutex.d.ts.map +1 -1
- package/dist/utils/keyed-mutex.js +3 -2
- package/dist/utils/keyed-mutex.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/live-telemetry.d.ts +0 -238
- package/dist/telemetry/live-telemetry.d.ts.map +0 -1
- package/dist/telemetry/live-telemetry.js +0 -853
- 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 -63
- package/dist/telemetry/telemetry-publisher.d.ts.map +0 -1
- package/dist/telemetry/telemetry-publisher.js +0 -229
- 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
|
@@ -16,10 +16,11 @@ import { parse as parseYaml } from "yaml";
|
|
|
16
16
|
import { join, dirname } from "node:path";
|
|
17
17
|
import { SenpiClient } from "./senpi/client.js";
|
|
18
18
|
import { buildWalletStatusHint } from "./senpi/strategy-status.js";
|
|
19
|
+
import { bootSkippedStatusLabel, partitionByWalletStatus, readBootWalletStatuses, trackableWalletKeys, } from "./runtime/boot-skip.js";
|
|
19
20
|
import { DeployJobController } from "./deploy/job.js";
|
|
20
21
|
import { registerDeployGatewayMethods } from "./deploy/register.js";
|
|
21
22
|
import { registerUpdateGatewayMethod } from "./runtime/update-register.js";
|
|
22
|
-
import { checkProofCoversRecipe, makeApplyToRuntime, makeWriteRegistry, } from "./runtime/update-apply-deps.js";
|
|
23
|
+
import { checkProofCoversRecipe, makeApplyToRuntime, makeWriteRegistry, runtimeChangeLock, } from "./runtime/update-apply-deps.js";
|
|
23
24
|
import { INSTALL_DEADLINE_MS, WALLET_INSTALL_IN_FLIGHT_CODE } from "./deploy/deadlines.js";
|
|
24
25
|
import { AuditQueryError } from "./runtime/audit-query.types.js";
|
|
25
26
|
import { buildRuntimeStateDir, archiveStateDir } from "./runtime/state-layout.js";
|
|
@@ -52,7 +53,8 @@ import { registerToolApprovalGate, resolveToolApprovalsMode, } from "./approvals
|
|
|
52
53
|
import { registerShowWidgetTool } from "./widgets/index.js";
|
|
53
54
|
import { EVENT_NAME, SENPI_ERROR_CODE, eventAttrs, scannerUnwiredErrorCode } from "./utils/event-catalog.js";
|
|
54
55
|
import { emitEvent } from "./actions/event-derivation.js";
|
|
55
|
-
import {
|
|
56
|
+
import { createLifecycleReaper, resolveReaperBootDelayMs, resolveReaperEnforcement, } from "./runtime/lifecycle-reaper.js";
|
|
57
|
+
import { buildRuntimeBootSkippedEvent, buildRuntimeBootStatusUnreadEvent, buildRuntimeCreatedEvent, buildRuntimeDeletedEvent, buildRuntimeUpdatedEvent, } from "./runtime/lifecycle-events.js";
|
|
56
58
|
import { isWalletAddress } from "./utils/wallet-address.js";
|
|
57
59
|
import { fileURLToPath } from "node:url";
|
|
58
60
|
import { loadApiConfig } from "./runtime-api/config.js";
|
|
@@ -62,11 +64,7 @@ import { createIntakeContext } from "./runtime-api/intake/intake-context.js";
|
|
|
62
64
|
import { wireRuntime, intakeUrlFor, selectScanners } from "./runtime-api/intake/intake-mount.js";
|
|
63
65
|
import { ingestDispatchMutex, ingestDispatchKey } from "./runtime-api/intake/ingest-mutex.js";
|
|
64
66
|
import { buildScaffoldErrorRouter } from "./runtime-api/intake/scaffold-error-router.js";
|
|
65
|
-
import { buildScaffoldTickRouter } from "./runtime-api/intake/scaffold-tick-router.js";
|
|
66
|
-
import { toIngestRequest } from "./runtime-api/intake/ingest-request.js";
|
|
67
67
|
import { loadRuntimeConfig } from "./runtime/load-runtime-config.js";
|
|
68
|
-
import { createLiveTelemetry } from "./telemetry/live-telemetry.js";
|
|
69
|
-
import { resolveTickTelemetry } from "./telemetry/tick-frame.js";
|
|
70
68
|
/** Initialize all core features (DSL, risk, health) on the given event bus. */
|
|
71
69
|
export function initCore(bus) {
|
|
72
70
|
initDsl(bus);
|
|
@@ -225,16 +223,6 @@ const senpiRuntimePlugin = {
|
|
|
225
223
|
? "env SENPI_LOG_LEVEL"
|
|
226
224
|
: "default";
|
|
227
225
|
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.`);
|
|
228
|
-
// Same shape as the log-level line above, and for the same reason: a box publishing less than
|
|
229
|
-
// an operator expects must say so at boot rather than be diagnosed from an empty screen. No env
|
|
230
|
-
// var is named because there is none — see `tickTelemetry` on SenpiRuntimePluginConfig.
|
|
231
|
-
const tickTelemetry = resolveTickTelemetry(config.tickTelemetry);
|
|
232
|
-
if (tickTelemetry.invalid !== undefined) {
|
|
233
|
-
pluginLogger.warn(`[senpi-runtime] Ignoring invalid plugin config ${tickTelemetry.invalid} (expected off|summary|verbose); keeping mcp=${tickTelemetry.settings.mcp} state=${tickTelemetry.settings.state}.`);
|
|
234
|
-
}
|
|
235
|
-
pluginLogger.info(tickTelemetry.settings.enabled
|
|
236
|
-
? `[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.`
|
|
237
|
-
: `[senpi-runtime] tick telemetry: off (source: plugin config tickTelemetry). No per-tick frame is published.`);
|
|
238
226
|
const senpiCliStore = readSenpiCliStore();
|
|
239
227
|
const baseStateDir = resolveSenpiBaseStateDir({
|
|
240
228
|
store: senpiCliStore,
|
|
@@ -367,13 +355,16 @@ const senpiRuntimePlugin = {
|
|
|
367
355
|
}
|
|
368
356
|
let routeAcceptedSignalToRuntime;
|
|
369
357
|
let routeScaffoldErrorToRuntime;
|
|
370
|
-
let routeScaffoldTickToTelemetry;
|
|
371
358
|
const launchers = new Map();
|
|
372
359
|
// Runtimes that are UP but whose external scanners never wired (mount/launch/install
|
|
373
360
|
// failure), keyed runtimeId → failed phase. Populated at the four unwired sites,
|
|
374
361
|
// cleared on a successful wire/install. senpi.listRuntimes reads it to render
|
|
375
362
|
// "running — NO ENTRY SCANNERS" instead of a bare "running" for a blind runtime.
|
|
376
363
|
const unwiredRuntimes = new Map();
|
|
364
|
+
// Registered runtimes boot left down because the platform reports their strategy wallet dead,
|
|
365
|
+
// keyed runtimeId → that status. senpi.listRuntimes reads it so the row says why it is stopped;
|
|
366
|
+
// cleared when the row is deleted.
|
|
367
|
+
const bootSkippedRuntimes = new Map();
|
|
377
368
|
/**
|
|
378
369
|
* Wallets with an install IN FLIGHT in this process, keyed by lowercased address.
|
|
379
370
|
*
|
|
@@ -391,149 +382,10 @@ const senpiRuntimePlugin = {
|
|
|
391
382
|
* engines, one margin account: the exact state `[E_WALLET_HAS_RUNTIME]` exists to prevent.
|
|
392
383
|
*/
|
|
393
384
|
const installsInFlight = new Map();
|
|
394
|
-
// ── Live telemetry ────────────────────────────────────────────────
|
|
395
|
-
// Every runtime this box knows about, running or not, keyed runtimeId. The topology payload has
|
|
396
|
-
// to name a stopped strategy too, so this is populated from the registry rather than from
|
|
397
|
-
// `runtimeHandles` (which holds only the live ones) and is the source of the two facts the
|
|
398
|
-
// registry entry does not carry: the strategy's display name and its declared scanners. Both come
|
|
399
|
-
// from the parsed YAML — `name` is the same `config.name` that `RuntimeHealthStatus.runtimeName`
|
|
400
|
-
// is built from, so a runtime whose config never parsed falls back to its id rather than to a
|
|
401
|
-
// guess, and its scanner list stays empty rather than inventing rows.
|
|
402
|
-
const telemetryStrategies = new Map();
|
|
403
|
-
const noteTelemetryRuntime = (runtimeId, address) => {
|
|
404
|
-
const existing = telemetryStrategies.get(runtimeId);
|
|
405
|
-
telemetryStrategies.set(runtimeId, {
|
|
406
|
-
address,
|
|
407
|
-
name: existing?.name ?? runtimeId,
|
|
408
|
-
scanners: existing?.scanners ?? [],
|
|
409
|
-
});
|
|
410
|
-
};
|
|
411
|
-
const noteTelemetryRuntimeConfig = (runtimeId, config) => {
|
|
412
|
-
telemetryStrategies.set(runtimeId, {
|
|
413
|
-
address: config.strategy?.wallet ?? telemetryStrategies.get(runtimeId)?.address ?? "",
|
|
414
|
-
name: config.name,
|
|
415
|
-
scanners: config.scanners ?? [],
|
|
416
|
-
});
|
|
417
|
-
};
|
|
418
385
|
// Dedicated logger for the runtime-api subtree — required because the
|
|
419
386
|
// PluginLogger surface (info/warn/error) lacks `debug`, while the
|
|
420
387
|
// Logger contract used by `runtime-api/` includes it.
|
|
421
388
|
const apiLogger = createLogger("[senpi-core][api]");
|
|
422
|
-
/**
|
|
423
|
-
* One gather per publish tick. Health and system state are read together because the two payloads
|
|
424
|
-
* need both: the topology reads health and `runtimeName`, the stats read the per-scanner rows,
|
|
425
|
-
* and `restartCount` / `degradedReason` live only on the health rows while every other scanner
|
|
426
|
-
* field lives only on the system-state rows — so they are joined here on `(runtimeId, scannerId)`.
|
|
427
|
-
*/
|
|
428
|
-
const collectTelemetryFacts = async () => {
|
|
429
|
-
const [statuses, states] = await Promise.all([
|
|
430
|
-
collectRuntimeHealthStatuses(runtimeHandles, api.logger),
|
|
431
|
-
collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir),
|
|
432
|
-
]);
|
|
433
|
-
const healthByRuntime = new Map();
|
|
434
|
-
for (const status of statuses) {
|
|
435
|
-
if (status.runtimeId !== undefined)
|
|
436
|
-
healthByRuntime.set(status.runtimeId, status);
|
|
437
|
-
}
|
|
438
|
-
const scannerState = [];
|
|
439
|
-
// Registration facts per (runtimeId, scannerId), so the topology can report a scanner's live
|
|
440
|
-
// schedule rather than only what its YAML declared.
|
|
441
|
-
const registrations = new Map();
|
|
442
|
-
for (const state of states) {
|
|
443
|
-
const runtimeId = state.runtimeId;
|
|
444
|
-
const healthRows = runtimeId === undefined
|
|
445
|
-
? []
|
|
446
|
-
: healthByRuntime.get(runtimeId)?.components.scanners.scanners ?? [];
|
|
447
|
-
for (const row of state.components.scanners.state.scanners) {
|
|
448
|
-
const supervision = healthRows.find((r) => r.scannerId === row.scannerId);
|
|
449
|
-
const snapshot = {
|
|
450
|
-
address: row.address,
|
|
451
|
-
scannerId: row.scannerId,
|
|
452
|
-
health: row.health,
|
|
453
|
-
degradedReason: supervision?.degradedReason ?? null,
|
|
454
|
-
enabled: row.enabled,
|
|
455
|
-
inFlightSince: row.inFlightSince,
|
|
456
|
-
lastRunStartedAt: row.lastRunStartedAt,
|
|
457
|
-
lastRunFinishedAt: row.lastRunFinishedAt,
|
|
458
|
-
lastRunStatus: row.lastRunStatus,
|
|
459
|
-
lastRunReason: row.lastRunReason,
|
|
460
|
-
nextRunAt: row.nextRunAt,
|
|
461
|
-
lastAliveAt: row.lastAliveAt,
|
|
462
|
-
// No supervision row means the restart count is unknown, not zero. A zero here would
|
|
463
|
-
// read as "this scanner has never restarted" on a scanner nobody is supervising.
|
|
464
|
-
restartCount: supervision?.restartCount ?? null,
|
|
465
|
-
};
|
|
466
|
-
scannerState.push(snapshot);
|
|
467
|
-
if (runtimeId !== undefined) {
|
|
468
|
-
registrations.set(`${runtimeId}::${row.scannerId}`, {
|
|
469
|
-
...snapshot,
|
|
470
|
-
scheduleMode: row.scheduleMode,
|
|
471
|
-
configVersion: row.configVersion,
|
|
472
|
-
intervalSeconds: row.intervalSeconds,
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
const strategies = [];
|
|
478
|
-
for (const [runtimeId, entry] of telemetryStrategies) {
|
|
479
|
-
const health = healthByRuntime.get(runtimeId);
|
|
480
|
-
const scanners = entry.scanners.map((declared) => {
|
|
481
|
-
const live = registrations.get(`${runtimeId}::${declared.name}`);
|
|
482
|
-
return live === undefined
|
|
483
|
-
? { entry: declared }
|
|
484
|
-
: {
|
|
485
|
-
entry: declared,
|
|
486
|
-
registration: {
|
|
487
|
-
enabled: live.enabled,
|
|
488
|
-
scheduleMode: live.scheduleMode,
|
|
489
|
-
// The engine's own cadence, which is what the scanner actually polls on — a
|
|
490
|
-
// recipe may omit it and take the scanner definition's default. An external
|
|
491
|
-
// scanner registers zero because it is POSTed to rather than polled, and only
|
|
492
|
-
// the recipe knows the cadence its scaffold was told to tick at.
|
|
493
|
-
intervalSeconds: live.intervalSeconds > 0 ? live.intervalSeconds : declared.interval_seconds ?? null,
|
|
494
|
-
configVersion: live.configVersion,
|
|
495
|
-
},
|
|
496
|
-
};
|
|
497
|
-
});
|
|
498
|
-
strategies.push({
|
|
499
|
-
runtimeId,
|
|
500
|
-
address: entry.address,
|
|
501
|
-
// `runtimeName` is built from the same `config.name`; the indexed value is used because it
|
|
502
|
-
// is available for a stopped runtime too, where there is no health status to read.
|
|
503
|
-
name: health?.runtimeName ?? entry.name,
|
|
504
|
-
scanners,
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
return {
|
|
508
|
-
strategies,
|
|
509
|
-
runningRuntimeIds: runtimeHandles,
|
|
510
|
-
unwiredRuntimeIds: unwiredRuntimes,
|
|
511
|
-
readHealth: (runtimeId) => healthByRuntime.get(runtimeId),
|
|
512
|
-
scannerState,
|
|
513
|
-
};
|
|
514
|
-
};
|
|
515
|
-
const liveTelemetry = createLiveTelemetry({
|
|
516
|
-
collectFacts: collectTelemetryFacts,
|
|
517
|
-
tickTelemetry: config.tickTelemetry,
|
|
518
|
-
});
|
|
519
|
-
/**
|
|
520
|
-
* Wrapped gateway methods capture the broadcast handle on the FIRST request to ANY of them —
|
|
521
|
-
* not only on subscribe. `ctx.context` is the only route to the websocket push layer and it
|
|
522
|
-
* exists only inside a handler, so capturing it in the subscribe method alone would mean a box
|
|
523
|
-
* whose client never subscribes can never publish anything, silently.
|
|
524
|
-
*
|
|
525
|
-
* The wrapper covers only what is registered through it. `senpi.deploy.start`,
|
|
526
|
-
* `senpi.deploy.status` and `senpi.update` are registered straight onto the plugin api by their
|
|
527
|
-
* own registrars and never arm the handle. That is not a gap in practice: one wrapped call arms
|
|
528
|
-
* it for the life of the process, and the websocket client admits `senpi.telemetry.subscribe`,
|
|
529
|
-
* which is wrapped.
|
|
530
|
-
*/
|
|
531
|
-
const registerGatewayMethod = (name, handler, opts) => {
|
|
532
|
-
api.registerGatewayMethod(name, (ctx) => {
|
|
533
|
-
liveTelemetry.captureBroadcast(ctx, name);
|
|
534
|
-
return handler(ctx);
|
|
535
|
-
}, opts);
|
|
536
|
-
};
|
|
537
389
|
// A gateway seam failed to wire/launch a runtime's external scanner stack: the runtime is up but
|
|
538
390
|
// runs blind on internal scanners only. Two records, by design: a first-class `runtime.scanners_unwired`
|
|
539
391
|
// event (the queryable/alertable fact, by phase) and the migrated exception-form `logger.error`
|
|
@@ -638,6 +490,39 @@ const senpiRuntimePlugin = {
|
|
|
638
490
|
now: () => Date.now(),
|
|
639
491
|
log: (line) => api.logger.info(`[senpi-runtime] ${line}`),
|
|
640
492
|
});
|
|
493
|
+
// ── lifecycle reaper ─────────────────────────────────────────────────────────────────────────
|
|
494
|
+
// A runtime exists iff its strategy is open. Every close path ends at the backend and none of
|
|
495
|
+
// them tells this box, so the reaper asks: once after boot, hourly, and whenever the backend
|
|
496
|
+
// rejects an order with STRATEGY_NOT_ACTIVE. Observe-only unless enforcement is on (see
|
|
497
|
+
// runtime/lifecycle-reaper.ts for the safety rule). Constructed here so both `run()` call sites
|
|
498
|
+
// (boot and install) can hand it their not-active rejections; it does no I/O until started.
|
|
499
|
+
const reaperBootDelayOverride = resolveReaperBootDelayMs(process.env);
|
|
500
|
+
const reaper = createLifecycleReaper({
|
|
501
|
+
readRegistry: async () => {
|
|
502
|
+
const { readRegistry } = await import("./runtime/strategy-registry.js");
|
|
503
|
+
return readRegistry(baseStateDir);
|
|
504
|
+
},
|
|
505
|
+
readStatuses: async (wallets, signal) => {
|
|
506
|
+
const client = mcpTransport ? new SenpiClient(mcpTransport) : new SenpiClient({ apiKey });
|
|
507
|
+
if (!mcpTransport)
|
|
508
|
+
await client.connect();
|
|
509
|
+
try {
|
|
510
|
+
return await client.listStrategies({ strategyAddresses: wallets }, { signal });
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
if (!mcpTransport)
|
|
514
|
+
await client.disconnect().catch(() => { });
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
deleteRuntime: async (id, reason, wallet) => {
|
|
518
|
+
const result = await deleteRuntimeCore({ id, deleteReason: reason, expectWallet: wallet });
|
|
519
|
+
return result.ok ? { ok: true } : { ok: false, code: result.error.code, message: result.error.message };
|
|
520
|
+
},
|
|
521
|
+
emit: (build) => emitEvent(apiLogger, build),
|
|
522
|
+
enforce: resolveReaperEnforcement(process.env),
|
|
523
|
+
logger: pluginLogger,
|
|
524
|
+
...(reaperBootDelayOverride !== undefined ? { bootDelayMs: reaperBootDelayOverride } : {}),
|
|
525
|
+
});
|
|
641
526
|
api.registerService({
|
|
642
527
|
id: "senpi-runtime",
|
|
643
528
|
async start() {
|
|
@@ -674,14 +559,26 @@ const senpiRuntimePlugin = {
|
|
|
674
559
|
// /signals will not be mounted and no signals will be routed.
|
|
675
560
|
if (sharedIntakeContext) {
|
|
676
561
|
const ctx = sharedIntakeContext;
|
|
677
|
-
routeAcceptedSignalToRuntime = (signal,
|
|
562
|
+
routeAcceptedSignalToRuntime = (signal, _correlationId, scannerId) => {
|
|
678
563
|
const resolved = ctx.registry.resolve(scannerId);
|
|
679
564
|
if (!resolved)
|
|
680
565
|
return;
|
|
681
566
|
const h = runtimeHandles.get(resolved.runtimeId);
|
|
682
567
|
if (!h)
|
|
683
568
|
return;
|
|
684
|
-
const request =
|
|
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
|
+
};
|
|
685
582
|
// Trace the sizing-relevant fields the narrowed request carries, so a
|
|
686
583
|
// field dropped at this seam is visible instead of vanishing silently.
|
|
687
584
|
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}`);
|
|
@@ -701,22 +598,58 @@ const senpiRuntimePlugin = {
|
|
|
701
598
|
// The single server-level onError closure — the `/errors` counterpart of
|
|
702
599
|
// routeAcceptedSignalToRuntime. It records a scaffold-reported tick failure into
|
|
703
600
|
// the owning runtime's scanner telemetry so a scanner that fails every tick stops
|
|
704
|
-
// rendering "healthy".
|
|
705
|
-
// `candidate_rejected` body is a per-candidate reject on an otherwise-OK tick, not
|
|
706
|
-
// a failed run.
|
|
601
|
+
// rendering "healthy".
|
|
707
602
|
//
|
|
708
|
-
// This is the SHARED implementation (scaffold-error-router.ts),
|
|
709
|
-
// buildIntakeMount's onScaffoldError
|
|
710
|
-
//
|
|
603
|
+
// This is the SHARED implementation (scaffold-error-router.ts), not a copy of
|
|
604
|
+
// buildIntakeMount's onScaffoldError. It used to be a copy, and the copy had its
|
|
605
|
+
// own hardcoded three-type filter: when `mcp_error` was added to the tick-failure
|
|
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.
|
|
711
609
|
routeScaffoldErrorToRuntime = buildScaffoldErrorRouter(ctx, runtimeHandles);
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
610
|
+
}
|
|
611
|
+
// One bounded read: which registered wallets the platform says are dead, so a restart does
|
|
612
|
+
// not re-scaffold runtimes on closed wallets (fail-open — see runtime/boot-skip.ts).
|
|
613
|
+
let walletStatuses = null;
|
|
614
|
+
// Lower-cased and trackable-only: the backend matches addresses case-sensitively against
|
|
615
|
+
// lower-case storage, and rejects the whole batch on one malformed address.
|
|
616
|
+
const registeredWallets = trackableWalletKeys(registry.runtimes);
|
|
617
|
+
if (registeredWallets.length > 0) {
|
|
618
|
+
const read = await readBootWalletStatuses(registeredWallets, async (wallets, signal) => {
|
|
619
|
+
const client = mcpTransport ? new SenpiClient(mcpTransport) : new SenpiClient({ apiKey });
|
|
620
|
+
if (!mcpTransport)
|
|
621
|
+
await client.connect();
|
|
622
|
+
try {
|
|
623
|
+
return await client.listStrategies({ strategyAddresses: wallets }, { signal });
|
|
624
|
+
}
|
|
625
|
+
finally {
|
|
626
|
+
if (!mcpTransport)
|
|
627
|
+
await client.disconnect().catch(() => { });
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
if (read.ok) {
|
|
631
|
+
walletStatuses = read.statuses;
|
|
632
|
+
reaper.recordStatuses(registeredWallets, read.statuses);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
pluginLogger.warn("[senpi-runtime] Could not read strategy statuses at boot; booting every registered runtime", { reason: read.reason, err: read.message });
|
|
636
|
+
emitEvent(apiLogger, () => buildRuntimeBootStatusUnreadEvent({ reason: read.reason, registeredCount: registeredWallets.length }));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const { boot: entriesToBoot, skipped } = partitionByWalletStatus(registry.runtimes, walletStatuses);
|
|
640
|
+
bootSkippedRuntimes.clear();
|
|
641
|
+
for (const { entry, status } of skipped) {
|
|
642
|
+
bootSkippedRuntimes.set(entry.id, status);
|
|
643
|
+
pluginLogger.info("[senpi-runtime] Not booting runtime: its strategy wallet is closed", {
|
|
644
|
+
runtimeId: entry.id,
|
|
645
|
+
wallet: entry.wallet,
|
|
646
|
+
status,
|
|
647
|
+
});
|
|
648
|
+
emitEvent(apiLogger, () => buildRuntimeBootSkippedEvent({ runtimeId: entry.id, address: entry.wallet ?? "", status }));
|
|
716
649
|
}
|
|
717
650
|
const started = [];
|
|
718
651
|
try {
|
|
719
|
-
for (const entry of
|
|
652
|
+
for (const entry of entriesToBoot) {
|
|
720
653
|
const hasPath = typeof entry.runtimeYamlPath === "string" && entry.runtimeYamlPath.length > 0;
|
|
721
654
|
const hasContent = typeof entry.runtimeYamlContent === "string";
|
|
722
655
|
if (hasPath && hasContent)
|
|
@@ -725,9 +658,6 @@ const senpiRuntimePlugin = {
|
|
|
725
658
|
continue;
|
|
726
659
|
const pathOrContent = entry.runtimeYamlPath ?? entry.runtimeYamlContent;
|
|
727
660
|
const entryStateDir = buildRuntimeStateDir(baseStateDir, entry.id, entry.wallet ?? "");
|
|
728
|
-
// Indexed before the boot attempt: a runtime whose start throws is installed but stopped,
|
|
729
|
-
// and the telemetry topology has to be able to say so.
|
|
730
|
-
noteTelemetryRuntime(entry.id, entry.wallet ?? "");
|
|
731
661
|
const handle = await run(pathOrContent, {
|
|
732
662
|
fromPath: hasPath,
|
|
733
663
|
stateDir: entryStateDir,
|
|
@@ -735,6 +665,7 @@ const senpiRuntimePlugin = {
|
|
|
735
665
|
apiKey,
|
|
736
666
|
scannerProviders,
|
|
737
667
|
runtimeId: entry.id,
|
|
668
|
+
onStrategyNotActive: (address) => reaper.checkWallet(address),
|
|
738
669
|
});
|
|
739
670
|
runtimeHandles.set(entry.id, handle);
|
|
740
671
|
started.push(entry.id);
|
|
@@ -750,7 +681,6 @@ const senpiRuntimePlugin = {
|
|
|
750
681
|
if (sharedIntakeContext) {
|
|
751
682
|
try {
|
|
752
683
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
753
|
-
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
754
684
|
// Content-installed runtimes carry their recipe dir in the registry
|
|
755
685
|
// entry (runtimeYamlDir) so relative scanner paths still resolve
|
|
756
686
|
// after a restart; path-installed ones derive it from the path.
|
|
@@ -802,7 +732,6 @@ const senpiRuntimePlugin = {
|
|
|
802
732
|
// event) so listRuntimes/health don't render a bare "running".
|
|
803
733
|
try {
|
|
804
734
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
805
|
-
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
806
735
|
if (selectScanners(runtimeConfig).length > 0) {
|
|
807
736
|
unwiredRuntimes.set(entry.id, "no_intake");
|
|
808
737
|
handle.attachSupervisionSource(() => ({
|
|
@@ -861,7 +790,6 @@ const senpiRuntimePlugin = {
|
|
|
861
790
|
intakeContext: sharedIntakeContext,
|
|
862
791
|
onAccepted: routeAcceptedSignalToRuntime,
|
|
863
792
|
onError: routeScaffoldErrorToRuntime,
|
|
864
|
-
onTick: routeScaffoldTickToTelemetry,
|
|
865
793
|
});
|
|
866
794
|
apiHandle.ready();
|
|
867
795
|
}
|
|
@@ -905,19 +833,27 @@ const senpiRuntimePlugin = {
|
|
|
905
833
|
}
|
|
906
834
|
}
|
|
907
835
|
}
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
// and
|
|
911
|
-
|
|
912
|
-
|
|
836
|
+
// Last, once everything above is up: the reaper's post-boot pass and hourly sweep. Its
|
|
837
|
+
// not-active escalation is also off until here, so no delete can land while the boot loop is
|
|
838
|
+
// still wiring runtimes and launchers. Nothing here may fail start() — a throw from inside the boot loop takes every runtime down, which
|
|
839
|
+
// is exactly the failure a lifecycle check must never cause. The unit-test seam
|
|
840
|
+
// SENPI_RUNTIME_API_DISABLED keeps its timers off unless a boot delay is set explicitly.
|
|
841
|
+
try {
|
|
842
|
+
if (process.env.SENPI_RUNTIME_API_DISABLED !== "true" || reaperBootDelayOverride !== undefined) {
|
|
843
|
+
reaper.start();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
catch (err) {
|
|
847
|
+
pluginLogger.warn("[senpi-runtime] lifecycle reaper did not start", {
|
|
848
|
+
err: err instanceof Error ? err.message : String(err),
|
|
849
|
+
});
|
|
913
850
|
}
|
|
914
851
|
},
|
|
915
852
|
async stop() {
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
|
|
920
|
-
liveTelemetry.stop();
|
|
853
|
+
// Stop the reaper first so no pass starts deleting while the runtimes are torn down below,
|
|
854
|
+
// and wait (bounded) for a pass already under way, so its delete cannot stop a handle the
|
|
855
|
+
// teardown below is stopping too.
|
|
856
|
+
await reaper.stop();
|
|
921
857
|
// Drain the HTTP server FIRST so in-flight POST /signals requests
|
|
922
858
|
// finish before the underlying runtime they target is torn down.
|
|
923
859
|
// `apiHandle.close()` never throws.
|
|
@@ -1250,12 +1186,9 @@ const senpiRuntimePlugin = {
|
|
|
1250
1186
|
apiKey,
|
|
1251
1187
|
scannerProviders,
|
|
1252
1188
|
runtimeId: resolvedId,
|
|
1189
|
+
onStrategyNotActive: (address) => reaper.checkWallet(address),
|
|
1253
1190
|
});
|
|
1254
1191
|
runtimeHandles.set(resolvedId, handle);
|
|
1255
|
-
if (parsedConfig)
|
|
1256
|
-
noteTelemetryRuntimeConfig(resolvedId, parsedConfig);
|
|
1257
|
-
else
|
|
1258
|
-
noteTelemetryRuntime(resolvedId, wallet);
|
|
1259
1192
|
// Registry create fact — distinct from the runtime.started boot transition (which also fires
|
|
1260
1193
|
// on a transient restart). Emitted inside emitEvent's guard so telemetry never fails the install.
|
|
1261
1194
|
emitEvent(apiLogger, () => buildRuntimeCreatedEvent({ runtimeId: resolvedId, address: wallet, recipeName: runtimeName }));
|
|
@@ -1368,7 +1301,7 @@ const senpiRuntimePlugin = {
|
|
|
1368
1301
|
}
|
|
1369
1302
|
}
|
|
1370
1303
|
/** Gateway respond accepts (ok, payload?, error?) with error as { code, message }; plugin SDK types may omit the third arg. */
|
|
1371
|
-
registerGatewayMethod("senpi.installRuntime", async (ctx) => {
|
|
1304
|
+
api.registerGatewayMethod("senpi.installRuntime", async (ctx) => {
|
|
1372
1305
|
const respond = ctx.respond;
|
|
1373
1306
|
const result = await installRuntimeCore(ctx.params);
|
|
1374
1307
|
if (result.ok)
|
|
@@ -1376,7 +1309,7 @@ const senpiRuntimePlugin = {
|
|
|
1376
1309
|
else
|
|
1377
1310
|
respond(false, undefined, result.error);
|
|
1378
1311
|
}, { scope: "operator.write" });
|
|
1379
|
-
registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
|
|
1312
|
+
api.registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
|
|
1380
1313
|
const respond = ctx.respond;
|
|
1381
1314
|
const { readRegistry } = await import("./runtime/strategy-registry.js");
|
|
1382
1315
|
const registry = await readRegistry(baseStateDir);
|
|
@@ -1385,8 +1318,11 @@ const senpiRuntimePlugin = {
|
|
|
1385
1318
|
// say so rather than reporting a bare "running" the operator would read as healthy.
|
|
1386
1319
|
// unwiredRuntimes is populated at the four wire/launch failure sites (and is
|
|
1387
1320
|
// exactly the set of "declared external scanners but has no launcher").
|
|
1321
|
+
const bootSkipStatus = bootSkippedRuntimes.get(e.id);
|
|
1388
1322
|
const status = !runtimeHandles.has(e.id)
|
|
1389
|
-
?
|
|
1323
|
+
? bootSkipStatus !== undefined
|
|
1324
|
+
? bootSkippedStatusLabel(bootSkipStatus)
|
|
1325
|
+
: "stopped"
|
|
1390
1326
|
: unwiredRuntimes.has(e.id)
|
|
1391
1327
|
? "running — NO ENTRY SCANNERS"
|
|
1392
1328
|
: "running";
|
|
@@ -1397,6 +1333,10 @@ const senpiRuntimePlugin = {
|
|
|
1397
1333
|
? e.runtimeYamlPath
|
|
1398
1334
|
: "(inline)",
|
|
1399
1335
|
status,
|
|
1336
|
+
// What the strategy behind this runtime last read as (open / closing / closed / absent /
|
|
1337
|
+
// unchecked / unknown) — separate from `status`, whose running…/stopped… prefixes other
|
|
1338
|
+
// readers key on.
|
|
1339
|
+
...reaper.lifecycleOf(e.wallet),
|
|
1400
1340
|
// The deploy path installs by CONTENT, not path (orchestrator.ts installRuntime), so
|
|
1401
1341
|
// `source` is "(inline)" for every deployed strategy and the YAML is unreachable through
|
|
1402
1342
|
// it. The engine renders the facts instead of shipping a blob to re-interpret.
|
|
@@ -1411,13 +1351,19 @@ const senpiRuntimePlugin = {
|
|
|
1411
1351
|
* unwired-verdict cleanup all happen exactly once, here.
|
|
1412
1352
|
*/
|
|
1413
1353
|
async function deleteRuntimeCore(params) {
|
|
1414
|
-
const { readRegistry, writeRegistry, withRegistryLock } = await import("./runtime/strategy-registry.js");
|
|
1415
|
-
const registry = await readRegistry(baseStateDir);
|
|
1416
1354
|
let targetId = typeof params?.id === "string" ? params.id : undefined;
|
|
1417
1355
|
const targetAddress = typeof params?.address === "string" ? params.address : undefined;
|
|
1356
|
+
// Set only by the lifecycle reaper: why the strategy counts as gone (stamped on runtime.deleted)
|
|
1357
|
+
// and which wallet that verdict was about (the concurrency guard below).
|
|
1358
|
+
const deleteReason = params?.deleteReason === "closed_status" ? params.deleteReason
|
|
1359
|
+
: undefined;
|
|
1360
|
+
const expectWallet = typeof params?.expectWallet === "string" && params.expectWallet.trim().length > 0
|
|
1361
|
+
? params.expectWallet.trim().toLowerCase()
|
|
1362
|
+
: undefined;
|
|
1418
1363
|
// Resolve target by address if id not provided
|
|
1419
1364
|
if (!targetId && targetAddress) {
|
|
1420
|
-
const
|
|
1365
|
+
const { readRegistry } = await import("./runtime/strategy-registry.js");
|
|
1366
|
+
const entry = (await readRegistry(baseStateDir)).runtimes.find((e) => e.wallet === targetAddress);
|
|
1421
1367
|
if (!entry) {
|
|
1422
1368
|
return { ok: false, error: {
|
|
1423
1369
|
code: "NOT_FOUND",
|
|
@@ -1432,6 +1378,24 @@ const senpiRuntimePlugin = {
|
|
|
1432
1378
|
message: "Please provide the runtime id or wallet address. Example: openclaw senpi runtime delete --id my-runtime",
|
|
1433
1379
|
} };
|
|
1434
1380
|
}
|
|
1381
|
+
// One delete or update apply per runtime at a time (see `runtimeChangeLock`). Operator, agent
|
|
1382
|
+
// and deploy deletes wait their turn; the lifecycle reaper never queues behind someone else's
|
|
1383
|
+
// change to the runtime it judged — it backs off and the next lifecycle check re-reads it.
|
|
1384
|
+
// The check and the acquire below run in one synchronous step.
|
|
1385
|
+
const id = targetId;
|
|
1386
|
+
if (expectWallet !== undefined && runtimeChangeLock.hasKey(id)) {
|
|
1387
|
+
return { ok: false, error: {
|
|
1388
|
+
code: "CONFLICT",
|
|
1389
|
+
message: `Runtime "${id}" was not removed: another delete or an update is already running on it. Nothing was changed; the next lifecycle check re-reads it.`,
|
|
1390
|
+
} };
|
|
1391
|
+
}
|
|
1392
|
+
return runtimeChangeLock.withKeyLock(id, () => deleteRuntimeLocked(id, deleteReason, expectWallet));
|
|
1393
|
+
}
|
|
1394
|
+
/** {@link deleteRuntimeCore} past target resolution, under the runtime's change lock. */
|
|
1395
|
+
async function deleteRuntimeLocked(targetId, deleteReason, expectWallet) {
|
|
1396
|
+
const { readRegistry, writeRegistry, withRegistryLock } = await import("./runtime/strategy-registry.js");
|
|
1397
|
+
// Read under the lock: a delete that waited behind another change sees what that change left.
|
|
1398
|
+
const registry = await readRegistry(baseStateDir);
|
|
1435
1399
|
const deletedEntry = registry.runtimes.find((e) => e.id === targetId);
|
|
1436
1400
|
const handle = runtimeHandles.get(targetId);
|
|
1437
1401
|
if (!deletedEntry && !handle) {
|
|
@@ -1440,6 +1404,35 @@ const senpiRuntimePlugin = {
|
|
|
1440
1404
|
message: `No runtime found with id "${targetId}".`,
|
|
1441
1405
|
} };
|
|
1442
1406
|
}
|
|
1407
|
+
/**
|
|
1408
|
+
* The lifecycle reaper's guard. Its verdict was about one wallet at one moment; the delete
|
|
1409
|
+
* below must not land on anything else. Evaluated synchronously between the registry read
|
|
1410
|
+
* above and the first teardown step, so nothing can interleave between check and act:
|
|
1411
|
+
* - an install on that wallet is running (it could land a live runtime under this id);
|
|
1412
|
+
* - a deploy job holds the single-flight slot (it retires and reinstalls runtimes);
|
|
1413
|
+
* - the row under this id no longer carries that wallet (ids derive from name + wallet and
|
|
1414
|
+
* are reused, so a reinstall onto a new wallet can take the old id);
|
|
1415
|
+
* - a live handle under this id trades a different wallet.
|
|
1416
|
+
* Operator, agent and deploy deletes pass no `expectWallet` and are unaffected.
|
|
1417
|
+
*/
|
|
1418
|
+
if (expectWallet !== undefined) {
|
|
1419
|
+
const fold = (w) => (w ?? "").trim().toLowerCase();
|
|
1420
|
+
const conflict = installsInFlight.has(expectWallet)
|
|
1421
|
+
? "an install is running on that wallet"
|
|
1422
|
+
: deployController.running() !== null
|
|
1423
|
+
? "a deploy is in progress"
|
|
1424
|
+
: !deletedEntry || fold(deletedEntry.wallet) !== expectWallet
|
|
1425
|
+
? "the registry row under that id no longer carries that wallet"
|
|
1426
|
+
: handle && fold(handle.wallet) !== expectWallet
|
|
1427
|
+
? "the running runtime under that id trades a different wallet"
|
|
1428
|
+
: undefined;
|
|
1429
|
+
if (conflict !== undefined) {
|
|
1430
|
+
return { ok: false, error: {
|
|
1431
|
+
code: "CONFLICT",
|
|
1432
|
+
message: `Runtime "${targetId}" was not removed: ${conflict}. Nothing was changed; the next lifecycle check re-reads it.`,
|
|
1433
|
+
} };
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1443
1436
|
if (handle) {
|
|
1444
1437
|
await handle.teardownDslForRuntimeDeletion();
|
|
1445
1438
|
await handle.stop();
|
|
@@ -1459,9 +1452,7 @@ const senpiRuntimePlugin = {
|
|
|
1459
1452
|
// "running — NO ENTRY SCANNERS" forever. The map is otherwise only cleared on a
|
|
1460
1453
|
// successful wire/install, which a scanner-less reinstall never reaches.
|
|
1461
1454
|
unwiredRuntimes.delete(targetId);
|
|
1462
|
-
|
|
1463
|
-
// longer exists, and would hand a reinstall the old name and scanner list.
|
|
1464
|
-
telemetryStrategies.delete(targetId);
|
|
1455
|
+
bootSkippedRuntimes.delete(targetId);
|
|
1465
1456
|
// Archive state directory before removing from registry
|
|
1466
1457
|
if (deletedEntry) {
|
|
1467
1458
|
const runtimeStateDir = buildRuntimeStateDir(baseStateDir, targetId, deletedEntry.wallet ?? "");
|
|
@@ -1487,10 +1478,11 @@ const senpiRuntimePlugin = {
|
|
|
1487
1478
|
runtimeId: targetId,
|
|
1488
1479
|
address: deletedEntry?.wallet,
|
|
1489
1480
|
hadLiveHandle: Boolean(handle),
|
|
1481
|
+
deleteReason,
|
|
1490
1482
|
}));
|
|
1491
1483
|
return { ok: true, payload: { id: targetId } };
|
|
1492
1484
|
}
|
|
1493
|
-
registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
|
|
1485
|
+
api.registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
|
|
1494
1486
|
const respond = ctx.respond;
|
|
1495
1487
|
const result = await deleteRuntimeCore(ctx.params);
|
|
1496
1488
|
if (result.ok)
|
|
@@ -1597,17 +1589,6 @@ const senpiRuntimePlugin = {
|
|
|
1597
1589
|
return checkProofCoversRecipe(packageDir, verifyProof(packageDir), appliedRecipe, (f) => readFileSync(f, "utf8"));
|
|
1598
1590
|
},
|
|
1599
1591
|
applyToRuntime: makeApplyToRuntime(runtimeHandles, async (runtimeId, next, recipe) => {
|
|
1600
|
-
// Re-index the topology facts FIRST, ahead of every early return and every failure path
|
|
1601
|
-
// below. `makeApplyToRuntime` only reaches this callback once the in-process swap has
|
|
1602
|
-
// succeeded, so `next` is already the recipe this box runs: its display name and its
|
|
1603
|
-
// declared scanner list are the ones the topology payload must name. Leaving the index on
|
|
1604
|
-
// the previous config would publish strategy rows the new scanner stats cannot join to.
|
|
1605
|
-
//
|
|
1606
|
-
// Ahead of the early return because an apply on a box with no intake still swapped the
|
|
1607
|
-
// recipe, and ahead of the launcher because a launcher that fails is a runtime running the
|
|
1608
|
-
// new recipe with no external scanners — the degraded state a dashboard most needs to show,
|
|
1609
|
-
// and one it can only render if the declared scanners are the new ones.
|
|
1610
|
-
noteTelemetryRuntimeConfig(runtimeId, next);
|
|
1611
1592
|
// Restart this runtime's external scanner children against the new package.
|
|
1612
1593
|
//
|
|
1613
1594
|
// The rebuild swaps in-process components; these are separate OS processes, spawned once
|
|
@@ -1755,7 +1736,7 @@ const senpiRuntimePlugin = {
|
|
|
1755
1736
|
* producers — slated for removal in Task 5 once the migration counter
|
|
1756
1737
|
* decays to zero (see `runtime-phase-2-api-plan.md` Section 8).
|
|
1757
1738
|
*/
|
|
1758
|
-
registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
|
|
1739
|
+
api.registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
|
|
1759
1740
|
const respond = ctx.respond;
|
|
1760
1741
|
// TODO(s11): wire actual telemetry counter for
|
|
1761
1742
|
// runtime.deprecated_gateway_ingest_call (event name, properties:
|
|
@@ -1774,7 +1755,6 @@ const senpiRuntimePlugin = {
|
|
|
1774
1755
|
...(request.direction !== undefined && { direction: request.direction }),
|
|
1775
1756
|
...(request.score !== undefined && { score: request.score }),
|
|
1776
1757
|
...(request.signal_type !== undefined && { signal_type: request.signal_type }),
|
|
1777
|
-
...(request.tick_id !== undefined && { tick_id: request.tick_id }),
|
|
1778
1758
|
...(request.data !== undefined && { data: request.data }),
|
|
1779
1759
|
...(request.signals !== undefined && { signals: request.signals }),
|
|
1780
1760
|
};
|
|
@@ -1832,7 +1812,7 @@ const senpiRuntimePlugin = {
|
|
|
1832
1812
|
});
|
|
1833
1813
|
}
|
|
1834
1814
|
}, { scope: "operator.write" });
|
|
1835
|
-
registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
|
|
1815
|
+
api.registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
|
|
1836
1816
|
const respond = ctx.respond;
|
|
1837
1817
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1838
1818
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1857,7 +1837,7 @@ const senpiRuntimePlugin = {
|
|
|
1857
1837
|
}
|
|
1858
1838
|
respond(true, { positions });
|
|
1859
1839
|
}, { scope: "operator.read" });
|
|
1860
|
-
registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
|
|
1840
|
+
api.registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
|
|
1861
1841
|
const respond = ctx.respond;
|
|
1862
1842
|
const asset = typeof ctx.params?.asset === "string" ? ctx.params.asset : undefined;
|
|
1863
1843
|
if (!asset) {
|
|
@@ -1898,7 +1878,7 @@ const senpiRuntimePlugin = {
|
|
|
1898
1878
|
}
|
|
1899
1879
|
respond(true, { positions: matches });
|
|
1900
1880
|
}, { scope: "operator.read" });
|
|
1901
|
-
registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
|
|
1881
|
+
api.registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
|
|
1902
1882
|
const respond = ctx.respond;
|
|
1903
1883
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1904
1884
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1924,7 +1904,7 @@ const senpiRuntimePlugin = {
|
|
|
1924
1904
|
closes = closes.slice(0, limit);
|
|
1925
1905
|
respond(true, { closes });
|
|
1926
1906
|
}, { scope: "operator.read" });
|
|
1927
|
-
registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
|
|
1907
|
+
api.registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
|
|
1928
1908
|
const respond = ctx.respond;
|
|
1929
1909
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
1930
1910
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -1952,7 +1932,7 @@ const senpiRuntimePlugin = {
|
|
|
1952
1932
|
const statuses = await collectRuntimeHealthStatuses(runtimeHandles, api.logger);
|
|
1953
1933
|
respond(true, { statuses, updater });
|
|
1954
1934
|
}, { scope: "operator.read" });
|
|
1955
|
-
registerGatewayMethod("senpi.getSystemState", async (ctx) => {
|
|
1935
|
+
api.registerGatewayMethod("senpi.getSystemState", async (ctx) => {
|
|
1956
1936
|
const respond = ctx.respond;
|
|
1957
1937
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
1958
1938
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -1980,36 +1960,6 @@ const senpiRuntimePlugin = {
|
|
|
1980
1960
|
const states = await collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir);
|
|
1981
1961
|
respond(true, { states, updater });
|
|
1982
1962
|
}, { scope: "operator.read" });
|
|
1983
|
-
/**
|
|
1984
|
-
* Live telemetry — the one method of the stream, called once per connect.
|
|
1985
|
-
*
|
|
1986
|
-
* It returns the current value of all three broadcast payloads plus the recent record window, so
|
|
1987
|
-
* a client joining a box that has been running for hours renders a full screen immediately
|
|
1988
|
-
* instead of waiting for the next thing to happen. `(afterEpoch, afterSeq)` resumes a client that
|
|
1989
|
-
* already holds part of this epoch's run — a cursor naming any other epoch is dropped, because a
|
|
1990
|
-
* seq from a previous run cannot address these records. `truncated` says when the records between
|
|
1991
|
-
* a cursor of this epoch and the oldest record still held are gone, which nothing in `recent`
|
|
1992
|
-
* alone can express.
|
|
1993
|
-
*
|
|
1994
|
-
* Read-only, like every other inspection method here. The scope that decides who RECEIVES the
|
|
1995
|
-
* broadcasts is a property of the client's own connection, not of this method.
|
|
1996
|
-
*/
|
|
1997
|
-
registerGatewayMethod("senpi.telemetry.subscribe", async (ctx) => {
|
|
1998
|
-
const respond = ctx.respond;
|
|
1999
|
-
try {
|
|
2000
|
-
respond(true, await liveTelemetry.snapshot({
|
|
2001
|
-
afterEpoch: ctx.params?.afterEpoch,
|
|
2002
|
-
afterSeq: ctx.params?.afterSeq,
|
|
2003
|
-
}));
|
|
2004
|
-
}
|
|
2005
|
-
catch (err) {
|
|
2006
|
-
api.logger.warn("[senpi-runtime] telemetry subscribe error", { err });
|
|
2007
|
-
respond(false, undefined, {
|
|
2008
|
-
code: "INTERNAL_ERROR",
|
|
2009
|
-
message: `Failed to build the telemetry snapshot: ${formatGatewayError(err)}`,
|
|
2010
|
-
});
|
|
2011
|
-
}
|
|
2012
|
-
}, { scope: "operator.read" });
|
|
2013
1963
|
/**
|
|
2014
1964
|
* Force the auto-update poll to run now instead of waiting for its timer.
|
|
2015
1965
|
*
|
|
@@ -2068,7 +2018,7 @@ const senpiRuntimePlugin = {
|
|
|
2068
2018
|
* persisted action-store machinery (`src/actions/store/`) usable
|
|
2069
2019
|
* from the operator-facing CLI again.
|
|
2070
2020
|
*/
|
|
2071
|
-
registerGatewayMethod("senpi.listActions", async (ctx) => {
|
|
2021
|
+
api.registerGatewayMethod("senpi.listActions", async (ctx) => {
|
|
2072
2022
|
const respond = ctx.respond;
|
|
2073
2023
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2074
2024
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -2098,7 +2048,7 @@ const senpiRuntimePlugin = {
|
|
|
2098
2048
|
}
|
|
2099
2049
|
respond(true, { actions: merged });
|
|
2100
2050
|
}, { scope: "operator.read" });
|
|
2101
|
-
registerGatewayMethod("senpi.getActionState", async (ctx) => {
|
|
2051
|
+
api.registerGatewayMethod("senpi.getActionState", async (ctx) => {
|
|
2102
2052
|
const respond = ctx.respond;
|
|
2103
2053
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2104
2054
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -2135,7 +2085,7 @@ const senpiRuntimePlugin = {
|
|
|
2135
2085
|
});
|
|
2136
2086
|
}
|
|
2137
2087
|
}, { scope: "operator.read" });
|
|
2138
|
-
registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
|
|
2088
|
+
api.registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
|
|
2139
2089
|
const respond = ctx.respond;
|
|
2140
2090
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2141
2091
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -2203,7 +2153,7 @@ const senpiRuntimePlugin = {
|
|
|
2203
2153
|
// reachable on HTTP `/audit`. Passes through the caller-controlled subset
|
|
2204
2154
|
// of the audit_query contract; resource scoping (wallet → strategyId) is
|
|
2205
2155
|
// owned by handle.auditQuery, never the caller.
|
|
2206
|
-
registerGatewayMethod("senpi.auditQuery", async (ctx) => {
|
|
2156
|
+
api.registerGatewayMethod("senpi.auditQuery", async (ctx) => {
|
|
2207
2157
|
const respond = ctx.respond;
|
|
2208
2158
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2209
2159
|
if (!runtimeId) {
|
|
@@ -2251,7 +2201,7 @@ const senpiRuntimePlugin = {
|
|
|
2251
2201
|
// its state root private, so re-derive it from baseStateDir + the handle's
|
|
2252
2202
|
// wallet (same helper the writer's stateDir was built with); the writer
|
|
2253
2203
|
// nests entries under `<root>/<address>/risk-guard/audit.jsonl`.
|
|
2254
|
-
registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
|
|
2204
|
+
api.registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
|
|
2255
2205
|
const respond = ctx.respond;
|
|
2256
2206
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2257
2207
|
if (!runtimeId) {
|
|
@@ -2294,7 +2244,7 @@ const senpiRuntimePlugin = {
|
|
|
2294
2244
|
// Per-strategy local domain-event ring (on-disk JSONL) — the trade narrative, queryable locally.
|
|
2295
2245
|
// Same state-root re-derivation as getRiskAudit: the Runtime keeps its root private, so rebuild it
|
|
2296
2246
|
// from baseStateDir + the handle's wallet; the writer nests rows under `<root>/strategies/<address>/events/`.
|
|
2297
|
-
registerGatewayMethod("senpi.getEvents", async (ctx) => {
|
|
2247
|
+
api.registerGatewayMethod("senpi.getEvents", async (ctx) => {
|
|
2298
2248
|
const respond = ctx.respond;
|
|
2299
2249
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2300
2250
|
if (!runtimeId) {
|
|
@@ -2351,12 +2301,12 @@ const senpiRuntimePlugin = {
|
|
|
2351
2301
|
}
|
|
2352
2302
|
}, { scope: "operator.read" });
|
|
2353
2303
|
// ─── Skills manager — read + mutate ──────────────────────────────────────
|
|
2354
|
-
registerGatewayMethod("senpi.skills.status", async (ctx) => {
|
|
2304
|
+
api.registerGatewayMethod("senpi.skills.status", async (ctx) => {
|
|
2355
2305
|
const respond = ctx.respond;
|
|
2356
2306
|
const snapshot = skillsCoordinator.getSnapshot();
|
|
2357
2307
|
respond(true, { snapshot });
|
|
2358
2308
|
}, { scope: "operator.read" });
|
|
2359
|
-
registerGatewayMethod("senpi.skills.update", async (ctx) => {
|
|
2309
|
+
api.registerGatewayMethod("senpi.skills.update", async (ctx) => {
|
|
2360
2310
|
const respond = ctx.respond;
|
|
2361
2311
|
const name = typeof ctx.params?.name === "string" ? ctx.params.name.trim() : undefined;
|
|
2362
2312
|
if (!name) {
|
|
@@ -2381,7 +2331,7 @@ const senpiRuntimePlugin = {
|
|
|
2381
2331
|
});
|
|
2382
2332
|
}
|
|
2383
2333
|
}, { scope: "operator.write" });
|
|
2384
|
-
registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
|
|
2334
|
+
api.registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
|
|
2385
2335
|
const respond = ctx.respond;
|
|
2386
2336
|
try {
|
|
2387
2337
|
const results = await skillsCoordinator.updateAll();
|
|
@@ -2447,7 +2397,6 @@ function parseGatewayExternalScannerIngestParams(params) {
|
|
|
2447
2397
|
direction: params?.direction,
|
|
2448
2398
|
score: params?.score,
|
|
2449
2399
|
signal_type: params?.signal_type,
|
|
2450
|
-
tick_id: params?.tick_id,
|
|
2451
2400
|
data: params?.data,
|
|
2452
2401
|
signals: params?.signals,
|
|
2453
2402
|
},
|