rterm-backend 2.7.5 → 2.7.7
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.js +220 -0
- package/package.json +1 -1
package/bin/gybackend.js
CHANGED
|
@@ -382209,6 +382209,188 @@ var EvidenceSealer = class {
|
|
|
382209
382209
|
}
|
|
382210
382210
|
};
|
|
382211
382211
|
|
|
382212
|
+
// ../../packages/backend/src/services/sre/monitorStatusService.ts
|
|
382213
|
+
var MonitorStatusService = class {
|
|
382214
|
+
constructor(monitor, terminalService, now = () => Date.now()) {
|
|
382215
|
+
this.monitor = monitor;
|
|
382216
|
+
this.terminalService = terminalService;
|
|
382217
|
+
this.now = now;
|
|
382218
|
+
}
|
|
382219
|
+
/** Build the full monitor status report. */
|
|
382220
|
+
report() {
|
|
382221
|
+
const publisherWired = this.monitor.publisher !== null;
|
|
382222
|
+
const entries = [];
|
|
382223
|
+
const issues = [];
|
|
382224
|
+
const terminals = this.terminalService.getDisplayTerminals();
|
|
382225
|
+
const now = this.now();
|
|
382226
|
+
for (const terminal of terminals) {
|
|
382227
|
+
const terminalId = terminal.id;
|
|
382228
|
+
const connected = terminal.runtimeState === "ready";
|
|
382229
|
+
const platform2 = terminal.remoteOs ?? terminal.type ?? "unknown";
|
|
382230
|
+
const sessions = this.monitor.sessions;
|
|
382231
|
+
const session = sessions?.get(terminalId);
|
|
382232
|
+
const hasSession = session !== void 0;
|
|
382233
|
+
const inFlight = session?.inFlight === true;
|
|
382234
|
+
const lastCollectAt = session?.lastCollectAt ?? 0;
|
|
382235
|
+
const lastCollectAgoMs = lastCollectAt > 0 ? now - lastCollectAt : -1;
|
|
382236
|
+
let diagnosis = "ok";
|
|
382237
|
+
if (!connected) {
|
|
382238
|
+
diagnosis = "terminal_not_connected";
|
|
382239
|
+
} else if (!hasSession) {
|
|
382240
|
+
diagnosis = "no_monitor_session";
|
|
382241
|
+
} else if (inFlight) {
|
|
382242
|
+
diagnosis = "collection_stuck_in_flight";
|
|
382243
|
+
} else if (lastCollectAt === 0) {
|
|
382244
|
+
diagnosis = "never_collected";
|
|
382245
|
+
} else if (lastCollectAgoMs > 3e4) {
|
|
382246
|
+
diagnosis = `stale_collection (${Math.round(lastCollectAgoMs / 1e3)}s ago)`;
|
|
382247
|
+
}
|
|
382248
|
+
entries.push({
|
|
382249
|
+
terminalId,
|
|
382250
|
+
connected,
|
|
382251
|
+
platform: platform2,
|
|
382252
|
+
hasSession,
|
|
382253
|
+
inFlight,
|
|
382254
|
+
lastCollectAt,
|
|
382255
|
+
lastCollectAgoMs,
|
|
382256
|
+
diagnosis
|
|
382257
|
+
});
|
|
382258
|
+
if (diagnosis !== "ok") {
|
|
382259
|
+
issues.push(`${terminalId}: ${diagnosis}`);
|
|
382260
|
+
}
|
|
382261
|
+
}
|
|
382262
|
+
if (!publisherWired) {
|
|
382263
|
+
issues.unshift("publisher_not_wired \u2014 createObservability may not have been called");
|
|
382264
|
+
}
|
|
382265
|
+
if (entries.length === 0) {
|
|
382266
|
+
issues.push("no_terminals \u2014 no terminals connected");
|
|
382267
|
+
}
|
|
382268
|
+
return {
|
|
382269
|
+
publisherWired,
|
|
382270
|
+
terminalCount: entries.length,
|
|
382271
|
+
entries,
|
|
382272
|
+
issues
|
|
382273
|
+
};
|
|
382274
|
+
}
|
|
382275
|
+
/** Build a compact summary string for the agent. */
|
|
382276
|
+
summary() {
|
|
382277
|
+
const r = this.report();
|
|
382278
|
+
const parts = [];
|
|
382279
|
+
parts.push(`publisher=${r.publisherWired ? "wired" : "NOT WIRED"}`);
|
|
382280
|
+
parts.push(`terminals=${r.terminalCount}`);
|
|
382281
|
+
if (r.issues.length > 0) {
|
|
382282
|
+
parts.push(`issues=${r.issues.length}:`);
|
|
382283
|
+
for (const issue2 of r.issues) {
|
|
382284
|
+
parts.push(` - ${issue2}`);
|
|
382285
|
+
}
|
|
382286
|
+
} else {
|
|
382287
|
+
parts.push("all terminals collecting normally");
|
|
382288
|
+
}
|
|
382289
|
+
return parts.join("\n");
|
|
382290
|
+
}
|
|
382291
|
+
};
|
|
382292
|
+
|
|
382293
|
+
// ../../packages/backend/src/services/governance/agtPolicyEngine.ts
|
|
382294
|
+
var AgtPolicyEngine = class {
|
|
382295
|
+
constructor(deps) {
|
|
382296
|
+
this.deps = deps;
|
|
382297
|
+
this.agentIdentity = deps.agentIdentity ?? "rterm-agent";
|
|
382298
|
+
this.sponsoringPrincipal = deps.sponsoringPrincipal ?? "unknown";
|
|
382299
|
+
}
|
|
382300
|
+
policy = null;
|
|
382301
|
+
agentIdentity;
|
|
382302
|
+
sponsoringPrincipal;
|
|
382303
|
+
/** Load the policy document. Must be called before evaluate(). */
|
|
382304
|
+
async load() {
|
|
382305
|
+
this.policy = await this.deps.loadPolicy();
|
|
382306
|
+
}
|
|
382307
|
+
/** Evaluate an action against the policy. Returns the decision. */
|
|
382308
|
+
evaluate(action, target) {
|
|
382309
|
+
if (!this.policy) {
|
|
382310
|
+
throw new Error("Policy not loaded. Call load() first.");
|
|
382311
|
+
}
|
|
382312
|
+
const actionLower = action.toLowerCase();
|
|
382313
|
+
const targetLower = (target ?? "").toLowerCase();
|
|
382314
|
+
for (const rule of this.policy.rules) {
|
|
382315
|
+
const actionMatch = this.matchPattern(actionLower, rule.actionPattern.toLowerCase());
|
|
382316
|
+
const targetMatch = !rule.targetPattern || this.matchPattern(targetLower, rule.targetPattern.toLowerCase());
|
|
382317
|
+
if (actionMatch && targetMatch) {
|
|
382318
|
+
return {
|
|
382319
|
+
decision: rule.decision,
|
|
382320
|
+
rule: rule.name,
|
|
382321
|
+
reason: rule.reason ?? `matched rule '${rule.name}'`,
|
|
382322
|
+
policyVersion: this.policy.version,
|
|
382323
|
+
action,
|
|
382324
|
+
target
|
|
382325
|
+
};
|
|
382326
|
+
}
|
|
382327
|
+
}
|
|
382328
|
+
return {
|
|
382329
|
+
decision: this.policy.defaultDecision,
|
|
382330
|
+
rule: "default",
|
|
382331
|
+
reason: `no rule matched, using default '${this.policy.defaultDecision}'`,
|
|
382332
|
+
policyVersion: this.policy.version,
|
|
382333
|
+
action,
|
|
382334
|
+
target
|
|
382335
|
+
};
|
|
382336
|
+
}
|
|
382337
|
+
/** Get the current policy document. */
|
|
382338
|
+
getPolicy() {
|
|
382339
|
+
return this.policy;
|
|
382340
|
+
}
|
|
382341
|
+
/** Get the agent identity. */
|
|
382342
|
+
getAgentIdentity() {
|
|
382343
|
+
return this.agentIdentity;
|
|
382344
|
+
}
|
|
382345
|
+
/** Get the sponsoring principal. */
|
|
382346
|
+
getSponsoringPrincipal() {
|
|
382347
|
+
return this.sponsoringPrincipal;
|
|
382348
|
+
}
|
|
382349
|
+
/** Simple glob-style pattern matching: * matches any suffix. The value matches
|
|
382350
|
+
* if it equals the pattern or starts with the pattern (for action patterns
|
|
382351
|
+
* like "read" to match "read /etc/passwd"). */
|
|
382352
|
+
matchPattern(value, pattern) {
|
|
382353
|
+
if (pattern === "*") return true;
|
|
382354
|
+
if (pattern.endsWith("*")) {
|
|
382355
|
+
return value.startsWith(pattern.slice(0, -1));
|
|
382356
|
+
}
|
|
382357
|
+
return value === pattern || value.startsWith(pattern + " ");
|
|
382358
|
+
}
|
|
382359
|
+
};
|
|
382360
|
+
function parsePolicyYaml(yaml) {
|
|
382361
|
+
const lines = yaml.split(/\r?\n/);
|
|
382362
|
+
const doc = { name: "", version: "1.0", defaultDecision: "deny", rules: [] };
|
|
382363
|
+
let currentRule = null;
|
|
382364
|
+
for (const line of lines) {
|
|
382365
|
+
const l = line.trim();
|
|
382366
|
+
if (!l || l.startsWith("#")) continue;
|
|
382367
|
+
if (l.startsWith("name:")) {
|
|
382368
|
+
if (currentRule?.name) {
|
|
382369
|
+
doc.rules.push(currentRule);
|
|
382370
|
+
currentRule = null;
|
|
382371
|
+
}
|
|
382372
|
+
doc.name = l.split(":")[1].trim();
|
|
382373
|
+
} else if (l.startsWith("version:")) {
|
|
382374
|
+
doc.version = l.split(":")[1].trim();
|
|
382375
|
+
} else if (l.startsWith("defaultDecision:")) {
|
|
382376
|
+
doc.defaultDecision = l.split(":")[1].trim();
|
|
382377
|
+
} else if (l.startsWith("- name:")) {
|
|
382378
|
+
if (currentRule?.name) doc.rules.push(currentRule);
|
|
382379
|
+
currentRule = { name: l.split(":")[1].trim() };
|
|
382380
|
+
} else if (l.startsWith("actionPattern:") && currentRule) {
|
|
382381
|
+
currentRule.actionPattern = l.split(":")[1].trim();
|
|
382382
|
+
} else if (l.startsWith("targetPattern:") && currentRule) {
|
|
382383
|
+
currentRule.targetPattern = l.split(":")[1].trim();
|
|
382384
|
+
} else if (l.startsWith("decision:") && currentRule) {
|
|
382385
|
+
currentRule.decision = l.split(":")[1].trim();
|
|
382386
|
+
} else if (l.startsWith("reason:") && currentRule) {
|
|
382387
|
+
currentRule.reason = l.split(":").slice(1).join(":").trim();
|
|
382388
|
+
}
|
|
382389
|
+
}
|
|
382390
|
+
if (currentRule?.name) doc.rules.push(currentRule);
|
|
382391
|
+
return doc;
|
|
382392
|
+
}
|
|
382393
|
+
|
|
382212
382394
|
// ../../packages/backend/src/services/observability.ts
|
|
382213
382395
|
function createObservability(deps) {
|
|
382214
382396
|
const log = deps.onLog ?? (() => {
|
|
@@ -382287,6 +382469,41 @@ function createObservability(deps) {
|
|
|
382287
382469
|
});
|
|
382288
382470
|
const auditLedger = new AuditLedger({});
|
|
382289
382471
|
const evidenceSealer = new EvidenceSealer({});
|
|
382472
|
+
const monitorStatus = new MonitorStatusService(deps.resourceMonitorService, deps.terminalService);
|
|
382473
|
+
const policyEngine = new AgtPolicyEngine({
|
|
382474
|
+
loadPolicy: async () => {
|
|
382475
|
+
const req = createRequire2(import.meta.url);
|
|
382476
|
+
const fs22 = req("node:fs");
|
|
382477
|
+
const path31 = req("node:path");
|
|
382478
|
+
const policyPath = path31.join(".", "policy.yaml");
|
|
382479
|
+
if (fs22.existsSync(policyPath)) {
|
|
382480
|
+
return parsePolicyYaml(fs22.readFileSync(policyPath, "utf8"));
|
|
382481
|
+
}
|
|
382482
|
+
return {
|
|
382483
|
+
name: "rterm-default",
|
|
382484
|
+
version: "1.0",
|
|
382485
|
+
defaultDecision: "deny",
|
|
382486
|
+
rules: [
|
|
382487
|
+
{ name: "allow-read", actionPattern: "read", decision: "allow" },
|
|
382488
|
+
{ name: "allow-status", actionPattern: "status", decision: "allow" },
|
|
382489
|
+
{ name: "allow-list", actionPattern: "list", decision: "allow" },
|
|
382490
|
+
{ name: "deny-delete", actionPattern: "delete", decision: "deny" },
|
|
382491
|
+
{ name: "deny-drop", actionPattern: "drop", decision: "deny" },
|
|
382492
|
+
{ name: "deny-format", actionPattern: "format", decision: "deny" },
|
|
382493
|
+
{ name: "escalate-restart-prod", actionPattern: "restart", targetPattern: "prod-*", decision: "escalate" },
|
|
382494
|
+
{ name: "escalate-patch-prod", actionPattern: "patch", targetPattern: "prod-*", decision: "escalate" },
|
|
382495
|
+
{ name: "escalate-deploy-prod", actionPattern: "deploy", targetPattern: "prod-*", decision: "escalate" },
|
|
382496
|
+
{ name: "allow-restart", actionPattern: "restart", decision: "allow" },
|
|
382497
|
+
{ name: "allow-patch", actionPattern: "patch", decision: "allow" },
|
|
382498
|
+
{ name: "allow-deploy", actionPattern: "deploy", decision: "allow" }
|
|
382499
|
+
]
|
|
382500
|
+
};
|
|
382501
|
+
},
|
|
382502
|
+
agentIdentity: `rterm-agent-v${process.env.GYBACKEND_VERSION ?? "2.7.7"}`,
|
|
382503
|
+
sponsoringPrincipal: process.env.USER ?? "unknown"
|
|
382504
|
+
});
|
|
382505
|
+
void policyEngine.load().catch(() => {
|
|
382506
|
+
});
|
|
382290
382507
|
const pluginScanRoot = (process.env.GYBACKEND_DATA_DIR ?? "./.gybackend-data") + "/plugins";
|
|
382291
382508
|
const bundlePluginRoot = new URL("../../plugins/", import.meta.url).pathname;
|
|
382292
382509
|
const scanRoots = [pluginScanRoot, "./plugins"];
|
|
@@ -382359,6 +382576,8 @@ function createObservability(deps) {
|
|
|
382359
382576
|
notify: { slackChannel, teamsChannel, smtpChannel, telegramChannel },
|
|
382360
382577
|
aperf: { service: aperfService, toMetricPoint: aperfSummaryToMetricPoint },
|
|
382361
382578
|
audit: { ledger: auditLedger, sealer: evidenceSealer },
|
|
382579
|
+
monitorStatus,
|
|
382580
|
+
governance: { policyEngine },
|
|
382362
382581
|
pluginRegistry
|
|
382363
382582
|
};
|
|
382364
382583
|
}
|
|
@@ -384364,6 +384583,7 @@ async function startGyBackend() {
|
|
|
384364
384583
|
automationManager,
|
|
384365
384584
|
agentRunLedger,
|
|
384366
384585
|
gatewayService,
|
|
384586
|
+
resourceMonitorService,
|
|
384367
384587
|
setMonitorPublisher: (pub) => resourceMonitorService.setPublisher((channel, data) => {
|
|
384368
384588
|
pub(channel, data);
|
|
384369
384589
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rterm-backend",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.7",
|
|
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",
|