neuralos 3.2.13 → 3.2.15

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
@@ -367973,6 +367973,8 @@ var AgentService_v2 = class {
367973
367973
  /** sessionId → ledger run id for the in-flight run (set for every run,
367974
367974
  * unlike activeAgentRunIdsBySession which requires caller metadata). */
367975
367975
  ledgerRunIdsBySession = /* @__PURE__ */ new Map();
367976
+ /** v3.2.13: gateway runId per session — used to correlate LLM trace spans. */
367977
+ currentRunIdBySession = /* @__PURE__ */ new Map();
367976
367978
  constructor(terminalService, commandPolicyService, mcpToolService, skillService, memoryService, uiHistoryService, chatHistoryService, imageAttachmentService, fileTransferService) {
367977
367979
  this.terminalService = terminalService;
367978
367980
  this.chatHistoryService = chatHistoryService;
@@ -368619,6 +368621,7 @@ var AgentService_v2 = class {
368619
368621
  let partialText = "";
368620
368622
  let reasoningContent = "";
368621
368623
  let debugRawChunks = [];
368624
+ const llmTraceStart = Date.now();
368622
368625
  const fullResponse = await invokeWithRetryAndSanitizedInput({
368623
368626
  helpers: this.helpers,
368624
368627
  messages: modelInputMessages,
@@ -368816,6 +368819,26 @@ var AgentService_v2 = class {
368816
368819
  debugRawChunks
368817
368820
  );
368818
368821
  let currentTokens = state.token_state.current_tokens;
368822
+ try {
368823
+ const traceModelName = getStreamedResponseModelName(fullResponse, debugRawChunks) || baseModel?.modelName || baseModel?.model || "unknown";
368824
+ const breakdown = usageInfo ? extractUsageTokenBreakdown(usageInfo.usage) : void 0;
368825
+ this.observability?.llmTrace?.record(
368826
+ {
368827
+ runId: state.runId,
368828
+ sessionId,
368829
+ operation: shouldUseThinkingModelOnThisPass ? "thinking" : "chat",
368830
+ model: traceModelName
368831
+ },
368832
+ {
368833
+ durationMs: Date.now() - llmTraceStart,
368834
+ ...breakdown?.promptTokens !== void 0 ? { inputTokens: breakdown.promptTokens } : {},
368835
+ ...breakdown?.completionTokens !== void 0 ? { outputTokens: breakdown.completionTokens } : {},
368836
+ ...usageInfo ? { totalTokens: usageInfo.totalTokens } : {},
368837
+ finishReason: describeStreamedResponseFinish(fullResponse, debugRawChunks) || void 0
368838
+ }
368839
+ );
368840
+ } catch {
368841
+ }
368819
368842
  if (usageInfo) {
368820
368843
  currentTokens = usageInfo.totalTokens;
368821
368844
  const modelName = getStreamedResponseModelName(fullResponse, debugRawChunks) || baseModel?.modelName || baseModel?.model || "unknown";
@@ -370899,60 +370922,99 @@ ${reminder}`;
370899
370922
  const processedMessages = buildDynamicRequestHistory(messages, {
370900
370923
  modelSupportsImage: sessionBinding.readFileSupport.image
370901
370924
  });
370925
+ const llmTraceStart = Date.now();
370926
+ const traceResult = (error40) => {
370927
+ try {
370928
+ this.observability?.llmTrace?.record(
370929
+ {
370930
+ runId: this.currentRunIdBySession.get(sessionId),
370931
+ sessionId,
370932
+ operation: `audit.${decisionName}`,
370933
+ model: model?.modelName || model?.model || "unknown"
370934
+ },
370935
+ {
370936
+ durationMs: Date.now() - llmTraceStart,
370937
+ ...error40 ? { error: error40 } : {}
370938
+ }
370939
+ );
370940
+ } catch {
370941
+ }
370942
+ };
370902
370943
  if (sessionBinding.thinkingModelSupportsStructuredOutput) {
370903
370944
  const structuredModel = model.withStructuredOutput(schema2, {
370904
370945
  method: "jsonSchema"
370905
370946
  });
370906
- return await invokeWithRetryAndSanitizedInput({
370907
- helpers: this.helpers,
370908
- messages: processedMessages,
370909
- modelSupportsImage: sessionBinding.readFileSupport.image,
370910
- signal,
370911
- operation: async (sanitizedMessages) => {
370912
- return await structuredModel.invoke(sanitizedMessages, {
370913
- signal
370914
- });
370915
- },
370916
- onRetry: (attempt) => {
370917
- console.log(
370918
- `[AgentService_v2] Retrying thinking model decision for ${decisionName} (attempt ${attempt + 1})...`
370919
- );
370920
- },
370921
- maxRetries: MODEL_RETRY_MAX,
370922
- delaysMs: MODEL_RETRY_DELAYS_MS
370923
- });
370947
+ try {
370948
+ const result = await invokeWithRetryAndSanitizedInput({
370949
+ helpers: this.helpers,
370950
+ messages: processedMessages,
370951
+ modelSupportsImage: sessionBinding.readFileSupport.image,
370952
+ signal,
370953
+ operation: async (sanitizedMessages) => {
370954
+ return await structuredModel.invoke(sanitizedMessages, {
370955
+ signal
370956
+ });
370957
+ },
370958
+ onRetry: (attempt) => {
370959
+ console.log(
370960
+ `[AgentService_v2] Retrying thinking model decision for ${decisionName} (attempt ${attempt + 1})...`
370961
+ );
370962
+ },
370963
+ maxRetries: MODEL_RETRY_MAX,
370964
+ delaysMs: MODEL_RETRY_DELAYS_MS
370965
+ });
370966
+ traceResult();
370967
+ return result;
370968
+ } catch (err) {
370969
+ traceResult(err instanceof Error ? err.message : String(err));
370970
+ throw err;
370971
+ }
370924
370972
  }
370925
370973
  if (sessionBinding.thinkingModelSupportsObjectToolChoice) {
370926
370974
  const functionCallingModel = model.withStructuredOutput(schema2, {
370927
370975
  method: "functionCalling"
370928
370976
  });
370929
- return await invokeWithRetryAndSanitizedInput({
370930
- helpers: this.helpers,
370931
- messages: processedMessages,
370932
- modelSupportsImage: sessionBinding.readFileSupport.image,
370977
+ try {
370978
+ const result = await invokeWithRetryAndSanitizedInput({
370979
+ helpers: this.helpers,
370980
+ messages: processedMessages,
370981
+ modelSupportsImage: sessionBinding.readFileSupport.image,
370982
+ signal,
370983
+ operation: async (sanitizedMessages) => {
370984
+ return await functionCallingModel.invoke(sanitizedMessages, {
370985
+ signal
370986
+ });
370987
+ },
370988
+ onRetry: (attempt) => {
370989
+ console.log(
370990
+ `[AgentService_v2] Retrying tool-call thinking decision for ${decisionName} (attempt ${attempt + 1})...`
370991
+ );
370992
+ },
370993
+ maxRetries: MODEL_RETRY_MAX,
370994
+ delaysMs: MODEL_RETRY_DELAYS_MS
370995
+ });
370996
+ traceResult();
370997
+ return result;
370998
+ } catch (err) {
370999
+ traceResult(err instanceof Error ? err.message : String(err));
371000
+ throw err;
371001
+ }
371002
+ }
371003
+ try {
371004
+ const result = await this.invokeModelDecisionByPlainToolCall(
371005
+ sessionId,
371006
+ processedMessages,
371007
+ schema2,
370933
371008
  signal,
370934
- operation: async (sanitizedMessages) => {
370935
- return await functionCallingModel.invoke(sanitizedMessages, {
370936
- signal
370937
- });
370938
- },
370939
- onRetry: (attempt) => {
370940
- console.log(
370941
- `[AgentService_v2] Retrying tool-call thinking decision for ${decisionName} (attempt ${attempt + 1})...`
370942
- );
370943
- },
370944
- maxRetries: MODEL_RETRY_MAX,
370945
- delaysMs: MODEL_RETRY_DELAYS_MS
370946
- });
371009
+ decisionName,
371010
+ "thinking"
371011
+ );
371012
+ traceResult(result);
371013
+ return result;
371014
+ } catch (err) {
371015
+ traceResult(err instanceof Error ? err.message : String(err));
371016
+ throw err;
370947
371017
  }
370948
- return await this.invokeModelDecisionByPlainToolCall(
370949
- sessionId,
370950
- processedMessages,
370951
- schema2,
370952
- signal,
370953
- decisionName,
370954
- "thinking"
370955
- );
370956
371018
  }
370957
371019
  async getCompactionModelDecision(sessionId, messages, schema2, signal, decisionName) {
370958
371020
  const sessionBinding = this.getSessionModelBinding(sessionId);
@@ -371103,6 +371165,7 @@ ${reminder}`;
371103
371165
  }
371104
371166
  const ledgerRunId = agentRunId ?? `run-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
371105
371167
  this.ledgerRunIdsBySession.set(sessionId, ledgerRunId);
371168
+ if (runId) this.currentRunIdBySession.set(sessionId, runId);
371106
371169
  const ledgerInputPreview = typeof input === "string" ? input : input?.text ?? "";
371107
371170
  this.agentRunLedger?.startRun({
371108
371171
  runId: ledgerRunId,
@@ -371208,6 +371271,7 @@ ${reminder}`;
371208
371271
  if (this.ledgerRunIdsBySession.get(sessionId) === ledgerRunId) {
371209
371272
  this.ledgerRunIdsBySession.delete(sessionId);
371210
371273
  }
371274
+ this.currentRunIdBySession.delete(sessionId);
371211
371275
  this.agentRunLedger?.finishRun(ledgerRunId, ledgerExitStatus, ledgerExitError);
371212
371276
  await this.clearCheckpoint(sessionId);
371213
371277
  }
@@ -387049,6 +387113,88 @@ var DriftDetector = class {
387049
387113
  // ../../packages/backend/src/services/observability.ts
387050
387114
  init_spanLedger();
387051
387115
 
387116
+ // ../../packages/backend/src/services/observability/llmTrace.ts
387117
+ init_spanLedger();
387118
+ var PROVIDER_FROM_MODEL_RE = /^([a-z0-9-]+)\//;
387119
+ function inferProvider(model) {
387120
+ const m2 = PROVIDER_FROM_MODEL_RE.exec(model);
387121
+ return m2 ? m2[1] : "unknown";
387122
+ }
387123
+ function traceIdFromRun(runId) {
387124
+ const stripped = runId.replace(/[^0-9a-fA-F]/g, "");
387125
+ if (stripped.length >= 32) return stripped.slice(0, 32);
387126
+ if (stripped.length === 0) return "0".repeat(32);
387127
+ let out = stripped;
387128
+ while (out.length < 32) out += stripped;
387129
+ return out.slice(0, 32);
387130
+ }
387131
+ var LlmTraceRecorder = class {
387132
+ spanLedger = null;
387133
+ otlpForward = null;
387134
+ enabled = true;
387135
+ setSpanLedger(ledger) {
387136
+ this.spanLedger = ledger;
387137
+ }
387138
+ /** Optional forwarder to an external OTLP endpoint (injected by observability.ts). */
387139
+ setOtlpTraceExporter(forward) {
387140
+ this.otlpForward = forward;
387141
+ }
387142
+ setEnabled(on) {
387143
+ this.enabled = on;
387144
+ }
387145
+ isEnabled() {
387146
+ return this.enabled && this.spanLedger !== null;
387147
+ }
387148
+ /**
387149
+ * Record one completed LLM call. Fire-and-forget safe: never throws.
387150
+ */
387151
+ record(input, result) {
387152
+ try {
387153
+ if (!this.enabled || !this.spanLedger) return;
387154
+ const now = Date.now();
387155
+ const startMs = now - Math.max(0, result.durationMs);
387156
+ const traceId = input.runId ? traceIdFromRun(input.runId) : traceIdFromRun(input.sessionId);
387157
+ const spanId = newSpanId();
387158
+ const isError3 = Boolean(result.error);
387159
+ this.spanLedger.ingest({
387160
+ traceId,
387161
+ spanId,
387162
+ service: `llm.${input.operation}`,
387163
+ name: `${input.model} ${input.operation}`,
387164
+ startMs,
387165
+ durationMs: Math.max(0, result.durationMs),
387166
+ status: isError3 ? "error" : "ok"
387167
+ });
387168
+ if (this.otlpForward) {
387169
+ const attributes = [
387170
+ { key: "gen_ai.system", value: { stringValue: input.provider || inferProvider(input.model) } },
387171
+ { key: "gen_ai.request.model", value: { stringValue: input.model } },
387172
+ { key: "gen_ai.operation.name", value: { stringValue: input.operation } },
387173
+ { key: "session.id", value: { stringValue: input.sessionId } }
387174
+ ];
387175
+ if (typeof result.inputTokens === "number") attributes.push({ key: "gen_ai.usage.prompt_tokens", value: { intValue: result.inputTokens } });
387176
+ if (typeof result.outputTokens === "number") attributes.push({ key: "gen_ai.usage.completion_tokens", value: { intValue: result.outputTokens } });
387177
+ if (typeof result.totalTokens === "number") attributes.push({ key: "gen_ai.usage.total_tokens", value: { intValue: result.totalTokens } });
387178
+ if (result.finishReason) attributes.push({ key: "gen_ai.response.finish_reasons", value: { stringValue: result.finishReason } });
387179
+ if (result.error) attributes.push({ key: "error.message", value: { stringValue: result.error.slice(0, 500) } });
387180
+ this.otlpForward([
387181
+ {
387182
+ traceId,
387183
+ spanId,
387184
+ parentSpanId: "",
387185
+ name: `${input.operation} ${input.model}`,
387186
+ startTimeUnixNano: String(BigInt(Math.floor(startMs)) * 1000000n),
387187
+ endTimeUnixNano: String(BigInt(Math.floor(now)) * 1000000n),
387188
+ attributes,
387189
+ status: { code: isError3 ? 2 : 1 }
387190
+ }
387191
+ ]);
387192
+ }
387193
+ } catch {
387194
+ }
387195
+ }
387196
+ };
387197
+
387052
387198
  // ../../packages/backend/src/services/dem/rumLedger.ts
387053
387199
  var import_crypto16 = require("crypto");
387054
387200
  var DEFAULT_LIMIT5 = 5e4;
@@ -392597,6 +392743,8 @@ function createObservability(deps) {
392597
392743
  getActual: async () => ""
392598
392744
  });
392599
392745
  const spanLedger = new SpanLedger({});
392746
+ const llmTrace = new LlmTraceRecorder();
392747
+ llmTrace.setSpanLedger(spanLedger);
392600
392748
  const rumLedger = new RumLedger({});
392601
392749
  const infraMonitor = new InfraMonitor({});
392602
392750
  const etwService = new EtwService({});
@@ -392737,7 +392885,7 @@ function createObservability(deps) {
392737
392885
  endpoint: otelEndpoint,
392738
392886
  resourceAttributes: { "service.name": "rterm", "service.version": process.env.GYBACKEND_VERSION ?? "dev" }
392739
392887
  }) : null;
392740
- const renderPrometheus = () => {
392888
+ const buildHostMetricsRegistry = () => {
392741
392889
  const series = [];
392742
392890
  for (const host of metricsLedger.hosts()) {
392743
392891
  const latest = metricsLedger.latest(host);
@@ -392747,9 +392895,9 @@ function createObservability(deps) {
392747
392895
  if (typeof v === "number" && Number.isFinite(v)) series.push({ host, metric: `host_${k}`, value: v });
392748
392896
  }
392749
392897
  }
392750
- const reg = registryFromHostMetrics(series, { prefix: "rterm", helpPrefix: "RTerm host metric" });
392751
- return reg.render();
392898
+ return registryFromHostMetrics(series, { prefix: "rterm", helpPrefix: "RTerm host metric" });
392752
392899
  };
392900
+ const renderPrometheus = () => buildHostMetricsRegistry().render();
392753
392901
  const secretsVault = new SecretsVault({
392754
392902
  masterKey: process.env.RTERM_SECRETS_MASTER_KEY,
392755
392903
  onAudit: (action, key) => {
@@ -393094,8 +393242,7 @@ function createObservability(deps) {
393094
393242
  const intervalMs = Number(process.env.OTEL_EXPORTER_OTLP_INTERVAL_MS ?? 3e4) || 3e4;
393095
393243
  const pushOnce = async () => {
393096
393244
  try {
393097
- renderPrometheus();
393098
- await otelExporter.push(prometheusRegistry);
393245
+ await otelExporter.push(buildHostMetricsRegistry());
393099
393246
  } catch {
393100
393247
  }
393101
393248
  };
@@ -393105,6 +393252,28 @@ function createObservability(deps) {
393105
393252
  if (typeof otelPushTimer.unref === "function") otelPushTimer.unref();
393106
393253
  void pushOnce();
393107
393254
  }
393255
+ if (otelEndpoint) {
393256
+ llmTrace.setOtlpTraceExporter((spans) => {
393257
+ void fetch(otelEndpoint.replace(/\/v1\/metrics$/, "/v1/traces"), {
393258
+ method: "POST",
393259
+ headers: { "content-type": "application/json" },
393260
+ body: JSON.stringify({
393261
+ resourceSpans: [
393262
+ {
393263
+ resource: {
393264
+ attributes: [
393265
+ { key: "service.name", value: { stringValue: "rterm-agent" } },
393266
+ { key: "service.version", value: { stringValue: process.env.GYBACKEND_VERSION ?? "dev" } }
393267
+ ]
393268
+ },
393269
+ scopeSpans: [{ spans }]
393270
+ }
393271
+ ]
393272
+ })
393273
+ }).catch(() => {
393274
+ });
393275
+ });
393276
+ }
393108
393277
  const oncallTickTimer = setInterval(() => {
393109
393278
  void escalationService.tick().catch(() => {
393110
393279
  });
@@ -393129,6 +393298,7 @@ function createObservability(deps) {
393129
393298
  anomalyDetector,
393130
393299
  earlyWarning,
393131
393300
  behaviorLedger,
393301
+ llmTrace,
393132
393302
  dagu: { parseDaguYaml, parseDaguWorkflow, daguExecutionPlan },
393133
393303
  notify: { slackChannel, teamsChannel, smtpChannel, telegramChannel },
393134
393304
  aperf: { service: aperfService, toMetricPoint: aperfSummaryToMetricPoint },
package/package.json CHANGED
@@ -1,48 +1,11 @@
1
1
  {
2
2
  "name": "neuralos",
3
- "version": "3.2.13",
4
- "description": "AI-native terminal & agentic-AI operations platform for Forward Deployed Engineers & SREs - AIOps closed-loop remediation, AI SRE, self-healing infrastructure, runbook automation, ChatOps over SSH/WinRM/serial under policy with tamper-evident audit.",
5
- "keywords": [
6
- "forward-deployed-engineer",
7
- "fde",
8
- "ai-terminal",
9
- "ai-agent",
10
- "ssh",
11
- "winrm",
12
- "serial-console",
13
- "cisco",
14
- "network-automation",
15
- "devops",
16
- "sre",
17
- "observability",
18
- "incident-response",
19
- "change-management",
20
- "infrastructure-automation",
21
- "fleet-management",
22
- "runbooks",
23
- "neuralos",
24
- "aiops",
25
- "ai-sre",
26
- "agentic-ai",
27
- "chatops",
28
- "self-healing",
29
- "runbook-automation",
30
- "closed-loop-remediation",
31
- "mcp"
32
- ],
33
- "license": "MIT",
34
- "homepage": "https://rterm.app",
35
- "repository": {
36
- "type": "git",
37
- "url": "git+https://github.com/DrOlu/RTerm.git"
38
- },
39
- "engines": {
40
- "node": ">=18"
41
- },
42
- "bin": {
43
- "gybackend": "bin/gybackend.cjs"
44
- },
3
+ "version": "3.2.15",
4
+ "description": "Headless AI-native backend for RTerm / neuralOS v3.2.15: offensive security suite (promptfoo LLM red-team, mitmproxy traffic capture with secret detection, NetExec attack simulation with target allowlists). OpenLLMetry-style LLM tracing + OTLP push fix from 3.2.14. 14 plugins, 61 plugin tools, parallel tool execution, captureStatus.",
45
5
  "main": "bin/gybackend.cjs",
6
+ "bin": { "gybackend": "bin/gybackend.cjs" },
7
+ "license": "MIT",
8
+ "engines": { "node": ">=18" },
46
9
  "dependencies": {
47
10
  "@nats-io/jetstream": "^3.4.0",
48
11
  "@nats-io/kv": "^3.4.0",
@@ -0,0 +1,20 @@
1
+ // mitmproxy-bridge plugin type declarations
2
+ export function register(ctx: any): void
3
+ export function buildMitmCommand(opts: {
4
+ mode?: 'regular' | 'reverse'
5
+ listenPort?: number
6
+ upstreamTarget?: string
7
+ flowsFile: string
8
+ filterExpr?: string
9
+ extraArgs?: string[]
10
+ }): { cmd: string; args: string[] }
11
+ export function parseFlows(rawFlows: unknown): {
12
+ total: number
13
+ byHost: Record<string, { count: number; methods: Record<string, number> }>
14
+ byStatus: Record<string, number>
15
+ requests: Array<{ host: string; method: string; path: string; status: string; contentType: string }>
16
+ error?: string
17
+ }
18
+ export function detectSecrets(text: string | null | undefined): Array<{ kind: string; count: number; preview: string }>
19
+ export function isHostAllowed(host: string, allowlist: string[]): boolean
20
+ export default any