@senpi/runtime 3.0.187 → 3.0.190

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.
Files changed (103) hide show
  1. package/dist/cli/guide-commands.d.ts.map +1 -1
  2. package/dist/cli/guide-commands.js +6 -0
  3. package/dist/cli/guide-commands.js.map +1 -1
  4. package/dist/cli/senpi-commands.d.ts.map +1 -1
  5. package/dist/cli/senpi-commands.js +4 -2
  6. package/dist/cli/senpi-commands.js.map +1 -1
  7. package/dist/deploy/box-cap.d.ts +7 -0
  8. package/dist/deploy/box-cap.d.ts.map +1 -0
  9. package/dist/deploy/box-cap.js +13 -0
  10. package/dist/deploy/box-cap.js.map +1 -0
  11. package/dist/deploy/funding.d.ts +13 -1
  12. package/dist/deploy/funding.d.ts.map +1 -1
  13. package/dist/deploy/funding.js +13 -4
  14. package/dist/deploy/funding.js.map +1 -1
  15. package/dist/deploy/job.d.ts +2 -0
  16. package/dist/deploy/job.d.ts.map +1 -1
  17. package/dist/deploy/job.js +2 -2
  18. package/dist/deploy/job.js.map +1 -1
  19. package/dist/deploy/messages.d.ts +14 -0
  20. package/dist/deploy/messages.d.ts.map +1 -1
  21. package/dist/deploy/messages.js +59 -25
  22. package/dist/deploy/messages.js.map +1 -1
  23. package/dist/deploy/orchestrator.d.ts +23 -1
  24. package/dist/deploy/orchestrator.d.ts.map +1 -1
  25. package/dist/deploy/orchestrator.js +87 -30
  26. package/dist/deploy/orchestrator.js.map +1 -1
  27. package/dist/deploy/package.d.ts +14 -1
  28. package/dist/deploy/package.d.ts.map +1 -1
  29. package/dist/deploy/package.js +37 -2
  30. package/dist/deploy/package.js.map +1 -1
  31. package/dist/deploy/register.d.ts +2 -0
  32. package/dist/deploy/register.d.ts.map +1 -1
  33. package/dist/deploy/register.js +48 -32
  34. package/dist/deploy/register.js.map +1 -1
  35. package/dist/index.d.ts +0 -4
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +43 -12
  38. package/dist/index.js.map +1 -1
  39. package/dist/runtime/boot-skip.d.ts +18 -0
  40. package/dist/runtime/boot-skip.d.ts.map +1 -1
  41. package/dist/runtime/boot-skip.js +25 -0
  42. package/dist/runtime/boot-skip.js.map +1 -1
  43. package/dist/runtime/lifecycle-events.d.ts +2 -2
  44. package/dist/runtime/lifecycle-events.d.ts.map +1 -1
  45. package/dist/runtime/lifecycle-events.js.map +1 -1
  46. package/dist/runtime/lifecycle-reaper.d.ts +11 -6
  47. package/dist/runtime/lifecycle-reaper.d.ts.map +1 -1
  48. package/dist/runtime/lifecycle-reaper.js +25 -15
  49. package/dist/runtime/lifecycle-reaper.js.map +1 -1
  50. package/dist/runtime/notifications.d.ts +10 -0
  51. package/dist/runtime/notifications.d.ts.map +1 -1
  52. package/dist/runtime/notifications.js +17 -6
  53. package/dist/runtime/notifications.js.map +1 -1
  54. package/dist/runtime/runtime.d.ts.map +1 -1
  55. package/dist/runtime/runtime.js +4 -1
  56. package/dist/runtime/runtime.js.map +1 -1
  57. package/dist/scanners/external/scaffold/preflight.py +492 -12
  58. package/dist/senpi/__tests__/helpers/fake-paper-mcp.d.ts +44 -0
  59. package/dist/senpi/__tests__/helpers/fake-paper-mcp.d.ts.map +1 -0
  60. package/dist/senpi/__tests__/helpers/fake-paper-mcp.js +129 -0
  61. package/dist/senpi/__tests__/helpers/fake-paper-mcp.js.map +1 -0
  62. package/dist/senpi/client.d.ts +35 -1
  63. package/dist/senpi/client.d.ts.map +1 -1
  64. package/dist/senpi/client.js +62 -8
  65. package/dist/senpi/client.js.map +1 -1
  66. package/dist/senpi/paper.d.ts +28 -0
  67. package/dist/senpi/paper.d.ts.map +1 -0
  68. package/dist/senpi/paper.js +35 -0
  69. package/dist/senpi/paper.js.map +1 -0
  70. package/dist/senpi/strategy-status.d.ts.map +1 -1
  71. package/dist/senpi/strategy-status.js +2 -0
  72. package/dist/senpi/strategy-status.js.map +1 -1
  73. package/dist/senpi/types.d.ts +8 -1
  74. package/dist/senpi/types.d.ts.map +1 -1
  75. package/dist/telemetry/box-memory.d.ts +29 -0
  76. package/dist/telemetry/box-memory.d.ts.map +1 -0
  77. package/dist/telemetry/box-memory.js +113 -0
  78. package/dist/telemetry/box-memory.js.map +1 -0
  79. package/dist/telemetry/metrics.d.ts +21 -0
  80. package/dist/telemetry/metrics.d.ts.map +1 -1
  81. package/dist/telemetry/metrics.js +82 -0
  82. package/dist/telemetry/metrics.js.map +1 -1
  83. package/dist/telemetry/spans/span-hooks.d.ts.map +1 -1
  84. package/dist/telemetry/spans/span-hooks.js +4 -1
  85. package/dist/telemetry/spans/span-hooks.js.map +1 -1
  86. package/dist/telemetry/trace-identity.d.ts.map +1 -1
  87. package/dist/telemetry/trace-identity.js +2 -0
  88. package/dist/telemetry/trace-identity.js.map +1 -1
  89. package/dist/universe/package-universe.js +1 -1
  90. package/dist/universe/package-universe.js.map +1 -1
  91. package/dist/utils/event-catalog.d.ts +7 -3
  92. package/dist/utils/event-catalog.d.ts.map +1 -1
  93. package/dist/utils/event-catalog.js.map +1 -1
  94. package/dist/validate/codes.d.ts +2 -0
  95. package/dist/validate/codes.d.ts.map +1 -1
  96. package/dist/validate/codes.js +6 -0
  97. package/dist/validate/codes.js.map +1 -1
  98. package/dist/validate/live-stage.d.ts +12 -0
  99. package/dist/validate/live-stage.d.ts.map +1 -1
  100. package/dist/validate/live-stage.js +16 -0
  101. package/dist/validate/live-stage.js.map +1 -1
  102. package/openclaw.plugin.json +1 -1
  103. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import { join, dirname } from "node:path";
17
17
  import { isMoneyCallTool, SenpiClient, senpiOrderCallsInFlight } from "./senpi/client.js";
18
18
  import { MoneyCallTracker, registerMoneyCallHooks } from "./runtime/auto-update/money-call-tracker.js";
19
19
  import { buildWalletStatusHint } from "./senpi/strategy-status.js";
20
- import { bootSkippedStatusLabel, partitionByWalletStatus, readBootWalletStatuses, trackableWalletKeys, } from "./runtime/boot-skip.js";
20
+ import { bootSkippedStatusLabel, partitionByWalletStatus, readBootWalletStatuses, readWalletStatusesByWorld, trackableWalletKeys, } from "./runtime/boot-skip.js";
21
21
  import { DeployJobController } from "./deploy/job.js";
22
22
  import { DEPLOY_CAPABILITIES, registerDeployGatewayMethods } from "./deploy/register.js";
23
23
  import { createQuantDesk } from "./quant/register.js";
@@ -60,6 +60,9 @@ import { registerSkillRouterHook } from "./prompt/skill-router-hook.js";
60
60
  import { registerCribSheet } from "./tool-surface/crib-hook.js";
61
61
  import { getDefaultPiiRedactor, initDefaultPiiRedactor } from "./telemetry/spans/redact-pii.js";
62
62
  import { flushOtelLogs, initOtelSdk, shutdownOtelSdk, waitForOtelInit } from "./telemetry/otel-sdk.js";
63
+ import { createBoxMemoryReader } from "./telemetry/box-memory.js";
64
+ import { registerBoxMemoryGauges } from "./telemetry/metrics.js";
65
+ import { walletMode } from "./senpi/paper.js";
63
66
  import { getLogLevel, getLogLevelSource, parseLogLevel, setLogLevel, } from "./utils/logger.js";
64
67
  import { ExternalScannerIngestError, } from "./scanners/external-scanner-receiver.js";
65
68
  import { createLogger } from "./utils/logger.js";
@@ -227,10 +230,19 @@ function registerSenpiCliMetadata(api) {
227
230
  registerSenpiCli(ctx.program);
228
231
  }, { commands: ["senpi"] });
229
232
  }
233
+ /** Running runtimes by world, read from each handle's wallet: a paper wallet is a simulation. */
234
+ function countRuntimesByMode(handles) {
235
+ const counts = { live: 0, paper: 0 };
236
+ for (const handle of handles)
237
+ counts[walletMode(handle.wallet)] += 1;
238
+ return counts;
239
+ }
230
240
  /**
231
241
  * OpenClaw plugin entry.
232
242
  * Boots from internal registry (installed_runtimes.json) in stateDir; does not read runtime list from config.
233
243
  */
244
+ /** How long service start waits for the OTel SDK before registering the box memory gauges anyway. */
245
+ const BOX_MEMORY_OTEL_READY_MS = 120_000;
234
246
  const senpiRuntimePlugin = {
235
247
  // Must match openclaw.plugin.json `id` — openclaw loader rejects the plugin
236
248
  // when `installed_plugins.json` record id (sourced from the manifest) and
@@ -656,6 +668,7 @@ const senpiRuntimePlugin = {
656
668
  */
657
669
  let serviceState = "idle";
658
670
  let serviceStops = 0;
671
+ let disposeBoxMemoryGauges;
659
672
  // Dedicated logger for the runtime-api subtree — required because the
660
673
  // PluginLogger surface (info/warn/error) lacks `debug`, while the
661
674
  // Logger contract used by `runtime-api/` includes it.
@@ -865,7 +878,7 @@ const senpiRuntimePlugin = {
865
878
  if (!mcpTransport)
866
879
  await client.connect();
867
880
  try {
868
- return await client.listStrategies({ strategyAddresses: wallets }, { signal });
881
+ return await client.listStrategiesForWallets(wallets, { signal });
869
882
  }
870
883
  finally {
871
884
  if (!mcpTransport)
@@ -997,26 +1010,26 @@ const senpiRuntimePlugin = {
997
1010
  // lower-case storage, and rejects the whole batch on one malformed address.
998
1011
  const registeredWallets = trackableWalletKeys(registry.runtimes);
999
1012
  if (registeredWallets.length > 0) {
1000
- const read = await readBootWalletStatuses(registeredWallets, async (wallets, signal) => {
1013
+ const read = await readWalletStatusesByWorld(registeredWallets, async (wallets, signal) => {
1001
1014
  const client = mcpTransport ? new SenpiClient(mcpTransport) : new SenpiClient({ apiKey });
1002
1015
  if (!mcpTransport)
1003
1016
  await client.connect();
1004
1017
  try {
1005
- return await client.listStrategies({ strategyAddresses: wallets }, { signal });
1018
+ return await client.listStrategiesForWallets(wallets, { signal });
1006
1019
  }
1007
1020
  finally {
1008
1021
  if (!mcpTransport)
1009
1022
  await client.disconnect().catch(() => { });
1010
1023
  }
1011
1024
  });
1012
- if (read.ok) {
1025
+ if (read.answered.size > 0) {
1013
1026
  walletStatuses = read.statuses;
1014
1027
  bootStrategyIds = read.strategyIds;
1015
- reaper.recordStatuses(registeredWallets, read.statuses);
1028
+ reaper.recordStatuses([...read.answered], read.statuses);
1016
1029
  }
1017
- else {
1018
- pluginLogger.warn("[senpi-runtime] Could not read strategy statuses at boot; booting every registered runtime", { reason: read.reason, err: read.message });
1019
- emitEvent(apiLogger, () => buildRuntimeBootStatusUnreadEvent({ reason: read.reason, registeredCount: registeredWallets.length }));
1030
+ for (const failure of read.failures) {
1031
+ pluginLogger.warn("[senpi-runtime] Could not read strategy statuses at boot; booting every registered runtime of that world", { reason: failure.reason, err: failure.message, paper: failure.paper });
1032
+ emitEvent(apiLogger, () => buildRuntimeBootStatusUnreadEvent({ reason: failure.reason, registeredCount: failure.walletCount }));
1020
1033
  }
1021
1034
  }
1022
1035
  const { boot: entriesToBoot, skipped } = partitionByWalletStatus(registry.runtimes, walletStatuses);
@@ -1244,6 +1257,17 @@ const senpiRuntimePlugin = {
1244
1257
  strategiesPublisher.start();
1245
1258
  // Sends the events added since the previous send, every 1 s.
1246
1259
  eventsPublisher.start();
1260
+ // The metrics API has no proxy meter: gauges made before the SDK sets its provider never export.
1261
+ const stopsAtStart = serviceStops;
1262
+ void waitForOtelInit(BOX_MEMORY_OTEL_READY_MS).then(() => {
1263
+ if (serviceStops !== stopsAtStart)
1264
+ return;
1265
+ disposeBoxMemoryGauges?.();
1266
+ disposeBoxMemoryGauges = registerBoxMemoryGauges({
1267
+ read: createBoxMemoryReader({ warn: (message, meta) => pluginLogger.warn(message, meta) }),
1268
+ runtimes: () => countRuntimesByMode(runtimeHandles.values()),
1269
+ });
1270
+ });
1247
1271
  // Last, once everything above is up: the reaper's post-boot pass and hourly sweep. Its
1248
1272
  // not-active escalation is also off until here, so no delete can land while the boot loop is
1249
1273
  // still wiring runtimes and launchers. Nothing here may fail start() — a throw from inside the boot loop takes every runtime down, which
@@ -1273,6 +1297,8 @@ const senpiRuntimePlugin = {
1273
1297
  await reaper.stop();
1274
1298
  strategiesPublisher.stop();
1275
1299
  eventsPublisher.stop();
1300
+ disposeBoxMemoryGauges?.();
1301
+ disposeBoxMemoryGauges = undefined;
1276
1302
  // Drain the HTTP server FIRST so in-flight POST /signals requests
1277
1303
  // finish before the underlying runtime they target is torn down.
1278
1304
  // `apiHandle.close()` never throws.
@@ -1833,7 +1859,7 @@ const senpiRuntimePlugin = {
1833
1859
  if (!mcpTransport)
1834
1860
  await client.connect();
1835
1861
  try {
1836
- const strategies = await client.listStrategies({ strategyAddresses: [wallet] });
1862
+ const strategies = await client.listStrategiesForWallets([wallet]);
1837
1863
  const match = strategies.find((s) => s.strategyWalletAddress.toLowerCase() === wallet.toLowerCase());
1838
1864
  if (!match) {
1839
1865
  pluginLogger.info("[senpi-runtime] Wallet validation failed: not owned", { wallet });
@@ -2021,7 +2047,8 @@ const senpiRuntimePlugin = {
2021
2047
  const targetAddress = typeof params?.address === "string" ? params.address : undefined;
2022
2048
  // Set only by the lifecycle reaper: why the strategy counts as gone (stamped on runtime.deleted)
2023
2049
  // and which wallet that verdict was about (the concurrency guard below).
2024
- const deleteReason = params?.deleteReason === "closed_status" ? params.deleteReason
2050
+ const deleteReason = params?.deleteReason === "closed_status" || params?.deleteReason === "paper_failed_status"
2051
+ ? params.deleteReason
2025
2052
  : undefined;
2026
2053
  const expectWallet = typeof params?.expectWallet === "string" && params.expectWallet.trim().length > 0
2027
2054
  ? params.expectWallet.trim().toLowerCase()
@@ -2265,7 +2292,7 @@ const senpiRuntimePlugin = {
2265
2292
  return;
2266
2293
  let ids = strategyIds ?? null;
2267
2294
  if (strategyIds === undefined && !plan.unchanged) {
2268
- const read = await readBootWalletStatuses([plan.wallet.trim().toLowerCase()], (wallets, signal) => withSenpiClient((client) => client.listStrategies({ strategyAddresses: wallets }, { signal })));
2295
+ const read = await readBootWalletStatuses([plan.wallet.trim().toLowerCase()], (wallets, signal) => withSenpiClient((client) => client.listStrategiesForWallets(wallets, { signal })));
2269
2296
  if (!read.ok) {
2270
2297
  factsLogger.warn("strategy facts failed: could not read the strategy id; retrying next boot", undefined, {
2271
2298
  runtimeId: plan.runtimeId,
@@ -2513,6 +2540,10 @@ const senpiRuntimePlugin = {
2513
2540
  },
2514
2541
  registryFindByWallet: findRegistryEntryByWallet,
2515
2542
  registryFindById: findRegistryEntryById,
2543
+ countRegistryRuntimes: async () => {
2544
+ const { readRegistry } = await import("./runtime/strategy-registry.js");
2545
+ return (await readRegistry(baseStateDir)).runtimes.length;
2546
+ },
2516
2547
  // A deploy job outlives a reload; it reads the runtime where it runs (see `liveHostIfStopped`).
2517
2548
  getScannerRows: (id) => liveHostIfStopped()?.getScannerRows(id) ?? readScannerRows(id),
2518
2549
  // H4: `runtimeHandles` holds the live `run()` handles. A registry row without one is a