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 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
- recordingId: requireObs(deps).recording.start(params.terminalId, params)
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
- recordingId: requireObs(deps).recording.start(params.terminalId, params)
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.3",
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: all agent tools now visible in the Tools section.",
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",