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.
- package/bin/gybackend.cjs +1039 -304
- package/bin/gybackend.js +1039 -304
- 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:
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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 ??
|
|
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
|
|
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 =
|
|
378019
|
-
r.latencyP95Ms =
|
|
378020
|
-
r.latencyP99Ms =
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
378376
|
-
var
|
|
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 ??
|
|
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,
|
|
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
|
|
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 ??
|
|
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
|
|
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,
|
|
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/
|
|
378717
|
-
|
|
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
|
|
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,
|
|
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/
|
|
378936
|
-
|
|
378937
|
-
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
383194
|
+
const hash2 = (0, import_crypto21.createHash)("sha256").update(contentHash + prevHash).digest("hex");
|
|
382634
383195
|
const record2 = {
|
|
382635
|
-
id: `audit-${(0,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
383787
|
+
return (0, import_crypto23.scryptSync)(masterKey, salt, 32);
|
|
383227
383788
|
}
|
|
383228
383789
|
function defaultSalt(masterKey) {
|
|
383229
|
-
return (0,
|
|
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,
|
|
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,
|
|
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 ??
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
384385
|
+
var import_crypto27 = require("crypto");
|
|
383825
384386
|
function hashDef(def) {
|
|
383826
|
-
return (0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
});
|