rterm-backend 2.9.3 → 2.9.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.
Files changed (3) hide show
  1. package/bin/gybackend.cjs +1039 -304
  2. package/bin/gybackend.js +1039 -304
  3. package/package.json +5 -2
package/bin/gybackend.js CHANGED
@@ -252183,6 +252183,477 @@ var init_orchestratedPlaybookRunner = __esm({
252183
252183
  }
252184
252184
  });
252185
252185
 
252186
+ // packages/backend/src/services/apm/spanLedger.ts
252187
+ var spanLedger_exports = {};
252188
+ __export(spanLedger_exports, {
252189
+ SpanLedger: () => SpanLedger,
252190
+ ingestOtlp: () => ingestOtlp,
252191
+ newSpanId: () => newSpanId,
252192
+ parseOtlpJson: () => parseOtlpJson,
252193
+ stableId: () => stableId
252194
+ });
252195
+ function percentile(sorted, p) {
252196
+ if (sorted.length === 0) return void 0;
252197
+ return sorted[Math.min(sorted.length - 1, Math.floor(p / 100 * sorted.length))];
252198
+ }
252199
+ function parseOtlpJson(payload, defaultService = "unknown") {
252200
+ const out = [];
252201
+ const root = payload;
252202
+ const rs = root?.resourceSpans;
252203
+ if (!Array.isArray(rs)) return out;
252204
+ for (const r of rs) {
252205
+ const rr = r;
252206
+ let service = defaultService;
252207
+ const attrs2 = rr?.resource?.attributes ?? [];
252208
+ const svc = attrs2.find((a) => a.key === "service.name")?.value?.stringValue;
252209
+ if (svc) service = svc;
252210
+ const scopeSpans = rr?.scopeSpans;
252211
+ if (!Array.isArray(scopeSpans)) continue;
252212
+ for (const ss of scopeSpans) {
252213
+ const spans = ss?.spans;
252214
+ if (!Array.isArray(spans)) continue;
252215
+ for (const sp of spans) {
252216
+ const s = sp;
252217
+ if (!s.traceId || !s.spanId) continue;
252218
+ const startNs = Number(s.startTimeUnixNano ?? 0);
252219
+ const endNs = Number(s.endTimeUnixNano ?? 0);
252220
+ const startMs = startNs / 1e6;
252221
+ const durationMs = Math.max(0, (endNs - startNs) / 1e6);
252222
+ const isErr = s.status?.code === 2 || s.status?.code === "STATUS_CODE_ERROR" || s.status?.code === "Error";
252223
+ out.push({
252224
+ traceId: String(s.traceId),
252225
+ spanId: String(s.spanId),
252226
+ ...s.parentSpanId ? { parentSpanId: String(s.parentSpanId) } : {},
252227
+ service,
252228
+ name: s.name ?? "span",
252229
+ startMs,
252230
+ durationMs,
252231
+ status: isErr ? "error" : "ok"
252232
+ });
252233
+ }
252234
+ }
252235
+ }
252236
+ return out;
252237
+ }
252238
+ function ingestOtlp(ledger, payload, defaultService = "unknown") {
252239
+ const spans = parseOtlpJson(payload, defaultService);
252240
+ return ledger.ingestBatch(spans);
252241
+ }
252242
+ function stableId(seed) {
252243
+ return (0, import_crypto7.createHash)("sha1").update(seed).digest("hex").slice(0, 16);
252244
+ }
252245
+ function newSpanId() {
252246
+ return (0, import_crypto7.randomUUID)().replace(/-/g, "").slice(0, 16);
252247
+ }
252248
+ var import_crypto7, DEFAULT_LIMIT, SpanLedger;
252249
+ var init_spanLedger = __esm({
252250
+ "packages/backend/src/services/apm/spanLedger.ts"() {
252251
+ "use strict";
252252
+ import_crypto7 = require("crypto");
252253
+ DEFAULT_LIMIT = 5e4;
252254
+ SpanLedger = class {
252255
+ spans = /* @__PURE__ */ new Map();
252256
+ // traceId -> spans
252257
+ order = [];
252258
+ // traceIds in insert order
252259
+ spanLimit;
252260
+ totalSpans = 0;
252261
+ constructor(opts = {}) {
252262
+ this.spanLimit = opts.spanLimit ?? DEFAULT_LIMIT;
252263
+ }
252264
+ /** Ingest one span. */
252265
+ ingest(span) {
252266
+ let arr3 = this.spans.get(span.traceId);
252267
+ if (!arr3) {
252268
+ arr3 = [];
252269
+ this.spans.set(span.traceId, arr3);
252270
+ this.order.push(span.traceId);
252271
+ }
252272
+ arr3.push(span);
252273
+ this.totalSpans += 1;
252274
+ while (this.totalSpans > this.spanLimit && this.order.length > 0) {
252275
+ const oldest = this.order.shift();
252276
+ const old = this.spans.get(oldest) ?? [];
252277
+ this.totalSpans -= old.length;
252278
+ this.spans.delete(oldest);
252279
+ }
252280
+ }
252281
+ /** Ingest many spans. */
252282
+ ingestBatch(spans) {
252283
+ for (const s of spans) this.ingest(s);
252284
+ return spans.length;
252285
+ }
252286
+ /** All spans for a trace. */
252287
+ trace(traceId) {
252288
+ return this.spans.get(traceId) ?? [];
252289
+ }
252290
+ traceIds() {
252291
+ return this.order;
252292
+ }
252293
+ /** Summarize a trace (root service, span count, total duration, error). */
252294
+ summarize(traceId) {
252295
+ const spans = this.spans.get(traceId);
252296
+ if (!spans || spans.length === 0) return void 0;
252297
+ const root = spans.find((s) => !s.parentSpanId) ?? spans[0];
252298
+ const services = Array.from(new Set(spans.map((s) => s.service)));
252299
+ const hasError = spans.some((s) => s.status === "error");
252300
+ const start = Math.min(...spans.map((s) => s.startMs));
252301
+ const end = Math.max(...spans.map((s) => s.startMs + s.durationMs));
252302
+ return {
252303
+ traceId,
252304
+ rootService: root.service,
252305
+ spanCount: spans.length,
252306
+ totalDurationMs: end - start,
252307
+ hasError,
252308
+ services,
252309
+ at: start
252310
+ };
252311
+ }
252312
+ /** Per-service stats over a window. */
252313
+ serviceStats(opts = {}) {
252314
+ const byService = /* @__PURE__ */ new Map();
252315
+ for (const arr3 of this.spans.values()) {
252316
+ for (const s of arr3) {
252317
+ if (opts.sinceMs !== void 0 && s.startMs < opts.sinceMs) continue;
252318
+ if (opts.service && s.service !== opts.service) continue;
252319
+ let e = byService.get(s.service);
252320
+ if (!e) {
252321
+ e = { durations: [], errors: 0 };
252322
+ byService.set(s.service, e);
252323
+ }
252324
+ e.durations.push(s.durationMs);
252325
+ if (s.status === "error") e.errors += 1;
252326
+ }
252327
+ }
252328
+ const out = [];
252329
+ for (const [service, e] of byService) {
252330
+ const durations = e.durations.slice().sort((a, b) => a - b);
252331
+ out.push({
252332
+ service,
252333
+ spanCount: durations.length,
252334
+ errorCount: e.errors,
252335
+ errorRate: durations.length > 0 ? e.errors / durations.length : 0,
252336
+ p50Ms: percentile(durations, 50),
252337
+ p95Ms: percentile(durations, 95),
252338
+ p99Ms: percentile(durations, 99),
252339
+ maxMs: durations.length > 0 ? durations[durations.length - 1] : void 0
252340
+ });
252341
+ }
252342
+ return out.sort((a, b) => (b.p95Ms ?? 0) - (a.p95Ms ?? 0));
252343
+ }
252344
+ /** Slowest traces by total duration. */
252345
+ slowestTraces(limit2 = 10, opts = {}) {
252346
+ const summaries = [];
252347
+ for (const id of this.order) {
252348
+ const s = this.summarize(id);
252349
+ if (!s) continue;
252350
+ if (opts.sinceMs !== void 0 && s.at < opts.sinceMs) continue;
252351
+ summaries.push(s);
252352
+ }
252353
+ return summaries.sort((a, b) => b.totalDurationMs - a.totalDurationMs).slice(0, limit2);
252354
+ }
252355
+ /** Services ranked by total error count (bottleneck/failing services). */
252356
+ bottleneckServices(opts = {}) {
252357
+ return this.serviceStats(opts).sort((a, b) => b.errorCount - a.errorCount);
252358
+ }
252359
+ /** Total stored spans. */
252360
+ size() {
252361
+ return this.totalSpans;
252362
+ }
252363
+ clear() {
252364
+ this.spans.clear();
252365
+ this.order.length = 0;
252366
+ this.totalSpans = 0;
252367
+ }
252368
+ };
252369
+ }
252370
+ });
252371
+
252372
+ // packages/backend/src/services/infra/infraMonitor.ts
252373
+ var infraMonitor_exports = {};
252374
+ __export(infraMonitor_exports, {
252375
+ InfraMonitor: () => InfraMonitor,
252376
+ clusterHealth: () => clusterHealth,
252377
+ newClusterContextId: () => newClusterContextId,
252378
+ parseKubectlPods: () => parseKubectlPods,
252379
+ podUnhealthy: () => podUnhealthy
252380
+ });
252381
+ function parseKubectlPods(text) {
252382
+ const out = [];
252383
+ const lines = text.split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim() !== "");
252384
+ const header = lines.find((l) => /^NAME(SPACE)?\s+/.test(l));
252385
+ const hasNamespaceCol = header !== void 0 && /^NAMESPACE\s+/.test(header);
252386
+ for (let i = 0; i < lines.length; i += 1) {
252387
+ const l = lines[i];
252388
+ if (/^NAME(SPACE)?\s+/.test(l)) continue;
252389
+ const parts = l.trim().split(/\s+/);
252390
+ const minFields = hasNamespaceCol ? 6 : 5;
252391
+ if (parts.length < minFields) continue;
252392
+ let namespace = "default";
252393
+ let name;
252394
+ let readyFrac;
252395
+ let status;
252396
+ let restartsStr;
252397
+ if (hasNamespaceCol) {
252398
+ ;
252399
+ [namespace, name, readyFrac, status, restartsStr] = parts;
252400
+ } else {
252401
+ ;
252402
+ [name, readyFrac, status, restartsStr] = parts;
252403
+ }
252404
+ const [readyNum, readyDen] = (readyFrac ?? "0/1").split("/").map((x) => parseInt(x, 10));
252405
+ const restarts = parseInt((restartsStr ?? "0").replace(/\D.*$/, ""), 10) || 0;
252406
+ const phase = ["Pending", "Running", "Succeeded", "Failed", "Unknown"].includes(status) ? status : "Unknown";
252407
+ out.push({
252408
+ name,
252409
+ namespace,
252410
+ phase,
252411
+ restarts,
252412
+ ready: readyNum === readyDen && readyDen > 0 && status === "Running"
252413
+ });
252414
+ }
252415
+ return out;
252416
+ }
252417
+ function clusterHealth(context2, pods, nodes) {
252418
+ const running = pods.filter((p) => p.phase === "Running");
252419
+ const notReady = pods.filter((p) => !p.ready).length;
252420
+ const crashLoop = pods.filter((p) => p.restarts >= 5 || p.phase === "Unknown" && p.restarts > 0).length;
252421
+ const totalRestarts = pods.reduce((s, p) => s + p.restarts, 0);
252422
+ const nodesReady = nodes.filter((n2) => n2.ready).length;
252423
+ let cpuPct;
252424
+ let memPct;
252425
+ const withCpu = pods.filter((p) => typeof p.cpuMillicores === "number" && typeof p.cpuLimitMillicores === "number" && p.cpuLimitMillicores > 0);
252426
+ if (withCpu.length > 0) {
252427
+ const used = withCpu.reduce((s, p) => s + (p.cpuMillicores ?? 0), 0);
252428
+ const limit2 = withCpu.reduce((s, p) => s + (p.cpuLimitMillicores ?? 0), 0);
252429
+ cpuPct = limit2 > 0 ? used / limit2 * 100 : void 0;
252430
+ }
252431
+ const withMem = pods.filter((p) => typeof p.memMiB === "number" && typeof p.memLimitMiB === "number" && p.memLimitMiB > 0);
252432
+ if (withMem.length > 0) {
252433
+ const used = withMem.reduce((s, p) => s + (p.memMiB ?? 0), 0);
252434
+ const limit2 = withMem.reduce((s, p) => s + (p.memLimitMiB ?? 0), 0);
252435
+ memPct = limit2 > 0 ? used / limit2 * 100 : void 0;
252436
+ }
252437
+ return {
252438
+ context: context2,
252439
+ totalPods: pods.length,
252440
+ runningPods: running.length,
252441
+ notReadyPods: notReady,
252442
+ crashLoopPods: crashLoop,
252443
+ totalRestarts,
252444
+ nodesReady,
252445
+ nodesTotal: nodes.length,
252446
+ ...cpuPct !== void 0 ? { cpuUsagePercentOfLimit: cpuPct } : {},
252447
+ ...memPct !== void 0 ? { memUsagePercentOfLimit: memPct } : {}
252448
+ };
252449
+ }
252450
+ function podUnhealthy(p) {
252451
+ return !p.ready || p.phase === "Failed" || p.phase === "Unknown" || p.restarts >= 5;
252452
+ }
252453
+ function newClusterContextId(name) {
252454
+ return `k8s-${name}-${(0, import_crypto8.randomUUID)().slice(0, 6)}`;
252455
+ }
252456
+ var import_crypto8, InfraMonitor;
252457
+ var init_infraMonitor = __esm({
252458
+ "packages/backend/src/services/infra/infraMonitor.ts"() {
252459
+ "use strict";
252460
+ import_crypto8 = require("crypto");
252461
+ InfraMonitor = class {
252462
+ clusters = /* @__PURE__ */ new Map();
252463
+ instances = /* @__PURE__ */ new Map();
252464
+ constructor(_deps = {}) {
252465
+ }
252466
+ /** Record a cluster health snapshot. */
252467
+ recordCluster(context2, pods, nodes) {
252468
+ const h = clusterHealth(context2, pods, nodes);
252469
+ this.clusters.set(context2, h);
252470
+ return h;
252471
+ }
252472
+ cluster(context2) {
252473
+ return this.clusters.get(context2);
252474
+ }
252475
+ clusters_() {
252476
+ return Array.from(this.clusters.values());
252477
+ }
252478
+ /** Unhealthy pods for a cluster (for alerting). */
252479
+ unhealthyPods(pods) {
252480
+ return pods.filter(podUnhealthy);
252481
+ }
252482
+ /** Record a cloud instance state. */
252483
+ recordInstance(inst) {
252484
+ this.instances.set(inst.id, inst);
252485
+ }
252486
+ instance(id) {
252487
+ return this.instances.get(id);
252488
+ }
252489
+ /** Instances that are not healthy (stopped/failed). */
252490
+ unhealthyInstances() {
252491
+ return Array.from(this.instances.values()).filter((i) => i.statusOk === false || /stopped|terminated|failed/i.test(i.state));
252492
+ }
252493
+ clear() {
252494
+ this.clusters.clear();
252495
+ this.instances.clear();
252496
+ }
252497
+ };
252498
+ }
252499
+ });
252500
+
252501
+ // packages/backend/src/services/etw/etwService.ts
252502
+ var etwService_exports = {};
252503
+ __export(etwService_exports, {
252504
+ ETW_PROVIDERS: () => ETW_PROVIDERS,
252505
+ EtwService: () => EtwService,
252506
+ buildCounterQuery: () => buildCounterQuery,
252507
+ buildStartCommands: () => buildStartCommands,
252508
+ buildStopCommands: () => buildStopCommands,
252509
+ buildWinEventQuery: () => buildWinEventQuery,
252510
+ extractFields: () => extractFields,
252511
+ parseCounterJson: () => parseCounterJson,
252512
+ parseWinEventJson: () => parseWinEventJson,
252513
+ parseWinEventText: () => parseWinEventText,
252514
+ topCounters: () => topCounters
252515
+ });
252516
+ function buildStartCommands(session) {
252517
+ const cmds = [];
252518
+ for (const kind of session.providers) {
252519
+ const provider = ETW_PROVIDERS[kind];
252520
+ const name = `${session.name}-${kind}`;
252521
+ cmds.push(`logman create trace "${name}" -p "${provider}" -o "${session.outFile}-${kind}.etl" -ets`);
252522
+ }
252523
+ return cmds;
252524
+ }
252525
+ function buildStopCommands(session) {
252526
+ return session.providers.map((kind) => `logman stop "${session.name}-${kind}" -ets`);
252527
+ }
252528
+ function buildWinEventQuery(logName, maxEvents = 100, filter) {
252529
+ const filterPart = filter ? ` | Where-Object { ${filter} }` : "";
252530
+ return `Get-WinEvent -LogName '${logName}' -MaxEvents ${maxEvents}${filterPart} | Select-Object TimeCreated,Id,ProviderName,Message | ConvertTo-Json -Depth 3 -Compress`;
252531
+ }
252532
+ function buildCounterQuery(counterPath, samples = 1) {
252533
+ return `Get-Counter -Counter '${counterPath}' -MaxSamples ${samples} | Select-Object -ExpandProperty CounterSamples | Select-Object Path,CookedValue | ConvertTo-Json -Depth 3 -Compress`;
252534
+ }
252535
+ function parseWinEventJson(output) {
252536
+ const trimmed = output.trim();
252537
+ if (!trimmed) return [];
252538
+ let parsed;
252539
+ try {
252540
+ parsed = JSON.parse(trimmed);
252541
+ } catch {
252542
+ return parseWinEventText(trimmed);
252543
+ }
252544
+ const arr3 = Array.isArray(parsed) ? parsed : [parsed];
252545
+ const out = [];
252546
+ for (const item of arr3) {
252547
+ const o = item;
252548
+ if (!o || typeof o !== "object") continue;
252549
+ const message = (o.Message ?? "").toString().trim();
252550
+ if (!message) continue;
252551
+ out.push({
252552
+ at: o.TimeCreated,
252553
+ provider: o.ProviderName,
252554
+ message,
252555
+ fields: extractFields(message)
252556
+ });
252557
+ }
252558
+ return out;
252559
+ }
252560
+ function parseWinEventText(output) {
252561
+ const out = [];
252562
+ const blocks2 = output.split(/\r?\n\r?\n/);
252563
+ for (const b of blocks2) {
252564
+ const msg = b.trim();
252565
+ if (!msg) continue;
252566
+ out.push({ message: msg.split(/\r?\n/).slice(0, 4).join(" "), fields: extractFields(msg) });
252567
+ }
252568
+ return out;
252569
+ }
252570
+ function extractFields(message) {
252571
+ const fields = {};
252572
+ const ip = message.match(/\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/);
252573
+ if (ip) fields.ip = ip[1];
252574
+ const port = message.match(/[Pp]ort[:\s]+(\d{1,5})/);
252575
+ if (port) fields.port = port[1];
252576
+ const path32 = message.match(/([A-Za-z]:\\[^\s"',;]+)/);
252577
+ if (path32) fields.path = path32[1];
252578
+ const key = message.match(/(HKLM|HKCU|HKCR|HKU)\\[^\s"',;]+/i);
252579
+ if (key) fields.key = key[0];
252580
+ const proc = message.match(/[Pp]rocess(?:Name)?[:\s]+([A-Za-z0-9_.-]+\.exe)/);
252581
+ if (proc) fields.processName = proc[1];
252582
+ const pid = message.match(/[Pp]rocess\s*[Ii]d[:\s]+(\d+)/);
252583
+ if (pid) fields.pid = pid[1];
252584
+ return fields;
252585
+ }
252586
+ function parseCounterJson(output) {
252587
+ const trimmed = output.trim();
252588
+ if (!trimmed) return [];
252589
+ let parsed;
252590
+ try {
252591
+ parsed = JSON.parse(trimmed);
252592
+ } catch {
252593
+ return [];
252594
+ }
252595
+ const arr3 = Array.isArray(parsed) ? parsed : [parsed];
252596
+ const out = [];
252597
+ for (const item of arr3) {
252598
+ const o = item;
252599
+ if (!o || o.Path === void 0) continue;
252600
+ const value = typeof o.CookedValue === "number" ? o.CookedValue : parseFloat(String(o.CookedValue ?? "NaN"));
252601
+ if (Number.isFinite(value)) out.push({ path: o.Path, value });
252602
+ }
252603
+ return out;
252604
+ }
252605
+ function topCounters(points, n2 = 5) {
252606
+ return points.slice().sort((a, b) => b.value - a.value).slice(0, n2);
252607
+ }
252608
+ var import_crypto9, ETW_PROVIDERS, now0, EtwService;
252609
+ var init_etwService = __esm({
252610
+ "packages/backend/src/services/etw/etwService.ts"() {
252611
+ "use strict";
252612
+ import_crypto9 = require("crypto");
252613
+ ETW_PROVIDERS = {
252614
+ network: "Microsoft-Windows-Kernel-Network",
252615
+ file: "Microsoft-Windows-Kernel-File",
252616
+ registry: "Microsoft-Windows-Kernel-Registry",
252617
+ process: "Microsoft-Windows-Kernel-Process",
252618
+ dns: "Microsoft-Windows-DNS-Client",
252619
+ power: "Microsoft-Windows-Kernel-Power"
252620
+ };
252621
+ now0 = () => Date.now();
252622
+ EtwService = class {
252623
+ sessions = /* @__PURE__ */ new Map();
252624
+ now;
252625
+ constructor(deps = {}) {
252626
+ this.now = deps.now ?? now0;
252627
+ }
252628
+ /** Create a session descriptor (commands are built from it by buildStartCommands). */
252629
+ createSession(name, providers, outDir = "C:\\temp") {
252630
+ const clean = name.replace(/[^A-Za-z0-9_-]/g, "");
252631
+ const s = {
252632
+ id: `etw-${(0, import_crypto9.randomUUID)().slice(0, 8)}`,
252633
+ name: clean,
252634
+ providers,
252635
+ outFile: `${outDir}\\${clean}`,
252636
+ createdAt: this.now()
252637
+ };
252638
+ this.sessions.set(s.id, s);
252639
+ return s;
252640
+ }
252641
+ session(id) {
252642
+ return this.sessions.get(id);
252643
+ }
252644
+ sessions_() {
252645
+ return Array.from(this.sessions.values());
252646
+ }
252647
+ removeSession(id) {
252648
+ return this.sessions.delete(id);
252649
+ }
252650
+ clear() {
252651
+ this.sessions.clear();
252652
+ }
252653
+ };
252654
+ }
252655
+ });
252656
+
252186
252657
  // packages/backend/src/services/history/HistorySqliteStore.ts
252187
252658
  var HistorySqliteStore_exports = {};
252188
252659
  __export(HistorySqliteStore_exports, {
@@ -254755,7 +255226,7 @@ var require_websocket = __commonJS({
254755
255226
  var http2 = require("http");
254756
255227
  var net2 = require("net");
254757
255228
  var tls = require("tls");
254758
- var { randomBytes: randomBytes3, createHash: createHash6 } = require("crypto");
255229
+ var { randomBytes: randomBytes3, createHash: createHash7 } = require("crypto");
254759
255230
  var { Duplex, Readable } = require("stream");
254760
255231
  var { URL: URL5 } = require("url");
254761
255232
  var PerMessageDeflate = require_permessage_deflate();
@@ -255415,7 +255886,7 @@ var require_websocket = __commonJS({
255415
255886
  abortHandshake(websocket, socket, "Invalid Upgrade header");
255416
255887
  return;
255417
255888
  }
255418
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
255889
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
255419
255890
  if (res.headers["sec-websocket-accept"] !== digest) {
255420
255891
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
255421
255892
  return;
@@ -255782,7 +256253,7 @@ var require_websocket_server = __commonJS({
255782
256253
  var EventEmitter4 = require("events");
255783
256254
  var http2 = require("http");
255784
256255
  var { Duplex } = require("stream");
255785
- var { createHash: createHash6 } = require("crypto");
256256
+ var { createHash: createHash7 } = require("crypto");
255786
256257
  var extension = require_extension();
255787
256258
  var PerMessageDeflate = require_permessage_deflate();
255788
256259
  var subprotocol = require_subprotocol();
@@ -256083,7 +256554,7 @@ var require_websocket_server = __commonJS({
256083
256554
  );
256084
256555
  }
256085
256556
  if (this._state > RUNNING) return abortHandshake(socket, 503);
256086
- const digest = createHash6("sha1").update(key + GUID).digest("base64");
256557
+ const digest = createHash7("sha1").update(key + GUID).digest("base64");
256087
256558
  const headers = [
256088
256559
  "HTTP/1.1 101 Switching Protocols",
256089
256560
  "Upgrade: websocket",
@@ -288865,6 +289336,32 @@ var TerminalService = class {
288865
289336
  setSessionLogger(logger) {
288866
289337
  this.sessionLogger = logger;
288867
289338
  }
289339
+ /** Wire the asciinema-style session recorder (v2.9.x). Output events feed all active recordings. */
289340
+ setSessionRecorder(recorder) {
289341
+ this.sessionRecorder = recorder;
289342
+ }
289343
+ /** Live-recording state: terminalId -> recordingId. */
289344
+ activeRecordings = /* @__PURE__ */ new Map();
289345
+ sessionRecorder = null;
289346
+ /** Begin recording a terminal's output. Returns the recordingId. */
289347
+ startRecording(terminalId, opts = {}) {
289348
+ if (!this.sessionRecorder) throw new Error("session recorder is not wired");
289349
+ const id = this.sessionRecorder.start(terminalId, opts);
289350
+ this.activeRecordings.set(terminalId, id);
289351
+ return id;
289352
+ }
289353
+ /** Stop the active recording for a terminal (if any). Returns the recordingId or null. */
289354
+ stopRecording(terminalId) {
289355
+ const id = this.activeRecordings.get(terminalId);
289356
+ if (!id) return null;
289357
+ this.activeRecordings.delete(terminalId);
289358
+ this.sessionRecorder?.stop(id);
289359
+ return id;
289360
+ }
289361
+ /** True if a terminal is currently being recorded. */
289362
+ isRecording(terminalId) {
289363
+ return this.activeRecordings.has(terminalId);
289364
+ }
288868
289365
  setRawEventPublisher(publisher) {
288869
289366
  this.rawEventPublisher = publisher;
288870
289367
  }
@@ -289326,6 +289823,13 @@ var TerminalService = class {
289326
289823
  handleData(terminalId, data) {
289327
289824
  const sanitizedData = stripInternalControlMarkers(data);
289328
289825
  const tab = this.terminals.get(terminalId);
289826
+ const recordingId = this.activeRecordings.get(terminalId);
289827
+ if (recordingId && this.sessionRecorder && sanitizedData) {
289828
+ try {
289829
+ this.sessionRecorder.out(recordingId, sanitizedData);
289830
+ } catch {
289831
+ }
289832
+ }
289329
289833
  if (this.sessionLogger && tab) {
289330
289834
  if (!this.sessionLogStarted.has(terminalId)) {
289331
289835
  this.sessionLogStarted.add(terminalId);
@@ -342346,6 +342850,30 @@ var BUILTIN_TOOL_INFO = [
342346
342850
  {
342347
342851
  name: "get_live_dashboard",
342348
342852
  description: "Live multi-client dashboard \u2014 read the current unified dashboard state/summary, or the number of connected dashboard subscribers."
342853
+ },
342854
+ {
342855
+ name: "ingest_apm_spans",
342856
+ description: "Ingest OTLP/HTTP-JSON distributed-trace spans into the APM trace store (feeds bottleneck/slowest-trace analysis and the dashboard APM section)."
342857
+ },
342858
+ {
342859
+ name: "get_apm_summary",
342860
+ description: "Read the APM summary \u2014 traces, spans, per-service error rates and p95 latency."
342861
+ },
342862
+ {
342863
+ name: "ingest_dem_beacon",
342864
+ description: "Ingest a Core Web Vitals RUM beacon (page, LCP/INP/CLS/TTFB, JS errors) into the DEM store (feeds per-page p75 + error rate and the dashboard DEM section)."
342865
+ },
342866
+ {
342867
+ name: "get_dem_summary",
342868
+ description: "Read the DEM summary \u2014 sessions, pages, p75 Core Web Vitals, error rate."
342869
+ },
342870
+ {
342871
+ name: "collect_infra",
342872
+ description: "Collect Kubernetes cluster health \u2014 runs `kubectl get pods -A -o json` (or accepts the payload), feeds the infra monitor (pod readiness, restarts, CrashLoopBackOff)."
342873
+ },
342874
+ {
342875
+ name: "manage_etw",
342876
+ description: "Windows ETW diagnostics \u2014 start/stop a trace (logman), parse captured Get-WinEvent/Get-Counter output, list sessions. Use against a Windows host for network/file/registry/process/DNS diagnostics."
342349
342877
  }
342350
342878
  ];
342351
342879
  function buildReadFileDescription(support) {
@@ -346758,6 +347286,152 @@ async function getLiveDashboard(args, context2) {
346758
347286
  emit9(context2, "get_live_dashboard", args, out);
346759
347287
  return out;
346760
347288
  }
347289
+ var ingestApmSpansSchema = external_exports.object({
347290
+ payload: external_exports.any().describe("OTLP/HTTP-JSON payload: {resourceSpans:[...]} with spans (traceId/spanId/name/startTimeUnixNano/endTimeUnixNano/status)."),
347291
+ defaultService: external_exports.string().optional().describe("Fallback service name when a span has none.")
347292
+ });
347293
+ async function ingestApmSpans(args, context2) {
347294
+ const { o, msg } = obs(context2, "ingest_apm_spans", args);
347295
+ if (!o) return msg;
347296
+ const { ingestOtlp: ingestOtlp2 } = await Promise.resolve().then(() => (init_spanLedger(), spanLedger_exports));
347297
+ const n2 = ingestOtlp2(o.spanLedger, args.payload, args.defaultService);
347298
+ const out = `Ingested ${n2} APM span(s) into the trace store.`;
347299
+ emit9(context2, "ingest_apm_spans", args, out);
347300
+ return out;
347301
+ }
347302
+ var getApmSummarySchema = external_exports.object({});
347303
+ async function getApmSummary(_args, context2) {
347304
+ const { o, msg } = obs(context2, "get_apm_summary", _args);
347305
+ if (!o) return msg;
347306
+ const stats = o.spanLedger.serviceStats();
347307
+ const out = stats.length ? `APM: ${o.spanLedger.size()} spans across ${stats.length} service(s). ` + stats.map((x) => `${x.service}(${x.spans}${x.errors ? ", " + x.errors + " err" : ""})`).join(", ") : "APM: no spans ingested yet.";
347308
+ emit9(context2, "get_apm_summary", _args, out);
347309
+ return out;
347310
+ }
347311
+ var ingestDemBeaconSchema = external_exports.object({
347312
+ payload: external_exports.any().describe("RUM beacon: {page, route?, region?, userAgent?, lcpMs?, inpMs?, cls?, ttfbMs?, jsErrors?, at?}.")
347313
+ });
347314
+ async function ingestDemBeacon(args, context2) {
347315
+ const { o, msg } = obs(context2, "ingest_dem_beacon", args);
347316
+ if (!o) return msg;
347317
+ const s = o.rumLedger.ingestBeacon(args.payload);
347318
+ const out = s ? `Ingested RUM beacon for ${s.page}${s.lcpMs ? ` (LCP ${s.lcpMs}ms)` : ""}.` : "Beacon ignored \u2014 needs a `page` field.";
347319
+ emit9(context2, "ingest_dem_beacon", args, out);
347320
+ return out;
347321
+ }
347322
+ var getDemSummarySchema = external_exports.object({});
347323
+ async function getDemSummary(_args, context2) {
347324
+ const { o, msg } = obs(context2, "get_dem_summary", _args);
347325
+ if (!o) return msg;
347326
+ const pages = o.rumLedger.pageStats();
347327
+ const out = pages.length ? `DEM: ${o.rumLedger.size()} sessions across ${pages.length} page(s). ` + pages.map((p) => `${p.page}(${p.sessions}${p.errorRate ? `, ${(p.errorRate * 100).toFixed(0)}% err` : ""})`).join(", ") : "DEM: no RUM sessions ingested yet.";
347328
+ emit9(context2, "get_dem_summary", _args, out);
347329
+ return out;
347330
+ }
347331
+ var collectInfraSchema = external_exports.object({
347332
+ context: external_exports.string().optional().describe("Cluster context name (default 'default')."),
347333
+ kubectlJson: external_exports.any().optional().describe("Parsed `kubectl get pods -A -o json` payload (or its text). If omitted, runs kubectl on the local shell.")
347334
+ });
347335
+ async function collectInfra(args, context2) {
347336
+ const { o, msg } = obs(context2, "collect_infra", args);
347337
+ if (!o) return msg;
347338
+ const { parseKubectlPods: parseKubectlPods2 } = await Promise.resolve().then(() => (init_infraMonitor(), infraMonitor_exports));
347339
+ const clusterCtx = args.context ?? "default";
347340
+ let out;
347341
+ try {
347342
+ let payload = args.kubectlJson;
347343
+ if (payload === void 0) {
347344
+ const { execFile: execFile2 } = await import("node:child_process");
347345
+ payload = await new Promise((resolve2, reject) => {
347346
+ execFile2("kubectl", ["get", "pods", "-A"], { timeout: 3e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
347347
+ if (err) return reject(err);
347348
+ resolve2(stdout);
347349
+ });
347350
+ });
347351
+ }
347352
+ const text = typeof payload === "string" ? payload : JSON.stringify(payload);
347353
+ let pods = parseKubectlPods2(text);
347354
+ if (pods.length === 0 && typeof payload === "object" && payload !== null) {
347355
+ const jsonObj = payload;
347356
+ const items = jsonObj.items ?? [];
347357
+ const table = ["NAMESPACE NAME READY STATUS RESTARTS AGE", ...items.map((it) => {
347358
+ const ns = it.metadata?.namespace ?? "default";
347359
+ const name = it.metadata?.name ?? "pod";
347360
+ const cs = it.status?.containerStatuses ?? [];
347361
+ const ready = `${cs.filter((c) => c.ready).length}/${cs.length || 1}`;
347362
+ const status = it.status?.phase ?? "Unknown";
347363
+ const restarts = String(cs.reduce((s, c) => s + (c.restartCount ?? 0), 0));
347364
+ return `${ns} ${name} ${ready} ${status} ${restarts} 1d`;
347365
+ })].join("\n");
347366
+ pods = parseKubectlPods2(table);
347367
+ }
347368
+ const health = o.infraMonitor.recordCluster(clusterCtx, pods, []);
347369
+ out = `k8s ${clusterCtx}: ${pods.length} pods, ${health.notReadyPods} not-ready, ${health.crashLoopPods} CrashLoopBackOff.`;
347370
+ } catch (e) {
347371
+ out = `infra collect failed (kubectl or cluster unavailable): ${e instanceof Error ? e.message : String(e)}`;
347372
+ }
347373
+ emit9(context2, "collect_infra", args, out);
347374
+ return out;
347375
+ }
347376
+ var manageEtwSchema = external_exports.object({
347377
+ action: external_exports.enum(["start", "stop", "parse", "sessions"]).describe("start a trace, stop it, parse captured output, or list sessions."),
347378
+ name: external_exports.string().optional().describe("Trace name (start)."),
347379
+ providers: external_exports.array(external_exports.enum(["network", "file", "registry", "process", "dns", "power"])).optional().describe("ETW providers to trace (start)."),
347380
+ sessionId: external_exports.string().optional().describe("Session id (stop)."),
347381
+ output: external_exports.string().optional().describe("Captured Get-WinEvent/Get-Counter output (parse)."),
347382
+ format: external_exports.enum(["winevent", "counter"]).optional().describe("Parse format (parse).")
347383
+ });
347384
+ async function manageEtw(args, context2) {
347385
+ const { o, msg } = obs(context2, "manage_etw", args);
347386
+ if (!o) return msg;
347387
+ const svc = o.etwService;
347388
+ let out;
347389
+ try {
347390
+ if (args.action === "start") {
347391
+ if (!args.name || !args.providers?.length) {
347392
+ out = "start requires name + providers.";
347393
+ } else {
347394
+ const s = svc.createSession(args.name, args.providers);
347395
+ const { buildStartCommands: buildStartCommands2 } = await Promise.resolve().then(() => (init_etwService(), etwService_exports));
347396
+ out = `ETW session ${s.id} created (${args.providers.join(", ")}). Start commands:
347397
+ ${buildStartCommands2(s).join("\n")}`;
347398
+ }
347399
+ } else if (args.action === "stop") {
347400
+ if (!args.sessionId) {
347401
+ out = "stop requires sessionId.";
347402
+ } else {
347403
+ const s = svc.session(args.sessionId);
347404
+ if (!s) {
347405
+ out = `No ETW session ${args.sessionId}.`;
347406
+ } else {
347407
+ const { buildStopCommands: buildStopCommands2 } = await Promise.resolve().then(() => (init_etwService(), etwService_exports));
347408
+ out = `Stop commands for ${args.sessionId}:
347409
+ ${buildStopCommands2(s).join("\n")}`;
347410
+ }
347411
+ }
347412
+ } else if (args.action === "parse") {
347413
+ if (!args.output) {
347414
+ out = "parse requires output.";
347415
+ } else {
347416
+ const { parseWinEventJson: parseWinEventJson2, parseWinEventText: parseWinEventText2, parseCounterJson: parseCounterJson2 } = await Promise.resolve().then(() => (init_etwService(), etwService_exports));
347417
+ if (args.format === "counter") {
347418
+ const c = parseCounterJson2(args.output);
347419
+ out = `${c.length} counter sample(s).`;
347420
+ } else {
347421
+ const ev = parseWinEventJson2(args.output) ?? parseWinEventText2(args.output);
347422
+ out = `${ev.length} event(s) parsed.`;
347423
+ }
347424
+ }
347425
+ } else {
347426
+ const sessions = svc.sessions_();
347427
+ out = sessions.length ? sessions.map((s) => `- ${s.id} (${s.name}): ${s.providers.join(",")}`).join("\n") : "No ETW sessions.";
347428
+ }
347429
+ } catch (e) {
347430
+ out = `etw error: ${e instanceof Error ? e.message : String(e)}`;
347431
+ }
347432
+ emit9(context2, "manage_etw", args, out);
347433
+ return out;
347434
+ }
346761
347435
 
346762
347436
  // packages/backend/src/services/AgentHelper/tools/skill_tools.ts
346763
347437
  var skillToolSchema = external_exports.object({
@@ -347071,6 +347745,36 @@ function buildToolsForModel(readFileSupport) {
347071
347745
  name: "get_live_dashboard",
347072
347746
  description: "Live multi-client dashboard \u2014 read the current unified dashboard state/summary, or the number of connected dashboard subscribers.",
347073
347747
  schema: getLiveDashboardSchema
347748
+ },
347749
+ {
347750
+ name: "ingest_apm_spans",
347751
+ description: "Ingest OTLP/HTTP-JSON distributed-trace spans into the APM trace store (feeds bottleneck/slowest-trace analysis and the dashboard APM section).",
347752
+ schema: ingestApmSpansSchema
347753
+ },
347754
+ {
347755
+ name: "get_apm_summary",
347756
+ description: "Read the APM summary \u2014 traces, spans, per-service error rates and p95 latency.",
347757
+ schema: getApmSummarySchema
347758
+ },
347759
+ {
347760
+ name: "ingest_dem_beacon",
347761
+ description: "Ingest a Core Web Vitals RUM beacon (page, LCP/INP/CLS/TTFB, JS errors) into the DEM store (feeds per-page p75 + error rate and the dashboard DEM section).",
347762
+ schema: ingestDemBeaconSchema
347763
+ },
347764
+ {
347765
+ name: "get_dem_summary",
347766
+ description: "Read the DEM summary \u2014 sessions, pages, p75 Core Web Vitals, error rate.",
347767
+ schema: getDemSummarySchema
347768
+ },
347769
+ {
347770
+ name: "collect_infra",
347771
+ description: "Collect Kubernetes cluster health \u2014 runs `kubectl get pods -A -o json` (or accepts the payload), feeds the infra monitor (pod readiness, restarts, CrashLoopBackOff).",
347772
+ schema: collectInfraSchema
347773
+ },
347774
+ {
347775
+ name: "manage_etw",
347776
+ description: "Windows ETW diagnostics \u2014 start/stop a trace (logman), parse captured Get-WinEvent/Get-Counter output, list sessions. Use against a Windows host for network/file/registry/process/DNS diagnostics.",
347777
+ schema: manageEtwSchema
347074
347778
  }
347075
347779
  ].map((tool2) => convertToOpenAITool(tool2));
347076
347780
  }
@@ -347116,6 +347820,12 @@ var toolImplementations = {
347116
347820
  managePlaybookVersion,
347117
347821
  getCloudInventory,
347118
347822
  getLiveDashboard,
347823
+ ingestApmSpans,
347824
+ getApmSummary,
347825
+ ingestDemBeacon,
347826
+ getDemSummary,
347827
+ collectInfra,
347828
+ manageEtw,
347119
347829
  writeFile,
347120
347830
  editFile,
347121
347831
  writeAndEdit,
@@ -361663,6 +362373,60 @@ Actually, your intention might be different. Please re-read the description of t
361663
362373
  }
361664
362374
  break;
361665
362375
  }
362376
+ case "ingest_apm_spans": {
362377
+ try {
362378
+ const validatedArgs = ingestApmSpansSchema.parse(toolCall.args || {});
362379
+ result = await toolImplementations.ingestApmSpans(validatedArgs, executionContext);
362380
+ } catch (err) {
362381
+ result = `Parameter validation error for ingest_apm_spans: ${err.message}`;
362382
+ }
362383
+ break;
362384
+ }
362385
+ case "get_apm_summary": {
362386
+ try {
362387
+ const validatedArgs = getApmSummarySchema.parse(toolCall.args || {});
362388
+ result = await toolImplementations.getApmSummary(validatedArgs, executionContext);
362389
+ } catch (err) {
362390
+ result = `Parameter validation error for get_apm_summary: ${err.message}`;
362391
+ }
362392
+ break;
362393
+ }
362394
+ case "ingest_dem_beacon": {
362395
+ try {
362396
+ const validatedArgs = ingestDemBeaconSchema.parse(toolCall.args || {});
362397
+ result = await toolImplementations.ingestDemBeacon(validatedArgs, executionContext);
362398
+ } catch (err) {
362399
+ result = `Parameter validation error for ingest_dem_beacon: ${err.message}`;
362400
+ }
362401
+ break;
362402
+ }
362403
+ case "get_dem_summary": {
362404
+ try {
362405
+ const validatedArgs = getDemSummarySchema.parse(toolCall.args || {});
362406
+ result = await toolImplementations.getDemSummary(validatedArgs, executionContext);
362407
+ } catch (err) {
362408
+ result = `Parameter validation error for get_dem_summary: ${err.message}`;
362409
+ }
362410
+ break;
362411
+ }
362412
+ case "collect_infra": {
362413
+ try {
362414
+ const validatedArgs = collectInfraSchema.parse(toolCall.args || {});
362415
+ result = await toolImplementations.collectInfra(validatedArgs, executionContext);
362416
+ } catch (err) {
362417
+ result = `Parameter validation error for collect_infra: ${err.message}`;
362418
+ }
362419
+ break;
362420
+ }
362421
+ case "manage_etw": {
362422
+ try {
362423
+ const validatedArgs = manageEtwSchema.parse(toolCall.args || {});
362424
+ result = await toolImplementations.manageEtw(validatedArgs, executionContext);
362425
+ } catch (err) {
362426
+ result = `Parameter validation error for manage_etw: ${err.message}`;
362427
+ }
362428
+ break;
362429
+ }
361666
362430
  case "manage_device_memory": {
361667
362431
  try {
361668
362432
  const validatedArgs = manageDeviceMemorySchema.parse(toolCall.args || {});
@@ -365996,6 +366760,29 @@ var WebSocketGatewayAdapter = class {
365996
366760
  `${request.method} is not available on this websocket gateway.`
365997
366761
  );
365998
366762
  }
366763
+ if (request.method === "observability:liveDashboardSubscribe") {
366764
+ const hub = bridge.liveDashboard;
366765
+ const filter = params.filter;
366766
+ const subId = `ws-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
366767
+ await hub.subscribe({
366768
+ id: subId,
366769
+ filter,
366770
+ send: (payload) => {
366771
+ this.safeSocketSend(socket, {
366772
+ type: "gateway:event",
366773
+ event: "observability:dashboard",
366774
+ data: payload
366775
+ });
366776
+ }
366777
+ });
366778
+ const sock = socket;
366779
+ const onClose = () => {
366780
+ hub.unsubscribe(subId);
366781
+ sock.off?.("close", onClose);
366782
+ };
366783
+ sock.on?.("close", onClose);
366784
+ return { subscribed: true, subscriberId: subId };
366785
+ }
365999
366786
  const fnName = request.method.slice("observability:".length);
366000
366787
  const fn = bridge[fnName];
366001
366788
  if (typeof fn !== "function") {
@@ -377642,7 +378429,7 @@ var AgentSettingProfileService = class {
377642
378429
  };
377643
378430
 
377644
378431
  // packages/backend/src/services/automation/triggerEngine.ts
377645
- var import_crypto7 = require("crypto");
378432
+ var import_crypto10 = require("crypto");
377646
378433
  var DEFAULT_COOLDOWN_S = 300;
377647
378434
  var HISTORY_LIMIT_DEFAULT2 = 200;
377648
378435
  var MAX_CONCURRENT_DEFAULT = 3;
@@ -377686,7 +378473,7 @@ var TriggerEngine = class {
377686
378473
  }
377687
378474
  const entry = {
377688
378475
  ...input,
377689
- id: input.id ?? `trg-${(0, import_crypto7.randomUUID)().slice(0, 8)}`,
378476
+ id: input.id ?? `trg-${(0, import_crypto10.randomUUID)().slice(0, 8)}`,
377690
378477
  createdAt: this.now(),
377691
378478
  fireCount: 0
377692
378479
  };
@@ -377791,7 +378578,7 @@ var TriggerEngine = class {
377791
378578
  t.lastFiredAt = now;
377792
378579
  t.fireCount = (t.fireCount ?? 0) + 1;
377793
378580
  const rec = {
377794
- id: `fire-${(0, import_crypto7.randomUUID)().slice(0, 8)}`,
378581
+ id: `fire-${(0, import_crypto10.randomUUID)().slice(0, 8)}`,
377795
378582
  triggerId: t.id,
377796
378583
  triggerName: t.name,
377797
378584
  at: now,
@@ -377883,7 +378670,7 @@ var import_node_module2 = require("node:module");
377883
378670
  var import_node_path27 = __toESM(require("node:path"), 1);
377884
378671
 
377885
378672
  // packages/backend/src/services/sre/metricsLedger.ts
377886
- var DEFAULT_LIMIT = 1e4;
378673
+ var DEFAULT_LIMIT2 = 1e4;
377887
378674
  function flattenSnapshot(host, snap) {
377888
378675
  const p = { host, at: snap.timestamp };
377889
378676
  if (snap.cpu && typeof snap.cpu.usagePercent === "number") p.cpuUsagePercent = snap.cpu.usagePercent;
@@ -377907,7 +378694,7 @@ var MetricsLedger = class {
377907
378694
  points = /* @__PURE__ */ new Map();
377908
378695
  perHostLimit;
377909
378696
  constructor(opts = {}) {
377910
- this.perHostLimit = opts.perHostLimit ?? DEFAULT_LIMIT;
378697
+ this.perHostLimit = opts.perHostLimit ?? DEFAULT_LIMIT2;
377911
378698
  }
377912
378699
  /** Ingest a resource snapshot for a host. Returns the stored point. */
377913
378700
  record(host, snap) {
@@ -377986,7 +378773,7 @@ var MetricsLedger = class {
377986
378773
 
377987
378774
  // packages/backend/src/services/sre/goldenSignals.ts
377988
378775
  var DEFAULT_WINDOW = 36e5;
377989
- function percentile(sorted, p) {
378776
+ function percentile2(sorted, p) {
377990
378777
  if (sorted.length === 0) return void 0;
377991
378778
  const rank = Math.max(1, Math.ceil(p / 100 * sorted.length));
377992
378779
  return sorted[Math.min(rank, sorted.length) - 1];
@@ -378015,9 +378802,9 @@ var GoldenSignals = class {
378015
378802
  if (this.deps.latencyFor) {
378016
378803
  const samples = this.deps.latencyFor(host, since).map((s) => s.ms).sort((a, b) => a - b);
378017
378804
  if (samples.length > 0) {
378018
- r.latencyP50Ms = percentile(samples, 50);
378019
- r.latencyP95Ms = percentile(samples, 95);
378020
- r.latencyP99Ms = percentile(samples, 99);
378805
+ r.latencyP50Ms = percentile2(samples, 50);
378806
+ r.latencyP95Ms = percentile2(samples, 95);
378807
+ r.latencyP99Ms = percentile2(samples, 99);
378021
378808
  }
378022
378809
  }
378023
378810
  if (this.deps.errorRateFor) {
@@ -378048,7 +378835,7 @@ var GoldenSignals = class {
378048
378835
  };
378049
378836
 
378050
378837
  // packages/backend/src/services/sre/sloService.ts
378051
- var import_crypto8 = require("crypto");
378838
+ var import_crypto11 = require("crypto");
378052
378839
  var DEFAULT_FAST_BURN = 2;
378053
378840
  var SloService = class {
378054
378841
  constructor(deps) {
@@ -378072,7 +378859,7 @@ var SloService = class {
378072
378859
  }
378073
378860
  const entry = {
378074
378861
  ...def,
378075
- id: def.id ?? `slo-${(0, import_crypto8.randomUUID)().slice(0, 8)}`,
378862
+ id: def.id ?? `slo-${(0, import_crypto11.randomUUID)().slice(0, 8)}`,
378076
378863
  createdAt: this.now()
378077
378864
  };
378078
378865
  this.slos.set(entry.id, entry);
@@ -378139,7 +378926,7 @@ var SloService = class {
378139
378926
  };
378140
378927
 
378141
378928
  // packages/backend/src/services/sre/uptimeWatchdog.ts
378142
- var import_crypto9 = require("crypto");
378929
+ var import_crypto12 = require("crypto");
378143
378930
  var UptimeWatchdog = class {
378144
378931
  constructor(deps) {
378145
378932
  this.deps = deps;
@@ -378151,7 +378938,7 @@ var UptimeWatchdog = class {
378151
378938
  upsert(target) {
378152
378939
  const t = {
378153
378940
  ...target,
378154
- id: target.id ?? `wt-${(0, import_crypto9.randomUUID)().slice(0, 8)}`,
378941
+ id: target.id ?? `wt-${(0, import_crypto12.randomUUID)().slice(0, 8)}`,
378155
378942
  downAfter: target.downAfter ?? 3,
378156
378943
  intervalMs: target.intervalMs ?? 6e4
378157
378944
  };
@@ -378250,7 +379037,7 @@ var UptimeWatchdog = class {
378250
379037
  };
378251
379038
 
378252
379039
  // packages/backend/src/services/sre/alertService.ts
378253
- var import_crypto10 = require("crypto");
379040
+ var import_crypto13 = require("crypto");
378254
379041
  var SEV_ORDER = { info: 0, warning: 1, critical: 2 };
378255
379042
  var DEFAULT_DEDUPE_MS = 3e5;
378256
379043
  var DEFAULT_HISTORY = 200;
@@ -378275,7 +379062,7 @@ var AlertService = class {
378275
379062
  }
378276
379063
  /** Create a silence that suppresses matching alerts until `untilMs`. */
378277
379064
  silence(matcher, untilMs, opts = {}) {
378278
- const s = { id: `sil-${(0, import_crypto10.randomUUID)().slice(0, 8)}`, matcher, until: untilMs, ...opts };
379065
+ const s = { id: `sil-${(0, import_crypto13.randomUUID)().slice(0, 8)}`, matcher, until: untilMs, ...opts };
378279
379066
  this.silences.set(s.id, s);
378280
379067
  return s;
378281
379068
  }
@@ -378372,8 +379159,8 @@ var AlertService = class {
378372
379159
  };
378373
379160
 
378374
379161
  // packages/backend/src/services/sre/incidentLedger.ts
378375
- var import_crypto11 = require("crypto");
378376
- var DEFAULT_LIMIT2 = 500;
379162
+ var import_crypto14 = require("crypto");
379163
+ var DEFAULT_LIMIT3 = 500;
378377
379164
  var IncidentLedger = class {
378378
379165
  incidents = /* @__PURE__ */ new Map();
378379
379166
  order = [];
@@ -378381,7 +379168,7 @@ var IncidentLedger = class {
378381
379168
  historyLimit;
378382
379169
  constructor(opts = {}) {
378383
379170
  this.now = opts.now ?? (() => Date.now());
378384
- this.historyLimit = opts.historyLimit ?? DEFAULT_LIMIT2;
379171
+ this.historyLimit = opts.historyLimit ?? DEFAULT_LIMIT3;
378385
379172
  }
378386
379173
  /** Create (or return the existing open incident for the same title+source). */
378387
379174
  create(input) {
@@ -378394,7 +379181,7 @@ var IncidentLedger = class {
378394
379181
  }
378395
379182
  const at = this.now();
378396
379183
  const inc = {
378397
- id: `inc-${(0, import_crypto11.randomUUID)().slice(0, 8)}`,
379184
+ id: `inc-${(0, import_crypto14.randomUUID)().slice(0, 8)}`,
378398
379185
  title: input.title,
378399
379186
  severity: input.severity ?? "sev3",
378400
379187
  status: "open",
@@ -378494,12 +379281,12 @@ Linked playbook: ${inc.runbookPlaybookId}` : ""
378494
379281
  };
378495
379282
 
378496
379283
  // packages/backend/src/services/sre/syntheticChecks.ts
378497
- var DEFAULT_LIMIT3 = 1e3;
379284
+ var DEFAULT_LIMIT4 = 1e3;
378498
379285
  var SyntheticChecks = class {
378499
379286
  constructor(deps) {
378500
379287
  this.deps = deps;
378501
379288
  this.now = deps.now ?? (() => Date.now());
378502
- this.perCheckLimit = deps.perCheckLimit ?? DEFAULT_LIMIT3;
379289
+ this.perCheckLimit = deps.perCheckLimit ?? DEFAULT_LIMIT4;
378503
379290
  }
378504
379291
  checks = /* @__PURE__ */ new Map();
378505
379292
  results = /* @__PURE__ */ new Map();
@@ -378617,7 +379404,7 @@ var SyntheticChecks = class {
378617
379404
  };
378618
379405
 
378619
379406
  // packages/backend/src/services/sre/driftDetector.ts
378620
- var import_crypto12 = require("crypto");
379407
+ var import_crypto15 = require("crypto");
378621
379408
  function normalize2(text, ignorePatterns = []) {
378622
379409
  const lines = text.split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim() !== "");
378623
379410
  const regexes = ignorePatterns.map((p) => {
@@ -378653,7 +379440,7 @@ var DriftDetector = class {
378653
379440
  lastResults = /* @__PURE__ */ new Map();
378654
379441
  now;
378655
379442
  upsert(target) {
378656
- const t = { ...target, id: target.id ?? `drift-${(0, import_crypto12.randomUUID)().slice(0, 8)}` };
379443
+ const t = { ...target, id: target.id ?? `drift-${(0, import_crypto15.randomUUID)().slice(0, 8)}` };
378657
379444
  this.targets.set(t.id, t);
378658
379445
  return t;
378659
379446
  }
@@ -378713,130 +379500,11 @@ var DriftDetector = class {
378713
379500
  }
378714
379501
  };
378715
379502
 
378716
- // packages/backend/src/services/apm/spanLedger.ts
378717
- var DEFAULT_LIMIT4 = 5e4;
378718
- function percentile2(sorted, p) {
378719
- if (sorted.length === 0) return void 0;
378720
- return sorted[Math.min(sorted.length - 1, Math.floor(p / 100 * sorted.length))];
378721
- }
378722
- var SpanLedger = class {
378723
- spans = /* @__PURE__ */ new Map();
378724
- // traceId -> spans
378725
- order = [];
378726
- // traceIds in insert order
378727
- spanLimit;
378728
- totalSpans = 0;
378729
- constructor(opts = {}) {
378730
- this.spanLimit = opts.spanLimit ?? DEFAULT_LIMIT4;
378731
- }
378732
- /** Ingest one span. */
378733
- ingest(span) {
378734
- let arr3 = this.spans.get(span.traceId);
378735
- if (!arr3) {
378736
- arr3 = [];
378737
- this.spans.set(span.traceId, arr3);
378738
- this.order.push(span.traceId);
378739
- }
378740
- arr3.push(span);
378741
- this.totalSpans += 1;
378742
- while (this.totalSpans > this.spanLimit && this.order.length > 0) {
378743
- const oldest = this.order.shift();
378744
- const old = this.spans.get(oldest) ?? [];
378745
- this.totalSpans -= old.length;
378746
- this.spans.delete(oldest);
378747
- }
378748
- }
378749
- /** Ingest many spans. */
378750
- ingestBatch(spans) {
378751
- for (const s of spans) this.ingest(s);
378752
- return spans.length;
378753
- }
378754
- /** All spans for a trace. */
378755
- trace(traceId) {
378756
- return this.spans.get(traceId) ?? [];
378757
- }
378758
- traceIds() {
378759
- return this.order;
378760
- }
378761
- /** Summarize a trace (root service, span count, total duration, error). */
378762
- summarize(traceId) {
378763
- const spans = this.spans.get(traceId);
378764
- if (!spans || spans.length === 0) return void 0;
378765
- const root = spans.find((s) => !s.parentSpanId) ?? spans[0];
378766
- const services = Array.from(new Set(spans.map((s) => s.service)));
378767
- const hasError = spans.some((s) => s.status === "error");
378768
- const start = Math.min(...spans.map((s) => s.startMs));
378769
- const end = Math.max(...spans.map((s) => s.startMs + s.durationMs));
378770
- return {
378771
- traceId,
378772
- rootService: root.service,
378773
- spanCount: spans.length,
378774
- totalDurationMs: end - start,
378775
- hasError,
378776
- services,
378777
- at: start
378778
- };
378779
- }
378780
- /** Per-service stats over a window. */
378781
- serviceStats(opts = {}) {
378782
- const byService = /* @__PURE__ */ new Map();
378783
- for (const arr3 of this.spans.values()) {
378784
- for (const s of arr3) {
378785
- if (opts.sinceMs !== void 0 && s.startMs < opts.sinceMs) continue;
378786
- if (opts.service && s.service !== opts.service) continue;
378787
- let e = byService.get(s.service);
378788
- if (!e) {
378789
- e = { durations: [], errors: 0 };
378790
- byService.set(s.service, e);
378791
- }
378792
- e.durations.push(s.durationMs);
378793
- if (s.status === "error") e.errors += 1;
378794
- }
378795
- }
378796
- const out = [];
378797
- for (const [service, e] of byService) {
378798
- const durations = e.durations.slice().sort((a, b) => a - b);
378799
- out.push({
378800
- service,
378801
- spanCount: durations.length,
378802
- errorCount: e.errors,
378803
- errorRate: durations.length > 0 ? e.errors / durations.length : 0,
378804
- p50Ms: percentile2(durations, 50),
378805
- p95Ms: percentile2(durations, 95),
378806
- p99Ms: percentile2(durations, 99),
378807
- maxMs: durations.length > 0 ? durations[durations.length - 1] : void 0
378808
- });
378809
- }
378810
- return out.sort((a, b) => (b.p95Ms ?? 0) - (a.p95Ms ?? 0));
378811
- }
378812
- /** Slowest traces by total duration. */
378813
- slowestTraces(limit2 = 10, opts = {}) {
378814
- const summaries = [];
378815
- for (const id of this.order) {
378816
- const s = this.summarize(id);
378817
- if (!s) continue;
378818
- if (opts.sinceMs !== void 0 && s.at < opts.sinceMs) continue;
378819
- summaries.push(s);
378820
- }
378821
- return summaries.sort((a, b) => b.totalDurationMs - a.totalDurationMs).slice(0, limit2);
378822
- }
378823
- /** Services ranked by total error count (bottleneck/failing services). */
378824
- bottleneckServices(opts = {}) {
378825
- return this.serviceStats(opts).sort((a, b) => b.errorCount - a.errorCount);
378826
- }
378827
- /** Total stored spans. */
378828
- size() {
378829
- return this.totalSpans;
378830
- }
378831
- clear() {
378832
- this.spans.clear();
378833
- this.order.length = 0;
378834
- this.totalSpans = 0;
378835
- }
378836
- };
379503
+ // packages/backend/src/services/observability.ts
379504
+ init_spanLedger();
378837
379505
 
378838
379506
  // packages/backend/src/services/dem/rumLedger.ts
378839
- var import_crypto13 = require("crypto");
379507
+ var import_crypto16 = require("crypto");
378840
379508
  var DEFAULT_LIMIT5 = 5e4;
378841
379509
  function percentile3(sorted, p) {
378842
379510
  if (sorted.length === 0) return void 0;
@@ -378854,7 +379522,7 @@ var RumLedger = class {
378854
379522
  ingest(session) {
378855
379523
  const s = {
378856
379524
  ...session,
378857
- id: session.id ?? `rum-${(0, import_crypto13.randomUUID)().slice(0, 12)}`,
379525
+ id: session.id ?? `rum-${(0, import_crypto16.randomUUID)().slice(0, 12)}`,
378858
379526
  at: session.at ?? this.now()
378859
379527
  };
378860
379528
  this.sessions.push(s);
@@ -378932,116 +379600,9 @@ var RumLedger = class {
378932
379600
  }
378933
379601
  };
378934
379602
 
378935
- // packages/backend/src/services/infra/infraMonitor.ts
378936
- function clusterHealth(context2, pods, nodes) {
378937
- const running = pods.filter((p) => p.phase === "Running");
378938
- const notReady = pods.filter((p) => !p.ready).length;
378939
- const crashLoop = pods.filter((p) => p.restarts >= 5 || p.phase === "Unknown" && p.restarts > 0).length;
378940
- const totalRestarts = pods.reduce((s, p) => s + p.restarts, 0);
378941
- const nodesReady = nodes.filter((n2) => n2.ready).length;
378942
- let cpuPct;
378943
- let memPct;
378944
- const withCpu = pods.filter((p) => typeof p.cpuMillicores === "number" && typeof p.cpuLimitMillicores === "number" && p.cpuLimitMillicores > 0);
378945
- if (withCpu.length > 0) {
378946
- const used = withCpu.reduce((s, p) => s + (p.cpuMillicores ?? 0), 0);
378947
- const limit2 = withCpu.reduce((s, p) => s + (p.cpuLimitMillicores ?? 0), 0);
378948
- cpuPct = limit2 > 0 ? used / limit2 * 100 : void 0;
378949
- }
378950
- const withMem = pods.filter((p) => typeof p.memMiB === "number" && typeof p.memLimitMiB === "number" && p.memLimitMiB > 0);
378951
- if (withMem.length > 0) {
378952
- const used = withMem.reduce((s, p) => s + (p.memMiB ?? 0), 0);
378953
- const limit2 = withMem.reduce((s, p) => s + (p.memLimitMiB ?? 0), 0);
378954
- memPct = limit2 > 0 ? used / limit2 * 100 : void 0;
378955
- }
378956
- return {
378957
- context: context2,
378958
- totalPods: pods.length,
378959
- runningPods: running.length,
378960
- notReadyPods: notReady,
378961
- crashLoopPods: crashLoop,
378962
- totalRestarts,
378963
- nodesReady,
378964
- nodesTotal: nodes.length,
378965
- ...cpuPct !== void 0 ? { cpuUsagePercentOfLimit: cpuPct } : {},
378966
- ...memPct !== void 0 ? { memUsagePercentOfLimit: memPct } : {}
378967
- };
378968
- }
378969
- function podUnhealthy(p) {
378970
- return !p.ready || p.phase === "Failed" || p.phase === "Unknown" || p.restarts >= 5;
378971
- }
378972
- var InfraMonitor = class {
378973
- clusters = /* @__PURE__ */ new Map();
378974
- instances = /* @__PURE__ */ new Map();
378975
- constructor(_deps = {}) {
378976
- }
378977
- /** Record a cluster health snapshot. */
378978
- recordCluster(context2, pods, nodes) {
378979
- const h = clusterHealth(context2, pods, nodes);
378980
- this.clusters.set(context2, h);
378981
- return h;
378982
- }
378983
- cluster(context2) {
378984
- return this.clusters.get(context2);
378985
- }
378986
- clusters_() {
378987
- return Array.from(this.clusters.values());
378988
- }
378989
- /** Unhealthy pods for a cluster (for alerting). */
378990
- unhealthyPods(pods) {
378991
- return pods.filter(podUnhealthy);
378992
- }
378993
- /** Record a cloud instance state. */
378994
- recordInstance(inst) {
378995
- this.instances.set(inst.id, inst);
378996
- }
378997
- instance(id) {
378998
- return this.instances.get(id);
378999
- }
379000
- /** Instances that are not healthy (stopped/failed). */
379001
- unhealthyInstances() {
379002
- return Array.from(this.instances.values()).filter((i) => i.statusOk === false || /stopped|terminated|failed/i.test(i.state));
379003
- }
379004
- clear() {
379005
- this.clusters.clear();
379006
- this.instances.clear();
379007
- }
379008
- };
379009
-
379010
- // packages/backend/src/services/etw/etwService.ts
379011
- var import_crypto14 = require("crypto");
379012
- var now0 = () => Date.now();
379013
- var EtwService = class {
379014
- sessions = /* @__PURE__ */ new Map();
379015
- now;
379016
- constructor(deps = {}) {
379017
- this.now = deps.now ?? now0;
379018
- }
379019
- /** Create a session descriptor (commands are built from it by buildStartCommands). */
379020
- createSession(name, providers, outDir = "C:\\temp") {
379021
- const clean = name.replace(/[^A-Za-z0-9_-]/g, "");
379022
- const s = {
379023
- id: `etw-${(0, import_crypto14.randomUUID)().slice(0, 8)}`,
379024
- name: clean,
379025
- providers,
379026
- outFile: `${outDir}\\${clean}`,
379027
- createdAt: this.now()
379028
- };
379029
- this.sessions.set(s.id, s);
379030
- return s;
379031
- }
379032
- session(id) {
379033
- return this.sessions.get(id);
379034
- }
379035
- sessions_() {
379036
- return Array.from(this.sessions.values());
379037
- }
379038
- removeSession(id) {
379039
- return this.sessions.delete(id);
379040
- }
379041
- clear() {
379042
- this.sessions.clear();
379043
- }
379044
- };
379603
+ // packages/backend/src/services/observability.ts
379604
+ init_infraMonitor();
379605
+ init_etwService();
379045
379606
 
379046
379607
  // packages/backend/src/services/dashboard/dashboardService.ts
379047
379608
  var DEFAULT_LIMIT6 = 10;
@@ -381860,7 +382421,7 @@ var safeLoadAll = renamed("safeLoadAll", "loadAll");
381860
382421
  var safeDump = renamed("safeDump", "dump");
381861
382422
 
381862
382423
  // packages/backend/src/services/dagu/daguParser.ts
381863
- var import_crypto15 = require("crypto");
382424
+ var import_crypto17 = require("crypto");
381864
382425
  init_dagScheduler();
381865
382426
  function stepId(step, index2) {
381866
382427
  return (step.id ?? step.name ?? `step-${index2 + 1}`).toString().replace(/[^A-Za-z0-9_-]/g, "-");
@@ -381923,7 +382484,7 @@ function parseDaguWorkflow(doc, opts = {}) {
381923
382484
  return { name, ...d !== "" ? { defaultValue: d } : {} };
381924
382485
  }) : void 0;
381925
382486
  const playbook = {
381926
- id: `dagu-${(0, import_crypto15.randomUUID)().slice(0, 8)}`,
382487
+ id: `dagu-${(0, import_crypto17.randomUUID)().slice(0, 8)}`,
381927
382488
  name: opts.name ?? doc.name ?? "dagu workflow",
381928
382489
  steps,
381929
382490
  ...doc.description || opts.description ? { description: opts.description ?? doc.description } : {},
@@ -381952,7 +382513,7 @@ function daguExecutionPlan(playbook) {
381952
382513
  // packages/backend/src/services/plugin/pluginRegistry.ts
381953
382514
  var import_node_path26 = __toESM(require("node:path"), 1);
381954
382515
  var import_node_fs11 = require("node:fs");
381955
- var import_crypto16 = require("crypto");
382516
+ var import_crypto18 = require("crypto");
381956
382517
  var DEFAULT_ENTRY_CANDIDATES = ["index.js", "index.ts", "index.mjs"];
381957
382518
  var PluginRegistry = class {
381958
382519
  constructor(opts) {
@@ -382019,7 +382580,7 @@ var PluginRegistry = class {
382019
382580
  this.plugins.delete(existing.id);
382020
382581
  }
382021
382582
  const record2 = {
382022
- id: `plugin-${(0, import_crypto16.randomUUID)().slice(0, 8)}`,
382583
+ id: `plugin-${(0, import_crypto18.randomUUID)().slice(0, 8)}`,
382023
382584
  dir,
382024
382585
  manifest,
382025
382586
  enabled: true,
@@ -382114,7 +382675,7 @@ var PluginRegistry = class {
382114
382675
  };
382115
382676
 
382116
382677
  // packages/backend/src/services/evals/evalHarness.ts
382117
- var import_crypto17 = require("crypto");
382678
+ var import_crypto19 = require("crypto");
382118
382679
  function subset(needles, haystack) {
382119
382680
  return needles.every((n2) => haystack.includes(n2));
382120
382681
  }
@@ -382177,7 +382738,7 @@ var EvalHarness = class {
382177
382738
  return arr3.length > 0 ? arr3.filter((r) => r.pass).length / arr3.length * 100 : void 0;
382178
382739
  };
382179
382740
  return {
382180
- id: `eval-${(0, import_crypto17.randomUUID)().slice(0, 8)}`,
382741
+ id: `eval-${(0, import_crypto19.randomUUID)().slice(0, 8)}`,
382181
382742
  at: this.now(),
382182
382743
  total: results.length,
382183
382744
  passed,
@@ -382460,7 +383021,7 @@ var BehaviorLedger = class {
382460
383021
  };
382461
383022
 
382462
383023
  // packages/backend/src/services/aperf/aperfService.ts
382463
- var import_crypto18 = require("crypto");
383024
+ var import_crypto20 = require("crypto");
382464
383025
  var DEFAULT_HOST_DIR = "/tmp/aperf";
382465
383026
  var DEFAULT_INTERVAL = 1;
382466
383027
  var DEFAULT_PERIOD = 60;
@@ -382559,7 +383120,7 @@ var AperfService = class {
382559
383120
  now;
382560
383121
  /** Full deep-dive: install aperf if needed, record, report, parse, return. */
382561
383122
  async deepDive(host, opts = {}) {
382562
- const runName = `rterm-${(0, import_crypto18.randomUUID)().slice(0, 8)}`;
383123
+ const runName = `rterm-${(0, import_crypto20.randomUUID)().slice(0, 8)}`;
382563
383124
  const plan = {
382564
383125
  runName,
382565
383126
  intervalSec: opts.intervalSec ?? DEFAULT_INTERVAL,
@@ -382602,18 +383163,18 @@ function aperfSummaryToMetricPoint(result) {
382602
383163
  }
382603
383164
 
382604
383165
  // packages/backend/src/services/audit/auditLedger.ts
382605
- var import_crypto19 = require("crypto");
383166
+ var import_crypto21 = require("crypto");
382606
383167
  var GENESIS_HASH = "0".repeat(64);
382607
383168
  function computeRecordHash(kind, actor, target, summary, detail, at, seq2) {
382608
383169
  const payload = JSON.stringify({ kind, actor, target, summary, detail, at, seq: seq2 });
382609
- return (0, import_crypto19.createHash)("sha256").update(payload).digest("hex");
383170
+ return (0, import_crypto21.createHash)("sha256").update(payload).digest("hex");
382610
383171
  }
382611
383172
  var AuditLedger = class _AuditLedger {
382612
383173
  records = [];
382613
383174
  hashFn;
382614
383175
  now;
382615
383176
  constructor(deps = {}) {
382616
- this.hashFn = deps.hashFn ?? ((data) => (0, import_crypto19.createHash)("sha256").update(data).digest("hex"));
383177
+ this.hashFn = deps.hashFn ?? ((data) => (0, import_crypto21.createHash)("sha256").update(data).digest("hex"));
382617
383178
  this.now = deps.now ?? (() => Date.now());
382618
383179
  }
382619
383180
  /** Append an event to the audit ledger. Returns the record with hash + chain. */
@@ -382630,9 +383191,9 @@ var AuditLedger = class _AuditLedger {
382630
383191
  at,
382631
383192
  seq2
382632
383193
  );
382633
- const hash2 = (0, import_crypto19.createHash)("sha256").update(contentHash + prevHash).digest("hex");
383194
+ const hash2 = (0, import_crypto21.createHash)("sha256").update(contentHash + prevHash).digest("hex");
382634
383195
  const record2 = {
382635
- id: `audit-${(0, import_crypto19.randomUUID)().slice(0, 12)}`,
383196
+ id: `audit-${(0, import_crypto21.randomUUID)().slice(0, 12)}`,
382636
383197
  kind: event.kind,
382637
383198
  actor: event.actor,
382638
383199
  target: event.target,
@@ -382683,7 +383244,7 @@ var AuditLedger = class _AuditLedger {
382683
383244
  record2.at,
382684
383245
  record2.seq
382685
383246
  );
382686
- const expectedHash = (0, import_crypto19.createHash)("sha256").update(expectedContentHash + record2.prevHash).digest("hex");
383247
+ const expectedHash = (0, import_crypto21.createHash)("sha256").update(expectedContentHash + record2.prevHash).digest("hex");
382687
383248
  if (record2.hash !== expectedHash) {
382688
383249
  return { valid: false, brokenAt: record2.seq, detail: `hash mismatch at seq ${record2.seq}` };
382689
383250
  }
@@ -382730,7 +383291,7 @@ var AuditLedger = class _AuditLedger {
382730
383291
  };
382731
383292
 
382732
383293
  // packages/backend/src/services/audit/evidenceSealer.ts
382733
- var import_crypto20 = require("crypto");
383294
+ var import_crypto22 = require("crypto");
382734
383295
  function computeMerkleRoot(hashes) {
382735
383296
  if (hashes.length === 0) return "0".repeat(64);
382736
383297
  if (hashes.length === 1) return hashes[0];
@@ -382738,7 +383299,7 @@ function computeMerkleRoot(hashes) {
382738
383299
  for (let i = 0; i < hashes.length; i += 2) {
382739
383300
  const left = hashes[i];
382740
383301
  const right = i + 1 < hashes.length ? hashes[i + 1] : left;
382741
- nextLevel.push((0, import_crypto20.createHash)("sha256").update(left + right).digest("hex"));
383302
+ nextLevel.push((0, import_crypto22.createHash)("sha256").update(left + right).digest("hex"));
382742
383303
  }
382743
383304
  return computeMerkleRoot(nextLevel);
382744
383305
  }
@@ -383219,17 +383780,17 @@ var OtelExporter = class {
383219
383780
  };
383220
383781
 
383221
383782
  // packages/backend/src/services/secrets/secretsVault.ts
383222
- var import_crypto21 = require("crypto");
383783
+ var import_crypto23 = require("crypto");
383223
383784
  var KEY_RE = /^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,127}$/;
383224
383785
  var NONCE_LEN = 12;
383225
383786
  function deriveKey(masterKey, salt) {
383226
- return (0, import_crypto21.scryptSync)(masterKey, salt, 32);
383787
+ return (0, import_crypto23.scryptSync)(masterKey, salt, 32);
383227
383788
  }
383228
383789
  function defaultSalt(masterKey) {
383229
- return (0, import_crypto21.createHash)("sha256").update(Buffer.concat([Buffer.from("rterm-secrets-v1"), Buffer.from(masterKey)])).digest().subarray(0, 16);
383790
+ return (0, import_crypto23.createHash)("sha256").update(Buffer.concat([Buffer.from("rterm-secrets-v1"), Buffer.from(masterKey)])).digest().subarray(0, 16);
383230
383791
  }
383231
383792
  function encryptSecret(plaintext, key, nonce) {
383232
- const cipher = (0, import_crypto21.createCipheriv)("aes-256-gcm", key, nonce);
383793
+ const cipher = (0, import_crypto23.createCipheriv)("aes-256-gcm", key, nonce);
383233
383794
  const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
383234
383795
  const tag = cipher.getAuthTag();
383235
383796
  return Buffer.concat([nonce, tag, ct]).toString("base64");
@@ -383240,7 +383801,7 @@ function decryptSecret(blob, key) {
383240
383801
  const nonce = buf.subarray(0, NONCE_LEN);
383241
383802
  const tag = buf.subarray(NONCE_LEN, NONCE_LEN + 16);
383242
383803
  const ct = buf.subarray(NONCE_LEN + 16);
383243
- const decipher = (0, import_crypto21.createDecipheriv)("aes-256-gcm", key, nonce);
383804
+ const decipher = (0, import_crypto23.createDecipheriv)("aes-256-gcm", key, nonce);
383244
383805
  decipher.setAuthTag(tag);
383245
383806
  return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
383246
383807
  }
@@ -383252,7 +383813,7 @@ var SecretsVault = class {
383252
383813
  onAudit;
383253
383814
  constructor(opts = {}) {
383254
383815
  this.now = opts.now ?? Date.now;
383255
- this.rand = opts.randomBytes ?? import_crypto21.randomBytes;
383816
+ this.rand = opts.randomBytes ?? import_crypto23.randomBytes;
383256
383817
  this.onAudit = opts.onAudit;
383257
383818
  if (opts.masterKey !== void 0) {
383258
383819
  const salt = opts.salt ?? defaultSalt(opts.masterKey);
@@ -383368,7 +383929,7 @@ var SecretsVault = class {
383368
383929
  };
383369
383930
 
383370
383931
  // packages/backend/src/services/oncall/escalationService.ts
383371
- var import_crypto22 = require("crypto");
383932
+ var import_crypto24 = require("crypto");
383372
383933
  var EscalationService = class {
383373
383934
  policies = /* @__PURE__ */ new Map();
383374
383935
  pages = /* @__PURE__ */ new Map();
@@ -383408,7 +383969,7 @@ var EscalationService = class {
383408
383969
  if (!policy) throw new Error(`unknown escalation policy: ${input.policyId}`);
383409
383970
  const at = this.now();
383410
383971
  const page = {
383411
- id: (0, import_crypto22.randomUUID)(),
383972
+ id: (0, import_crypto24.randomUUID)(),
383412
383973
  incidentId: input.incidentId,
383413
383974
  policyId: input.policyId,
383414
383975
  levelIndex: 0,
@@ -383622,7 +384183,7 @@ var CostBudgetService = class {
383622
384183
  };
383623
384184
 
383624
384185
  // packages/backend/src/services/recording/sessionRecorder.ts
383625
- var import_crypto23 = require("crypto");
384186
+ var import_crypto25 = require("crypto");
383626
384187
  function toAsciinema(rec) {
383627
384188
  const header = {
383628
384189
  version: 2,
@@ -383651,7 +384212,7 @@ var SessionRecorder = class {
383651
384212
  }
383652
384213
  /** Start recording a terminal. Returns the recording id. */
383653
384214
  start(terminalId, opts = {}) {
383654
- const id = (0, import_crypto23.randomUUID)();
384215
+ const id = (0, import_crypto25.randomUUID)();
383655
384216
  this.recordings.set(id, {
383656
384217
  id,
383657
384218
  terminalId,
@@ -383716,7 +384277,7 @@ var SessionRecorder = class {
383716
384277
  };
383717
384278
 
383718
384279
  // packages/backend/src/services/gitops/gitOpsService.ts
383719
- var import_crypto24 = require("crypto");
384280
+ var import_crypto26 = require("crypto");
383720
384281
  function stableStringify(v) {
383721
384282
  if (v === null || typeof v !== "object") return JSON.stringify(v);
383722
384283
  if (Array.isArray(v)) return `[${v.map(stableStringify).join(",")}]`;
@@ -383724,11 +384285,11 @@ function stableStringify(v) {
383724
384285
  return `{${Object.keys(o).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(o[k])}`).join(",")}}`;
383725
384286
  }
383726
384287
  function specHash(spec) {
383727
- return (0, import_crypto24.createHash)("sha256").update(stableStringify(spec)).digest("hex").slice(0, 12);
384288
+ return (0, import_crypto26.createHash)("sha256").update(stableStringify(spec)).digest("hex").slice(0, 12);
383728
384289
  }
383729
384290
  function buildManifest(entities) {
383730
384291
  const sorted = [...entities].sort((a, b) => a.id.localeCompare(b.id));
383731
- const stateHash = (0, import_crypto24.createHash)("sha256").update(sorted.map((e) => `${e.id}:${specHash(e.spec)}`).join("\n")).digest("hex");
384292
+ const stateHash = (0, import_crypto26.createHash)("sha256").update(sorted.map((e) => `${e.id}:${specHash(e.spec)}`).join("\n")).digest("hex");
383732
384293
  return { version: 1, stateHash, entities: sorted };
383733
384294
  }
383734
384295
  function specDiff(repo, live) {
@@ -383821,9 +384382,9 @@ var GitOpsService = class {
383821
384382
  };
383822
384383
 
383823
384384
  // packages/backend/src/services/automation/playbookVersioning.ts
383824
- var import_crypto25 = require("crypto");
384385
+ var import_crypto27 = require("crypto");
383825
384386
  function hashDef(def) {
383826
- return (0, import_crypto25.createHash)("sha256").update(JSON.stringify(def)).digest("hex").slice(0, 16);
384387
+ return (0, import_crypto27.createHash)("sha256").update(JSON.stringify(def)).digest("hex").slice(0, 16);
383827
384388
  }
383828
384389
  function lintPlaybook(def) {
383829
384390
  const issues = [];
@@ -384014,7 +384575,7 @@ function diffText(aText, bText, aLabel = "a", bLabel = "b") {
384014
384575
  }
384015
384576
 
384016
384577
  // packages/backend/src/services/cloud/cloudInventory.ts
384017
- var import_crypto26 = require("crypto");
384578
+ var import_crypto28 = require("crypto");
384018
384579
  function parseAwsInstances(payload, accountId = "") {
384019
384580
  const out = [];
384020
384581
  const reservations = payload?.Reservations ?? [];
@@ -384027,7 +384588,7 @@ function parseAwsInstances(payload, accountId = "") {
384027
384588
  if (t?.Key) tags[t.Key] = t.Value ?? "";
384028
384589
  }
384029
384590
  out.push({
384030
- id: `aws:${accountId}:${String(i.InstanceId ?? (0, import_crypto26.randomUUID)())}`,
384591
+ id: `aws:${accountId}:${String(i.InstanceId ?? (0, import_crypto28.randomUUID)())}`,
384031
384592
  provider: "aws",
384032
384593
  kind: "instance",
384033
384594
  name: tags["Name"] ?? String(i.InstanceId ?? ""),
@@ -384059,7 +384620,7 @@ function parseGcpInstances(payload, accountId = "") {
384059
384620
  const accessCfgs = nics[0]?.accessConfigs ?? [];
384060
384621
  const publicIp = accessCfgs[0]?.natIP;
384061
384622
  out.push({
384062
- id: `gcp:${accountId}:${String(i.id ?? i.name ?? (0, import_crypto26.randomUUID)())}`,
384623
+ id: `gcp:${accountId}:${String(i.id ?? i.name ?? (0, import_crypto28.randomUUID)())}`,
384063
384624
  provider: "gcp",
384064
384625
  kind: "instance",
384065
384626
  name: String(i.name ?? ""),
@@ -384081,7 +384642,7 @@ function parseAzureVms(payload, accountId = "") {
384081
384642
  const v = vm;
384082
384643
  const props = v.properties ?? {};
384083
384644
  out.push({
384084
- id: `azure:${accountId}:${String(v.id ?? v.name ?? (0, import_crypto26.randomUUID)())}`,
384645
+ id: `azure:${accountId}:${String(v.id ?? v.name ?? (0, import_crypto28.randomUUID)())}`,
384085
384646
  provider: "azure",
384086
384647
  kind: "vm",
384087
384648
  name: String(v.name ?? ""),
@@ -384456,15 +385017,73 @@ function createObservability(deps) {
384456
385017
  const escalationService = new EscalationService({ channels: deps.pagingChannels ?? [] });
384457
385018
  const costBudgetService = new CostBudgetService({ prices: deps.modelPrices });
384458
385019
  const sessionRecorder = new SessionRecorder({});
385020
+ const defaultGitopsReadLive = () => {
385021
+ const out = [];
385022
+ try {
385023
+ const settings = deps.settingsService?.getSettings?.();
385024
+ const conns = settings?.connections ?? {};
385025
+ for (const [kind, list] of Object.entries(conns)) {
385026
+ if (!Array.isArray(list)) continue;
385027
+ for (const c of list) {
385028
+ const id = c?.id ?? c?.name;
385029
+ if (id) out.push({ id: `connection:${kind}:${String(id)}`, kind: "connection", spec: c });
385030
+ }
385031
+ }
385032
+ } catch {
385033
+ }
385034
+ try {
385035
+ const am = deps.automationManager;
385036
+ const groups = am?.listGroups?.() ?? [];
385037
+ for (const g of groups) {
385038
+ const id = g?.id ?? g?.name;
385039
+ if (id) out.push({ id: `group:${String(id)}`, kind: "group", spec: g });
385040
+ }
385041
+ const scripts = am?.listScripts?.() ?? [];
385042
+ for (const s of scripts) {
385043
+ const id = s?.id ?? s?.name;
385044
+ if (id) out.push({ id: `script:${String(id)}`, kind: "script", spec: s });
385045
+ }
385046
+ const playbooks = am?.listPlaybooks?.() ?? [];
385047
+ for (const p of playbooks) {
385048
+ const id = p?.id ?? p?.name;
385049
+ if (id) out.push({ id: `playbook:${String(id)}`, kind: "playbook", spec: p });
385050
+ }
385051
+ const templates = am?.listTemplates?.() ?? [];
385052
+ for (const t of templates) {
385053
+ const id = t?.id ?? t?.name;
385054
+ if (id) out.push({ id: `template:${String(id)}`, kind: "template", spec: t });
385055
+ }
385056
+ const tasks = am?.listScheduledTasks?.() ?? [];
385057
+ for (const t of tasks) {
385058
+ const id = t?.id ?? t?.name;
385059
+ if (id) out.push({ id: `scheduledTask:${String(id)}`, kind: "scheduledTask", spec: t });
385060
+ }
385061
+ } catch {
385062
+ }
385063
+ return out;
385064
+ };
384459
385065
  const gitopsService = new GitOpsService({
384460
- readLive: deps.gitopsReadLive ?? (() => []),
385066
+ readLive: deps.gitopsReadLive ?? defaultGitopsReadLive,
384461
385067
  applyEntity: deps.gitopsApplyEntity
384462
385068
  });
384463
385069
  const playbookVersioning = new PlaybookVersioning({});
385070
+ const runCliJson = async (bin, args) => {
385071
+ const { execFile: execFile2 } = await import("node:child_process");
385072
+ return new Promise((resolve2, reject) => {
385073
+ execFile2(bin, args, { timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
385074
+ if (err) return reject(err);
385075
+ try {
385076
+ resolve2(JSON.parse(stdout));
385077
+ } catch (e) {
385078
+ reject(e instanceof Error ? e : new Error("invalid JSON"));
385079
+ }
385080
+ });
385081
+ });
385082
+ };
384464
385083
  const cloudInventory = new CloudInventory({
384465
- fetchAwsInstances: deps.fetchAwsInstances,
384466
- fetchGcpInstances: deps.fetchGcpInstances,
384467
- fetchAzureVms: deps.fetchAzureVms
385084
+ fetchAwsInstances: deps.fetchAwsInstances ?? (async () => runCliJson("aws", ["ec2", "describe-instances", "--output", "json"])),
385085
+ fetchGcpInstances: deps.fetchGcpInstances ?? (async () => runCliJson("gcloud", ["compute", "instances", "list", "--format", "json"])),
385086
+ fetchAzureVms: deps.fetchAzureVms ?? (async () => runCliJson("az", ["vm", "list", "--show-details", "--output", "json"]))
384468
385087
  });
384469
385088
  const liveDashboardHub = new LiveDashboardHub({
384470
385089
  getState: () => dashboard.state()
@@ -384480,6 +385099,45 @@ function createObservability(deps) {
384480
385099
  } catch {
384481
385100
  }
384482
385101
  });
385102
+ let lastCostRunId = null;
385103
+ const feedCostFromRuns = () => {
385104
+ try {
385105
+ const runs = deps.agentRunLedger?.listRuns?.({ limit: 100 }) ?? [];
385106
+ for (const r of runs) {
385107
+ if (r.runId === lastCostRunId) break;
385108
+ if (!r.model || r.promptTokens === 0 && r.completionTokens === 0) continue;
385109
+ costBudgetService.record({ model: r.model, promptTokens: r.promptTokens, completionTokens: r.completionTokens });
385110
+ }
385111
+ const newest = runs[0];
385112
+ if (newest) lastCostRunId = newest.runId;
385113
+ } catch {
385114
+ }
385115
+ };
385116
+ const costFeedTimer = setInterval(feedCostFromRuns, 6e4);
385117
+ if (typeof costFeedTimer.unref === "function") costFeedTimer.unref();
385118
+ feedCostFromRuns();
385119
+ let otelPushTimer;
385120
+ if (otelExporter) {
385121
+ const intervalMs = Number(process.env.OTEL_EXPORTER_OTLP_INTERVAL_MS ?? 3e4) || 3e4;
385122
+ const pushOnce = async () => {
385123
+ try {
385124
+ renderPrometheus();
385125
+ await otelExporter.push(prometheusRegistry);
385126
+ } catch {
385127
+ }
385128
+ };
385129
+ otelPushTimer = setInterval(() => {
385130
+ void pushOnce();
385131
+ }, intervalMs);
385132
+ if (typeof otelPushTimer.unref === "function") otelPushTimer.unref();
385133
+ void pushOnce();
385134
+ }
385135
+ const oncallTickTimer = setInterval(() => {
385136
+ void escalationService.tick().catch(() => {
385137
+ });
385138
+ }, 3e4);
385139
+ if (typeof oncallTickTimer.unref === "function") oncallTickTimer.unref();
385140
+ deps.onBackgroundDrivers?.({ otelPushTimer, oncallTickTimer });
384483
385141
  return {
384484
385142
  metricsLedger,
384485
385143
  goldenSignals,
@@ -384572,10 +385230,17 @@ function createObservabilityBridge(deps) {
384572
385230
  costRemoveBudget: async (params) => ({ removed: requireObs(deps).cost.removeBudget(params.id) }),
384573
385231
  // ── session recording ───────────────────────────────────────────────────
384574
385232
  recordingList: async () => requireObs(deps).recording.list(),
384575
- recordingStart: async (params) => ({
384576
- recordingId: requireObs(deps).recording.start(params.terminalId, params)
384577
- }),
385233
+ recordingStart: async (params) => {
385234
+ const ts = deps.terminalService?.();
385235
+ if (ts) return { recordingId: ts.startRecording(params.terminalId, params) };
385236
+ return { recordingId: requireObs(deps).recording.start(params.terminalId, params) };
385237
+ },
384578
385238
  recordingStop: async (params) => requireObs(deps).recording.stop(params.recordingId),
385239
+ recordingStopTerminal: async (params) => {
385240
+ const ts = deps.terminalService?.();
385241
+ const id = ts?.stopRecording(params.terminalId);
385242
+ return { recordingId: id, stopped: id !== null && id !== void 0 };
385243
+ },
384579
385244
  recordingReplay: async (params) => requireObs(deps).recording.replay(params.recordingId, params),
384580
385245
  recordingExportCast: async (params) => requireObs(deps).recording.exportCast(params.recordingId),
384581
385246
  recordingDelete: async (params) => ({ deleted: requireObs(deps).recording.delete(params.recordingId) }),
@@ -384600,9 +385265,76 @@ function createObservabilityBridge(deps) {
384600
385265
  requireObs(deps).cloud.addAccount(params);
384601
385266
  return { ok: true };
384602
385267
  },
385268
+ // ── APM (OTLP span ingestion) ───────────────────────────────────────────
385269
+ apmIngestSpans: async (params) => {
385270
+ const { ingestOtlp: ingestOtlp2 } = await Promise.resolve().then(() => (init_spanLedger(), spanLedger_exports));
385271
+ const count = ingestOtlp2(requireObs(deps).spanLedger, params.payload, params.defaultService);
385272
+ return { ingested: count };
385273
+ },
385274
+ apmSummary: async () => ({
385275
+ spans: requireObs(deps).spanLedger.size(),
385276
+ traces: requireObs(deps).spanLedger.traceIds().length,
385277
+ byService: requireObs(deps).spanLedger.serviceStats()
385278
+ }),
385279
+ // ── DEM (RUM beacon ingestion) ──────────────────────────────────────────
385280
+ demIngestBeacon: async (params) => {
385281
+ const s = requireObs(deps).rumLedger.ingestBeacon(params.payload);
385282
+ return { ingested: s !== void 0, id: s?.id };
385283
+ },
385284
+ demSummary: async () => ({
385285
+ sessions: requireObs(deps).rumLedger.size(),
385286
+ byPage: requireObs(deps).rumLedger.pageStats()
385287
+ }),
385288
+ // ── Infra (k8s collect) ─────────────────────────────────────────────────
385289
+ infraCollect: async (params) => {
385290
+ const { parseKubectlPods: parseKubectlPods2 } = await Promise.resolve().then(() => (init_infraMonitor(), infraMonitor_exports));
385291
+ const context2 = params.context ?? "default";
385292
+ let podsPayload = params.kubectlJson;
385293
+ if (podsPayload === void 0) {
385294
+ if (!params.execKubectl) throw new Error("infraCollect needs kubectlJson or an injected execKubectl");
385295
+ podsPayload = await params.execKubectl();
385296
+ }
385297
+ const text = typeof podsPayload === "string" ? podsPayload : JSON.stringify(podsPayload);
385298
+ let pods = parseKubectlPods2(text);
385299
+ if (pods.length === 0 && typeof podsPayload === "object" && podsPayload !== null) {
385300
+ const items = podsPayload.items ?? [];
385301
+ const table = ["NAMESPACE NAME READY STATUS RESTARTS AGE", ...items.map((it) => {
385302
+ const ns = it.metadata?.namespace ?? "default";
385303
+ const name = it.metadata?.name ?? "pod";
385304
+ const cs = it.status?.containerStatuses ?? [];
385305
+ const ready = `${cs.filter((c) => c.ready).length}/${cs.length || 1}`;
385306
+ const status = it.status?.phase ?? "Unknown";
385307
+ const restarts = String(cs.reduce((s, c) => s + (c.restartCount ?? 0), 0));
385308
+ return `${ns} ${name} ${ready} ${status} ${restarts} 1d`;
385309
+ })].join("\n");
385310
+ pods = parseKubectlPods2(table);
385311
+ }
385312
+ const health = requireObs(deps).infraMonitor.recordCluster(context2, pods, []);
385313
+ return { context: context2, pods: pods.length, notReady: health.notReadyPods, crashLoop: health.crashLoopPods };
385314
+ },
385315
+ infraClusters: async () => requireObs(deps).infraMonitor.clusters_(),
385316
+ infraUnhealthy: async () => requireObs(deps).infraMonitor.unhealthyInstances(),
385317
+ // ── ETW (Windows trace) ─────────────────────────────────────────────────
385318
+ etwStartTrace: async (params) => requireObs(deps).etwService.createSession(params.name, params.providers, params.outDir),
385319
+ etwStopTrace: async (params) => {
385320
+ const s = requireObs(deps).etwService.session(params.sessionId);
385321
+ if (!s) throw new Error(`etw session not found: ${params.sessionId}`);
385322
+ const { buildStopCommands: buildStopCommands2 } = await Promise.resolve().then(() => (init_etwService(), etwService_exports));
385323
+ return { commands: buildStopCommands2(s) };
385324
+ },
385325
+ etwParse: async (params) => {
385326
+ const { parseWinEventJson: parseWinEventJson2, parseWinEventText: parseWinEventText2, parseCounterJson: parseCounterJson2 } = await Promise.resolve().then(() => (init_etwService(), etwService_exports));
385327
+ if (params.format === "counter") return { counters: parseCounterJson2(params.output) };
385328
+ return { events: parseWinEventJson2(params.output) ?? parseWinEventText2(params.output) };
385329
+ },
385330
+ etwSessions: async () => requireObs(deps).etwService.sessions_(),
384603
385331
  // ── live dashboard hub ──────────────────────────────────────────────────
384604
385332
  liveDashboardState: async () => requireObs(deps).liveDashboard.lastState() ?? await requireObs(deps).dashboard.state(),
384605
- liveDashboardSubscriberCount: async () => ({ count: requireObs(deps).liveDashboard.subscriberCount() })
385333
+ liveDashboardSubscriberCount: async () => ({ count: requireObs(deps).liveDashboard.subscriberCount() }),
385334
+ /** the raw hub (used by the gateway adapter for liveDashboardSubscribe). */
385335
+ get liveDashboard() {
385336
+ return requireObs(deps).liveDashboard;
385337
+ }
384606
385338
  };
384607
385339
  }
384608
385340
 
@@ -386608,6 +387340,7 @@ async function startGyBackend() {
386608
387340
  agentRunLedger,
386609
387341
  gatewayService,
386610
387342
  resourceMonitorService,
387343
+ settingsService,
386611
387344
  setMonitorPublisher: (pub) => resourceMonitorService.setPublisher((channel, data) => {
386612
387345
  pub(channel, data);
386613
387346
  }),
@@ -386616,6 +387349,7 @@ async function startGyBackend() {
386616
387349
  });
386617
387350
  console.log(`[gybackend] Observability wired: dashboard state available (hosts=${observability.metricsLedger.hosts().length})`);
386618
387351
  agentService.setObservability(observability);
387352
+ terminalService.setSessionRecorder(observability.recording);
386619
387353
  if (settingsService.getSettings().sessionLogging?.enabled) {
386620
387354
  const logDir = import_node_path28.default.join(
386621
387355
  import_node_process2.default.env.GYSHELL_STORE_DIR || "",
@@ -387060,7 +387794,8 @@ async function startGyBackend() {
387060
387794
  // (metrics export, secrets, on-call, cost, recording, gitops, playbooks,
387061
387795
  // cloud, live dashboard) as observability:* RPC methods.
387062
387796
  observabilityBridge: createObservabilityBridge({
387063
- observability: () => observability
387797
+ observability: () => observability,
387798
+ terminalService: () => terminalService
387064
387799
  })
387065
387800
  })
387066
387801
  });