@senpi/runtime 3.0.66 → 3.0.68
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/event-derivation.d.ts.map +1 -1
- package/dist/actions/event-derivation.js +2 -0
- package/dist/actions/event-derivation.js.map +1 -1
- package/dist/deploy/job.d.ts.map +1 -1
- package/dist/deploy/job.js +11 -2
- package/dist/deploy/job.js.map +1 -1
- package/dist/deploy/orchestrator.d.ts.map +1 -1
- package/dist/deploy/orchestrator.js +9 -1
- package/dist/deploy/orchestrator.js.map +1 -1
- package/dist/deploy/register.d.ts.map +1 -1
- package/dist/deploy/register.js +10 -2
- package/dist/deploy/register.js.map +1 -1
- package/dist/health/types.d.ts +6 -0
- package/dist/health/types.d.ts.map +1 -1
- 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 +202 -18
- package/dist/index.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +7 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/scanners/engine/engine.d.ts +5 -0
- package/dist/scanners/engine/engine.d.ts.map +1 -1
- package/dist/scanners/engine/engine.js +1 -0
- package/dist/scanners/engine/engine.js.map +1 -1
- package/dist/scanners/runtime-module.d.ts.map +1 -1
- package/dist/scanners/runtime-module.js +5 -0
- package/dist/scanners/runtime-module.js.map +1 -1
- package/dist/skills-manager/manifest.js +2 -2
- package/dist/skills-manager/skills-manifest.json +2 -2
- package/dist/telemetry/event-builders.d.ts +12 -0
- package/dist/telemetry/event-builders.d.ts.map +1 -1
- package/dist/telemetry/event-builders.js +2 -0
- 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 +6 -6
- package/dist/telemetry/event-store.js.map +1 -1
- package/dist/telemetry/live-telemetry.d.ts +153 -0
- package/dist/telemetry/live-telemetry.d.ts.map +1 -0
- package/dist/telemetry/live-telemetry.js +471 -0
- package/dist/telemetry/live-telemetry.js.map +1 -0
- package/dist/telemetry/scanner-stats.d.ts +136 -0
- package/dist/telemetry/scanner-stats.d.ts.map +1 -0
- package/dist/telemetry/scanner-stats.js +304 -0
- package/dist/telemetry/scanner-stats.js.map +1 -0
- 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/utils/event-catalog.d.ts +8 -0
- package/dist/utils/event-catalog.d.ts.map +1 -1
- package/dist/utils/event-catalog.js.map +1 -1
- 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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -108,6 +108,21 @@ interface PluginLogger {
|
|
|
108
108
|
* managed agent can approve. See `registerGatewayMethod` call sites below.
|
|
109
109
|
*/
|
|
110
110
|
type OperatorScope = "operator.admin" | "operator.read" | "operator.write" | "operator.approvals" | "operator.pairing" | "operator.talk.secrets";
|
|
111
|
+
/**
|
|
112
|
+
* What a gateway RPC handler receives. `context` is openclaw's `GatewayRequestContext`, narrowed to
|
|
113
|
+
* the one member the plugin uses: it is the ONLY route to the websocket push layer — the plugin api
|
|
114
|
+
* object carries no broadcast, emit, push or publish. Optional because a host that predates it, and
|
|
115
|
+
* every test double, calls handlers without one.
|
|
116
|
+
*/
|
|
117
|
+
interface GatewayRequestCtx {
|
|
118
|
+
respond: (ok: boolean, value: unknown) => void;
|
|
119
|
+
params?: Record<string, unknown>;
|
|
120
|
+
context?: {
|
|
121
|
+
broadcast?: (event: string, payload: unknown, opts?: {
|
|
122
|
+
dropIfSlow?: boolean;
|
|
123
|
+
}) => void;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
111
126
|
interface PluginApi {
|
|
112
127
|
id: string;
|
|
113
128
|
name: string;
|
|
@@ -139,10 +154,7 @@ interface PluginApi {
|
|
|
139
154
|
}) => void, opts?: {
|
|
140
155
|
commands: string[];
|
|
141
156
|
}): void;
|
|
142
|
-
registerGatewayMethod(name: string, handler: (ctx: {
|
|
143
|
-
respond: (ok: boolean, value: unknown) => void;
|
|
144
|
-
params?: Record<string, unknown>;
|
|
145
|
-
}) => void | Promise<void>, opts?: {
|
|
157
|
+
registerGatewayMethod(name: string, handler: (ctx: GatewayRequestCtx) => void | Promise<void>, opts?: {
|
|
146
158
|
scope?: OperatorScope;
|
|
147
159
|
}): void;
|
|
148
160
|
registerReload?: (registration: {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAsCH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAmB3D,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,7 @@ import { createIntakeContext } from "./runtime-api/intake/intake-context.js";
|
|
|
56
56
|
import { wireRuntime, intakeUrlFor, selectScanners } from "./runtime-api/intake/intake-mount.js";
|
|
57
57
|
import { ingestDispatchMutex, ingestDispatchKey } from "./runtime-api/intake/ingest-mutex.js";
|
|
58
58
|
import { loadRuntimeConfig } from "./runtime/load-runtime-config.js";
|
|
59
|
+
import { createLiveTelemetry } from "./telemetry/live-telemetry.js";
|
|
59
60
|
/** Initialize all core features (DSL, risk, health) on the given event bus. */
|
|
60
61
|
export function initCore(bus) {
|
|
61
62
|
initDsl(bus);
|
|
@@ -316,10 +317,140 @@ const senpiRuntimePlugin = {
|
|
|
316
317
|
// cleared on a successful wire/install. senpi.listRuntimes reads it to render
|
|
317
318
|
// "running — NO ENTRY SCANNERS" instead of a bare "running" for a blind runtime.
|
|
318
319
|
const unwiredRuntimes = new Map();
|
|
320
|
+
// ── Live telemetry ────────────────────────────────────────────────
|
|
321
|
+
// Every runtime this box knows about, running or not, keyed runtimeId. The topology payload has
|
|
322
|
+
// to name a stopped strategy too, so this is populated from the registry rather than from
|
|
323
|
+
// `runtimeHandles` (which holds only the live ones) and is the source of the two facts the
|
|
324
|
+
// registry entry does not carry: the strategy's display name and its declared scanners. Both come
|
|
325
|
+
// from the parsed YAML — `name` is the same `config.name` that `RuntimeHealthStatus.runtimeName`
|
|
326
|
+
// is built from, so a runtime whose config never parsed falls back to its id rather than to a
|
|
327
|
+
// guess, and its scanner list stays empty rather than inventing rows.
|
|
328
|
+
const telemetryStrategies = new Map();
|
|
329
|
+
const noteTelemetryRuntime = (runtimeId, address) => {
|
|
330
|
+
const existing = telemetryStrategies.get(runtimeId);
|
|
331
|
+
telemetryStrategies.set(runtimeId, {
|
|
332
|
+
address,
|
|
333
|
+
name: existing?.name ?? runtimeId,
|
|
334
|
+
scanners: existing?.scanners ?? [],
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
const noteTelemetryRuntimeConfig = (runtimeId, config) => {
|
|
338
|
+
telemetryStrategies.set(runtimeId, {
|
|
339
|
+
address: config.strategy?.wallet ?? telemetryStrategies.get(runtimeId)?.address ?? "",
|
|
340
|
+
name: config.name,
|
|
341
|
+
scanners: config.scanners ?? [],
|
|
342
|
+
});
|
|
343
|
+
};
|
|
319
344
|
// Dedicated logger for the runtime-api subtree — required because the
|
|
320
345
|
// PluginLogger surface (info/warn/error) lacks `debug`, while the
|
|
321
346
|
// Logger contract used by `runtime-api/` includes it.
|
|
322
347
|
const apiLogger = createLogger("[senpi-core][api]");
|
|
348
|
+
/**
|
|
349
|
+
* One gather per publish tick. Health and system state are read together because the two payloads
|
|
350
|
+
* need both: the topology reads health and `runtimeName`, the stats read the per-scanner rows,
|
|
351
|
+
* and `restartCount` / `degradedReason` live only on the health rows while every other scanner
|
|
352
|
+
* field lives only on the system-state rows — so they are joined here on `(runtimeId, scannerId)`.
|
|
353
|
+
*/
|
|
354
|
+
const collectTelemetryFacts = async () => {
|
|
355
|
+
const [statuses, states] = await Promise.all([
|
|
356
|
+
collectRuntimeHealthStatuses(runtimeHandles, api.logger),
|
|
357
|
+
collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir),
|
|
358
|
+
]);
|
|
359
|
+
const healthByRuntime = new Map();
|
|
360
|
+
for (const status of statuses) {
|
|
361
|
+
if (status.runtimeId !== undefined)
|
|
362
|
+
healthByRuntime.set(status.runtimeId, status);
|
|
363
|
+
}
|
|
364
|
+
const scannerState = [];
|
|
365
|
+
// Registration facts per (runtimeId, scannerId), so the topology can report a scanner's live
|
|
366
|
+
// schedule rather than only what its YAML declared.
|
|
367
|
+
const registrations = new Map();
|
|
368
|
+
for (const state of states) {
|
|
369
|
+
const runtimeId = state.runtimeId;
|
|
370
|
+
const healthRows = runtimeId === undefined
|
|
371
|
+
? []
|
|
372
|
+
: healthByRuntime.get(runtimeId)?.components.scanners.scanners ?? [];
|
|
373
|
+
for (const row of state.components.scanners.state.scanners) {
|
|
374
|
+
const supervision = healthRows.find((r) => r.scannerId === row.scannerId);
|
|
375
|
+
const snapshot = {
|
|
376
|
+
address: row.address,
|
|
377
|
+
scannerId: row.scannerId,
|
|
378
|
+
health: row.health,
|
|
379
|
+
degradedReason: supervision?.degradedReason ?? null,
|
|
380
|
+
enabled: row.enabled,
|
|
381
|
+
inFlightSince: row.inFlightSince,
|
|
382
|
+
lastRunStartedAt: row.lastRunStartedAt,
|
|
383
|
+
lastRunFinishedAt: row.lastRunFinishedAt,
|
|
384
|
+
lastRunStatus: row.lastRunStatus,
|
|
385
|
+
lastRunReason: row.lastRunReason,
|
|
386
|
+
nextRunAt: row.nextRunAt,
|
|
387
|
+
lastAliveAt: row.lastAliveAt,
|
|
388
|
+
// No supervision row means the restart count is unknown, not zero. A zero here would
|
|
389
|
+
// read as "this scanner has never restarted" on a scanner nobody is supervising.
|
|
390
|
+
restartCount: supervision?.restartCount ?? null,
|
|
391
|
+
};
|
|
392
|
+
scannerState.push(snapshot);
|
|
393
|
+
if (runtimeId !== undefined) {
|
|
394
|
+
registrations.set(`${runtimeId}::${row.scannerId}`, {
|
|
395
|
+
...snapshot,
|
|
396
|
+
scheduleMode: row.scheduleMode,
|
|
397
|
+
configVersion: row.configVersion,
|
|
398
|
+
intervalSeconds: row.intervalSeconds,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const strategies = [];
|
|
404
|
+
for (const [runtimeId, entry] of telemetryStrategies) {
|
|
405
|
+
const health = healthByRuntime.get(runtimeId);
|
|
406
|
+
const scanners = entry.scanners.map((declared) => {
|
|
407
|
+
const live = registrations.get(`${runtimeId}::${declared.name}`);
|
|
408
|
+
return live === undefined
|
|
409
|
+
? { entry: declared }
|
|
410
|
+
: {
|
|
411
|
+
entry: declared,
|
|
412
|
+
registration: {
|
|
413
|
+
enabled: live.enabled,
|
|
414
|
+
scheduleMode: live.scheduleMode,
|
|
415
|
+
// The engine's own cadence, which is what the scanner actually polls on — a
|
|
416
|
+
// recipe may omit it and take the scanner definition's default. An external
|
|
417
|
+
// scanner registers zero because it is POSTed to rather than polled, and only
|
|
418
|
+
// the recipe knows the cadence its scaffold was told to tick at.
|
|
419
|
+
intervalSeconds: live.intervalSeconds > 0 ? live.intervalSeconds : declared.interval_seconds ?? null,
|
|
420
|
+
configVersion: live.configVersion,
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
strategies.push({
|
|
425
|
+
runtimeId,
|
|
426
|
+
address: entry.address,
|
|
427
|
+
// `runtimeName` is built from the same `config.name`; the indexed value is used because it
|
|
428
|
+
// is available for a stopped runtime too, where there is no health status to read.
|
|
429
|
+
name: health?.runtimeName ?? entry.name,
|
|
430
|
+
scanners,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
strategies,
|
|
435
|
+
runningRuntimeIds: runtimeHandles,
|
|
436
|
+
unwiredRuntimeIds: unwiredRuntimes,
|
|
437
|
+
readHealth: (runtimeId) => healthByRuntime.get(runtimeId),
|
|
438
|
+
scannerState,
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
const liveTelemetry = createLiveTelemetry({ collectFacts: collectTelemetryFacts });
|
|
442
|
+
/**
|
|
443
|
+
* Every gateway method goes through here so the broadcast handle is captured on the FIRST request
|
|
444
|
+
* of any kind. `ctx.context` is the only route to the websocket push layer and it exists only
|
|
445
|
+
* inside a handler — capturing it in the subscribe method alone would mean a box whose client
|
|
446
|
+
* never subscribes can never publish anything, silently.
|
|
447
|
+
*/
|
|
448
|
+
const registerGatewayMethod = (name, handler, opts) => {
|
|
449
|
+
api.registerGatewayMethod(name, (ctx) => {
|
|
450
|
+
liveTelemetry.captureBroadcast(ctx, name);
|
|
451
|
+
return handler(ctx);
|
|
452
|
+
}, opts);
|
|
453
|
+
};
|
|
323
454
|
// A gateway seam failed to wire/launch a runtime's external scanner stack: the runtime is up but
|
|
324
455
|
// runs blind on internal scanners only. Two records, by design: a first-class `runtime.scanners_unwired`
|
|
325
456
|
// event (the queryable/alertable fact, by phase) and the migrated exception-form `logger.error`
|
|
@@ -527,6 +658,9 @@ const senpiRuntimePlugin = {
|
|
|
527
658
|
continue;
|
|
528
659
|
const pathOrContent = entry.runtimeYamlPath ?? entry.runtimeYamlContent;
|
|
529
660
|
const entryStateDir = buildRuntimeStateDir(baseStateDir, entry.id, entry.wallet ?? "");
|
|
661
|
+
// Indexed before the boot attempt: a runtime whose start throws is installed but stopped,
|
|
662
|
+
// and the telemetry topology has to be able to say so.
|
|
663
|
+
noteTelemetryRuntime(entry.id, entry.wallet ?? "");
|
|
530
664
|
const handle = await run(pathOrContent, {
|
|
531
665
|
fromPath: hasPath,
|
|
532
666
|
stateDir: entryStateDir,
|
|
@@ -549,6 +683,7 @@ const senpiRuntimePlugin = {
|
|
|
549
683
|
if (sharedIntakeContext) {
|
|
550
684
|
try {
|
|
551
685
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
686
|
+
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
552
687
|
// Content-installed runtimes carry their recipe dir in the registry
|
|
553
688
|
// entry (runtimeYamlDir) so relative scanner paths still resolve
|
|
554
689
|
// after a restart; path-installed ones derive it from the path.
|
|
@@ -600,6 +735,7 @@ const senpiRuntimePlugin = {
|
|
|
600
735
|
// event) so listRuntimes/health don't render a bare "running".
|
|
601
736
|
try {
|
|
602
737
|
const runtimeConfig = await loadRuntimeConfig(pathOrContent, { fromPath: hasPath });
|
|
738
|
+
noteTelemetryRuntimeConfig(entry.id, runtimeConfig);
|
|
603
739
|
if (selectScanners(runtimeConfig).length > 0) {
|
|
604
740
|
unwiredRuntimes.set(entry.id, "no_intake");
|
|
605
741
|
handle.attachSupervisionSource(() => ({
|
|
@@ -701,8 +837,19 @@ const senpiRuntimePlugin = {
|
|
|
701
837
|
}
|
|
702
838
|
}
|
|
703
839
|
}
|
|
840
|
+
// Publish ticks start only once the runtimes are up, so the first frames describe a real
|
|
841
|
+
// box. Gated by the same switch the skills coordinator uses: unit tests boot `register()`
|
|
842
|
+
// and must not acquire background timers. The subscribe method still answers without them.
|
|
843
|
+
if (process.env.SENPI_RUNTIME_API_DISABLED !== "true") {
|
|
844
|
+
liveTelemetry.start();
|
|
845
|
+
}
|
|
704
846
|
},
|
|
705
847
|
async stop() {
|
|
848
|
+
// Live telemetry goes first: it owns a timer and an in-memory buffer, nothing downstream
|
|
849
|
+
// depends on it, and stopping it here guarantees no publish tick reads a half-torn-down
|
|
850
|
+
// runtime handle. Records emitted during the teardown below land in a buffer nobody drains,
|
|
851
|
+
// which is the right trade for a process on its way out.
|
|
852
|
+
liveTelemetry.stop();
|
|
706
853
|
// Drain the HTTP server FIRST so in-flight POST /signals requests
|
|
707
854
|
// finish before the underlying runtime they target is torn down.
|
|
708
855
|
// `apiHandle.close()` never throws.
|
|
@@ -1009,6 +1156,10 @@ const senpiRuntimePlugin = {
|
|
|
1009
1156
|
runtimeId: resolvedId,
|
|
1010
1157
|
});
|
|
1011
1158
|
runtimeHandles.set(resolvedId, handle);
|
|
1159
|
+
if (parsedConfig)
|
|
1160
|
+
noteTelemetryRuntimeConfig(resolvedId, parsedConfig);
|
|
1161
|
+
else
|
|
1162
|
+
noteTelemetryRuntime(resolvedId, wallet);
|
|
1012
1163
|
// Registry create fact — distinct from the runtime.started boot transition (which also fires
|
|
1013
1164
|
// on a transient restart). Emitted inside emitEvent's guard so telemetry never fails the install.
|
|
1014
1165
|
emitEvent(apiLogger, () => buildRuntimeCreatedEvent({ runtimeId: resolvedId, address: wallet, recipeName: runtimeName }));
|
|
@@ -1114,7 +1265,7 @@ const senpiRuntimePlugin = {
|
|
|
1114
1265
|
}
|
|
1115
1266
|
}
|
|
1116
1267
|
/** Gateway respond accepts (ok, payload?, error?) with error as { code, message }; plugin SDK types may omit the third arg. */
|
|
1117
|
-
|
|
1268
|
+
registerGatewayMethod("senpi.installRuntime", async (ctx) => {
|
|
1118
1269
|
const respond = ctx.respond;
|
|
1119
1270
|
const result = await installRuntimeCore(ctx.params);
|
|
1120
1271
|
if (result.ok)
|
|
@@ -1122,7 +1273,7 @@ const senpiRuntimePlugin = {
|
|
|
1122
1273
|
else
|
|
1123
1274
|
respond(false, undefined, result.error);
|
|
1124
1275
|
}, { scope: "operator.write" });
|
|
1125
|
-
|
|
1276
|
+
registerGatewayMethod("senpi.listRuntimes", async (ctx) => {
|
|
1126
1277
|
const respond = ctx.respond;
|
|
1127
1278
|
const { readRegistry } = await import("./runtime/strategy-registry.js");
|
|
1128
1279
|
const registry = await readRegistry(baseStateDir);
|
|
@@ -1201,6 +1352,9 @@ const senpiRuntimePlugin = {
|
|
|
1201
1352
|
// "running — NO ENTRY SCANNERS" forever. The map is otherwise only cleared on a
|
|
1202
1353
|
// successful wire/install, which a scanner-less reinstall never reaches.
|
|
1203
1354
|
unwiredRuntimes.delete(targetId);
|
|
1355
|
+
// Same reuse hazard: a deleted id left in the index would keep publishing a strategy that no
|
|
1356
|
+
// longer exists, and would hand a reinstall the old name and scanner list.
|
|
1357
|
+
telemetryStrategies.delete(targetId);
|
|
1204
1358
|
// Archive state directory before removing from registry
|
|
1205
1359
|
if (deletedEntry) {
|
|
1206
1360
|
const runtimeStateDir = buildRuntimeStateDir(baseStateDir, targetId, deletedEntry.wallet ?? "");
|
|
@@ -1227,7 +1381,7 @@ const senpiRuntimePlugin = {
|
|
|
1227
1381
|
}));
|
|
1228
1382
|
return { ok: true, payload: { id: targetId } };
|
|
1229
1383
|
}
|
|
1230
|
-
|
|
1384
|
+
registerGatewayMethod("senpi.deleteRuntime", async (ctx) => {
|
|
1231
1385
|
const respond = ctx.respond;
|
|
1232
1386
|
const result = await deleteRuntimeCore(ctx.params);
|
|
1233
1387
|
if (result.ok)
|
|
@@ -1321,7 +1475,7 @@ const senpiRuntimePlugin = {
|
|
|
1321
1475
|
* producers — slated for removal in Task 5 once the migration counter
|
|
1322
1476
|
* decays to zero (see `runtime-phase-2-api-plan.md` Section 8).
|
|
1323
1477
|
*/
|
|
1324
|
-
|
|
1478
|
+
registerGatewayMethod("senpi.ingestExternalScannerData", async (ctx) => {
|
|
1325
1479
|
const respond = ctx.respond;
|
|
1326
1480
|
// TODO(s11): wire actual telemetry counter for
|
|
1327
1481
|
// runtime.deprecated_gateway_ingest_call (event name, properties:
|
|
@@ -1397,7 +1551,7 @@ const senpiRuntimePlugin = {
|
|
|
1397
1551
|
});
|
|
1398
1552
|
}
|
|
1399
1553
|
}, { scope: "operator.write" });
|
|
1400
|
-
|
|
1554
|
+
registerGatewayMethod("senpi.listDslPositions", async (ctx) => {
|
|
1401
1555
|
const respond = ctx.respond;
|
|
1402
1556
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1403
1557
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1422,7 +1576,7 @@ const senpiRuntimePlugin = {
|
|
|
1422
1576
|
}
|
|
1423
1577
|
respond(true, { positions });
|
|
1424
1578
|
}, { scope: "operator.read" });
|
|
1425
|
-
|
|
1579
|
+
registerGatewayMethod("senpi.getDslPositionState", async (ctx) => {
|
|
1426
1580
|
const respond = ctx.respond;
|
|
1427
1581
|
const asset = typeof ctx.params?.asset === "string" ? ctx.params.asset : undefined;
|
|
1428
1582
|
if (!asset) {
|
|
@@ -1463,7 +1617,7 @@ const senpiRuntimePlugin = {
|
|
|
1463
1617
|
}
|
|
1464
1618
|
respond(true, { positions: matches });
|
|
1465
1619
|
}, { scope: "operator.read" });
|
|
1466
|
-
|
|
1620
|
+
registerGatewayMethod("senpi.listDslArchives", async (ctx) => {
|
|
1467
1621
|
const respond = ctx.respond;
|
|
1468
1622
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1469
1623
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1489,7 +1643,7 @@ const senpiRuntimePlugin = {
|
|
|
1489
1643
|
closes = closes.slice(0, limit);
|
|
1490
1644
|
respond(true, { closes });
|
|
1491
1645
|
}, { scope: "operator.read" });
|
|
1492
|
-
|
|
1646
|
+
registerGatewayMethod("senpi.getHealthStatus", async (ctx) => {
|
|
1493
1647
|
const respond = ctx.respond;
|
|
1494
1648
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
1495
1649
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -1517,7 +1671,7 @@ const senpiRuntimePlugin = {
|
|
|
1517
1671
|
const statuses = await collectRuntimeHealthStatuses(runtimeHandles, api.logger);
|
|
1518
1672
|
respond(true, { statuses, updater });
|
|
1519
1673
|
}, { scope: "operator.read" });
|
|
1520
|
-
|
|
1674
|
+
registerGatewayMethod("senpi.getSystemState", async (ctx) => {
|
|
1521
1675
|
const respond = ctx.respond;
|
|
1522
1676
|
const updater = autoUpdateCoordinator.getSnapshot();
|
|
1523
1677
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
@@ -1545,6 +1699,36 @@ const senpiRuntimePlugin = {
|
|
|
1545
1699
|
const states = await collectRuntimeSystemStates(runtimeHandles, api.logger, baseStateDir);
|
|
1546
1700
|
respond(true, { states, updater });
|
|
1547
1701
|
}, { scope: "operator.read" });
|
|
1702
|
+
/**
|
|
1703
|
+
* Live telemetry — the one method of the stream, called once per connect.
|
|
1704
|
+
*
|
|
1705
|
+
* It returns the current value of all three broadcast payloads plus the recent record window, so
|
|
1706
|
+
* a client joining a box that has been running for hours renders a full screen immediately
|
|
1707
|
+
* instead of waiting for the next thing to happen. `(afterEpoch, afterSeq)` resumes a client that
|
|
1708
|
+
* already holds part of this epoch's run — a cursor naming any other epoch is dropped, because a
|
|
1709
|
+
* seq from a previous run cannot address these records. `truncated` says when the records between
|
|
1710
|
+
* a cursor of this epoch and the oldest record still held are gone, which nothing in `recent`
|
|
1711
|
+
* alone can express.
|
|
1712
|
+
*
|
|
1713
|
+
* Read-only, like every other inspection method here. The scope that decides who RECEIVES the
|
|
1714
|
+
* broadcasts is a property of the client's own connection, not of this method.
|
|
1715
|
+
*/
|
|
1716
|
+
registerGatewayMethod("senpi.telemetry.subscribe", async (ctx) => {
|
|
1717
|
+
const respond = ctx.respond;
|
|
1718
|
+
try {
|
|
1719
|
+
respond(true, await liveTelemetry.snapshot({
|
|
1720
|
+
afterEpoch: ctx.params?.afterEpoch,
|
|
1721
|
+
afterSeq: ctx.params?.afterSeq,
|
|
1722
|
+
}));
|
|
1723
|
+
}
|
|
1724
|
+
catch (err) {
|
|
1725
|
+
api.logger.warn("[senpi-runtime] telemetry subscribe error", { err });
|
|
1726
|
+
respond(false, undefined, {
|
|
1727
|
+
code: "INTERNAL_ERROR",
|
|
1728
|
+
message: `Failed to build the telemetry snapshot: ${formatGatewayError(err)}`,
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
}, { scope: "operator.read" });
|
|
1548
1732
|
/**
|
|
1549
1733
|
* Action layer — read-only inspection.
|
|
1550
1734
|
*
|
|
@@ -1564,7 +1748,7 @@ const senpiRuntimePlugin = {
|
|
|
1564
1748
|
* persisted action-store machinery (`src/actions/store/`) usable
|
|
1565
1749
|
* from the operator-facing CLI again.
|
|
1566
1750
|
*/
|
|
1567
|
-
|
|
1751
|
+
registerGatewayMethod("senpi.listActions", async (ctx) => {
|
|
1568
1752
|
const respond = ctx.respond;
|
|
1569
1753
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1570
1754
|
const address = typeof ctx.params?.address === "string" ? ctx.params.address : undefined;
|
|
@@ -1594,7 +1778,7 @@ const senpiRuntimePlugin = {
|
|
|
1594
1778
|
}
|
|
1595
1779
|
respond(true, { actions: merged });
|
|
1596
1780
|
}, { scope: "operator.read" });
|
|
1597
|
-
|
|
1781
|
+
registerGatewayMethod("senpi.getActionState", async (ctx) => {
|
|
1598
1782
|
const respond = ctx.respond;
|
|
1599
1783
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1600
1784
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -1631,7 +1815,7 @@ const senpiRuntimePlugin = {
|
|
|
1631
1815
|
});
|
|
1632
1816
|
}
|
|
1633
1817
|
}, { scope: "operator.read" });
|
|
1634
|
-
|
|
1818
|
+
registerGatewayMethod("senpi.getActionHistory", async (ctx) => {
|
|
1635
1819
|
const respond = ctx.respond;
|
|
1636
1820
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1637
1821
|
const actionName = typeof ctx.params?.actionName === "string" ? ctx.params.actionName : undefined;
|
|
@@ -1699,7 +1883,7 @@ const senpiRuntimePlugin = {
|
|
|
1699
1883
|
// reachable on HTTP `/audit`. Passes through the caller-controlled subset
|
|
1700
1884
|
// of the audit_query contract; resource scoping (wallet → strategyId) is
|
|
1701
1885
|
// owned by handle.auditQuery, never the caller.
|
|
1702
|
-
|
|
1886
|
+
registerGatewayMethod("senpi.auditQuery", async (ctx) => {
|
|
1703
1887
|
const respond = ctx.respond;
|
|
1704
1888
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1705
1889
|
if (!runtimeId) {
|
|
@@ -1747,7 +1931,7 @@ const senpiRuntimePlugin = {
|
|
|
1747
1931
|
// its state root private, so re-derive it from baseStateDir + the handle's
|
|
1748
1932
|
// wallet (same helper the writer's stateDir was built with); the writer
|
|
1749
1933
|
// nests entries under `<root>/<address>/risk-guard/audit.jsonl`.
|
|
1750
|
-
|
|
1934
|
+
registerGatewayMethod("senpi.getRiskAudit", async (ctx) => {
|
|
1751
1935
|
const respond = ctx.respond;
|
|
1752
1936
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1753
1937
|
if (!runtimeId) {
|
|
@@ -1790,7 +1974,7 @@ const senpiRuntimePlugin = {
|
|
|
1790
1974
|
// Per-strategy local domain-event ring (on-disk JSONL) — the trade narrative, queryable locally.
|
|
1791
1975
|
// Same state-root re-derivation as getRiskAudit: the Runtime keeps its root private, so rebuild it
|
|
1792
1976
|
// from baseStateDir + the handle's wallet; the writer nests rows under `<root>/strategies/<address>/events/`.
|
|
1793
|
-
|
|
1977
|
+
registerGatewayMethod("senpi.getEvents", async (ctx) => {
|
|
1794
1978
|
const respond = ctx.respond;
|
|
1795
1979
|
const runtimeId = typeof ctx.params?.runtimeId === "string" ? ctx.params.runtimeId : undefined;
|
|
1796
1980
|
if (!runtimeId) {
|
|
@@ -1847,12 +2031,12 @@ const senpiRuntimePlugin = {
|
|
|
1847
2031
|
}
|
|
1848
2032
|
}, { scope: "operator.read" });
|
|
1849
2033
|
// ─── Skills manager — read + mutate ──────────────────────────────────────
|
|
1850
|
-
|
|
2034
|
+
registerGatewayMethod("senpi.skills.status", async (ctx) => {
|
|
1851
2035
|
const respond = ctx.respond;
|
|
1852
2036
|
const snapshot = skillsCoordinator.getSnapshot();
|
|
1853
2037
|
respond(true, { snapshot });
|
|
1854
2038
|
}, { scope: "operator.read" });
|
|
1855
|
-
|
|
2039
|
+
registerGatewayMethod("senpi.skills.update", async (ctx) => {
|
|
1856
2040
|
const respond = ctx.respond;
|
|
1857
2041
|
const name = typeof ctx.params?.name === "string" ? ctx.params.name.trim() : undefined;
|
|
1858
2042
|
if (!name) {
|
|
@@ -1877,7 +2061,7 @@ const senpiRuntimePlugin = {
|
|
|
1877
2061
|
});
|
|
1878
2062
|
}
|
|
1879
2063
|
}, { scope: "operator.write" });
|
|
1880
|
-
|
|
2064
|
+
registerGatewayMethod("senpi.skills.updateAll", async (ctx) => {
|
|
1881
2065
|
const respond = ctx.respond;
|
|
1882
2066
|
try {
|
|
1883
2067
|
const results = await skillsCoordinator.updateAll();
|