@senpi/runtime 3.0.6-dev.runtime-phase-2.20260630134451 → 3.0.6-dev.runtime-phase-2.20260630134536

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 (141) hide show
  1. package/dist/actions/action-events.d.ts.map +1 -1
  2. package/dist/actions/action-events.js +39 -5
  3. package/dist/actions/action-events.js.map +1 -1
  4. package/dist/actions/event-derivation.d.ts +2 -0
  5. package/dist/actions/event-derivation.d.ts.map +1 -1
  6. package/dist/actions/event-derivation.js +2 -0
  7. package/dist/actions/event-derivation.js.map +1 -1
  8. package/dist/cli/guide-commands.d.ts.map +1 -1
  9. package/dist/cli/guide-commands.js +61 -0
  10. package/dist/cli/guide-commands.js.map +1 -1
  11. package/dist/cli/senpi-commands.d.ts +14 -0
  12. package/dist/cli/senpi-commands.d.ts.map +1 -1
  13. package/dist/cli/senpi-commands.js +386 -2
  14. package/dist/cli/senpi-commands.js.map +1 -1
  15. package/dist/dsl/events/initial-state.d.ts.map +1 -1
  16. package/dist/dsl/events/initial-state.js +1 -0
  17. package/dist/dsl/events/initial-state.js.map +1 -1
  18. package/dist/dsl/monitor/dsl-failure-events.d.ts +42 -0
  19. package/dist/dsl/monitor/dsl-failure-events.d.ts.map +1 -0
  20. package/dist/dsl/monitor/dsl-failure-events.js +73 -0
  21. package/dist/dsl/monitor/dsl-failure-events.js.map +1 -0
  22. package/dist/dsl/monitor/process-one-position.d.ts.map +1 -1
  23. package/dist/dsl/monitor/process-one-position.js +46 -37
  24. package/dist/dsl/monitor/process-one-position.js.map +1 -1
  25. package/dist/dsl/plugin/index.d.ts.map +1 -1
  26. package/dist/dsl/plugin/index.js +6 -0
  27. package/dist/dsl/plugin/index.js.map +1 -1
  28. package/dist/health/types.d.ts +14 -0
  29. package/dist/health/types.d.ts.map +1 -1
  30. package/dist/health/types.js.map +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +220 -7
  33. package/dist/index.js.map +1 -1
  34. package/dist/risk/risk-audit-log.d.ts +7 -0
  35. package/dist/risk/risk-audit-log.d.ts.map +1 -1
  36. package/dist/risk/risk-audit-log.js +48 -1
  37. package/dist/risk/risk-audit-log.js.map +1 -1
  38. package/dist/runtime/lifecycle-events.d.ts +43 -0
  39. package/dist/runtime/lifecycle-events.d.ts.map +1 -1
  40. package/dist/runtime/lifecycle-events.js +61 -0
  41. package/dist/runtime/lifecycle-events.js.map +1 -1
  42. package/dist/runtime/runtime-schema.d.ts +63 -9
  43. package/dist/runtime/runtime-schema.d.ts.map +1 -1
  44. package/dist/runtime/runtime-schema.js +8 -1
  45. package/dist/runtime/runtime-schema.js.map +1 -1
  46. package/dist/runtime/runtime.d.ts +8 -0
  47. package/dist/runtime/runtime.d.ts.map +1 -1
  48. package/dist/runtime/runtime.js +104 -5
  49. package/dist/runtime/runtime.js.map +1 -1
  50. package/dist/runtime/supervisor/long-running-supervisor.d.ts +3 -0
  51. package/dist/runtime/supervisor/long-running-supervisor.d.ts.map +1 -1
  52. package/dist/runtime/supervisor/long-running-supervisor.js +27 -53
  53. package/dist/runtime/supervisor/long-running-supervisor.js.map +1 -1
  54. package/dist/runtime-api/intake/intake-events.d.ts +44 -0
  55. package/dist/runtime-api/intake/intake-events.d.ts.map +1 -0
  56. package/dist/runtime-api/intake/intake-events.js +131 -0
  57. package/dist/runtime-api/intake/intake-events.js.map +1 -0
  58. package/dist/runtime-api/intake/scanner-launcher.d.ts +2 -0
  59. package/dist/runtime-api/intake/scanner-launcher.d.ts.map +1 -1
  60. package/dist/runtime-api/intake/scanner-launcher.js +58 -15
  61. package/dist/runtime-api/intake/scanner-launcher.js.map +1 -1
  62. package/dist/runtime-api/intake/scanner-registry.d.ts +3 -0
  63. package/dist/runtime-api/intake/scanner-registry.d.ts.map +1 -1
  64. package/dist/runtime-api/intake/scanner-registry.js +3 -0
  65. package/dist/runtime-api/intake/scanner-registry.js.map +1 -1
  66. package/dist/runtime-api/intake/supervisor-events.d.ts +61 -0
  67. package/dist/runtime-api/intake/supervisor-events.d.ts.map +1 -0
  68. package/dist/runtime-api/intake/supervisor-events.js +173 -0
  69. package/dist/runtime-api/intake/supervisor-events.js.map +1 -0
  70. package/dist/runtime-api/routes/signals.d.ts.map +1 -1
  71. package/dist/runtime-api/routes/signals.js +119 -27
  72. package/dist/runtime-api/routes/signals.js.map +1 -1
  73. package/dist/runtime-api/server.d.ts.map +1 -1
  74. package/dist/runtime-api/server.js +10 -1
  75. package/dist/runtime-api/server.js.map +1 -1
  76. package/dist/scanners/engine/engine.d.ts.map +1 -1
  77. package/dist/scanners/engine/engine.js +3 -0
  78. package/dist/scanners/engine/engine.js.map +1 -1
  79. package/dist/scanners/runtime-module.d.ts.map +1 -1
  80. package/dist/scanners/runtime-module.js +6 -0
  81. package/dist/scanners/runtime-module.js.map +1 -1
  82. package/dist/senpi/client.d.ts.map +1 -1
  83. package/dist/senpi/client.js +50 -5
  84. package/dist/senpi/client.js.map +1 -1
  85. package/dist/state/state-events.d.ts +13 -0
  86. package/dist/state/state-events.d.ts.map +1 -0
  87. package/dist/state/state-events.js +22 -0
  88. package/dist/state/state-events.js.map +1 -0
  89. package/dist/state/state-manager.d.ts.map +1 -1
  90. package/dist/state/state-manager.js +14 -1
  91. package/dist/state/state-manager.js.map +1 -1
  92. package/dist/telemetry/event-builders.d.ts +0 -2
  93. package/dist/telemetry/event-builders.d.ts.map +1 -1
  94. package/dist/telemetry/event-builders.js.map +1 -1
  95. package/dist/telemetry/event-store.d.ts +66 -0
  96. package/dist/telemetry/event-store.d.ts.map +1 -0
  97. package/dist/telemetry/event-store.js +202 -0
  98. package/dist/telemetry/event-store.js.map +1 -0
  99. package/dist/telemetry/identity.d.ts +10 -0
  100. package/dist/telemetry/identity.d.ts.map +1 -1
  101. package/dist/telemetry/identity.js +1 -0
  102. package/dist/telemetry/identity.js.map +1 -1
  103. package/dist/telemetry/metrics.d.ts +137 -15
  104. package/dist/telemetry/metrics.d.ts.map +1 -1
  105. package/dist/telemetry/metrics.js +357 -74
  106. package/dist/telemetry/metrics.js.map +1 -1
  107. package/dist/telemetry/otel-sdk.d.ts +1 -1
  108. package/dist/telemetry/otel-sdk.js +1 -1
  109. package/dist/telemetry/spans/gen-ai.d.ts +4 -0
  110. package/dist/telemetry/spans/gen-ai.d.ts.map +1 -1
  111. package/dist/telemetry/spans/gen-ai.js +9 -0
  112. package/dist/telemetry/spans/gen-ai.js.map +1 -1
  113. package/dist/telemetry/spans/redact-pii.d.ts.map +1 -1
  114. package/dist/telemetry/spans/redact-pii.js +4 -1
  115. package/dist/telemetry/spans/redact-pii.js.map +1 -1
  116. package/dist/telemetry/spans/span-hooks.d.ts +7 -0
  117. package/dist/telemetry/spans/span-hooks.d.ts.map +1 -1
  118. package/dist/telemetry/spans/span-hooks.js +179 -24
  119. package/dist/telemetry/spans/span-hooks.js.map +1 -1
  120. package/dist/telemetry/tool-plane.d.ts +31 -0
  121. package/dist/telemetry/tool-plane.d.ts.map +1 -0
  122. package/dist/telemetry/tool-plane.js +70 -0
  123. package/dist/telemetry/tool-plane.js.map +1 -0
  124. package/dist/telemetry/trace-identity.d.ts.map +1 -1
  125. package/dist/telemetry/trace-identity.js +3 -0
  126. package/dist/telemetry/trace-identity.js.map +1 -1
  127. package/dist/types/dsl/index.d.ts +6 -0
  128. package/dist/types/dsl/index.d.ts.map +1 -1
  129. package/dist/utils/event-catalog.d.ts +237 -0
  130. package/dist/utils/event-catalog.d.ts.map +1 -1
  131. package/dist/utils/event-catalog.js +51 -0
  132. package/dist/utils/event-catalog.js.map +1 -1
  133. package/dist/utils/logger.d.ts +5 -0
  134. package/dist/utils/logger.d.ts.map +1 -1
  135. package/dist/utils/logger.js +24 -22
  136. package/dist/utils/logger.js.map +1 -1
  137. package/dist/utils/wallet-address.d.ts +3 -0
  138. package/dist/utils/wallet-address.d.ts.map +1 -0
  139. package/dist/utils/wallet-address.js +17 -0
  140. package/dist/utils/wallet-address.js.map +1 -0
  141. package/package.json +1 -1
@@ -659,6 +659,236 @@ export function registerSenpiCli(program) {
659
659
  console.log("");
660
660
  }
661
661
  });
662
+ senpi
663
+ .command("scanner")
664
+ .description("Per-scanner supervisor health: runs, errors, liveness, barren detection")
665
+ .option("-r, --runtime <id>", "Filter by runtime id")
666
+ .option("--json", "Print raw JSON from the gateway")
667
+ .action(async (opts) => {
668
+ const params = {};
669
+ if (typeof opts.runtime === "string" && opts.runtime.length > 0)
670
+ params.runtimeId = opts.runtime;
671
+ const data = callGatewayJson("senpi.getSystemState", params);
672
+ if (!data) {
673
+ return;
674
+ }
675
+ if (data.ok === false) {
676
+ const err = data.error;
677
+ console.error(err?.message ?? "Request failed.");
678
+ process.exitCode = 1;
679
+ return;
680
+ }
681
+ const payload = (data.payload ?? data);
682
+ const states = selectMany(payload.state, payload.states ?? []);
683
+ if (opts.json) {
684
+ const scanners = states.map((s) => ({
685
+ runtimeId: s.runtimeId ?? s.runtimeName,
686
+ scanners: s.components?.scanners?.state?.scanners ?? [],
687
+ }));
688
+ console.log(JSON.stringify({ ok: true, scanners }, null, 2));
689
+ return;
690
+ }
691
+ if (states.length === 0) {
692
+ console.log("No running runtime scanners.");
693
+ return;
694
+ }
695
+ for (const state of states) {
696
+ printScanners(state);
697
+ if (states.length > 1)
698
+ console.log("");
699
+ }
700
+ });
701
+ const riskCmd = senpi.command("risk");
702
+ riskCmd
703
+ .description("Risk eligibility + per-gate status — am I allowed to trade, and why not")
704
+ .option("-r, --runtime <id>", "Filter by runtime id")
705
+ .option("--json", "Print raw JSON from the gateway")
706
+ .action(async (opts) => {
707
+ const params = {};
708
+ if (typeof opts.runtime === "string" && opts.runtime.length > 0)
709
+ params.runtimeId = opts.runtime;
710
+ const data = callGatewayJson("senpi.getHealthStatus", params);
711
+ if (!data) {
712
+ return;
713
+ }
714
+ if (data.ok === false) {
715
+ const err = data.error;
716
+ console.error(err?.message ?? "Request failed.");
717
+ process.exitCode = 1;
718
+ return;
719
+ }
720
+ const payload = (data.payload ?? data);
721
+ const statuses = selectMany(payload.status, payload.statuses ?? []);
722
+ if (opts.json) {
723
+ const risk = statuses.map((s) => ({
724
+ runtimeId: s.runtimeId ?? s.runtimeName,
725
+ risk: s.components?.risk ?? null,
726
+ }));
727
+ console.log(JSON.stringify({ ok: true, risk }, null, 2));
728
+ return;
729
+ }
730
+ if (statuses.length === 0) {
731
+ console.log("No running runtime risk status.");
732
+ return;
733
+ }
734
+ for (const status of statuses) {
735
+ printRisk(status);
736
+ if (statuses.length > 1)
737
+ console.log("");
738
+ }
739
+ });
740
+ riskCmd
741
+ .command("audit")
742
+ .description("Risk gate-check audit log — per-evaluation pass/CLOSED/COOLDOWN history")
743
+ .option("-r, --runtime <id>", "Runtime id (required)")
744
+ .option("-a, --address <addr>", "Strategy address (defaults to runtime wallet)")
745
+ .option("--since <iso>", "ISO 8601 lower bound on entry time")
746
+ .option("-l, --limit <n>", "Max entries (default 100)")
747
+ .option("--json", "Print raw JSON from the gateway")
748
+ .action(async (opts) => {
749
+ if (!opts.runtime) {
750
+ console.error("--runtime <id> is required.");
751
+ process.exitCode = 1;
752
+ return;
753
+ }
754
+ const params = { runtimeId: opts.runtime };
755
+ if (opts.address)
756
+ params.address = opts.address;
757
+ const limit = opts.limit ? Number.parseInt(opts.limit, 10) : 100;
758
+ if (Number.isFinite(limit))
759
+ params.limit = limit;
760
+ const data = callGatewayJson("senpi.getRiskAudit", params);
761
+ if (!data) {
762
+ return;
763
+ }
764
+ if (data.ok === false) {
765
+ const err = data.error;
766
+ console.error(err?.message ?? "Request failed.");
767
+ process.exitCode = 1;
768
+ return;
769
+ }
770
+ const inner = (data.payload ?? data);
771
+ let entries = inner.entries ?? [];
772
+ // ISO 8601 UTC timestamps sort lexicographically, so a string compare
773
+ // is a correct lower-bound filter.
774
+ if (opts.since)
775
+ entries = entries.filter((e) => e.ts >= opts.since);
776
+ if (opts.json) {
777
+ console.log(JSON.stringify({ ok: true, entries }, null, 2));
778
+ return;
779
+ }
780
+ console.log(renderRiskAuditDigest(entries));
781
+ });
782
+ senpi
783
+ .command("audit")
784
+ .description("Backend trade-audit trail (MCP tool calls + AI reasoning)")
785
+ .option("-r, --runtime <id>", "Runtime id (required)")
786
+ .option("--tool <name>", "Filter by exact tool name")
787
+ .option("--action-type <type>", "Filter by action type (read|create|update|delete)")
788
+ .option("--success <bool>", "Filter by success (true|false)")
789
+ .option("--since <iso>", "ISO 8601 lower bound on request time")
790
+ .option("--until <iso>", "ISO 8601 upper bound on request time")
791
+ .option("-l, --limit <n>", "Max rows (default 50)")
792
+ .option("--json", "Print raw JSON from the gateway")
793
+ .action(async (opts) => {
794
+ if (!opts.runtime) {
795
+ console.error("--runtime <id> is required.");
796
+ process.exitCode = 1;
797
+ return;
798
+ }
799
+ const params = { runtimeId: opts.runtime };
800
+ if (opts.tool)
801
+ params.tool_name = opts.tool;
802
+ if (opts.actionType)
803
+ params.action_type = opts.actionType;
804
+ if (opts.success === "true")
805
+ params.success = true;
806
+ else if (opts.success === "false")
807
+ params.success = false;
808
+ if (opts.since)
809
+ params.start_time = opts.since;
810
+ if (opts.until)
811
+ params.end_time = opts.until;
812
+ const limit = opts.limit ? Number.parseInt(opts.limit, 10) : 50;
813
+ if (Number.isFinite(limit))
814
+ params.limit = limit;
815
+ const data = callGatewayJson("senpi.auditQuery", params);
816
+ if (!data) {
817
+ return;
818
+ }
819
+ if (data.ok === false) {
820
+ const err = data.error;
821
+ console.error(err?.message ?? "Request failed.");
822
+ process.exitCode = 1;
823
+ return;
824
+ }
825
+ const inner = (data.payload ?? data);
826
+ const result = inner.result ?? { entries: [], count: 0, total: 0, has_more: false };
827
+ if (opts.json) {
828
+ console.log(JSON.stringify({ ok: true, result }, null, 2));
829
+ return;
830
+ }
831
+ if (result.entries.length === 0) {
832
+ console.log("No audit entries.");
833
+ return;
834
+ }
835
+ console.log("Time\tTool\tType\tOK\tDur(ms)\tReasoning");
836
+ for (const e of result.entries) {
837
+ // A malformed backend timestamp makes toISOString() throw RangeError; show a placeholder.
838
+ const parsed = new Date(e.timestamp);
839
+ const t = Number.isNaN(parsed.getTime()) ? "—" : parsed.toISOString().slice(11, 19);
840
+ const dur = e.duration_ms == null ? "—" : String(e.duration_ms);
841
+ const reason = (e.ai_reasoning ?? "").replace(/\s+/g, " ").slice(0, 60);
842
+ console.log([t, e.tool, e.action_type, e.success ? "ok" : "FAIL", dur, reason].join("\t"));
843
+ }
844
+ if (result.has_more) {
845
+ console.log(`… more (showing ${result.count} of ~${result.total}; raise -l or use --json)`);
846
+ }
847
+ });
848
+ senpi
849
+ .command("events")
850
+ .description("Local domain-event log — the trade narrative (position/dsl/order/signal events)")
851
+ .option("-r, --runtime <id>", "Runtime id (required)")
852
+ .option("-a, --address <addr>", "Strategy address (defaults to runtime wallet)")
853
+ .option("--name <event>", "Filter by exact event name (e.g. position.opened)")
854
+ .option("--asset <symbol>", "Filter by asset (e.g. BTC)")
855
+ .option("--level <level>", "Filter by severity (debug|info|warn|error)")
856
+ .option("--since <iso>", "ISO 8601 lower bound on event time")
857
+ .option("--until <iso>", "ISO 8601 upper bound on event time")
858
+ .option("-l, --limit <n>", "Max events (default 200)")
859
+ .option("--json", "Print raw JSON from the gateway")
860
+ .action(async (opts) => {
861
+ const entries = await fetchEvents(opts);
862
+ if (entries === null)
863
+ return;
864
+ if (opts.json) {
865
+ console.log(JSON.stringify({ ok: true, entries }, null, 2));
866
+ return;
867
+ }
868
+ console.log(renderEventsDigest(entries));
869
+ });
870
+ senpi
871
+ .command("explain <asset>")
872
+ .description("Stitch one asset's position lifecycle from the local event log (opened → dsl → closed)")
873
+ .option("-r, --runtime <id>", "Runtime id (required)")
874
+ .option("-a, --address <addr>", "Strategy address (defaults to runtime wallet)")
875
+ .option("-l, --limit <n>", "Max events to scan (default 500)")
876
+ .option("--json", "Print raw JSON from the gateway")
877
+ .action(async (asset, opts) => {
878
+ const entries = await fetchEvents({
879
+ runtime: opts.runtime,
880
+ address: opts.address,
881
+ asset,
882
+ limit: opts.limit ?? "500",
883
+ });
884
+ if (entries === null)
885
+ return;
886
+ if (opts.json) {
887
+ console.log(JSON.stringify({ ok: true, asset, entries }, null, 2));
888
+ return;
889
+ }
890
+ console.log(renderExplain(asset, entries));
891
+ });
662
892
  // ── Skills subcommand ────────────────────────────────────────────────────────
663
893
  const skills = senpi.command("skills");
664
894
  skills.description("Manage Senpi skills (status, update)");
@@ -891,12 +1121,23 @@ function printHealthStatus(status) {
891
1121
  const scannerSummary = scanners?.summary ?? "Scanner summary unavailable.";
892
1122
  const enabled = typeof scanners?.totals?.enabled === "number" ? scanners.totals.enabled : "?";
893
1123
  const registered = typeof scanners?.totals?.registered === "number" ? scanners.totals.registered : "?";
1124
+ const degraded = typeof scanners?.totals?.degraded === "number" ? scanners.totals.degraded : 0;
1125
+ const degradedSuffix = degraded > 0 ? `, ${degraded} degraded` : "";
894
1126
  console.log(`Runtime: ${status.runtimeId ?? status.runtimeName ?? "unknown"}`);
895
1127
  console.log(`Overall Health: ${status.health ?? "unknown"}`);
896
1128
  console.log(`Log Level: ${status.logLevel ?? "unknown"}`);
897
- console.log(`Scanners: ${scannerSummary} (enabled ${enabled}/${registered})`);
1129
+ console.log(`Scanners: ${scannerSummary} (enabled ${enabled}/${registered}${degradedSuffix})`);
898
1130
  if (dsl?.summary) {
899
1131
  console.log(`DSL: ${dsl.summary}`);
1132
+ const monitor = dsl.monitor;
1133
+ if (monitor) {
1134
+ const flight = monitor.inFlight ? " (tick in flight)" : "";
1135
+ const next = monitor.nextTickAt
1136
+ ? `, next tick ${new Date(monitor.nextTickAt).toISOString().slice(11, 19)}`
1137
+ : "";
1138
+ const err = monitor.lastTickError ? ` — last tick error: ${monitor.lastTickError}` : "";
1139
+ console.log(` Monitor: ${monitor.running ? "running" : "stopped"}${flight}${next}${err}`);
1140
+ }
900
1141
  }
901
1142
  if (actions?.summary) {
902
1143
  const ar = typeof actions.totals?.registered === "number" ? actions.totals.registered : "?";
@@ -904,7 +1145,7 @@ function printHealthStatus(status) {
904
1145
  console.log(`Actions: ${actions.summary} (registered ${ar}, ${ae} enabled)`);
905
1146
  }
906
1147
  if (risk && risk.enabled) {
907
- console.log(`Risk Health: ${risk.health}`);
1148
+ console.log(`Risk Eligibility: ${risk.eligibility} | Health: ${risk.health}`);
908
1149
  console.log("Risk Gates:");
909
1150
  for (const gate of risk.gates) {
910
1151
  const label = gate.gateName.padEnd(22);
@@ -913,6 +1154,117 @@ function printHealthStatus(status) {
913
1154
  }
914
1155
  }
915
1156
  }
1157
+ function printRisk(status) {
1158
+ const risk = status.components?.risk;
1159
+ console.log(`Runtime: ${status.runtimeId ?? status.runtimeName ?? "unknown"}`);
1160
+ if (!risk || !risk.enabled) {
1161
+ console.log("Risk guardrails: disabled.");
1162
+ return;
1163
+ }
1164
+ console.log(`Eligibility: ${risk.eligibility} | Health: ${risk.health}`);
1165
+ const t = risk.totals;
1166
+ console.log(`Gates: ${t.open} open, ${t.cooldown} cooldown, ${t.closed} closed (${t.fallbackCount} fallback, ${t.failedCount} failed)`);
1167
+ for (const g of risk.gates) {
1168
+ const label = g.gateName.padEnd(22);
1169
+ const reason = g.reason && g.status !== "OPEN" ? ` — ${g.reason}` : "";
1170
+ // Surface evaluation faults the status summary hides: a gate that errored
1171
+ // and the fallback it fell back to.
1172
+ const flag = !g.evaluationOk ? ` [${g.failureKind}${g.fallbackApplied ? ",fallback" : ""}]` : "";
1173
+ console.log(` ${label}${g.status}${reason}${flag}`);
1174
+ }
1175
+ }
1176
+ /**
1177
+ * Render the risk audit log as a tab-separated digest (newest entries last,
1178
+ * as returned). Each row: time, source, then `guardrail=result` per
1179
+ * evaluation (with the reason appended for non-pass results).
1180
+ */
1181
+ export function renderRiskAuditDigest(entries) {
1182
+ if (entries.length === 0)
1183
+ return "No risk audit.";
1184
+ const lines = ["Time\tSource\tEvaluations"];
1185
+ for (const e of entries) {
1186
+ const t = new Date(e.ts).toISOString().slice(11, 19);
1187
+ const evals = (e.evaluations ?? [])
1188
+ .map((ev) => `${ev.guardrail}=${ev.result}${ev.reason && ev.result !== "pass" ? `(${ev.reason})` : ""}`)
1189
+ .join(" ");
1190
+ lines.push([t, e.source, evals || "—"].join("\t"));
1191
+ }
1192
+ return lines.join("\n");
1193
+ }
1194
+ /**
1195
+ * Shared fetch for `senpi events` / `senpi explain`: validate --runtime, call senpi.getEvents, and
1196
+ * return the entries. Returns `null` (and sets exitCode/prints) on a missing runtime, transport
1197
+ * failure, or a gateway error — the caller stops.
1198
+ */
1199
+ function fetchEvents(opts) {
1200
+ if (!opts.runtime) {
1201
+ console.error("--runtime <id> is required.");
1202
+ process.exitCode = 1;
1203
+ return null;
1204
+ }
1205
+ const params = { runtimeId: opts.runtime };
1206
+ if (opts.address)
1207
+ params.address = opts.address;
1208
+ if (opts.name)
1209
+ params.name = opts.name;
1210
+ if (opts.asset)
1211
+ params.asset = opts.asset;
1212
+ if (opts.level)
1213
+ params.level = opts.level;
1214
+ if (opts.since)
1215
+ params.since = opts.since;
1216
+ if (opts.until)
1217
+ params.until = opts.until;
1218
+ if (opts.limit) {
1219
+ const limit = Number.parseInt(opts.limit, 10);
1220
+ if (Number.isFinite(limit))
1221
+ params.limit = limit;
1222
+ }
1223
+ const data = callGatewayJson("senpi.getEvents", params);
1224
+ if (!data)
1225
+ return null;
1226
+ if (data.ok === false) {
1227
+ const err = data.error;
1228
+ console.error(err?.message ?? "Request failed.");
1229
+ process.exitCode = 1;
1230
+ return null;
1231
+ }
1232
+ const inner = (data.payload ?? data);
1233
+ return inner.entries ?? [];
1234
+ }
1235
+ /** The asset an event concerns, if any (position/dsl carry `senpi.asset`; signals carry `senpi.signal.asset`). */
1236
+ function eventAsset(e) {
1237
+ const a = e.attrs?.["senpi.asset"] ?? e.attrs?.["senpi.signal.asset"];
1238
+ return typeof a === "string" ? a : undefined;
1239
+ }
1240
+ export function renderEventsDigest(entries) {
1241
+ if (entries.length === 0)
1242
+ return "No events.";
1243
+ const lines = ["Time\tLevel\tEvent\tAsset\tDetail"];
1244
+ for (const e of entries) {
1245
+ const t = new Date(e.ts).toISOString().slice(11, 19);
1246
+ const detail = (e.body ?? "").replace(/\s+/g, " ").slice(0, 60);
1247
+ lines.push([t, e.level, e.name, eventAsset(e) ?? "—", detail].join("\t"));
1248
+ }
1249
+ return lines.join("\n");
1250
+ }
1251
+ /**
1252
+ * Stitch an asset's lifecycle: events oldest-first, each line tagged with its position id where one is
1253
+ * present, so a reader can follow opened → dsl transitions → close across one or more positions.
1254
+ */
1255
+ export function renderExplain(asset, entries) {
1256
+ if (entries.length === 0)
1257
+ return `No events for ${asset}.`;
1258
+ const lines = [`${asset} — ${entries.length} event(s)`];
1259
+ for (const e of entries) {
1260
+ const t = new Date(e.ts).toISOString().slice(11, 19);
1261
+ const pos = typeof e.attrs?.["senpi.position.id"] === "string" ? e.attrs["senpi.position.id"] : undefined;
1262
+ const tag = pos ? ` [pos ${truncateAddr(String(pos))}]` : "";
1263
+ const detail = (e.body ?? "").replace(/\s+/g, " ");
1264
+ lines.push(` ${t} ${e.name}\t${detail}${tag}`);
1265
+ }
1266
+ return lines.join("\n");
1267
+ }
916
1268
  function printSystemState(state) {
917
1269
  const scanners = state.components?.scanners?.state;
918
1270
  const dsl = state.components?.dsl;
@@ -983,4 +1335,36 @@ function printSystemState(state) {
983
1335
  }
984
1336
  }
985
1337
  }
1338
+ function printScanners(state) {
1339
+ const rows = state.components?.scanners?.state?.scanners ?? [];
1340
+ console.log(`Runtime: ${state.runtimeId ?? state.runtimeName ?? "unknown"}`);
1341
+ console.log(`Scanners: ${rows.length}`);
1342
+ for (const s of rows) {
1343
+ // Barren = wired and running but never emitted a signal — a real "alive but producing nothing" gap.
1344
+ const barren = s.health !== "disabled" && (s.runCount ?? 0) > 0 && (s.signalsProduced ?? 0) === 0;
1345
+ // Liveness clock is external-only: n/a for interval scanners, — for an external one not yet heard from.
1346
+ const alive = s.lastAliveAt != null
1347
+ ? String(s.lastAliveAt)
1348
+ : s.scheduleMode === "external"
1349
+ ? "—"
1350
+ : "n/a";
1351
+ console.log([
1352
+ " scanner",
1353
+ s.scannerId ?? "—",
1354
+ `mode=${s.scheduleMode ?? "—"}`,
1355
+ `health=${s.health ?? "unknown"}`,
1356
+ `enabled=${String(s.enabled ?? false)}`,
1357
+ `inFlight=${String(s.inFlight ?? false)}`,
1358
+ `runs=${s.runCount ?? 0}`,
1359
+ `errors=${s.errorCount ?? 0}`,
1360
+ `consec_errors=${s.consecutiveErrorCount ?? 0}`,
1361
+ `signals=${s.signalsProduced ?? 0}`,
1362
+ `alive=${alive}`,
1363
+ `next=${s.nextRunAt == null ? "—" : String(s.nextRunAt)}`,
1364
+ `last=${s.lastRunStatus ?? "—"}`,
1365
+ ...(s.lastRunReason ? [`reason=${s.lastRunReason}`] : []),
1366
+ ...(barren ? ["BARREN"] : []),
1367
+ ].join(" "));
1368
+ }
1369
+ }
986
1370
  //# sourceMappingURL=senpi-commands.js.map