neuralos 3.3.3 → 3.3.5

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/bin/gybackend.cjs CHANGED
@@ -389282,6 +389282,233 @@ var RumLedger = class {
389282
389282
  init_infraMonitor();
389283
389283
  init_etwService();
389284
389284
 
389285
+ // ../../packages/backend/src/services/dashboard/dashboardPresentation.ts
389286
+ var WORK_LIMIT = 8;
389287
+ var SITUATION_LIMIT = 8;
389288
+ function emptyHint(section, hasRows, message) {
389289
+ if (hasRows) return null;
389290
+ return { section, message };
389291
+ }
389292
+ function dashboardEmptyHints(state) {
389293
+ const hints = [];
389294
+ const push2 = (section, hasRows, message) => {
389295
+ const h = emptyHint(section, hasRows, message);
389296
+ if (h) hints.push(h);
389297
+ };
389298
+ push2(
389299
+ "hosts",
389300
+ (state.hosts?.length ?? 0) > 0,
389301
+ "No hosts reporting yet. Open a terminal so Monitor can collect CPU/mem/disk, or add a watchdog target."
389302
+ );
389303
+ push2(
389304
+ "slos",
389305
+ (state.slos?.length ?? 0) > 0,
389306
+ "No SLOs defined yet. Define an SLO to track error budget and burn rate."
389307
+ );
389308
+ push2(
389309
+ "uptime",
389310
+ (state.uptime?.length ?? 0) > 0,
389311
+ "No watchdog targets yet. Uptime probes appear here once a host is watched."
389312
+ );
389313
+ push2("incidents", (state.incidents?.length ?? 0) > 0, "No open incidents.");
389314
+ push2(
389315
+ "apm",
389316
+ (state.apm?.bottleneckServices?.length ?? 0) > 0 || (state.apm?.slowestTraces?.length ?? 0) > 0,
389317
+ "No APM spans ingested yet. Model calls and OTLP spans appear here when tracing is on."
389318
+ );
389319
+ push2(
389320
+ "dem",
389321
+ (state.dem?.slowestPages?.length ?? 0) > 0,
389322
+ "No RUM sessions yet. Core Web Vitals appear after DEM beacons are ingested."
389323
+ );
389324
+ push2(
389325
+ "clusters",
389326
+ (state.clusters?.length ?? 0) > 0,
389327
+ "No Kubernetes clusters reporting yet. Run collect_infra / kubectl ingest to populate."
389328
+ );
389329
+ push2(
389330
+ "capacity",
389331
+ (state.capacity?.length ?? 0) > 0,
389332
+ "No capacity forecast yet. Disk-days-to-full needs Monitor disk samples."
389333
+ );
389334
+ return hints;
389335
+ }
389336
+ function rankSituation(state, extras = {}) {
389337
+ const items = [];
389338
+ for (const u of state.uptime ?? []) {
389339
+ const name = u.target?.name;
389340
+ if (!name) continue;
389341
+ if (u.state === "down") {
389342
+ items.push({
389343
+ id: `down:${name}`,
389344
+ severity: "critical",
389345
+ kind: "host-down",
389346
+ title: `${name} is down`,
389347
+ detail: u.lastError || `${u.consecutiveFailures ?? 0} consecutive probe failure(s)`,
389348
+ action: { type: "open-host", target: name, label: "Open host" }
389349
+ });
389350
+ } else if (u.state === "degraded") {
389351
+ items.push({
389352
+ id: `deg:${name}`,
389353
+ severity: "warning",
389354
+ kind: "host-degraded",
389355
+ title: `${name} is degraded`,
389356
+ detail: u.lastError || "Watchdog reports degraded",
389357
+ action: { type: "open-host", target: name, label: "Open host" }
389358
+ });
389359
+ }
389360
+ }
389361
+ for (const i of state.incidents ?? []) {
389362
+ const sev = String(i.severity || "sev3");
389363
+ const critical = sev === "sev1" || sev === "sev2";
389364
+ items.push({
389365
+ id: `inc:${i.id || i.title || "unknown"}`,
389366
+ severity: critical ? "critical" : "warning",
389367
+ kind: "incident",
389368
+ title: String(i.title || "Open incident"),
389369
+ detail: `${sev} \xB7 ${(i.affected || []).join(", ") || "no affected hosts"}`,
389370
+ action: { type: "open-chat", label: "Ask agent" }
389371
+ });
389372
+ }
389373
+ for (const s of state.slos ?? []) {
389374
+ if (!s.fastBurning) continue;
389375
+ items.push({
389376
+ id: `slo:${s.sloId}`,
389377
+ severity: "critical",
389378
+ kind: "slo-burn",
389379
+ title: `SLO ${s.sloId} is fast-burning`,
389380
+ detail: `burn ${s.burnRate != null ? s.burnRate.toFixed(2) : "\u2014"}x \xB7 budget ${s.errorBudgetRemaining != null ? Math.round(s.errorBudgetRemaining * 100) : "\u2014"}%`,
389381
+ action: { type: "open-connections", target: "playbooks", label: "Playbooks" }
389382
+ });
389383
+ }
389384
+ for (const c of state.capacity ?? []) {
389385
+ if (c.daysToFull != null && c.daysToFull < 7) {
389386
+ items.push({
389387
+ id: `disk:${c.host}`,
389388
+ severity: c.daysToFull < 2 ? "critical" : "warning",
389389
+ kind: "disk-full",
389390
+ title: `${c.host} disk full in ${c.daysToFull.toFixed(1)}d`,
389391
+ detail: `disk ${c.diskPercent != null ? c.diskPercent.toFixed(1) : "\u2014"}%`,
389392
+ action: { type: "open-host", target: c.host, label: "Open host" }
389393
+ });
389394
+ }
389395
+ }
389396
+ for (const cl of state.clusters ?? []) {
389397
+ const crash = cl.crashLoopPods ?? 0;
389398
+ const notReady = cl.notReadyPods ?? 0;
389399
+ if (crash > 0 || notReady > 0) {
389400
+ items.push({
389401
+ id: `k8s:${cl.context || "cluster"}`,
389402
+ severity: crash > 0 ? "critical" : "warning",
389403
+ kind: "cluster-unhealthy",
389404
+ title: `${cl.context || "cluster"} unhealthy`,
389405
+ detail: `${crash} crashloop \xB7 ${notReady} not ready \xB7 ${cl.totalPods ?? 0} pods`
389406
+ });
389407
+ }
389408
+ }
389409
+ for (const p of extras.playbooks ?? []) {
389410
+ if (p.lastRunOk === false) {
389411
+ items.push({
389412
+ id: `pb:${p.id}`,
389413
+ severity: "warning",
389414
+ kind: "playbook-failed",
389415
+ title: `Playbook "${p.name}" last run failed`,
389416
+ detail: p.lastRunAt ? `last run ${p.lastRunAt}` : "last run failed",
389417
+ action: { type: "open-connections", target: "playbooks", label: "Open playbooks" }
389418
+ });
389419
+ }
389420
+ }
389421
+ for (const r of extras.agentRuns ?? []) {
389422
+ if (r.status === "failed" || r.status === "aborted") {
389423
+ items.push({
389424
+ id: `run:${r.runId}`,
389425
+ severity: r.status === "failed" ? "warning" : "info",
389426
+ kind: "agent-failed",
389427
+ title: `Agent run ${r.status}`,
389428
+ detail: r.error || r.inputPreview || r.sessionId,
389429
+ action: { type: "open-chat", target: r.sessionId, label: "Open chat" }
389430
+ });
389431
+ }
389432
+ }
389433
+ const order = { critical: 0, warning: 1, info: 2 };
389434
+ items.sort((a, b) => order[a.severity] - order[b.severity]);
389435
+ const sliced = items.slice(0, SITUATION_LIMIT);
389436
+ if (sliced.length === 0) {
389437
+ const n2 = state.hosts?.length ?? 0;
389438
+ return [
389439
+ {
389440
+ id: "clear",
389441
+ severity: "info",
389442
+ kind: "all-clear",
389443
+ title: n2 > 0 ? `All clear \xB7 ${n2} host${n2 === 1 ? "" : "s"} reporting` : "All clear \xB7 nothing to act on",
389444
+ detail: "No down hosts, fast-burning SLOs, or failed runs."
389445
+ }
389446
+ ];
389447
+ }
389448
+ return sliced;
389449
+ }
389450
+ function collectWork(extras = {}) {
389451
+ const playbooks = (extras.playbooks ?? []).filter((p) => p.lastRunAt).sort((a, b) => String(b.lastRunAt).localeCompare(String(a.lastRunAt))).slice(0, WORK_LIMIT).map((p) => ({
389452
+ id: p.id,
389453
+ kind: "playbook",
389454
+ name: p.name,
389455
+ ok: p.lastRunOk ?? null,
389456
+ at: p.lastRunAt,
389457
+ detail: p.lastRunOk === false ? "failed" : p.lastRunOk === true ? "ok" : "ran",
389458
+ action: { type: "open-connections", target: "playbooks", label: "Open" }
389459
+ }));
389460
+ const triggers = (extras.triggers ?? []).filter((t) => t.lastFiredAt || (t.fireCount ?? 0) > 0).sort((a, b) => (b.lastFiredAt ?? 0) - (a.lastFiredAt ?? 0)).slice(0, WORK_LIMIT).map((t) => ({
389461
+ id: t.id,
389462
+ kind: "trigger",
389463
+ name: t.name,
389464
+ ok: true,
389465
+ at: t.lastFiredAt,
389466
+ detail: `${t.kind || "trigger"} \xB7 fired ${t.fireCount ?? 0}\xD7`,
389467
+ action: { type: "open-connections", target: "triggers", label: "Open" }
389468
+ }));
389469
+ const scheduledTasks = (extras.scheduledTasks ?? []).filter((t) => t.lastRunAt).sort((a, b) => String(b.lastRunAt).localeCompare(String(a.lastRunAt))).slice(0, WORK_LIMIT).map((t) => ({
389470
+ id: t.id,
389471
+ kind: "scheduled-task",
389472
+ name: t.name,
389473
+ ok: t.enabled === false ? null : true,
389474
+ at: t.lastRunAt,
389475
+ detail: t.enabled === false ? "disabled" : "last run",
389476
+ action: { type: "open-connections", target: "scheduledTasks", label: "Open" }
389477
+ }));
389478
+ const agentRuns = (extras.agentRuns ?? []).slice().sort((a, b) => (b.startedAt || 0) - (a.startedAt || 0)).slice(0, WORK_LIMIT).map((r) => ({
389479
+ id: r.runId,
389480
+ kind: "agent-run",
389481
+ name: r.inputPreview?.slice(0, 60) || r.runId,
389482
+ ok: r.status === "completed" ? true : r.status === "failed" || r.status === "aborted" ? false : null,
389483
+ at: r.endedAt || r.startedAt,
389484
+ detail: r.status + (r.error ? ` \xB7 ${r.error}` : ""),
389485
+ action: { type: "open-chat", target: r.sessionId, label: "Open chat" }
389486
+ }));
389487
+ return { playbooks, triggers, scheduledTasks, agentRuns };
389488
+ }
389489
+ function situationHeadline(items) {
389490
+ const crit = items.filter((i) => i.severity === "critical" && i.kind !== "all-clear").length;
389491
+ const warn = items.filter((i) => i.severity === "warning").length;
389492
+ if (items.length === 1 && items[0].kind === "all-clear") return items[0].title;
389493
+ const parts = [];
389494
+ if (crit) parts.push(`${crit} critical`);
389495
+ if (warn) parts.push(`${warn} warning`);
389496
+ const top = items[0];
389497
+ return `${parts.join(" \xB7 ") || "attention"} \xB7 ${top.title}`;
389498
+ }
389499
+ function presentDashboard(state, extras = {}) {
389500
+ const situation = rankSituation(state, extras);
389501
+ return {
389502
+ situation,
389503
+ headline: situationHeadline(situation),
389504
+ work: collectWork(extras),
389505
+ empty: dashboardEmptyHints(state),
389506
+ hostCount: state.hosts?.length ?? 0,
389507
+ downCount: (state.uptime ?? []).filter((u) => u.state === "down").length,
389508
+ degradedCount: (state.uptime ?? []).filter((u) => u.state === "degraded").length
389509
+ };
389510
+ }
389511
+
389285
389512
  // ../../packages/backend/src/services/dashboard/dashboardService.ts
389286
389513
  var DEFAULT_LIMIT6 = 10;
389287
389514
  var DashboardService = class {
@@ -389317,7 +389544,7 @@ var DashboardService = class {
389317
389544
  } : { slowestPages: [], poorPages: [] };
389318
389545
  const clusters = d.infraMonitor ? d.infraMonitor.clusters_().slice(0, this.limit) : [];
389319
389546
  const capacity = d.goldenSignals.capacityForecast();
389320
- return {
389547
+ const fleet = {
389321
389548
  at: this.now(),
389322
389549
  hosts,
389323
389550
  slos,
@@ -389328,6 +389555,20 @@ var DashboardService = class {
389328
389555
  clusters,
389329
389556
  capacity
389330
389557
  };
389558
+ let extras = {};
389559
+ try {
389560
+ extras = this.deps.getExtras?.() ?? {};
389561
+ } catch {
389562
+ extras = {};
389563
+ }
389564
+ const presented = presentDashboard(fleet, extras);
389565
+ return {
389566
+ ...fleet,
389567
+ situation: presented.situation,
389568
+ headline: presented.headline,
389569
+ work: presented.work,
389570
+ empty: presented.empty
389571
+ };
389331
389572
  }
389332
389573
  /** A compact summary line for the gateway/agent (e.g. "3 hosts, 1 degraded, 2 open incidents, 1 SLO fast-burning"). */
389333
389574
  async summary() {
@@ -394742,7 +394983,40 @@ function createObservability(deps) {
394742
394983
  incidentLedger,
394743
394984
  spanLedger,
394744
394985
  rumLedger,
394745
- infraMonitor
394986
+ infraMonitor,
394987
+ getExtras: () => {
394988
+ const am = deps.automationManager;
394989
+ const playbooks = am.listPlaybooks().map((p) => ({
394990
+ id: p.id,
394991
+ name: p.name,
394992
+ lastRunAt: p.lastRunAt,
394993
+ lastRunOk: p.lastRunOk
394994
+ }));
394995
+ const scheduledTasks = am.listScheduledTasks().map((t) => ({
394996
+ id: t.id,
394997
+ name: t.name,
394998
+ enabled: t.enabled,
394999
+ lastRunAt: t.lastRunAt
395000
+ }));
395001
+ const triggers = am.listTriggers().map((t) => ({
395002
+ id: t.id,
395003
+ name: t.name,
395004
+ enabled: t.enabled,
395005
+ kind: t.kind,
395006
+ lastFiredAt: t.lastFiredAt,
395007
+ fireCount: t.fireCount
395008
+ }));
395009
+ const agentRuns = deps.agentRunLedger.listRuns({ limit: 12 }).map((r) => ({
395010
+ runId: r.runId,
395011
+ sessionId: r.sessionId,
395012
+ status: r.status,
395013
+ error: r.error,
395014
+ startedAt: r.startedAt,
395015
+ endedAt: r.endedAt,
395016
+ inputPreview: r.inputPreview
395017
+ }));
395018
+ return { playbooks, scheduledTasks, triggers, agentRuns };
395019
+ }
394746
395020
  });
394747
395021
  const anomalyDetector = new AnomalyDetector(metricsLedger);
394748
395022
  const earlyWarning = new EarlyWarningService({
@@ -395411,7 +395685,30 @@ function renderDashboardHtml(state, opts = {}) {
395411
395685
  <td>${pct(c.diskPercent)}</td>
395412
395686
  <td>${c.daysToFull !== void 0 ? num2(c.daysToFull, 1) + " days" : "\u2014"}</td>
395413
395687
  </tr>`).join("\n");
395414
- const section = (id, label, rows, emptyMsg) => live ? `<section><h2>${label}</h2><div id="${id}">${rows ? `<table>${rows}</table>` : `<p class="empty">${esc2(emptyMsg)}</p>`}</div></section>` : rows ? `<section id="${id}"><h2>${label}</h2><table>${rows}</table></section>` : `<section id="${id}"><h2>${label}</h2><p class="empty">${esc2(emptyMsg)}</p></section>`;
395688
+ const emptyMsg = (sectionId, fallback) => state.empty?.find((e) => e.section === sectionId)?.message ?? fallback;
395689
+ const sitItems = state.situation ?? [];
395690
+ const sitHtml = sitItems.map((s) => {
395691
+ const cls = s.severity === "critical" ? "bad" : s.severity === "warning" ? "warn" : "ok";
395692
+ const act = s.action ? `<button class="act" data-action="${esc2(s.action.type)}" data-target="${esc2(s.action.target ?? "")}">${esc2(s.action.label)}</button>` : "";
395693
+ return `<div class="sit ${cls}"><span class="badge ${cls}">${esc2(s.severity)}</span><div><strong>${esc2(s.title)}</strong><div class="dim">${esc2(s.detail)}</div></div>${act}</div>`;
395694
+ }).join("");
395695
+ const workBlock = (title2, items) => {
395696
+ if (!items.length) return "";
395697
+ const rows = items.map((w) => {
395698
+ const badge = w.ok === false ? "bad" : w.ok === true ? "ok" : "mute";
395699
+ const act = w.action ? `<button class="act" data-action="${esc2(w.action.type)}" data-target="${esc2(w.action.target ?? "")}">${esc2(w.action.label)}</button>` : "";
395700
+ return `<tr><td class="host">${esc2(w.name)}</td><td><span class="badge ${badge}">${esc2(w.detail)}</span></td><td>${act}</td></tr>`;
395701
+ }).join("");
395702
+ return `<h3>${esc2(title2)}</h3><table>${rows}</table>`;
395703
+ };
395704
+ const work = state.work;
395705
+ const workHtml = work ? [
395706
+ workBlock("Playbooks", work.playbooks),
395707
+ workBlock("Triggers", work.triggers),
395708
+ workBlock("Scheduled tasks", work.scheduledTasks),
395709
+ workBlock("Agent runs", work.agentRuns)
395710
+ ].join("") : "";
395711
+ const section = (id, label, rows, emptyMsgText) => live ? `<section><h2>${label}</h2><div id="${id}">${rows ? `<table>${rows}</table>` : `<p class="empty">${esc2(emptyMsgText)}</p>`}</div></section>` : rows ? `<section id="${id}"><h2>${label}</h2><table>${rows}</table></section>` : `<section id="${id}"><h2>${label}</h2><p class="empty">${esc2(emptyMsgText)}</p></section>`;
395415
395712
  return `<!DOCTYPE html>
395416
395713
  <html lang="en">
395417
395714
  <head>
@@ -395451,6 +395748,16 @@ ${refresh > 0 && !live ? `<meta http-equiv="refresh" content="${refresh}">` : ""
395451
395748
  .row-bad td { background: rgba(255,93,126,0.05); }
395452
395749
  .empty { color: var(--muted); font-size: 12px; padding: 8px 0; }
395453
395750
  .span2 { grid-column: span 2; }
395751
+ .sit-strip { display: flex; flex-direction: column; gap: 8px; }
395752
+ .sit { display: flex; align-items: flex-start; gap: 10px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--border); }
395753
+ .sit.bad { border-color: rgba(255,93,126,0.35); background: rgba(255,93,126,0.06); }
395754
+ .sit.warn { border-color: rgba(255,196,77,0.3); background: rgba(255,196,77,0.05); }
395755
+ .sit.ok { border-color: rgba(61,220,151,0.25); }
395756
+ .sit strong { display: block; font-size: 13px; }
395757
+ .act { margin-left: auto; background: transparent; color: var(--accent); border: 1px solid rgba(79,216,232,0.35); border-radius: 6px; padding: 3px 8px; font-size: 11px; cursor: pointer; white-space: nowrap; }
395758
+ .act:hover { background: rgba(79,216,232,0.12); }
395759
+ section h3 { font-size: 11px; color: var(--muted); margin: 10px 0 6px; text-transform: uppercase; letter-spacing: 0.06em; }
395760
+ .filter { margin: 0 0 10px; padding: 6px 10px; width: 100%; max-width: 320px; background: var(--panel2); color: var(--fg); border: 1px solid var(--border); border-radius: 8px; }
395454
395761
  footer { color: var(--muted); font-size: 11px; padding: 14px 26px 30px; }
395455
395762
  @media (max-width: 900px) { .grid { grid-template-columns: 1fr; } .span2 { grid-column: span 1; } }
395456
395763
  </style>
@@ -395462,25 +395769,36 @@ ${refresh > 0 && !live ? `<meta http-equiv="refresh" content="${refresh}">` : ""
395462
395769
  <span class="live" id="live-indicator">LIVE \xB7 updated ${esc2(new Date(state.at).toISOString().slice(11, 19))} UTC</span>
395463
395770
  </header>
395464
395771
  <main>
395772
+ <section class="span2" id="situation-wrap">
395773
+ <h2>Situation</h2>
395774
+ <div class="sub dim" id="headline">${esc2(state.headline || "")}</div>
395775
+ <div class="sit-strip" id="situation">${sitHtml || '<p class="empty">All clear.</p>'}</div>
395776
+ </section>
395465
395777
  <div class="grid">
395466
395778
  <section class="span2">
395467
395779
  <h2>Fleet health</h2>
395780
+ <input class="filter" id="host-filter" type="search" placeholder="Filter hosts\u2026" />
395468
395781
  <div id="fleet">
395469
395782
  <table>
395470
395783
  <tr><th>host</th><th>state</th><th>cpu</th><th>mem</th><th>disk</th><th>cpu trend</th><th>disk full in</th></tr>
395471
- ${hostRows || '<tr><td class="empty">No hosts reporting yet.</td></tr>'}
395784
+ ${hostRows || `<tr><td class="empty">${esc2(emptyMsg("hosts", "No hosts reporting yet. Open a terminal so Monitor can collect CPU/mem/disk, or add a watchdog target."))}</td></tr>`}
395472
395785
  </table>
395473
395786
  </div>
395474
395787
  </section>
395475
395788
 
395476
- ${section("slo", "SLO board", sloRows ? `<tr><th>slo</th><th>sli</th><th>burn rate</th><th>budget</th><th>status</th></tr>${sloRows}` : "", "No SLOs defined yet.")}
395477
- ${section("uptime", "Uptime", upRows ? `<tr><th>host</th><th>state</th><th>failures</th><th>latency</th><th>error</th></tr>${upRows}` : "", "No watchdog targets yet.")}
395478
- ${section("incidents", "Open incidents", incRows ? `<tr><th>incident</th><th>sev</th><th>status</th><th>affected</th><th>rca</th></tr>${incRows}` : "", "No open incidents.")}
395479
- ${section("apm-svc", "APM \xB7 bottleneck services", apmSvcRows ? `<tr><th>service</th><th>spans</th><th>errors</th><th>error rate</th><th>p95</th></tr>${apmSvcRows}` : "", "No APM spans ingested yet.")}
395480
- ${section("apm-trace", "APM \xB7 slowest traces", apmTraceRows ? `<tr><th>trace</th><th>root</th><th>spans</th><th>duration</th><th>status</th></tr>${apmTraceRows}` : "", "No traces yet.")}
395481
- ${section("dem", "DEM \xB7 slowest pages (Core Web Vitals)", demRows ? `<tr><th>page</th><th>sessions</th><th>p75 lcp</th><th>p75 inp</th><th>error rate</th></tr>${demRows}` : "", "No RUM sessions yet.")}
395482
- ${section("clusters", "Kubernetes / cloud clusters", clusterRows ? `<tr><th>context</th><th>pods</th><th>not ready</th><th>crashloop</th><th>restarts</th><th>nodes</th></tr>${clusterRows}` : "", "No clusters reporting yet.")}
395483
- ${section("capacity", "Capacity forecast", capRows ? `<tr><th>host</th><th>disk</th><th>full in</th></tr>${capRows}` : "", "No capacity data yet.")}
395789
+ <section class="span2" id="work-wrap">
395790
+ <h2>RTerm work</h2>
395791
+ <div id="work">${workHtml || '<p class="empty">No playbook runs, trigger fires, scheduled tasks, or agent runs yet.</p>'}</div>
395792
+ </section>
395793
+
395794
+ ${section("slo", "SLO board", sloRows ? `<tr><th>slo</th><th>sli</th><th>burn rate</th><th>budget</th><th>status</th></tr>${sloRows}` : "", emptyMsg("slos", "No SLOs defined yet. Define an SLO to track error budget and burn rate."))}
395795
+ ${section("uptime", "Uptime", upRows ? `<tr><th>host</th><th>state</th><th>failures</th><th>latency</th><th>error</th></tr>${upRows}` : "", emptyMsg("uptime", "No watchdog targets yet. Uptime probes appear here once a host is watched."))}
395796
+ ${section("incidents", "Open incidents", incRows ? `<tr><th>incident</th><th>sev</th><th>status</th><th>affected</th><th>rca</th></tr>${incRows}` : "", emptyMsg("incidents", "No open incidents."))}
395797
+ ${section("apm-svc", "APM \xB7 bottleneck services", apmSvcRows ? `<tr><th>service</th><th>spans</th><th>errors</th><th>error rate</th><th>p95</th></tr>${apmSvcRows}` : "", emptyMsg("apm", "No APM spans ingested yet. Model calls and OTLP spans appear here when tracing is on."))}
395798
+ ${section("apm-trace", "APM \xB7 slowest traces", apmTraceRows ? `<tr><th>trace</th><th>root</th><th>spans</th><th>duration</th><th>status</th></tr>${apmTraceRows}` : "", emptyMsg("apm", "No traces yet."))}
395799
+ ${section("dem", "DEM \xB7 slowest pages (Core Web Vitals)", demRows ? `<tr><th>page</th><th>sessions</th><th>p75 lcp</th><th>p75 inp</th><th>error rate</th></tr>${demRows}` : "", emptyMsg("dem", "No RUM sessions yet. Core Web Vitals appear after DEM beacons are ingested."))}
395800
+ ${section("clusters", "Kubernetes / cloud clusters", clusterRows ? `<tr><th>context</th><th>pods</th><th>not ready</th><th>crashloop</th><th>restarts</th><th>nodes</th></tr>${clusterRows}` : "", emptyMsg("clusters", "No Kubernetes clusters reporting yet. Run collect_infra / kubectl ingest to populate."))}
395801
+ ${section("capacity", "Capacity forecast", capRows ? `<tr><th>host</th><th>disk</th><th>full in</th></tr>${capRows}` : "", emptyMsg("capacity", "No capacity forecast yet. Disk-days-to-full needs Monitor disk samples."))}
395484
395802
  </div>
395485
395803
  </main>
395486
395804
  <footer>
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuralos",
3
- "version": "3.3.3",
3
+ "version": "3.3.5",
4
4
  "description": "AI-native terminal & agentic-AI operations platform for Forward Deployed Engineers & SREs: AIOps closed-loop remediation, AI SRE, self-healing infrastructure, runbook automation, ChatOps; executes over SSH/WinRM/serial under policy with tamper-evident audit.",
5
5
  "keywords": [
6
6
  "forward-deployed-engineer",