rterm-backend 2.7.0 → 2.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/gybackend.js +218 -20
  2. package/package.json +1 -1
package/bin/gybackend.js CHANGED
@@ -254727,7 +254727,7 @@ var require_websocket = __commonJS({
254727
254727
  var http2 = __require("http");
254728
254728
  var net2 = __require("net");
254729
254729
  var tls = __require("tls");
254730
- var { randomBytes: randomBytes2, createHash } = __require("crypto");
254730
+ var { randomBytes: randomBytes2, createHash: createHash3 } = __require("crypto");
254731
254731
  var { Duplex, Readable } = __require("stream");
254732
254732
  var { URL: URL5 } = __require("url");
254733
254733
  var PerMessageDeflate = require_permessage_deflate();
@@ -255387,7 +255387,7 @@ var require_websocket = __commonJS({
255387
255387
  abortHandshake(websocket, socket, "Invalid Upgrade header");
255388
255388
  return;
255389
255389
  }
255390
- const digest = createHash("sha1").update(key + GUID).digest("base64");
255390
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
255391
255391
  if (res.headers["sec-websocket-accept"] !== digest) {
255392
255392
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
255393
255393
  return;
@@ -255754,7 +255754,7 @@ var require_websocket_server = __commonJS({
255754
255754
  var EventEmitter4 = __require("events");
255755
255755
  var http2 = __require("http");
255756
255756
  var { Duplex } = __require("stream");
255757
- var { createHash } = __require("crypto");
255757
+ var { createHash: createHash3 } = __require("crypto");
255758
255758
  var extension = require_extension();
255759
255759
  var PerMessageDeflate = require_permessage_deflate();
255760
255760
  var subprotocol = require_subprotocol();
@@ -256055,7 +256055,7 @@ var require_websocket_server = __commonJS({
256055
256055
  );
256056
256056
  }
256057
256057
  if (this._state > RUNNING) return abortHandshake(socket, 503);
256058
- const digest = createHash("sha1").update(key + GUID).digest("base64");
256058
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
256059
256059
  const headers = [
256060
256060
  "HTTP/1.1 101 Switching Protocols",
256061
256061
  "Upgrade: websocket",
@@ -360739,7 +360739,7 @@ var AgentService_v2 = class {
360739
360739
  state.messages
360740
360740
  );
360741
360741
  const messages = [...state.messages];
360742
- const lastMessage = messages[messages.length - 1];
360742
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : void 0;
360743
360743
  let pendingToolCalls = [];
360744
360744
  if (AIMessage.isInstance(lastMessage) && (hasEmptyMalformedToolCallFinishFlag(lastMessage) || isEmptyMalformedToolCallFinish(lastMessage, []))) {
360745
360745
  this.helpers.sendEvent(sessionId, {
@@ -361623,7 +361623,7 @@ ${reminder}`;
361623
361623
  const sessionId = state.sessionId;
361624
361624
  if (!sessionId) throw new Error("No session ID in state");
361625
361625
  const messages = [...state.messages];
361626
- const lastMessage = messages[messages.length - 1];
361626
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : void 0;
361627
361627
  const lastMessageIsAi = AIMessage.isInstance(lastMessage);
361628
361628
  const guardMessages = lastMessageIsAi || messages.length === 0 ? messages : messages.slice(0, -1);
361629
361629
  if (!lastMessageIsAi && lastMessage) {
@@ -361800,7 +361800,7 @@ ${reminder}`;
361800
361800
  const sessionId = state.sessionId;
361801
361801
  if (!sessionId) return state;
361802
361802
  const messages = Array.isArray(state.messages) ? [...state.messages] : [];
361803
- const lastMessage = messages[messages.length - 1];
361803
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : void 0;
361804
361804
  const finalBoundaryMessages = AIMessage.isInstance(lastMessage) || messages.length === 0 ? messages : messages.slice(0, -1);
361805
361805
  const queuedInsertionResult = this.appendQueuedInsertionMessagesForContinue(
361806
361806
  sessionId,
@@ -362362,7 +362362,7 @@ ${reminder}`;
362362
362362
  return "tools";
362363
362363
  }
362364
362364
  const messages = Array.isArray(state.messages) ? state.messages : [];
362365
- const lastMessage = messages[messages.length - 1];
362365
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : void 0;
362366
362366
  if (AIMessage.isInstance(lastMessage) && (hasEmptyMalformedToolCallFinishFlag(lastMessage) || isEmptyMalformedToolCallFinish(lastMessage, []))) {
362367
362367
  return "final_output";
362368
362368
  }
@@ -362409,8 +362409,8 @@ ${reminder}`;
362409
362409
  }
362410
362410
  }
362411
362411
  trimInterruptedToolCallHistory(messages, executedToolCall) {
362412
- const lastMessage = messages[messages.length - 1];
362413
- if (!AIMessage.isInstance(lastMessage)) {
362412
+ const lastMessage = messages.length > 0 ? messages[messages.length - 1] : void 0;
362413
+ if (!lastMessage || !AIMessage.isInstance(lastMessage)) {
362414
362414
  return;
362415
362415
  }
362416
362416
  const toolCalls = Array.isArray(lastMessage.tool_calls) ? lastMessage.tool_calls : [];
@@ -377401,8 +377401,8 @@ var MetricsLedger = class {
377401
377401
  var DEFAULT_WINDOW = 36e5;
377402
377402
  function percentile(sorted, p) {
377403
377403
  if (sorted.length === 0) return void 0;
377404
- const idx = Math.min(sorted.length - 1, Math.floor(p / 100 * sorted.length));
377405
- return sorted[idx];
377404
+ const rank = Math.max(1, Math.ceil(p / 100 * sorted.length));
377405
+ return sorted[Math.min(rank, sorted.length) - 1];
377406
377406
  }
377407
377407
  var GoldenSignals = class {
377408
377408
  constructor(deps) {
@@ -381941,16 +381941,19 @@ function parseAperfReport(text) {
381941
381941
  topProcs.sort((a, b) => b.cpuPercent - a.cpuPercent);
381942
381942
  if (topProcs.length > 0) summary.topCpuProcesses = topProcs.slice(0, 5);
381943
381943
  if (findings.length === 0) {
381944
- if ((summary.cpuUsagePercent ?? 0) >= 90) {
381945
- findings.push({ metric: "cpu_utilization", value: summary.cpuUsagePercent, severity: "critical", message: `aggregate CPU utilization is ${summary.cpuUsagePercent}% (>= 90%)` });
381946
- } else if ((summary.cpuUsagePercent ?? 0) >= 75) {
381947
- findings.push({ metric: "cpu_utilization", value: summary.cpuUsagePercent, severity: "warning", message: `aggregate CPU utilization is ${summary.cpuUsagePercent}% (>= 75%)` });
381944
+ const cpu = summary.cpuUsagePercent;
381945
+ if (cpu !== void 0 && cpu >= 90) {
381946
+ findings.push({ metric: "cpu_utilization", value: cpu, severity: "critical", message: `aggregate CPU utilization is ${cpu}% (>= 90%)` });
381947
+ } else if (cpu !== void 0 && cpu >= 75) {
381948
+ findings.push({ metric: "cpu_utilization", value: cpu, severity: "warning", message: `aggregate CPU utilization is ${cpu}% (>= 75%)` });
381948
381949
  }
381949
- if ((summary.memUsagePercent ?? 0) >= 90) {
381950
- findings.push({ metric: "memory", value: summary.memUsagePercent, severity: "critical", message: `memory utilization is ${summary.memUsagePercent}% (>= 90%)` });
381950
+ const mem = summary.memUsagePercent;
381951
+ if (mem !== void 0 && mem >= 90) {
381952
+ findings.push({ metric: "memory", value: mem, severity: "critical", message: `memory utilization is ${mem}% (>= 90%)` });
381951
381953
  }
381952
- if ((summary.diskUsagePercentMax ?? 0) >= 90) {
381953
- findings.push({ metric: "disk", value: summary.diskUsagePercentMax, severity: "critical", message: `a disk is at ${summary.diskUsagePercentMax}% utilization (>= 90%)` });
381954
+ const disk = summary.diskUsagePercentMax;
381955
+ if (disk !== void 0 && disk >= 90) {
381956
+ findings.push({ metric: "disk", value: disk, severity: "critical", message: `a disk is at ${disk}% utilization (>= 90%)` });
381954
381957
  }
381955
381958
  if (summary.topCpuProcesses && summary.topCpuProcesses.length > 0) {
381956
381959
  const top = summary.topCpuProcesses[0];
@@ -382011,6 +382014,198 @@ function aperfSummaryToMetricPoint(result) {
382011
382014
  };
382012
382015
  }
382013
382016
 
382017
+ // ../../packages/backend/src/services/audit/auditLedger.ts
382018
+ import { createHash, randomUUID as randomUUID21 } from "crypto";
382019
+ var GENESIS_HASH = "0".repeat(64);
382020
+ function computeRecordHash(kind, actor, target, summary, detail, at, seq2) {
382021
+ const payload = JSON.stringify({ kind, actor, target, summary, detail, at, seq: seq2 });
382022
+ return createHash("sha256").update(payload).digest("hex");
382023
+ }
382024
+ var AuditLedger = class _AuditLedger {
382025
+ records = [];
382026
+ hashFn;
382027
+ now;
382028
+ constructor(deps = {}) {
382029
+ this.hashFn = deps.hashFn ?? ((data) => createHash("sha256").update(data).digest("hex"));
382030
+ this.now = deps.now ?? (() => Date.now());
382031
+ }
382032
+ /** Append an event to the audit ledger. Returns the record with hash + chain. */
382033
+ append(event) {
382034
+ const seq2 = this.records.length + 1;
382035
+ const at = event.at ?? this.now();
382036
+ const prevHash = this.records.length > 0 ? this.records[this.records.length - 1].hash : GENESIS_HASH;
382037
+ const contentHash = computeRecordHash(
382038
+ event.kind,
382039
+ event.actor,
382040
+ event.target,
382041
+ event.summary,
382042
+ event.detail,
382043
+ at,
382044
+ seq2
382045
+ );
382046
+ const hash2 = createHash("sha256").update(contentHash + prevHash).digest("hex");
382047
+ const record2 = {
382048
+ id: `audit-${randomUUID21().slice(0, 12)}`,
382049
+ kind: event.kind,
382050
+ actor: event.actor,
382051
+ target: event.target,
382052
+ summary: event.summary,
382053
+ ...event.detail ? { detail: event.detail } : {},
382054
+ at,
382055
+ seq: seq2,
382056
+ hash: hash2,
382057
+ prevHash
382058
+ };
382059
+ this.records.push(record2);
382060
+ return record2;
382061
+ }
382062
+ /** Get all records (ordered by seq). */
382063
+ list() {
382064
+ return [...this.records];
382065
+ }
382066
+ /** Get records by kind. */
382067
+ listByKind(kind) {
382068
+ return this.records.filter((r) => r.kind === kind);
382069
+ }
382070
+ /** Get records for a specific target. */
382071
+ listByTarget(target) {
382072
+ return this.records.filter((r) => r.target === target);
382073
+ }
382074
+ /** Get records for a specific actor. */
382075
+ listByActor(actor) {
382076
+ return this.records.filter((r) => r.actor === actor);
382077
+ }
382078
+ /** Get records in a time range. */
382079
+ listInRange(fromAt, toAt) {
382080
+ return this.records.filter((r) => r.at >= fromAt && r.at <= toAt);
382081
+ }
382082
+ /** Verify the hash chain integrity. Returns { valid: true } or { valid: false, brokenAt: seq }. */
382083
+ verify() {
382084
+ if (this.records.length === 0) return { valid: true };
382085
+ for (let i = 0; i < this.records.length; i++) {
382086
+ const record2 = this.records[i];
382087
+ if (record2.seq !== i + 1) {
382088
+ return { valid: false, brokenAt: record2.seq, detail: `seq mismatch: expected ${i + 1}, got ${record2.seq}` };
382089
+ }
382090
+ const expectedContentHash = computeRecordHash(
382091
+ record2.kind,
382092
+ record2.actor,
382093
+ record2.target,
382094
+ record2.summary,
382095
+ record2.detail,
382096
+ record2.at,
382097
+ record2.seq
382098
+ );
382099
+ const expectedHash = createHash("sha256").update(expectedContentHash + record2.prevHash).digest("hex");
382100
+ if (record2.hash !== expectedHash) {
382101
+ return { valid: false, brokenAt: record2.seq, detail: `hash mismatch at seq ${record2.seq}` };
382102
+ }
382103
+ const expectedPrevHash = i === 0 ? GENESIS_HASH : this.records[i - 1].hash;
382104
+ if (record2.prevHash !== expectedPrevHash) {
382105
+ return { valid: false, brokenAt: record2.seq, detail: `prevHash mismatch at seq ${record2.seq}` };
382106
+ }
382107
+ }
382108
+ return { valid: true };
382109
+ }
382110
+ /** Get the current chain tip (the latest record's hash). */
382111
+ tip() {
382112
+ if (this.records.length === 0) return GENESIS_HASH;
382113
+ return this.records[this.records.length - 1].hash;
382114
+ }
382115
+ /** Get the total number of records. */
382116
+ size() {
382117
+ return this.records.length;
382118
+ }
382119
+ /** Export all records as JSON (for persistence). */
382120
+ export() {
382121
+ return JSON.stringify(this.records, null, 2);
382122
+ }
382123
+ /** Import records from JSON (for recovery). Verifies the chain on import. */
382124
+ import(json3) {
382125
+ let parsed;
382126
+ try {
382127
+ parsed = JSON.parse(json3);
382128
+ } catch {
382129
+ return { imported: 0, valid: false, detail: "invalid JSON" };
382130
+ }
382131
+ if (!Array.isArray(parsed)) {
382132
+ return { imported: 0, valid: false, detail: "not an array" };
382133
+ }
382134
+ const tempLedger = new _AuditLedger({ hashFn: this.hashFn, now: this.now });
382135
+ tempLedger.records = parsed;
382136
+ const result = tempLedger.verify();
382137
+ if (!result.valid) {
382138
+ return { imported: 0, valid: false, detail: result.detail };
382139
+ }
382140
+ this.records = parsed;
382141
+ return { imported: parsed.length, valid: true };
382142
+ }
382143
+ };
382144
+
382145
+ // ../../packages/backend/src/services/audit/evidenceSealer.ts
382146
+ import { createHash as createHash2 } from "crypto";
382147
+ function computeMerkleRoot(hashes) {
382148
+ if (hashes.length === 0) return "0".repeat(64);
382149
+ if (hashes.length === 1) return hashes[0];
382150
+ const nextLevel = [];
382151
+ for (let i = 0; i < hashes.length; i += 2) {
382152
+ const left = hashes[i];
382153
+ const right = i + 1 < hashes.length ? hashes[i + 1] : left;
382154
+ nextLevel.push(createHash2("sha256").update(left + right).digest("hex"));
382155
+ }
382156
+ return computeMerkleRoot(nextLevel);
382157
+ }
382158
+ function verifyMerkleRoot(hashes, expectedRoot) {
382159
+ return computeMerkleRoot(hashes) === expectedRoot;
382160
+ }
382161
+ var EvidenceSealer = class {
382162
+ bundles = [];
382163
+ now;
382164
+ constructor(deps = {}) {
382165
+ this.now = deps.now ?? (() => Date.now());
382166
+ }
382167
+ /** Seal the current state of the audit ledger into an evidence bundle. */
382168
+ seal(ledger, sealId) {
382169
+ const records = ledger.list();
382170
+ const recordHashes = records.map((r) => r.hash);
382171
+ const merkleRoot = computeMerkleRoot(recordHashes);
382172
+ const bundle = {
382173
+ merkleRoot,
382174
+ recordCount: records.length,
382175
+ fromAt: records.length > 0 ? records[0].at : 0,
382176
+ toAt: records.length > 0 ? records[records.length - 1].at : 0,
382177
+ tipHash: ledger.tip(),
382178
+ recordHashes,
382179
+ sealedAt: this.now(),
382180
+ sealId: sealId ?? `seal-${Date.now().toString(36)}`
382181
+ };
382182
+ this.bundles.push(bundle);
382183
+ return bundle;
382184
+ }
382185
+ /** Verify a sealed bundle against a set of records. */
382186
+ verify(bundle, records) {
382187
+ if (records.length !== bundle.recordCount) {
382188
+ return { valid: false, detail: `record count mismatch: expected ${bundle.recordCount}, got ${records.length}` };
382189
+ }
382190
+ if (records.length > 0 && records[records.length - 1].hash !== bundle.tipHash) {
382191
+ return { valid: false, detail: `tip hash mismatch` };
382192
+ }
382193
+ const hashes = records.map((r) => r.hash);
382194
+ if (!verifyMerkleRoot(hashes, bundle.merkleRoot)) {
382195
+ return { valid: false, detail: `Merkle root mismatch` };
382196
+ }
382197
+ return { valid: true };
382198
+ }
382199
+ /** Get all sealed bundles. */
382200
+ listBundles() {
382201
+ return [...this.bundles];
382202
+ }
382203
+ /** Get the latest sealed bundle. */
382204
+ latest() {
382205
+ return this.bundles.length > 0 ? this.bundles[this.bundles.length - 1] : void 0;
382206
+ }
382207
+ };
382208
+
382014
382209
  // ../../packages/backend/src/services/observability.ts
382015
382210
  function createObservability(deps) {
382016
382211
  const log = deps.onLog ?? (() => {
@@ -382087,6 +382282,8 @@ function createObservability(deps) {
382087
382282
  const aperfService = new AperfService({
382088
382283
  execSsh: async () => ""
382089
382284
  });
382285
+ const auditLedger = new AuditLedger({});
382286
+ const evidenceSealer = new EvidenceSealer({});
382090
382287
  const pluginScanRoot = (process.env.GYBACKEND_DATA_DIR ?? "./.gybackend-data") + "/plugins";
382091
382288
  const pluginRegistry = new PluginRegistry({
382092
382289
  scanRoots: [pluginScanRoot, "./plugins"],
@@ -382139,6 +382336,7 @@ function createObservability(deps) {
382139
382336
  dagu: { parseDaguYaml, parseDaguWorkflow, daguExecutionPlan },
382140
382337
  notify: { slackChannel, teamsChannel, smtpChannel, telegramChannel },
382141
382338
  aperf: { service: aperfService, toMetricPoint: aperfSummaryToMetricPoint },
382339
+ audit: { ledger: auditLedger, sealer: evidenceSealer },
382142
382340
  pluginRegistry
382143
382341
  };
382144
382342
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rterm-backend",
3
- "version": "2.7.0",
3
+ "version": "2.7.2",
4
4
  "description": "RTerm headless backend — run RTerm-as-a-service (AI agent, SSH/WinRM/Serial/local terminals, fleet orchestration, advanced automation with event-driven triggers + NATS event mesh, scheduled automation, SRE observability, Netdata integration, AWS APerf performance deep-dive, plugin system) and drive it over a WebSocket JSON-RPC gateway. No desktop UI required.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",