neuralos 2.9.3 → 2.9.4
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 +176 -9
- package/bin/gybackend.js +176 -9
- package/package.json +2 -2
package/bin/gybackend.cjs
CHANGED
|
@@ -288865,6 +288865,32 @@ var TerminalService = class {
|
|
|
288865
288865
|
setSessionLogger(logger) {
|
|
288866
288866
|
this.sessionLogger = logger;
|
|
288867
288867
|
}
|
|
288868
|
+
/** Wire the asciinema-style session recorder (v2.9.x). Output events feed all active recordings. */
|
|
288869
|
+
setSessionRecorder(recorder) {
|
|
288870
|
+
this.sessionRecorder = recorder;
|
|
288871
|
+
}
|
|
288872
|
+
/** Live-recording state: terminalId -> recordingId. */
|
|
288873
|
+
activeRecordings = /* @__PURE__ */ new Map();
|
|
288874
|
+
sessionRecorder = null;
|
|
288875
|
+
/** Begin recording a terminal's output. Returns the recordingId. */
|
|
288876
|
+
startRecording(terminalId, opts = {}) {
|
|
288877
|
+
if (!this.sessionRecorder) throw new Error("session recorder is not wired");
|
|
288878
|
+
const id = this.sessionRecorder.start(terminalId, opts);
|
|
288879
|
+
this.activeRecordings.set(terminalId, id);
|
|
288880
|
+
return id;
|
|
288881
|
+
}
|
|
288882
|
+
/** Stop the active recording for a terminal (if any). Returns the recordingId or null. */
|
|
288883
|
+
stopRecording(terminalId) {
|
|
288884
|
+
const id = this.activeRecordings.get(terminalId);
|
|
288885
|
+
if (!id) return null;
|
|
288886
|
+
this.activeRecordings.delete(terminalId);
|
|
288887
|
+
this.sessionRecorder?.stop(id);
|
|
288888
|
+
return id;
|
|
288889
|
+
}
|
|
288890
|
+
/** True if a terminal is currently being recorded. */
|
|
288891
|
+
isRecording(terminalId) {
|
|
288892
|
+
return this.activeRecordings.has(terminalId);
|
|
288893
|
+
}
|
|
288868
288894
|
setRawEventPublisher(publisher) {
|
|
288869
288895
|
this.rawEventPublisher = publisher;
|
|
288870
288896
|
}
|
|
@@ -289326,6 +289352,13 @@ var TerminalService = class {
|
|
|
289326
289352
|
handleData(terminalId, data) {
|
|
289327
289353
|
const sanitizedData = stripInternalControlMarkers(data);
|
|
289328
289354
|
const tab = this.terminals.get(terminalId);
|
|
289355
|
+
const recordingId = this.activeRecordings.get(terminalId);
|
|
289356
|
+
if (recordingId && this.sessionRecorder && sanitizedData) {
|
|
289357
|
+
try {
|
|
289358
|
+
this.sessionRecorder.out(recordingId, sanitizedData);
|
|
289359
|
+
} catch {
|
|
289360
|
+
}
|
|
289361
|
+
}
|
|
289329
289362
|
if (this.sessionLogger && tab) {
|
|
289330
289363
|
if (!this.sessionLogStarted.has(terminalId)) {
|
|
289331
289364
|
this.sessionLogStarted.add(terminalId);
|
|
@@ -365996,6 +366029,29 @@ var WebSocketGatewayAdapter = class {
|
|
|
365996
366029
|
`${request.method} is not available on this websocket gateway.`
|
|
365997
366030
|
);
|
|
365998
366031
|
}
|
|
366032
|
+
if (request.method === "observability:liveDashboardSubscribe") {
|
|
366033
|
+
const hub = bridge.liveDashboard;
|
|
366034
|
+
const filter = params.filter;
|
|
366035
|
+
const subId = `ws-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
366036
|
+
await hub.subscribe({
|
|
366037
|
+
id: subId,
|
|
366038
|
+
filter,
|
|
366039
|
+
send: (payload) => {
|
|
366040
|
+
this.safeSocketSend(socket, {
|
|
366041
|
+
type: "gateway:event",
|
|
366042
|
+
event: "observability:dashboard",
|
|
366043
|
+
data: payload
|
|
366044
|
+
});
|
|
366045
|
+
}
|
|
366046
|
+
});
|
|
366047
|
+
const sock = socket;
|
|
366048
|
+
const onClose = () => {
|
|
366049
|
+
hub.unsubscribe(subId);
|
|
366050
|
+
sock.off?.("close", onClose);
|
|
366051
|
+
};
|
|
366052
|
+
sock.on?.("close", onClose);
|
|
366053
|
+
return { subscribed: true, subscriberId: subId };
|
|
366054
|
+
}
|
|
365999
366055
|
const fnName = request.method.slice("observability:".length);
|
|
366000
366056
|
const fn = bridge[fnName];
|
|
366001
366057
|
if (typeof fn !== "function") {
|
|
@@ -384456,15 +384512,73 @@ function createObservability(deps) {
|
|
|
384456
384512
|
const escalationService = new EscalationService({ channels: deps.pagingChannels ?? [] });
|
|
384457
384513
|
const costBudgetService = new CostBudgetService({ prices: deps.modelPrices });
|
|
384458
384514
|
const sessionRecorder = new SessionRecorder({});
|
|
384515
|
+
const defaultGitopsReadLive = () => {
|
|
384516
|
+
const out = [];
|
|
384517
|
+
try {
|
|
384518
|
+
const settings = deps.settingsService?.getSettings?.();
|
|
384519
|
+
const conns = settings?.connections ?? {};
|
|
384520
|
+
for (const [kind, list] of Object.entries(conns)) {
|
|
384521
|
+
if (!Array.isArray(list)) continue;
|
|
384522
|
+
for (const c of list) {
|
|
384523
|
+
const id = c?.id ?? c?.name;
|
|
384524
|
+
if (id) out.push({ id: `connection:${kind}:${String(id)}`, kind: "connection", spec: c });
|
|
384525
|
+
}
|
|
384526
|
+
}
|
|
384527
|
+
} catch {
|
|
384528
|
+
}
|
|
384529
|
+
try {
|
|
384530
|
+
const am = deps.automationManager;
|
|
384531
|
+
const groups = am?.listGroups?.() ?? [];
|
|
384532
|
+
for (const g of groups) {
|
|
384533
|
+
const id = g?.id ?? g?.name;
|
|
384534
|
+
if (id) out.push({ id: `group:${String(id)}`, kind: "group", spec: g });
|
|
384535
|
+
}
|
|
384536
|
+
const scripts = am?.listScripts?.() ?? [];
|
|
384537
|
+
for (const s of scripts) {
|
|
384538
|
+
const id = s?.id ?? s?.name;
|
|
384539
|
+
if (id) out.push({ id: `script:${String(id)}`, kind: "script", spec: s });
|
|
384540
|
+
}
|
|
384541
|
+
const playbooks = am?.listPlaybooks?.() ?? [];
|
|
384542
|
+
for (const p of playbooks) {
|
|
384543
|
+
const id = p?.id ?? p?.name;
|
|
384544
|
+
if (id) out.push({ id: `playbook:${String(id)}`, kind: "playbook", spec: p });
|
|
384545
|
+
}
|
|
384546
|
+
const templates = am?.listTemplates?.() ?? [];
|
|
384547
|
+
for (const t of templates) {
|
|
384548
|
+
const id = t?.id ?? t?.name;
|
|
384549
|
+
if (id) out.push({ id: `template:${String(id)}`, kind: "template", spec: t });
|
|
384550
|
+
}
|
|
384551
|
+
const tasks = am?.listScheduledTasks?.() ?? [];
|
|
384552
|
+
for (const t of tasks) {
|
|
384553
|
+
const id = t?.id ?? t?.name;
|
|
384554
|
+
if (id) out.push({ id: `scheduledTask:${String(id)}`, kind: "scheduledTask", spec: t });
|
|
384555
|
+
}
|
|
384556
|
+
} catch {
|
|
384557
|
+
}
|
|
384558
|
+
return out;
|
|
384559
|
+
};
|
|
384459
384560
|
const gitopsService = new GitOpsService({
|
|
384460
|
-
readLive: deps.gitopsReadLive ??
|
|
384561
|
+
readLive: deps.gitopsReadLive ?? defaultGitopsReadLive,
|
|
384461
384562
|
applyEntity: deps.gitopsApplyEntity
|
|
384462
384563
|
});
|
|
384463
384564
|
const playbookVersioning = new PlaybookVersioning({});
|
|
384565
|
+
const runCliJson = async (bin, args) => {
|
|
384566
|
+
const { execFile: execFile2 } = await import("node:child_process");
|
|
384567
|
+
return new Promise((resolve2, reject) => {
|
|
384568
|
+
execFile2(bin, args, { timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
|
|
384569
|
+
if (err) return reject(err);
|
|
384570
|
+
try {
|
|
384571
|
+
resolve2(JSON.parse(stdout));
|
|
384572
|
+
} catch (e) {
|
|
384573
|
+
reject(e instanceof Error ? e : new Error("invalid JSON"));
|
|
384574
|
+
}
|
|
384575
|
+
});
|
|
384576
|
+
});
|
|
384577
|
+
};
|
|
384464
384578
|
const cloudInventory = new CloudInventory({
|
|
384465
|
-
fetchAwsInstances: deps.fetchAwsInstances,
|
|
384466
|
-
fetchGcpInstances: deps.fetchGcpInstances,
|
|
384467
|
-
fetchAzureVms: deps.fetchAzureVms
|
|
384579
|
+
fetchAwsInstances: deps.fetchAwsInstances ?? (async () => runCliJson("aws", ["ec2", "describe-instances", "--output", "json"])),
|
|
384580
|
+
fetchGcpInstances: deps.fetchGcpInstances ?? (async () => runCliJson("gcloud", ["compute", "instances", "list", "--format", "json"])),
|
|
384581
|
+
fetchAzureVms: deps.fetchAzureVms ?? (async () => runCliJson("az", ["vm", "list", "--show-details", "--output", "json"]))
|
|
384468
384582
|
});
|
|
384469
384583
|
const liveDashboardHub = new LiveDashboardHub({
|
|
384470
384584
|
getState: () => dashboard.state()
|
|
@@ -384480,6 +384594,45 @@ function createObservability(deps) {
|
|
|
384480
384594
|
} catch {
|
|
384481
384595
|
}
|
|
384482
384596
|
});
|
|
384597
|
+
let lastCostRunId = null;
|
|
384598
|
+
const feedCostFromRuns = () => {
|
|
384599
|
+
try {
|
|
384600
|
+
const runs = deps.agentRunLedger?.listRuns?.({ limit: 100 }) ?? [];
|
|
384601
|
+
for (const r of runs) {
|
|
384602
|
+
if (r.runId === lastCostRunId) break;
|
|
384603
|
+
if (!r.model || r.promptTokens === 0 && r.completionTokens === 0) continue;
|
|
384604
|
+
costBudgetService.record({ model: r.model, promptTokens: r.promptTokens, completionTokens: r.completionTokens });
|
|
384605
|
+
}
|
|
384606
|
+
const newest = runs[0];
|
|
384607
|
+
if (newest) lastCostRunId = newest.runId;
|
|
384608
|
+
} catch {
|
|
384609
|
+
}
|
|
384610
|
+
};
|
|
384611
|
+
const costFeedTimer = setInterval(feedCostFromRuns, 6e4);
|
|
384612
|
+
if (typeof costFeedTimer.unref === "function") costFeedTimer.unref();
|
|
384613
|
+
feedCostFromRuns();
|
|
384614
|
+
let otelPushTimer;
|
|
384615
|
+
if (otelExporter) {
|
|
384616
|
+
const intervalMs = Number(process.env.OTEL_EXPORTER_OTLP_INTERVAL_MS ?? 3e4) || 3e4;
|
|
384617
|
+
const pushOnce = async () => {
|
|
384618
|
+
try {
|
|
384619
|
+
renderPrometheus();
|
|
384620
|
+
await otelExporter.push(prometheusRegistry);
|
|
384621
|
+
} catch {
|
|
384622
|
+
}
|
|
384623
|
+
};
|
|
384624
|
+
otelPushTimer = setInterval(() => {
|
|
384625
|
+
void pushOnce();
|
|
384626
|
+
}, intervalMs);
|
|
384627
|
+
if (typeof otelPushTimer.unref === "function") otelPushTimer.unref();
|
|
384628
|
+
void pushOnce();
|
|
384629
|
+
}
|
|
384630
|
+
const oncallTickTimer = setInterval(() => {
|
|
384631
|
+
void escalationService.tick().catch(() => {
|
|
384632
|
+
});
|
|
384633
|
+
}, 3e4);
|
|
384634
|
+
if (typeof oncallTickTimer.unref === "function") oncallTickTimer.unref();
|
|
384635
|
+
deps.onBackgroundDrivers?.({ otelPushTimer, oncallTickTimer });
|
|
384483
384636
|
return {
|
|
384484
384637
|
metricsLedger,
|
|
384485
384638
|
goldenSignals,
|
|
@@ -384572,10 +384725,17 @@ function createObservabilityBridge(deps) {
|
|
|
384572
384725
|
costRemoveBudget: async (params) => ({ removed: requireObs(deps).cost.removeBudget(params.id) }),
|
|
384573
384726
|
// ── session recording ───────────────────────────────────────────────────
|
|
384574
384727
|
recordingList: async () => requireObs(deps).recording.list(),
|
|
384575
|
-
recordingStart: async (params) =>
|
|
384576
|
-
|
|
384577
|
-
|
|
384728
|
+
recordingStart: async (params) => {
|
|
384729
|
+
const ts = deps.terminalService?.();
|
|
384730
|
+
if (ts) return { recordingId: ts.startRecording(params.terminalId, params) };
|
|
384731
|
+
return { recordingId: requireObs(deps).recording.start(params.terminalId, params) };
|
|
384732
|
+
},
|
|
384578
384733
|
recordingStop: async (params) => requireObs(deps).recording.stop(params.recordingId),
|
|
384734
|
+
recordingStopTerminal: async (params) => {
|
|
384735
|
+
const ts = deps.terminalService?.();
|
|
384736
|
+
const id = ts?.stopRecording(params.terminalId);
|
|
384737
|
+
return { recordingId: id, stopped: id !== null && id !== void 0 };
|
|
384738
|
+
},
|
|
384579
384739
|
recordingReplay: async (params) => requireObs(deps).recording.replay(params.recordingId, params),
|
|
384580
384740
|
recordingExportCast: async (params) => requireObs(deps).recording.exportCast(params.recordingId),
|
|
384581
384741
|
recordingDelete: async (params) => ({ deleted: requireObs(deps).recording.delete(params.recordingId) }),
|
|
@@ -384602,7 +384762,11 @@ function createObservabilityBridge(deps) {
|
|
|
384602
384762
|
},
|
|
384603
384763
|
// ── live dashboard hub ──────────────────────────────────────────────────
|
|
384604
384764
|
liveDashboardState: async () => requireObs(deps).liveDashboard.lastState() ?? await requireObs(deps).dashboard.state(),
|
|
384605
|
-
liveDashboardSubscriberCount: async () => ({ count: requireObs(deps).liveDashboard.subscriberCount() })
|
|
384765
|
+
liveDashboardSubscriberCount: async () => ({ count: requireObs(deps).liveDashboard.subscriberCount() }),
|
|
384766
|
+
/** the raw hub (used by the gateway adapter for liveDashboardSubscribe). */
|
|
384767
|
+
get liveDashboard() {
|
|
384768
|
+
return requireObs(deps).liveDashboard;
|
|
384769
|
+
}
|
|
384606
384770
|
};
|
|
384607
384771
|
}
|
|
384608
384772
|
|
|
@@ -386608,6 +386772,7 @@ async function startGyBackend() {
|
|
|
386608
386772
|
agentRunLedger,
|
|
386609
386773
|
gatewayService,
|
|
386610
386774
|
resourceMonitorService,
|
|
386775
|
+
settingsService,
|
|
386611
386776
|
setMonitorPublisher: (pub) => resourceMonitorService.setPublisher((channel, data) => {
|
|
386612
386777
|
pub(channel, data);
|
|
386613
386778
|
}),
|
|
@@ -386616,6 +386781,7 @@ async function startGyBackend() {
|
|
|
386616
386781
|
});
|
|
386617
386782
|
console.log(`[gybackend] Observability wired: dashboard state available (hosts=${observability.metricsLedger.hosts().length})`);
|
|
386618
386783
|
agentService.setObservability(observability);
|
|
386784
|
+
terminalService.setSessionRecorder(observability.recording);
|
|
386619
386785
|
if (settingsService.getSettings().sessionLogging?.enabled) {
|
|
386620
386786
|
const logDir = import_node_path28.default.join(
|
|
386621
386787
|
import_node_process2.default.env.GYSHELL_STORE_DIR || "",
|
|
@@ -387060,7 +387226,8 @@ async function startGyBackend() {
|
|
|
387060
387226
|
// (metrics export, secrets, on-call, cost, recording, gitops, playbooks,
|
|
387061
387227
|
// cloud, live dashboard) as observability:* RPC methods.
|
|
387062
387228
|
observabilityBridge: createObservabilityBridge({
|
|
387063
|
-
observability: () => observability
|
|
387229
|
+
observability: () => observability,
|
|
387230
|
+
terminalService: () => terminalService
|
|
387064
387231
|
})
|
|
387065
387232
|
})
|
|
387066
387233
|
});
|
package/bin/gybackend.js
CHANGED
|
@@ -288865,6 +288865,32 @@ var TerminalService = class {
|
|
|
288865
288865
|
setSessionLogger(logger) {
|
|
288866
288866
|
this.sessionLogger = logger;
|
|
288867
288867
|
}
|
|
288868
|
+
/** Wire the asciinema-style session recorder (v2.9.x). Output events feed all active recordings. */
|
|
288869
|
+
setSessionRecorder(recorder) {
|
|
288870
|
+
this.sessionRecorder = recorder;
|
|
288871
|
+
}
|
|
288872
|
+
/** Live-recording state: terminalId -> recordingId. */
|
|
288873
|
+
activeRecordings = /* @__PURE__ */ new Map();
|
|
288874
|
+
sessionRecorder = null;
|
|
288875
|
+
/** Begin recording a terminal's output. Returns the recordingId. */
|
|
288876
|
+
startRecording(terminalId, opts = {}) {
|
|
288877
|
+
if (!this.sessionRecorder) throw new Error("session recorder is not wired");
|
|
288878
|
+
const id = this.sessionRecorder.start(terminalId, opts);
|
|
288879
|
+
this.activeRecordings.set(terminalId, id);
|
|
288880
|
+
return id;
|
|
288881
|
+
}
|
|
288882
|
+
/** Stop the active recording for a terminal (if any). Returns the recordingId or null. */
|
|
288883
|
+
stopRecording(terminalId) {
|
|
288884
|
+
const id = this.activeRecordings.get(terminalId);
|
|
288885
|
+
if (!id) return null;
|
|
288886
|
+
this.activeRecordings.delete(terminalId);
|
|
288887
|
+
this.sessionRecorder?.stop(id);
|
|
288888
|
+
return id;
|
|
288889
|
+
}
|
|
288890
|
+
/** True if a terminal is currently being recorded. */
|
|
288891
|
+
isRecording(terminalId) {
|
|
288892
|
+
return this.activeRecordings.has(terminalId);
|
|
288893
|
+
}
|
|
288868
288894
|
setRawEventPublisher(publisher) {
|
|
288869
288895
|
this.rawEventPublisher = publisher;
|
|
288870
288896
|
}
|
|
@@ -289326,6 +289352,13 @@ var TerminalService = class {
|
|
|
289326
289352
|
handleData(terminalId, data) {
|
|
289327
289353
|
const sanitizedData = stripInternalControlMarkers(data);
|
|
289328
289354
|
const tab = this.terminals.get(terminalId);
|
|
289355
|
+
const recordingId = this.activeRecordings.get(terminalId);
|
|
289356
|
+
if (recordingId && this.sessionRecorder && sanitizedData) {
|
|
289357
|
+
try {
|
|
289358
|
+
this.sessionRecorder.out(recordingId, sanitizedData);
|
|
289359
|
+
} catch {
|
|
289360
|
+
}
|
|
289361
|
+
}
|
|
289329
289362
|
if (this.sessionLogger && tab) {
|
|
289330
289363
|
if (!this.sessionLogStarted.has(terminalId)) {
|
|
289331
289364
|
this.sessionLogStarted.add(terminalId);
|
|
@@ -365996,6 +366029,29 @@ var WebSocketGatewayAdapter = class {
|
|
|
365996
366029
|
`${request.method} is not available on this websocket gateway.`
|
|
365997
366030
|
);
|
|
365998
366031
|
}
|
|
366032
|
+
if (request.method === "observability:liveDashboardSubscribe") {
|
|
366033
|
+
const hub = bridge.liveDashboard;
|
|
366034
|
+
const filter = params.filter;
|
|
366035
|
+
const subId = `ws-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
366036
|
+
await hub.subscribe({
|
|
366037
|
+
id: subId,
|
|
366038
|
+
filter,
|
|
366039
|
+
send: (payload) => {
|
|
366040
|
+
this.safeSocketSend(socket, {
|
|
366041
|
+
type: "gateway:event",
|
|
366042
|
+
event: "observability:dashboard",
|
|
366043
|
+
data: payload
|
|
366044
|
+
});
|
|
366045
|
+
}
|
|
366046
|
+
});
|
|
366047
|
+
const sock = socket;
|
|
366048
|
+
const onClose = () => {
|
|
366049
|
+
hub.unsubscribe(subId);
|
|
366050
|
+
sock.off?.("close", onClose);
|
|
366051
|
+
};
|
|
366052
|
+
sock.on?.("close", onClose);
|
|
366053
|
+
return { subscribed: true, subscriberId: subId };
|
|
366054
|
+
}
|
|
365999
366055
|
const fnName = request.method.slice("observability:".length);
|
|
366000
366056
|
const fn = bridge[fnName];
|
|
366001
366057
|
if (typeof fn !== "function") {
|
|
@@ -384456,15 +384512,73 @@ function createObservability(deps) {
|
|
|
384456
384512
|
const escalationService = new EscalationService({ channels: deps.pagingChannels ?? [] });
|
|
384457
384513
|
const costBudgetService = new CostBudgetService({ prices: deps.modelPrices });
|
|
384458
384514
|
const sessionRecorder = new SessionRecorder({});
|
|
384515
|
+
const defaultGitopsReadLive = () => {
|
|
384516
|
+
const out = [];
|
|
384517
|
+
try {
|
|
384518
|
+
const settings = deps.settingsService?.getSettings?.();
|
|
384519
|
+
const conns = settings?.connections ?? {};
|
|
384520
|
+
for (const [kind, list] of Object.entries(conns)) {
|
|
384521
|
+
if (!Array.isArray(list)) continue;
|
|
384522
|
+
for (const c of list) {
|
|
384523
|
+
const id = c?.id ?? c?.name;
|
|
384524
|
+
if (id) out.push({ id: `connection:${kind}:${String(id)}`, kind: "connection", spec: c });
|
|
384525
|
+
}
|
|
384526
|
+
}
|
|
384527
|
+
} catch {
|
|
384528
|
+
}
|
|
384529
|
+
try {
|
|
384530
|
+
const am = deps.automationManager;
|
|
384531
|
+
const groups = am?.listGroups?.() ?? [];
|
|
384532
|
+
for (const g of groups) {
|
|
384533
|
+
const id = g?.id ?? g?.name;
|
|
384534
|
+
if (id) out.push({ id: `group:${String(id)}`, kind: "group", spec: g });
|
|
384535
|
+
}
|
|
384536
|
+
const scripts = am?.listScripts?.() ?? [];
|
|
384537
|
+
for (const s of scripts) {
|
|
384538
|
+
const id = s?.id ?? s?.name;
|
|
384539
|
+
if (id) out.push({ id: `script:${String(id)}`, kind: "script", spec: s });
|
|
384540
|
+
}
|
|
384541
|
+
const playbooks = am?.listPlaybooks?.() ?? [];
|
|
384542
|
+
for (const p of playbooks) {
|
|
384543
|
+
const id = p?.id ?? p?.name;
|
|
384544
|
+
if (id) out.push({ id: `playbook:${String(id)}`, kind: "playbook", spec: p });
|
|
384545
|
+
}
|
|
384546
|
+
const templates = am?.listTemplates?.() ?? [];
|
|
384547
|
+
for (const t of templates) {
|
|
384548
|
+
const id = t?.id ?? t?.name;
|
|
384549
|
+
if (id) out.push({ id: `template:${String(id)}`, kind: "template", spec: t });
|
|
384550
|
+
}
|
|
384551
|
+
const tasks = am?.listScheduledTasks?.() ?? [];
|
|
384552
|
+
for (const t of tasks) {
|
|
384553
|
+
const id = t?.id ?? t?.name;
|
|
384554
|
+
if (id) out.push({ id: `scheduledTask:${String(id)}`, kind: "scheduledTask", spec: t });
|
|
384555
|
+
}
|
|
384556
|
+
} catch {
|
|
384557
|
+
}
|
|
384558
|
+
return out;
|
|
384559
|
+
};
|
|
384459
384560
|
const gitopsService = new GitOpsService({
|
|
384460
|
-
readLive: deps.gitopsReadLive ??
|
|
384561
|
+
readLive: deps.gitopsReadLive ?? defaultGitopsReadLive,
|
|
384461
384562
|
applyEntity: deps.gitopsApplyEntity
|
|
384462
384563
|
});
|
|
384463
384564
|
const playbookVersioning = new PlaybookVersioning({});
|
|
384565
|
+
const runCliJson = async (bin, args) => {
|
|
384566
|
+
const { execFile: execFile2 } = await import("node:child_process");
|
|
384567
|
+
return new Promise((resolve2, reject) => {
|
|
384568
|
+
execFile2(bin, args, { timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }, (err, stdout) => {
|
|
384569
|
+
if (err) return reject(err);
|
|
384570
|
+
try {
|
|
384571
|
+
resolve2(JSON.parse(stdout));
|
|
384572
|
+
} catch (e) {
|
|
384573
|
+
reject(e instanceof Error ? e : new Error("invalid JSON"));
|
|
384574
|
+
}
|
|
384575
|
+
});
|
|
384576
|
+
});
|
|
384577
|
+
};
|
|
384464
384578
|
const cloudInventory = new CloudInventory({
|
|
384465
|
-
fetchAwsInstances: deps.fetchAwsInstances,
|
|
384466
|
-
fetchGcpInstances: deps.fetchGcpInstances,
|
|
384467
|
-
fetchAzureVms: deps.fetchAzureVms
|
|
384579
|
+
fetchAwsInstances: deps.fetchAwsInstances ?? (async () => runCliJson("aws", ["ec2", "describe-instances", "--output", "json"])),
|
|
384580
|
+
fetchGcpInstances: deps.fetchGcpInstances ?? (async () => runCliJson("gcloud", ["compute", "instances", "list", "--format", "json"])),
|
|
384581
|
+
fetchAzureVms: deps.fetchAzureVms ?? (async () => runCliJson("az", ["vm", "list", "--show-details", "--output", "json"]))
|
|
384468
384582
|
});
|
|
384469
384583
|
const liveDashboardHub = new LiveDashboardHub({
|
|
384470
384584
|
getState: () => dashboard.state()
|
|
@@ -384480,6 +384594,45 @@ function createObservability(deps) {
|
|
|
384480
384594
|
} catch {
|
|
384481
384595
|
}
|
|
384482
384596
|
});
|
|
384597
|
+
let lastCostRunId = null;
|
|
384598
|
+
const feedCostFromRuns = () => {
|
|
384599
|
+
try {
|
|
384600
|
+
const runs = deps.agentRunLedger?.listRuns?.({ limit: 100 }) ?? [];
|
|
384601
|
+
for (const r of runs) {
|
|
384602
|
+
if (r.runId === lastCostRunId) break;
|
|
384603
|
+
if (!r.model || r.promptTokens === 0 && r.completionTokens === 0) continue;
|
|
384604
|
+
costBudgetService.record({ model: r.model, promptTokens: r.promptTokens, completionTokens: r.completionTokens });
|
|
384605
|
+
}
|
|
384606
|
+
const newest = runs[0];
|
|
384607
|
+
if (newest) lastCostRunId = newest.runId;
|
|
384608
|
+
} catch {
|
|
384609
|
+
}
|
|
384610
|
+
};
|
|
384611
|
+
const costFeedTimer = setInterval(feedCostFromRuns, 6e4);
|
|
384612
|
+
if (typeof costFeedTimer.unref === "function") costFeedTimer.unref();
|
|
384613
|
+
feedCostFromRuns();
|
|
384614
|
+
let otelPushTimer;
|
|
384615
|
+
if (otelExporter) {
|
|
384616
|
+
const intervalMs = Number(process.env.OTEL_EXPORTER_OTLP_INTERVAL_MS ?? 3e4) || 3e4;
|
|
384617
|
+
const pushOnce = async () => {
|
|
384618
|
+
try {
|
|
384619
|
+
renderPrometheus();
|
|
384620
|
+
await otelExporter.push(prometheusRegistry);
|
|
384621
|
+
} catch {
|
|
384622
|
+
}
|
|
384623
|
+
};
|
|
384624
|
+
otelPushTimer = setInterval(() => {
|
|
384625
|
+
void pushOnce();
|
|
384626
|
+
}, intervalMs);
|
|
384627
|
+
if (typeof otelPushTimer.unref === "function") otelPushTimer.unref();
|
|
384628
|
+
void pushOnce();
|
|
384629
|
+
}
|
|
384630
|
+
const oncallTickTimer = setInterval(() => {
|
|
384631
|
+
void escalationService.tick().catch(() => {
|
|
384632
|
+
});
|
|
384633
|
+
}, 3e4);
|
|
384634
|
+
if (typeof oncallTickTimer.unref === "function") oncallTickTimer.unref();
|
|
384635
|
+
deps.onBackgroundDrivers?.({ otelPushTimer, oncallTickTimer });
|
|
384483
384636
|
return {
|
|
384484
384637
|
metricsLedger,
|
|
384485
384638
|
goldenSignals,
|
|
@@ -384572,10 +384725,17 @@ function createObservabilityBridge(deps) {
|
|
|
384572
384725
|
costRemoveBudget: async (params) => ({ removed: requireObs(deps).cost.removeBudget(params.id) }),
|
|
384573
384726
|
// ── session recording ───────────────────────────────────────────────────
|
|
384574
384727
|
recordingList: async () => requireObs(deps).recording.list(),
|
|
384575
|
-
recordingStart: async (params) =>
|
|
384576
|
-
|
|
384577
|
-
|
|
384728
|
+
recordingStart: async (params) => {
|
|
384729
|
+
const ts = deps.terminalService?.();
|
|
384730
|
+
if (ts) return { recordingId: ts.startRecording(params.terminalId, params) };
|
|
384731
|
+
return { recordingId: requireObs(deps).recording.start(params.terminalId, params) };
|
|
384732
|
+
},
|
|
384578
384733
|
recordingStop: async (params) => requireObs(deps).recording.stop(params.recordingId),
|
|
384734
|
+
recordingStopTerminal: async (params) => {
|
|
384735
|
+
const ts = deps.terminalService?.();
|
|
384736
|
+
const id = ts?.stopRecording(params.terminalId);
|
|
384737
|
+
return { recordingId: id, stopped: id !== null && id !== void 0 };
|
|
384738
|
+
},
|
|
384579
384739
|
recordingReplay: async (params) => requireObs(deps).recording.replay(params.recordingId, params),
|
|
384580
384740
|
recordingExportCast: async (params) => requireObs(deps).recording.exportCast(params.recordingId),
|
|
384581
384741
|
recordingDelete: async (params) => ({ deleted: requireObs(deps).recording.delete(params.recordingId) }),
|
|
@@ -384602,7 +384762,11 @@ function createObservabilityBridge(deps) {
|
|
|
384602
384762
|
},
|
|
384603
384763
|
// ── live dashboard hub ──────────────────────────────────────────────────
|
|
384604
384764
|
liveDashboardState: async () => requireObs(deps).liveDashboard.lastState() ?? await requireObs(deps).dashboard.state(),
|
|
384605
|
-
liveDashboardSubscriberCount: async () => ({ count: requireObs(deps).liveDashboard.subscriberCount() })
|
|
384765
|
+
liveDashboardSubscriberCount: async () => ({ count: requireObs(deps).liveDashboard.subscriberCount() }),
|
|
384766
|
+
/** the raw hub (used by the gateway adapter for liveDashboardSubscribe). */
|
|
384767
|
+
get liveDashboard() {
|
|
384768
|
+
return requireObs(deps).liveDashboard;
|
|
384769
|
+
}
|
|
384606
384770
|
};
|
|
384607
384771
|
}
|
|
384608
384772
|
|
|
@@ -386608,6 +386772,7 @@ async function startGyBackend() {
|
|
|
386608
386772
|
agentRunLedger,
|
|
386609
386773
|
gatewayService,
|
|
386610
386774
|
resourceMonitorService,
|
|
386775
|
+
settingsService,
|
|
386611
386776
|
setMonitorPublisher: (pub) => resourceMonitorService.setPublisher((channel, data) => {
|
|
386612
386777
|
pub(channel, data);
|
|
386613
386778
|
}),
|
|
@@ -386616,6 +386781,7 @@ async function startGyBackend() {
|
|
|
386616
386781
|
});
|
|
386617
386782
|
console.log(`[gybackend] Observability wired: dashboard state available (hosts=${observability.metricsLedger.hosts().length})`);
|
|
386618
386783
|
agentService.setObservability(observability);
|
|
386784
|
+
terminalService.setSessionRecorder(observability.recording);
|
|
386619
386785
|
if (settingsService.getSettings().sessionLogging?.enabled) {
|
|
386620
386786
|
const logDir = import_node_path28.default.join(
|
|
386621
386787
|
import_node_process2.default.env.GYSHELL_STORE_DIR || "",
|
|
@@ -387060,7 +387226,8 @@ async function startGyBackend() {
|
|
|
387060
387226
|
// (metrics export, secrets, on-call, cost, recording, gitops, playbooks,
|
|
387061
387227
|
// cloud, live dashboard) as observability:* RPC methods.
|
|
387062
387228
|
observabilityBridge: createObservabilityBridge({
|
|
387063
|
-
observability: () => observability
|
|
387229
|
+
observability: () => observability,
|
|
387230
|
+
terminalService: () => terminalService
|
|
387064
387231
|
})
|
|
387065
387232
|
})
|
|
387066
387233
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neuralos",
|
|
3
|
-
"version": "2.9.
|
|
4
|
-
"description": "neuralOS \u2014 the headless AI-native backend for RTerm. run RTerm-as-a-service (AI agent, SSH/WinRM/Serial/local terminals, fleet orchestration, advanced automation, SRE observability, Netdata, AWS APerf, plugin system, governance/audit, Prometheus/OTel metrics export, secrets vault, on-call paging, AI cost budgets, GitOps, cloud inventory). The RTerm desktop app stays RTerm; neuralOS is the standalone backend daemon. Dual-published as rterm-backend. v2.9.
|
|
3
|
+
"version": "2.9.4",
|
|
4
|
+
"description": "neuralOS \u2014 the headless AI-native backend for RTerm. run RTerm-as-a-service (AI agent, SSH/WinRM/Serial/local terminals, fleet orchestration, advanced automation, SRE observability, Netdata, AWS APerf, plugin system, governance/audit, Prometheus/OTel metrics export, secrets vault, on-call paging, AI cost budgets, GitOps, cloud inventory). The RTerm desktop app stays RTerm; neuralOS is the standalone backend daemon. Dual-published as rterm-backend. v2.9.4: the 9 capabilities actually work out of the box (no placeholders).",
|
|
5
5
|
"main": "bin/gybackend.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"neuralos": "bin/gybackend.js",
|