@senpi/runtime 3.0.108 → 3.0.111
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 +21 -7
- package/dist/actions/action-events.d.ts.map +1 -1
- package/dist/actions/action-events.js +54 -26
- package/dist/actions/action-events.js.map +1 -1
- package/dist/actions/action-runtime-module.d.ts.map +1 -1
- package/dist/actions/action-runtime-module.js +38 -7
- package/dist/actions/action-runtime-module.js.map +1 -1
- package/dist/actions/close-position/close-position.action.d.ts.map +1 -1
- package/dist/actions/close-position/close-position.action.js +122 -15
- package/dist/actions/close-position/close-position.action.js.map +1 -1
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +15 -2
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/actions/event-derivation.d.ts +71 -2
- package/dist/actions/event-derivation.d.ts.map +1 -1
- package/dist/actions/event-derivation.js +251 -8
- package/dist/actions/event-derivation.js.map +1 -1
- package/dist/actions/open-position/open-position.action.d.ts +28 -2
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -1
- package/dist/actions/open-position/open-position.action.js +220 -52
- package/dist/actions/open-position/open-position.action.js.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.d.ts +2 -1
- package/dist/actions/position-tracker/position-tracker.action.d.ts.map +1 -1
- package/dist/actions/position-tracker/position-tracker.action.js +62 -17
- package/dist/actions/position-tracker/position-tracker.action.js.map +1 -1
- package/dist/actions/skipped-signal-results.d.ts +25 -0
- package/dist/actions/skipped-signal-results.d.ts.map +1 -0
- package/dist/actions/skipped-signal-results.js +71 -0
- package/dist/actions/skipped-signal-results.js.map +1 -0
- package/dist/actions/store/types.d.ts +9 -2
- package/dist/actions/store/types.d.ts.map +1 -1
- package/dist/actions/store/types.js.map +1 -1
- package/dist/actions/types.d.ts +13 -0
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js +13 -0
- package/dist/actions/types.js.map +1 -1
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +3 -0
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/constants/dsl/index.d.ts +15 -0
- package/dist/constants/dsl/index.d.ts.map +1 -1
- package/dist/constants/dsl/index.js +15 -0
- package/dist/constants/dsl/index.js.map +1 -1
- package/dist/dsl/engine-client/event-mapping.d.ts.map +1 -1
- package/dist/dsl/engine-client/event-mapping.js +14 -0
- package/dist/dsl/engine-client/event-mapping.js.map +1 -1
- package/dist/dsl/events/build-closed-event.d.ts +16 -1
- package/dist/dsl/events/build-closed-event.d.ts.map +1 -1
- package/dist/dsl/events/build-closed-event.js +6 -0
- package/dist/dsl/events/build-closed-event.js.map +1 -1
- package/dist/dsl/events/handlers.d.ts +7 -0
- package/dist/dsl/events/handlers.d.ts.map +1 -1
- package/dist/dsl/events/handlers.js +37 -7
- package/dist/dsl/events/handlers.js.map +1 -1
- package/dist/dsl/monitor/create-monitor.d.ts +6 -2
- package/dist/dsl/monitor/create-monitor.d.ts.map +1 -1
- package/dist/dsl/monitor/create-monitor.js +15 -9
- package/dist/dsl/monitor/create-monitor.js.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.d.ts +2 -0
- package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -1
- package/dist/dsl/monitor/dsl-failure-events.js +2 -1
- package/dist/dsl/monitor/dsl-failure-events.js.map +1 -1
- package/dist/dsl/monitor/process-one-position.d.ts +1 -0
- package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
- package/dist/dsl/monitor/process-one-position.js +72 -29
- package/dist/dsl/monitor/process-one-position.js.map +1 -1
- package/dist/dsl/plugin/index.d.ts +8 -2
- package/dist/dsl/plugin/index.d.ts.map +1 -1
- package/dist/dsl/plugin/index.js +8 -6
- package/dist/dsl/plugin/index.js.map +1 -1
- package/dist/health/types.d.ts +13 -0
- package/dist/health/types.d.ts.map +1 -1
- package/dist/health/types.js +12 -0
- package/dist/health/types.js.map +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +230 -39
- package/dist/index.js.map +1 -1
- package/dist/runtime/action-registry.d.ts.map +1 -1
- package/dist/runtime/action-registry.js +4 -3
- package/dist/runtime/action-registry.js.map +1 -1
- package/dist/runtime/domain-events.d.ts.map +1 -1
- package/dist/runtime/domain-events.js +7 -0
- package/dist/runtime/domain-events.js.map +1 -1
- package/dist/runtime/lifecycle-events.d.ts +9 -1
- package/dist/runtime/lifecycle-events.d.ts.map +1 -1
- package/dist/runtime/lifecycle-events.js +11 -2
- package/dist/runtime/lifecycle-events.js.map +1 -1
- package/dist/runtime/reconcile-run.d.ts +13 -3
- package/dist/runtime/reconcile-run.d.ts.map +1 -1
- package/dist/runtime/reconcile-run.js +4 -3
- package/dist/runtime/reconcile-run.js.map +1 -1
- package/dist/runtime/reconcile-state.d.ts +8 -0
- package/dist/runtime/reconcile-state.d.ts.map +1 -1
- package/dist/runtime/reconcile-state.js +4 -1
- package/dist/runtime/reconcile-state.js.map +1 -1
- package/dist/runtime/run.d.ts +1 -0
- package/dist/runtime/run.d.ts.map +1 -1
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/runtime-schema.js +3 -3
- package/dist/runtime/runtime-schema.js.map +1 -1
- package/dist/runtime/runtime.d.ts +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +27 -3
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.d.ts +13 -0
- package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
- package/dist/runtime/supervisor/long-running-supervisor.js +1 -0
- package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
- package/dist/runtime/validate-dsl-position-tracking.d.ts.map +1 -1
- package/dist/runtime/validate-dsl-position-tracking.js +2 -1
- package/dist/runtime/validate-dsl-position-tracking.js.map +1 -1
- package/dist/runtime-api/handlers/ingest-signals.d.ts.map +1 -1
- package/dist/runtime-api/handlers/ingest-signals.js +1 -0
- package/dist/runtime-api/handlers/ingest-signals.js.map +1 -1
- package/dist/runtime-api/handlers/types.d.ts +5 -0
- package/dist/runtime-api/handlers/types.d.ts.map +1 -1
- package/dist/runtime-api/intake/ingest-request.d.ts +17 -0
- package/dist/runtime-api/intake/ingest-request.d.ts.map +1 -0
- package/dist/runtime-api/intake/ingest-request.js +34 -0
- package/dist/runtime-api/intake/ingest-request.js.map +1 -0
- package/dist/runtime-api/intake/intake-events.d.ts +1 -1
- package/dist/runtime-api/intake/intake-events.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-events.js +9 -3
- package/dist/runtime-api/intake/intake-events.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +4 -19
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/runtime-api/intake/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 +15 -0
- package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
- package/dist/runtime-api/intake/types.d.ts +6 -0
- package/dist/runtime-api/intake/types.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.js +2 -1
- package/dist/runtime-api/routes/signals.js.map +1 -1
- package/dist/runtime-api/routes/signals.schema.d.ts +1 -0
- package/dist/runtime-api/routes/signals.schema.d.ts.map +1 -1
- package/dist/runtime-api/routes/signals.schema.js +6 -0
- package/dist/runtime-api/routes/signals.schema.js.map +1 -1
- package/dist/scanners/engine/engine.d.ts +22 -1
- package/dist/scanners/engine/engine.d.ts.map +1 -1
- package/dist/scanners/engine/engine.js +78 -11
- package/dist/scanners/engine/engine.js.map +1 -1
- package/dist/scanners/engine/lifecycle.d.ts +7 -1
- package/dist/scanners/engine/lifecycle.d.ts.map +1 -1
- package/dist/scanners/events.d.ts +4 -0
- package/dist/scanners/events.d.ts.map +1 -1
- package/dist/scanners/events.js +2 -0
- package/dist/scanners/events.js.map +1 -1
- package/dist/scanners/external/scaffold/delivery.py +136 -14
- package/dist/scanners/external/scaffold/scaffold.py +61 -8
- package/dist/scanners/external/scaffold/ulid.py +58 -0
- package/dist/scanners/external-scanner-receiver.d.ts +17 -0
- package/dist/scanners/external-scanner-receiver.d.ts.map +1 -1
- package/dist/scanners/external-scanner-receiver.js +8 -0
- package/dist/scanners/external-scanner-receiver.js.map +1 -1
- package/dist/scanners/runtime-module.d.ts +1 -0
- package/dist/scanners/runtime-module.d.ts.map +1 -1
- package/dist/scanners/runtime-module.js +59 -9
- package/dist/scanners/runtime-module.js.map +1 -1
- package/dist/scripts/run-dsl-standalone-mock.js +6 -2
- package/dist/scripts/run-dsl-standalone-mock.js.map +1 -1
- package/dist/senpi/client.d.ts +16 -3
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +44 -13
- package/dist/senpi/client.js.map +1 -1
- package/dist/strategy/strategy-state.d.ts +9 -1
- package/dist/strategy/strategy-state.d.ts.map +1 -1
- package/dist/strategy/strategy-state.js +7 -2
- package/dist/strategy/strategy-state.js.map +1 -1
- package/dist/telemetry/attributes.d.ts +20 -0
- package/dist/telemetry/attributes.d.ts.map +1 -1
- package/dist/telemetry/attributes.js +24 -0
- package/dist/telemetry/attributes.js.map +1 -1
- package/dist/telemetry/event-builders.d.ts +81 -6
- package/dist/telemetry/event-builders.d.ts.map +1 -1
- package/dist/telemetry/event-builders.js +141 -17
- package/dist/telemetry/event-builders.js.map +1 -1
- package/dist/telemetry/event-store.d.ts +6 -0
- package/dist/telemetry/event-store.d.ts.map +1 -1
- package/dist/telemetry/event-store.js +12 -8
- package/dist/telemetry/event-store.js.map +1 -1
- package/dist/telemetry/live-telemetry.d.ts +192 -0
- package/dist/telemetry/live-telemetry.d.ts.map +1 -0
- package/dist/telemetry/live-telemetry.js +676 -0
- package/dist/telemetry/live-telemetry.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +14 -2
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +19 -4
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/telemetry/scanner-stats.d.ts +143 -0
- package/dist/telemetry/scanner-stats.d.ts.map +1 -0
- package/dist/telemetry/scanner-stats.js +426 -0
- package/dist/telemetry/scanner-stats.js.map +1 -0
- package/dist/telemetry/spans/redact-pii.d.ts.map +1 -1
- package/dist/telemetry/spans/redact-pii.js +18 -2
- package/dist/telemetry/spans/redact-pii.js.map +1 -1
- package/dist/telemetry/strategy-topology.d.ts +99 -0
- package/dist/telemetry/strategy-topology.d.ts.map +1 -0
- package/dist/telemetry/strategy-topology.js +93 -0
- package/dist/telemetry/strategy-topology.js.map +1 -0
- package/dist/telemetry/telemetry-publisher.d.ts +63 -0
- package/dist/telemetry/telemetry-publisher.d.ts.map +1 -0
- package/dist/telemetry/telemetry-publisher.js +229 -0
- package/dist/telemetry/telemetry-publisher.js.map +1 -0
- package/dist/telemetry/timeline-buffer.d.ts +95 -0
- package/dist/telemetry/timeline-buffer.d.ts.map +1 -0
- package/dist/telemetry/timeline-buffer.js +237 -0
- package/dist/telemetry/timeline-buffer.js.map +1 -0
- package/dist/telemetry/tracing.d.ts +9 -0
- package/dist/telemetry/tracing.d.ts.map +1 -1
- package/dist/telemetry/tracing.js +36 -0
- package/dist/telemetry/tracing.js.map +1 -1
- package/dist/types/dsl/index.d.ts +11 -0
- package/dist/types/dsl/index.d.ts.map +1 -1
- package/dist/types/scanner.d.ts +15 -0
- package/dist/types/scanner.d.ts.map +1 -1
- package/dist/types/strategy.d.ts +5 -1
- package/dist/types/strategy.d.ts.map +1 -1
- package/dist/utils/event-catalog.d.ts +166 -0
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js +18 -0
- package/dist/utils/event-catalog.js.map +1 -1
- package/dist/utils/logger.d.ts +4 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/telemetry-required-log.d.ts +27 -0
- package/dist/utils/telemetry-required-log.d.ts.map +1 -0
- package/dist/utils/telemetry-required-log.js +68 -0
- package/dist/utils/telemetry-required-log.js.map +1 -0
- package/dist/utils/ulid.d.ts +9 -0
- package/dist/utils/ulid.d.ts.map +1 -0
- package/dist/utils/ulid.js +43 -0
- package/dist/utils/ulid.js.map +1 -0
- package/dist/validate/recipe-checks.d.ts.map +1 -1
- package/dist/validate/recipe-checks.js +2 -2
- package/dist/validate/recipe-checks.js.map +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,7 +62,9 @@ import { createIntakeContext } from "./runtime-api/intake/intake-context.js";
|
|
|
62
62
|
import { wireRuntime, intakeUrlFor, selectScanners } from "./runtime-api/intake/intake-mount.js";
|
|
63
63
|
import { ingestDispatchMutex, ingestDispatchKey } from "./runtime-api/intake/ingest-mutex.js";
|
|
64
64
|
import { buildScaffoldErrorRouter } from "./runtime-api/intake/scaffold-error-router.js";
|
|
65
|
+
import { toIngestRequest } from "./runtime-api/intake/ingest-request.js";
|
|
65
66
|
import { loadRuntimeConfig } from "./runtime/load-runtime-config.js";
|
|
67
|
+
import { createLiveTelemetry } from "./telemetry/live-telemetry.js";
|
|
66
68
|
/** Initialize all core features (DSL, risk, health) on the given event bus. */
|
|
67
69
|
export function initCore(bus) {
|
|
68
70
|
initDsl(bus);
|
|
@@ -376,10 +378,146 @@ const senpiRuntimePlugin = {
|
|
|
376
378
|
* engines, one margin account: the exact state `[E_WALLET_HAS_RUNTIME]` exists to prevent.
|
|
377
379
|
*/
|
|
378
380
|
const installsInFlight = new Map();
|
|
381
|
+
// ── Live telemetry ────────────────────────────────────────────────
|
|
382
|
+
// Every runtime this box knows about, running or not, keyed runtimeId. The topology payload has
|
|
383
|
+
// to name a stopped strategy too, so this is populated from the registry rather than from
|
|
384
|
+
// `runtimeHandles` (which holds only the live ones) and is the source of the two facts the
|
|
385
|
+
// registry entry does not carry: the strategy's display name and its declared scanners. Both come
|
|
386
|
+
// from the parsed YAML — `name` is the same `config.name` that `RuntimeHealthStatus.runtimeName`
|
|
387
|
+
// is built from, so a runtime whose config never parsed falls back to its id rather than to a
|
|
388
|
+
// guess, and its scanner list stays empty rather than inventing rows.
|
|
389
|
+
const telemetryStrategies = new Map();
|
|
390
|
+
const noteTelemetryRuntime = (runtimeId, address) => {
|
|
391
|
+
const existing = telemetryStrategies.get(runtimeId);
|
|
392
|
+
telemetryStrategies.set(runtimeId, {
|
|
393
|
+
address,
|
|
394
|
+
name: existing?.name ?? runtimeId,
|
|
395
|
+
scanners: existing?.scanners ?? [],
|
|
396
|
+
});
|
|
397
|
+
};
|
|
398
|
+
const noteTelemetryRuntimeConfig = (runtimeId, config) => {
|
|
399
|
+
telemetryStrategies.set(runtimeId, {
|
|
400
|
+
address: config.strategy?.wallet ?? telemetryStrategies.get(runtimeId)?.address ?? "",
|
|
401
|
+
name: config.name,
|
|
402
|
+
scanners: config.scanners ?? [],
|
|
403
|
+
});
|
|
404
|
+
};
|
|
379
405
|
// Dedicated logger for the runtime-api subtree — required because the
|
|
380
406
|
// PluginLogger surface (info/warn/error) lacks `debug`, while the
|
|
381
407
|
// Logger contract used by `runtime-api/` includes it.
|
|
382
408
|
const apiLogger = createLogger("[senpi-core][api]");
|
|
409
|
+
/**
|
|
410
|
+
* One gather per publish tick. Health and system state are read together because the two payloads
|
|
411
|
+
* need both: the topology reads health and `runtimeName`, the stats read the per-scanner rows,
|
|
412
|
+
* and `restartCount` / `degradedReason` live only on the health rows while every other scanner
|
|
413
|
+
* field lives only on the system-state rows — so they are joined here on `(runtimeId, scannerId)`.
|
|
414
|
+
*/
|
|
415
|
+
const collectTelemetryFacts = async () => {
|
|
416
|
+
const [statuses, states] = await Promise.all([
|
|
417
|
+
collectRuntimeHealthStatuses(runtimeHandles, api.logger),
|
|
418
|
+
collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir),
|
|
419
|
+
]);
|
|
420
|
+
const healthByRuntime = new Map();
|
|
421
|
+
for (const status of statuses) {
|
|
422
|
+
if (status.runtimeId !== undefined)
|
|
423
|
+
healthByRuntime.set(status.runtimeId, status);
|
|
424
|
+
}
|
|
425
|
+
const scannerState = [];
|
|
426
|
+
// Registration facts per (runtimeId, scannerId), so the topology can report a scanner's live
|
|
427
|
+
// schedule rather than only what its YAML declared.
|
|
428
|
+
const registrations = new Map();
|
|
429
|
+
for (const state of states) {
|
|
430
|
+
const runtimeId = state.runtimeId;
|
|
431
|
+
const healthRows = runtimeId === undefined
|
|
432
|
+
? []
|
|
433
|
+
: healthByRuntime.get(runtimeId)?.components.scanners.scanners ?? [];
|
|
434
|
+
for (const row of state.components.scanners.state.scanners) {
|
|
435
|
+
const supervision = healthRows.find((r) => r.scannerId === row.scannerId);
|
|
436
|
+
const snapshot = {
|
|
437
|
+
address: row.address,
|
|
438
|
+
scannerId: row.scannerId,
|
|
439
|
+
health: row.health,
|
|
440
|
+
degradedReason: supervision?.degradedReason ?? null,
|
|
441
|
+
enabled: row.enabled,
|
|
442
|
+
inFlightSince: row.inFlightSince,
|
|
443
|
+
lastRunStartedAt: row.lastRunStartedAt,
|
|
444
|
+
lastRunFinishedAt: row.lastRunFinishedAt,
|
|
445
|
+
lastRunStatus: row.lastRunStatus,
|
|
446
|
+
lastRunReason: row.lastRunReason,
|
|
447
|
+
nextRunAt: row.nextRunAt,
|
|
448
|
+
lastAliveAt: row.lastAliveAt,
|
|
449
|
+
// No supervision row means the restart count is unknown, not zero. A zero here would
|
|
450
|
+
// read as "this scanner has never restarted" on a scanner nobody is supervising.
|
|
451
|
+
restartCount: supervision?.restartCount ?? null,
|
|
452
|
+
};
|
|
453
|
+
scannerState.push(snapshot);
|
|
454
|
+
if (runtimeId !== undefined) {
|
|
455
|
+
registrations.set(`${runtimeId}::${row.scannerId}`, {
|
|
456
|
+
...snapshot,
|
|
457
|
+
scheduleMode: row.scheduleMode,
|
|
458
|
+
configVersion: row.configVersion,
|
|
459
|
+
intervalSeconds: row.intervalSeconds,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const strategies = [];
|
|
465
|
+
for (const [runtimeId, entry] of telemetryStrategies) {
|
|
466
|
+
const health = healthByRuntime.get(runtimeId);
|
|
467
|
+
const scanners = entry.scanners.map((declared) => {
|
|
468
|
+
const live = registrations.get(`${runtimeId}::${declared.name}`);
|
|
469
|
+
return live === undefined
|
|
470
|
+
? { entry: declared }
|
|
471
|
+
: {
|
|
472
|
+
entry: declared,
|
|
473
|
+
registration: {
|
|
474
|
+
enabled: live.enabled,
|
|
475
|
+
scheduleMode: live.scheduleMode,
|
|
476
|
+
// The engine's own cadence, which is what the scanner actually polls on — a
|
|
477
|
+
// recipe may omit it and take the scanner definition's default. An external
|
|
478
|
+
// scanner registers zero because it is POSTed to rather than polled, and only
|
|
479
|
+
// the recipe knows the cadence its scaffold was told to tick at.
|
|
480
|
+
intervalSeconds: live.intervalSeconds > 0 ? live.intervalSeconds : declared.interval_seconds ?? null,
|
|
481
|
+
configVersion: live.configVersion,
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
});
|
|
485
|
+
strategies.push({
|
|
486
|
+
runtimeId,
|
|
487
|
+
address: entry.address,
|
|
488
|
+
// `runtimeName` is built from the same `config.name`; the indexed value is used because it
|
|
489
|
+
// is available for a stopped runtime too, where there is no health status to read.
|
|
490
|
+
name: health?.runtimeName ?? entry.name,
|
|
491
|
+
scanners,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
strategies,
|
|
496
|
+
runningRuntimeIds: runtimeHandles,
|
|
497
|
+
unwiredRuntimeIds: unwiredRuntimes,
|
|
498
|
+
readHealth: (runtimeId) => healthByRuntime.get(runtimeId),
|
|
499
|
+
scannerState,
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
const liveTelemetry = createLiveTelemetry({ collectFacts: collectTelemetryFacts });
|
|
503
|
+
/**
|
|
504
|
+
* Wrapped gateway methods capture the broadcast handle on the FIRST request to ANY of them —
|
|
505
|
+
* not only on subscribe. `ctx.context` is the only route to the websocket push layer and it
|
|
506
|
+
* exists only inside a handler, so capturing it in the subscribe method alone would mean a box
|
|
507
|
+
* whose client never subscribes can never publish anything, silently.
|
|
508
|
+
*
|
|
509
|
+
* The wrapper covers only what is registered through it. `senpi.deploy.start`,
|
|
510
|
+
* `senpi.deploy.status` and `senpi.update` are registered straight onto the plugin api by their
|
|
511
|
+
* own registrars and never arm the handle. That is not a gap in practice: one wrapped call arms
|
|
512
|
+
* it for the life of the process, and the websocket client admits `senpi.telemetry.subscribe`,
|
|
513
|
+
* which is wrapped.
|
|
514
|
+
*/
|
|
515
|
+
const registerGatewayMethod = (name, handler, opts) => {
|
|
516
|
+
api.registerGatewayMethod(name, (ctx) => {
|
|
517
|
+
liveTelemetry.captureBroadcast(ctx, name);
|
|
518
|
+
return handler(ctx);
|
|
519
|
+
}, opts);
|
|
520
|
+
};
|
|
383
521
|
// A gateway seam failed to wire/launch a runtime's external scanner stack: the runtime is up but
|
|
384
522
|
// runs blind on internal scanners only. Two records, by design: a first-class `runtime.scanners_unwired`
|
|
385
523
|
// event (the queryable/alertable fact, by phase) and the migrated exception-form `logger.error`
|
|
@@ -520,26 +658,14 @@ const senpiRuntimePlugin = {
|
|
|
520
658
|
// /signals will not be mounted and no signals will be routed.
|
|
521
659
|
if (sharedIntakeContext) {
|
|
522
660
|
const ctx = sharedIntakeContext;
|
|
523
|
-
routeAcceptedSignalToRuntime = (signal,
|
|
661
|
+
routeAcceptedSignalToRuntime = (signal, correlationId, scannerId) => {
|
|
524
662
|
const resolved = ctx.registry.resolve(scannerId);
|
|
525
663
|
if (!resolved)
|
|
526
664
|
return;
|
|
527
665
|
const h = runtimeHandles.get(resolved.runtimeId);
|
|
528
666
|
if (!h)
|
|
529
667
|
return;
|
|
530
|
-
const request =
|
|
531
|
-
scanner: resolved.scannerName,
|
|
532
|
-
signal_id: signal.signal_id, // greppable handle across intake→ingest (obs only)
|
|
533
|
-
asset: signal.asset,
|
|
534
|
-
direction: signal.direction,
|
|
535
|
-
...(signal.signal_type !== undefined && { signal_type: signal.signal_type }),
|
|
536
|
-
data: signal.data,
|
|
537
|
-
// Per-signal sizing rides at the top level; the open-position action
|
|
538
|
-
// reads request.marginPct / request.leverage directly. Without this a
|
|
539
|
-
// supervised signal's sizing is lost ("no margin source resolved").
|
|
540
|
-
...(signal.marginPct !== undefined && { marginPct: signal.marginPct }),
|
|
541
|
-
...(signal.leverage !== undefined && { leverage: signal.leverage }),
|
|
542
|
-
};
|
|
668
|
+
const request = toIngestRequest(signal, correlationId, resolved.scannerName);
|
|
543
669
|
// Trace the sizing-relevant fields the narrowed request carries, so a
|
|
544
670
|
// field dropped at this seam is visible instead of vanishing silently.
|
|
545
671
|
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}`);
|
|
@@ -559,14 +685,13 @@ const senpiRuntimePlugin = {
|
|
|
559
685
|
// The single server-level onError closure — the `/errors` counterpart of
|
|
560
686
|
// routeAcceptedSignalToRuntime. It records a scaffold-reported tick failure into
|
|
561
687
|
// the owning runtime's scanner telemetry so a scanner that fails every tick stops
|
|
562
|
-
// rendering "healthy".
|
|
688
|
+
// rendering "healthy". Filter: only tick-level failure types count — a
|
|
689
|
+
// `candidate_rejected` body is a per-candidate reject on an otherwise-OK tick, not
|
|
690
|
+
// a failed run.
|
|
563
691
|
//
|
|
564
|
-
// This is the SHARED implementation (scaffold-error-router.ts),
|
|
565
|
-
// buildIntakeMount's onScaffoldError
|
|
566
|
-
//
|
|
567
|
-
// list it reached the tested seam and never reached this route, so the
|
|
568
|
-
// wedged-scanner signal was green in CI and inert in production. There is now one
|
|
569
|
-
// filter, and scaffold-error-router.test.ts drives THIS closure.
|
|
692
|
+
// This is the SHARED implementation (scaffold-error-router.ts), so this route and
|
|
693
|
+
// buildIntakeMount's onScaffoldError apply one filter, and scaffold-error-router.test.ts
|
|
694
|
+
// drives THIS closure.
|
|
570
695
|
routeScaffoldErrorToRuntime = buildScaffoldErrorRouter(ctx, runtimeHandles);
|
|
571
696
|
}
|
|
572
697
|
const started = [];
|
|
@@ -580,6 +705,9 @@ const senpiRuntimePlugin = {
|
|
|
580
705
|
continue;
|
|
581
706
|
const pathOrContent = entry.runtimeYamlPath ?? entry.runtimeYamlContent;
|
|
582
707
|
const entryStateDir = buildRuntimeStateDir(baseStateDir, entry.id, entry.wallet ?? "");
|
|
708
|
+
// Indexed before the boot attempt: a runtime whose start throws is installed but stopped,
|
|
709
|
+
// and the telemetry topology has to be able to say so.
|
|
710
|
+
noteTelemetryRuntime(entry.id, entry.wallet ?? "");
|
|
583
711
|
const handle = await run(pathOrContent, {
|
|
584
712
|
fromPath: hasPath,
|
|
585
713
|
stateDir: entryStateDir,
|
|
@@ -602,6 +730,7 @@ const senpiRuntimePlugin = {
|
|
|
602
730
|
if (sharedIntakeContext) {
|
|
603
731
|
try {
|
|
604
732
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
733
|
+
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
605
734
|
// Content-installed runtimes carry their recipe dir in the registry
|
|
606
735
|
// entry (runtimeYamlDir) so relative scanner paths still resolve
|
|
607
736
|
// after a restart; path-installed ones derive it from the path.
|
|
@@ -653,6 +782,7 @@ const senpiRuntimePlugin = {
|
|
|
653
782
|
// event) so listRuntimes/health don't render a bare "running".
|
|
654
783
|
try {
|
|
655
784
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
785
|
+
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
656
786
|
if (selectScanners(runtimeConfig).length > 0) {
|
|
657
787
|
unwiredRuntimes.set(entry.id, "no_intake");
|
|
658
788
|
handle.attachSupervisionSource(() => ({
|
|
@@ -754,8 +884,19 @@ const senpiRuntimePlugin = {
|
|
|
754
884
|
}
|
|
755
885
|
}
|
|
756
886
|
}
|
|
887
|
+
// Publish ticks start only once the runtimes are up, so the first frames describe a real
|
|
888
|
+
// box. Gated by the same switch the skills coordinator uses: unit tests boot `register()`
|
|
889
|
+
// and must not acquire background timers. The subscribe method still answers without them.
|
|
890
|
+
if (process.env.SENPI_RUNTIME_API_DISABLED !== "true") {
|
|
891
|
+
liveTelemetry.start();
|
|
892
|
+
}
|
|
757
893
|
},
|
|
758
894
|
async stop() {
|
|
895
|
+
// Live telemetry goes first: it owns a timer and an in-memory buffer, nothing downstream
|
|
896
|
+
// depends on it, and stopping it here guarantees no publish tick reads a half-torn-down
|
|
897
|
+
// runtime handle. Records emitted during the teardown below land in a buffer nobody drains,
|
|
898
|
+
// which is the right trade for a process on its way out.
|
|
899
|
+
liveTelemetry.stop();
|
|
759
900
|
// Drain the HTTP server FIRST so in-flight POST /signals requests
|
|
760
901
|
// finish before the underlying runtime they target is torn down.
|
|
761
902
|
// `apiHandle.close()` never throws.
|
|
@@ -1090,6 +1231,10 @@ const senpiRuntimePlugin = {
|
|
|
1090
1231
|
runtimeId: resolvedId,
|
|
1091
1232
|
});
|
|
1092
1233
|
runtimeHandles.set(resolvedId, handle);
|
|
1234
|
+
if (parsedConfig)
|
|
1235
|
+
noteTelemetryRuntimeConfig(resolvedId, parsedConfig);
|
|
1236
|
+
else
|
|
1237
|
+
noteTelemetryRuntime(resolvedId, wallet);
|
|
1093
1238
|
// Registry create fact — distinct from the runtime.started boot transition (which also fires
|
|
1094
1239
|
// on a transient restart). Emitted inside emitEvent's guard so telemetry never fails the install.
|
|
1095
1240
|
emitEvent(apiLogger, () => buildRuntimeCreatedEvent({ runtimeId: resolvedId, address: wallet, recipeName: runtimeName }));
|
|
@@ -1202,7 +1347,7 @@ const senpiRuntimePlugin = {
|
|
|
1202
1347
|
}
|
|
1203
1348
|
}
|
|
1204
1349
|
/** Gateway respond accepts (ok, payload?, error?) with error as { code, message }; plugin SDK types may omit the third arg. */
|
|
1205
|
-
|
|
1350
|
+
registerGatewayMethod("senpi.installRuntime", async (ctx) => {
|
|
1206
1351
|
const respond = ctx.respond;
|
|
1207
1352
|
const result = await installRuntimeCore(ctx.params);
|
|
1208
1353
|
if (result.ok)
|
|
@@ -1210,7 +1355,7 @@ const senpiRuntimePlugin = {
|
|
|
1210
1355
|
else
|
|
1211
1356
|
respond(false, undefined, result.error);
|
|
1212
1357
|
}, { scope: "operator.write" });
|
|
1213
|
-
|
|
1358
|
+
registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
|
|
1214
1359
|
const respond = ctx.respond;
|
|
1215
1360
|
const { readRegistry } = await import("./runtime/strategy-registry.js");
|
|
1216
1361
|
const registry = await readRegistry(baseStateDir);
|
|
@@ -1293,6 +1438,9 @@ const senpiRuntimePlugin = {
|
|
|
1293
1438
|
// "running — NO ENTRY SCANNERS" forever. The map is otherwise only cleared on a
|
|
1294
1439
|
// successful wire/install, which a scanner-less reinstall never reaches.
|
|
1295
1440
|
unwiredRuntimes.delete(targetId);
|
|
1441
|
+
// Same reuse hazard: a deleted id left in the index would keep publishing a strategy that no
|
|
1442
|
+
// longer exists, and would hand a reinstall the old name and scanner list.
|
|
1443
|
+
telemetryStrategies.delete(targetId);
|
|
1296
1444
|
// Archive state directory before removing from registry
|
|
1297
1445
|
if (deletedEntry) {
|
|
1298
1446
|
const runtimeStateDir = buildRuntimeStateDir(baseStateDir, targetId, deletedEntry.wallet ?? "");
|
|
@@ -1321,7 +1469,7 @@ const senpiRuntimePlugin = {
|
|
|
1321
1469
|
}));
|
|
1322
1470
|
return { ok: true, payload: { id: targetId } };
|
|
1323
1471
|
}
|
|
1324
|
-
|
|
1472
|
+
registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
|
|
1325
1473
|
const respond = ctx.respond;
|
|
1326
1474
|
const result = await deleteRuntimeCore(ctx.params);
|
|
1327
1475
|
if (result.ok)
|
|
@@ -1428,6 +1576,17 @@ const senpiRuntimePlugin = {
|
|
|
1428
1576
|
return checkProofCoversRecipe(packageDir, verifyProof(packageDir), appliedRecipe, (f) => readFileSync(f, "utf8"));
|
|
1429
1577
|
},
|
|
1430
1578
|
applyToRuntime: makeApplyToRuntime(runtimeHandles, async (runtimeId, next, recipe) => {
|
|
1579
|
+
// Re-index the topology facts FIRST, ahead of every early return and every failure path
|
|
1580
|
+
// below. `makeApplyToRuntime` only reaches this callback once the in-process swap has
|
|
1581
|
+
// succeeded, so `next` is already the recipe this box runs: its display name and its
|
|
1582
|
+
// declared scanner list are the ones the topology payload must name. Leaving the index on
|
|
1583
|
+
// the previous config would publish strategy rows the new scanner stats cannot join to.
|
|
1584
|
+
//
|
|
1585
|
+
// Ahead of the early return because an apply on a box with no intake still swapped the
|
|
1586
|
+
// recipe, and ahead of the launcher because a launcher that fails is a runtime running the
|
|
1587
|
+
// new recipe with no external scanners — the degraded state a dashboard most needs to show,
|
|
1588
|
+
// and one it can only render if the declared scanners are the new ones.
|
|
1589
|
+
noteTelemetryRuntimeConfig(runtimeId, next);
|
|
1431
1590
|
// Restart this runtime's external scanner children against the new package.
|
|
1432
1591
|
//
|
|
1433
1592
|
// The rebuild swaps in-process components; these are separate OS processes, spawned once
|
|
@@ -1575,7 +1734,7 @@ const senpiRuntimePlugin = {
|
|
|
1575
1734
|
* producers — slated for removal in Task 5 once the migration counter
|
|
1576
1735
|
* decays to zero (see `runtime-phase-2-api-plan.md` Section 8).
|
|
1577
1736
|
*/
|
|
1578
|
-
|
|
1737
|
+
registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
|
|
1579
1738
|
const respond = ctx.respond;
|
|
1580
1739
|
// TODO(s11): wire actual telemetry counter for
|
|
1581
1740
|
// runtime.deprecated_gateway_ingest_call (event name, properties:
|
|
@@ -1594,6 +1753,7 @@ const senpiRuntimePlugin = {
|
|
|
1594
1753
|
...(request.direction !== undefined && { direction: request.direction }),
|
|
1595
1754
|
...(request.score !== undefined && { score: request.score }),
|
|
1596
1755
|
...(request.signal_type !== undefined && { signal_type: request.signal_type }),
|
|
1756
|
+
...(request.tick_id !== undefined && { tick_id: request.tick_id }),
|
|
1597
1757
|
...(request.data !== undefined && { data: request.data }),
|
|
1598
1758
|
...(request.signals !== undefined && { signals: request.signals }),
|
|
1599
1759
|
};
|
|
@@ -1651,7 +1811,7 @@ const senpiRuntimePlugin = {
|
|
|
1651
1811
|
});
|
|
1652
1812
|
}
|
|
1653
1813
|
}, { scope: "operator.write" });
|
|
1654
|
-
|
|
1814
|
+
registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
|
|
1655
1815
|
const respond = ctx.respond;
|
|
1656
1816
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1657
1817
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1676,7 +1836,7 @@ const senpiRuntimePlugin = {
|
|
|
1676
1836
|
}
|
|
1677
1837
|
respond(true, { positions });
|
|
1678
1838
|
}, { scope: "operator.read" });
|
|
1679
|
-
|
|
1839
|
+
registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
|
|
1680
1840
|
const respond = ctx.respond;
|
|
1681
1841
|
const asset = typeof ctx.params?.asset === "string" ? ctx.params.asset : undefined;
|
|
1682
1842
|
if (!asset) {
|
|
@@ -1717,7 +1877,7 @@ const senpiRuntimePlugin = {
|
|
|
1717
1877
|
}
|
|
1718
1878
|
respond(true, { positions: matches });
|
|
1719
1879
|
}, { scope: "operator.read" });
|
|
1720
|
-
|
|
1880
|
+
registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
|
|
1721
1881
|
const respond = ctx.respond;
|
|
1722
1882
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1723
1883
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1743,7 +1903,7 @@ const senpiRuntimePlugin = {
|
|
|
1743
1903
|
closes = closes.slice(0, limit);
|
|
1744
1904
|
respond(true, { closes });
|
|
1745
1905
|
}, { scope: "operator.read" });
|
|
1746
|
-
|
|
1906
|
+
registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
|
|
1747
1907
|
const respond = ctx.respond;
|
|
1748
1908
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
1749
1909
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -1771,7 +1931,7 @@ const senpiRuntimePlugin = {
|
|
|
1771
1931
|
const statuses = await collectRuntimeHealthStatuses(runtimeHandles, api.logger);
|
|
1772
1932
|
respond(true, { statuses, updater });
|
|
1773
1933
|
}, { scope: "operator.read" });
|
|
1774
|
-
|
|
1934
|
+
registerGatewayMethod("senpi.getSystemState", async (ctx) => {
|
|
1775
1935
|
const respond = ctx.respond;
|
|
1776
1936
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
1777
1937
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -1799,6 +1959,36 @@ const senpiRuntimePlugin = {
|
|
|
1799
1959
|
const states = await collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir);
|
|
1800
1960
|
respond(true, { states, updater });
|
|
1801
1961
|
}, { scope: "operator.read" });
|
|
1962
|
+
/**
|
|
1963
|
+
* Live telemetry — the one method of the stream, called once per connect.
|
|
1964
|
+
*
|
|
1965
|
+
* It returns the current value of all three broadcast payloads plus the recent record window, so
|
|
1966
|
+
* a client joining a box that has been running for hours renders a full screen immediately
|
|
1967
|
+
* instead of waiting for the next thing to happen. `(afterEpoch, afterSeq)` resumes a client that
|
|
1968
|
+
* already holds part of this epoch's run — a cursor naming any other epoch is dropped, because a
|
|
1969
|
+
* seq from a previous run cannot address these records. `truncated` says when the records between
|
|
1970
|
+
* a cursor of this epoch and the oldest record still held are gone, which nothing in `recent`
|
|
1971
|
+
* alone can express.
|
|
1972
|
+
*
|
|
1973
|
+
* Read-only, like every other inspection method here. The scope that decides who RECEIVES the
|
|
1974
|
+
* broadcasts is a property of the client's own connection, not of this method.
|
|
1975
|
+
*/
|
|
1976
|
+
registerGatewayMethod("senpi.telemetry.subscribe", async (ctx) => {
|
|
1977
|
+
const respond = ctx.respond;
|
|
1978
|
+
try {
|
|
1979
|
+
respond(true, await liveTelemetry.snapshot({
|
|
1980
|
+
afterEpoch: ctx.params?.afterEpoch,
|
|
1981
|
+
afterSeq: ctx.params?.afterSeq,
|
|
1982
|
+
}));
|
|
1983
|
+
}
|
|
1984
|
+
catch (err) {
|
|
1985
|
+
api.logger.warn("[senpi-runtime] telemetry subscribe error", { err });
|
|
1986
|
+
respond(false, undefined, {
|
|
1987
|
+
code: "INTERNAL_ERROR",
|
|
1988
|
+
message: `Failed to build the telemetry snapshot: ${formatGatewayError(err)}`,
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
}, { scope: "operator.read" });
|
|
1802
1992
|
/**
|
|
1803
1993
|
* Action layer — read-only inspection.
|
|
1804
1994
|
*
|
|
@@ -1818,7 +2008,7 @@ const senpiRuntimePlugin = {
|
|
|
1818
2008
|
* persisted action-store machinery (`src/actions/store/`) usable
|
|
1819
2009
|
* from the operator-facing CLI again.
|
|
1820
2010
|
*/
|
|
1821
|
-
|
|
2011
|
+
registerGatewayMethod("senpi.listActions", async (ctx) => {
|
|
1822
2012
|
const respond = ctx.respond;
|
|
1823
2013
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1824
2014
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1848,7 +2038,7 @@ const senpiRuntimePlugin = {
|
|
|
1848
2038
|
}
|
|
1849
2039
|
respond(true, { actions: merged });
|
|
1850
2040
|
}, { scope: "operator.read" });
|
|
1851
|
-
|
|
2041
|
+
registerGatewayMethod("senpi.getActionState", async (ctx) => {
|
|
1852
2042
|
const respond = ctx.respond;
|
|
1853
2043
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1854
2044
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -1885,7 +2075,7 @@ const senpiRuntimePlugin = {
|
|
|
1885
2075
|
});
|
|
1886
2076
|
}
|
|
1887
2077
|
}, { scope: "operator.read" });
|
|
1888
|
-
|
|
2078
|
+
registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
|
|
1889
2079
|
const respond = ctx.respond;
|
|
1890
2080
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1891
2081
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -1953,7 +2143,7 @@ const senpiRuntimePlugin = {
|
|
|
1953
2143
|
// reachable on HTTP `/audit`. Passes through the caller-controlled subset
|
|
1954
2144
|
// of the audit_query contract; resource scoping (wallet → strategyId) is
|
|
1955
2145
|
// owned by handle.auditQuery, never the caller.
|
|
1956
|
-
|
|
2146
|
+
registerGatewayMethod("senpi.auditQuery", async (ctx) => {
|
|
1957
2147
|
const respond = ctx.respond;
|
|
1958
2148
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1959
2149
|
if (!runtimeId) {
|
|
@@ -2001,7 +2191,7 @@ const senpiRuntimePlugin = {
|
|
|
2001
2191
|
// its state root private, so re-derive it from baseStateDir + the handle's
|
|
2002
2192
|
// wallet (same helper the writer's stateDir was built with); the writer
|
|
2003
2193
|
// nests entries under `<root>/<address>/risk-guard/audit.jsonl`.
|
|
2004
|
-
|
|
2194
|
+
registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
|
|
2005
2195
|
const respond = ctx.respond;
|
|
2006
2196
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2007
2197
|
if (!runtimeId) {
|
|
@@ -2044,7 +2234,7 @@ const senpiRuntimePlugin = {
|
|
|
2044
2234
|
// Per-strategy local domain-event ring (on-disk JSONL) — the trade narrative, queryable locally.
|
|
2045
2235
|
// Same state-root re-derivation as getRiskAudit: the Runtime keeps its root private, so rebuild it
|
|
2046
2236
|
// from baseStateDir + the handle's wallet; the writer nests rows under `<root>/strategies/<address>/events/`.
|
|
2047
|
-
|
|
2237
|
+
registerGatewayMethod("senpi.getEvents", async (ctx) => {
|
|
2048
2238
|
const respond = ctx.respond;
|
|
2049
2239
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
2050
2240
|
if (!runtimeId) {
|
|
@@ -2101,12 +2291,12 @@ const senpiRuntimePlugin = {
|
|
|
2101
2291
|
}
|
|
2102
2292
|
}, { scope: "operator.read" });
|
|
2103
2293
|
// ─── Skills manager — read + mutate ──────────────────────────────────────
|
|
2104
|
-
|
|
2294
|
+
registerGatewayMethod("senpi.skills.status", async (ctx) => {
|
|
2105
2295
|
const respond = ctx.respond;
|
|
2106
2296
|
const snapshot = skillsCoordinator.getSnapshot();
|
|
2107
2297
|
respond(true, { snapshot });
|
|
2108
2298
|
}, { scope: "operator.read" });
|
|
2109
|
-
|
|
2299
|
+
registerGatewayMethod("senpi.skills.update", async (ctx) => {
|
|
2110
2300
|
const respond = ctx.respond;
|
|
2111
2301
|
const name = typeof ctx.params?.name === "string" ? ctx.params.name.trim() : undefined;
|
|
2112
2302
|
if (!name) {
|
|
@@ -2131,7 +2321,7 @@ const senpiRuntimePlugin = {
|
|
|
2131
2321
|
});
|
|
2132
2322
|
}
|
|
2133
2323
|
}, { scope: "operator.write" });
|
|
2134
|
-
|
|
2324
|
+
registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
|
|
2135
2325
|
const respond = ctx.respond;
|
|
2136
2326
|
try {
|
|
2137
2327
|
const results = await skillsCoordinator.updateAll();
|
|
@@ -2197,6 +2387,7 @@ function parseGatewayExternalScannerIngestParams(params) {
|
|
|
2197
2387
|
direction: params?.direction,
|
|
2198
2388
|
score: params?.score,
|
|
2199
2389
|
signal_type: params?.signal_type,
|
|
2390
|
+
tick_id: params?.tick_id,
|
|
2200
2391
|
data: params?.data,
|
|
2201
2392
|
signals: params?.signals,
|
|
2202
2393
|
},
|