rterm-backend 3.7.6 → 3.8.0
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 +347 -142
- package/package.json +1 -1
package/bin/gybackend.cjs
CHANGED
|
@@ -276397,9 +276397,12 @@ var init_compoundingStore = __esm({
|
|
|
276397
276397
|
init_betterSqlite3Runtime();
|
|
276398
276398
|
init_historyStoragePaths();
|
|
276399
276399
|
COMPOUNDING_DB_FILE = "gyshell-compounding.sqlite";
|
|
276400
|
-
CompoundingStore = class {
|
|
276400
|
+
CompoundingStore = class _CompoundingStore {
|
|
276401
276401
|
filePath;
|
|
276402
276402
|
db;
|
|
276403
|
+
/** Avoid sync SQLite on every agent turn (desktop freeze). Invalidated on writes. */
|
|
276404
|
+
promptCache = null;
|
|
276405
|
+
static PROMPT_CACHE_MS = 2e3;
|
|
276403
276406
|
constructor(options) {
|
|
276404
276407
|
this.filePath = options?.filePath || import_node_path28.default.join(resolveHistoryStorageDir(), COMPOUNDING_DB_FILE);
|
|
276405
276408
|
import_node_fs12.default.mkdirSync(import_node_path28.default.dirname(this.filePath), { recursive: true });
|
|
@@ -276453,6 +276456,7 @@ var init_compoundingStore = __esm({
|
|
|
276453
276456
|
recordLessons(lessons, runId) {
|
|
276454
276457
|
const now = Date.now();
|
|
276455
276458
|
const out = [];
|
|
276459
|
+
this.promptCache = null;
|
|
276456
276460
|
try {
|
|
276457
276461
|
const sel = this.db.prepare("SELECT * FROM lessons WHERE fingerprint = ?");
|
|
276458
276462
|
const ins = this.db.prepare(
|
|
@@ -276506,6 +276510,7 @@ var init_compoundingStore = __esm({
|
|
|
276506
276510
|
}
|
|
276507
276511
|
}
|
|
276508
276512
|
upsertEstateFact(input) {
|
|
276513
|
+
this.promptCache = null;
|
|
276509
276514
|
try {
|
|
276510
276515
|
const now = Date.now();
|
|
276511
276516
|
this.db.prepare(
|
|
@@ -276552,6 +276557,7 @@ var init_compoundingStore = __esm({
|
|
|
276552
276557
|
}
|
|
276553
276558
|
}
|
|
276554
276559
|
upsertGoal(input) {
|
|
276560
|
+
this.promptCache = null;
|
|
276555
276561
|
try {
|
|
276556
276562
|
this.db.prepare(
|
|
276557
276563
|
`INSERT INTO goals (id, session_id, text, status, blocked_by, next_probe, updated_at)
|
|
@@ -276589,6 +276595,7 @@ var init_compoundingStore = __esm({
|
|
|
276589
276595
|
}
|
|
276590
276596
|
}
|
|
276591
276597
|
recordProbe(sessionId, tag, hypothesis, command, ok) {
|
|
276598
|
+
this.promptCache = null;
|
|
276592
276599
|
try {
|
|
276593
276600
|
this.db.prepare(
|
|
276594
276601
|
`INSERT INTO probes (session_id, tag, hypothesis, command, ok, at)
|
|
@@ -276620,6 +276627,11 @@ var init_compoundingStore = __esm({
|
|
|
276620
276627
|
*/
|
|
276621
276628
|
promptBlock(maxChars = 4e3) {
|
|
276622
276629
|
try {
|
|
276630
|
+
const now = Date.now();
|
|
276631
|
+
if (this.promptCache && now - this.promptCache.at < _CompoundingStore.PROMPT_CACHE_MS) {
|
|
276632
|
+
const cached2 = this.promptCache.text;
|
|
276633
|
+
return cached2.length > maxChars ? cached2.slice(0, maxChars) + "\n\u2026" : cached2;
|
|
276634
|
+
}
|
|
276623
276635
|
const lessons = this.listLessons(20);
|
|
276624
276636
|
const facts = this.listEstateFacts();
|
|
276625
276637
|
const lines = ["# Compounding knowledge (auto)"];
|
|
@@ -276641,6 +276653,7 @@ var init_compoundingStore = __esm({
|
|
|
276641
276653
|
}
|
|
276642
276654
|
}
|
|
276643
276655
|
const text = lines.join("\n");
|
|
276656
|
+
this.promptCache = { at: now, text };
|
|
276644
276657
|
return text.length > maxChars ? text.slice(0, maxChars) + "\n\u2026" : text;
|
|
276645
276658
|
} catch {
|
|
276646
276659
|
return "";
|
|
@@ -296488,6 +296501,8 @@ var PSRPTransport = class {
|
|
|
296488
296501
|
/** pypsrp WSMan.session_id — one uuid for the life of the transport. */
|
|
296489
296502
|
sessionId = `uuid:${(0, import_node_crypto3.randomUUID)().toUpperCase()}`;
|
|
296490
296503
|
http;
|
|
296504
|
+
/** Persistent runspace (v3.8.0): reuse one Microsoft.PowerShell shell across commands. */
|
|
296505
|
+
pool = null;
|
|
296491
296506
|
constructor(opts) {
|
|
296492
296507
|
this.http = new WinHttpAuth({
|
|
296493
296508
|
host: opts.host,
|
|
@@ -296511,6 +296526,7 @@ var PSRPTransport = class {
|
|
|
296511
296526
|
const wsmv = "http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd";
|
|
296512
296527
|
return `<s:Envelope xmlns:s="${NS2.s}" xmlns:wsa="${NS2.a}" xmlns:wsman="${NS2.w}" xmlns:wsmv="${wsmv}" xmlns:rsp="${NS2.rsp}" xml:lang="en-US"><s:Header><wsa:Action s:mustUnderstand="true">${action}</wsa:Action><wsmv:DataLocale s:mustUnderstand="false" xml:lang="en-US" /><wsman:Locale s:mustUnderstand="false" xml:lang="en-US" /><wsman:MaxEnvelopeSize s:mustUnderstand="true">512000</wsman:MaxEnvelopeSize><wsa:MessageID>${mid}</wsa:MessageID><wsman:OperationTimeout>PT60.000S</wsman:OperationTimeout><wsa:ReplyTo><wsa:Address s:mustUnderstand="true">${NS2.a}/role/anonymous</wsa:Address></wsa:ReplyTo><wsman:ResourceURI s:mustUnderstand="true">${PSRP_SHELL_URI}</wsman:ResourceURI><wsmv:SessionId s:mustUnderstand="false">${this.sessionId}</wsmv:SessionId><wsa:To>${to}</wsa:To>${extraHeaders}</s:Header><s:Body>${body}</s:Body></s:Envelope>`;
|
|
296513
296528
|
}
|
|
296529
|
+
/** Test hook: subclasses may override. */
|
|
296514
296530
|
async post(action, body, extraHeaders) {
|
|
296515
296531
|
const envelope = this.envelope(action, body, extraHeaders);
|
|
296516
296532
|
const res = await this.http.post(envelope);
|
|
@@ -296524,6 +296540,57 @@ var PSRPTransport = class {
|
|
|
296524
296540
|
* Receive loop (PIPELINE_OUTPUT / ERROR_RECORD / PIPELINE_STATE) → Delete.
|
|
296525
296541
|
* The script travels inside the PSRP message body — no command-line length limit.
|
|
296526
296542
|
*/
|
|
296543
|
+
/** Open (or return) a persistent runspace pool. */
|
|
296544
|
+
async ensurePool(opts) {
|
|
296545
|
+
if (this.pool) return { shellId: this.pool.shellId, rpid: this.pool.rpid };
|
|
296546
|
+
const deadline = Date.now() + (opts?.timeoutMs ?? 12e4);
|
|
296547
|
+
const rpid = (0, import_node_crypto3.randomUUID)().toUpperCase();
|
|
296548
|
+
const creation = fragmentMessages([
|
|
296549
|
+
psrpMessage(MSG.SESSION_CAPABILITY, rpid, "00000000-0000-0000-0000-000000000000", sessionCapabilityXml()),
|
|
296550
|
+
psrpMessage(MSG.INIT_RUNSPACEPOOL, rpid, "00000000-0000-0000-0000-000000000000", initRunspacePoolXml())
|
|
296551
|
+
], 1);
|
|
296552
|
+
const createBody = `<rsp:Shell ShellId="${rpid}"><rsp:InputStreams>stdin pr</rsp:InputStreams><rsp:OutputStreams>stdout</rsp:OutputStreams><creationXml xmlns="http://schemas.microsoft.com/powershell">` + creation.blob.toString("base64") + `</creationXml></rsp:Shell>`;
|
|
296553
|
+
const createHeaders = `<wsman:OptionSet s:mustUnderstand="true"><wsman:Option MustComply="true" Name="protocolversion">2.3</wsman:Option></wsman:OptionSet>`;
|
|
296554
|
+
const created = await this.post(
|
|
296555
|
+
"http://schemas.xmlsoap.org/ws/2004/09/transfer/Create",
|
|
296556
|
+
createBody,
|
|
296557
|
+
createHeaders
|
|
296558
|
+
);
|
|
296559
|
+
this.assertOk(created, "Create");
|
|
296560
|
+
const shellId = extractShellId2(created.body);
|
|
296561
|
+
if (!shellId) throw new Error("PSRP Create succeeded but no ShellId returned.");
|
|
296562
|
+
await this.waitForRunspaceOpened(shellId, deadline, opts?.signal);
|
|
296563
|
+
this.pool = { shellId, rpid, nextObjectId: creation.nextObjectId };
|
|
296564
|
+
return { shellId, rpid };
|
|
296565
|
+
}
|
|
296566
|
+
async closePool() {
|
|
296567
|
+
const p = this.pool;
|
|
296568
|
+
this.pool = null;
|
|
296569
|
+
if (p) await this.deleteShell(p.shellId);
|
|
296570
|
+
}
|
|
296571
|
+
/**
|
|
296572
|
+
* Run a script on the persistent pool (opens one if needed). Does NOT delete
|
|
296573
|
+
* the shell — call closePool() when the tab dies.
|
|
296574
|
+
*/
|
|
296575
|
+
async runScriptOnPool(script, opts) {
|
|
296576
|
+
const deadline = Date.now() + (opts?.timeoutMs ?? 12e4);
|
|
296577
|
+
const pool = await this.ensurePool(opts);
|
|
296578
|
+
const pipelineId = (0, import_node_crypto3.randomUUID)().toUpperCase();
|
|
296579
|
+
const createPipelineMsg = psrpMessage(MSG.CREATE_PIPELINE, pool.rpid, pipelineId, createPipelineXml(script));
|
|
296580
|
+
const frag = fragmentMessages([createPipelineMsg], this.pool.nextObjectId);
|
|
296581
|
+
this.pool.nextObjectId = frag.nextObjectId;
|
|
296582
|
+
const commandBody = `<rsp:CommandLine CommandId="${pipelineId}"><rsp:Command></rsp:Command><rsp:Arguments>${frag.blob.toString("base64")}</rsp:Arguments></rsp:CommandLine>`;
|
|
296583
|
+
const commandHeaders = `<wsman:OptionSet s:mustUnderstand="true"><wsman:Option Name="WINRS_SKIP_CMD_SHELL">False</wsman:Option></wsman:OptionSet>` + this.shellHeaders(pool.shellId);
|
|
296584
|
+
const commanded = await this.post(
|
|
296585
|
+
"http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command",
|
|
296586
|
+
commandBody,
|
|
296587
|
+
commandHeaders
|
|
296588
|
+
);
|
|
296589
|
+
this.assertOk(commanded, "Command");
|
|
296590
|
+
const commandId = firstText2(commanded.body, "CommandId");
|
|
296591
|
+
if (!commandId) throw new Error("PSRP Command succeeded but no CommandId returned.");
|
|
296592
|
+
return this.receivePipeline(pool.shellId, commandId, deadline, opts);
|
|
296593
|
+
}
|
|
296527
296594
|
async runScript(script, opts) {
|
|
296528
296595
|
const deadline = Date.now() + (opts?.timeoutMs ?? 12e4);
|
|
296529
296596
|
const rpid = (0, import_node_crypto3.randomUUID)().toUpperCase();
|
|
@@ -296556,63 +296623,76 @@ var PSRPTransport = class {
|
|
|
296556
296623
|
this.assertOk(commanded, "Command");
|
|
296557
296624
|
const commandId = firstText2(commanded.body, "CommandId");
|
|
296558
296625
|
if (!commandId) throw new Error("PSRP Command succeeded but no CommandId returned.");
|
|
296559
|
-
|
|
296560
|
-
let stderr = "";
|
|
296561
|
-
let hadErrors = false;
|
|
296562
|
-
let exitCode = 0;
|
|
296563
|
-
let pipelineDone = false;
|
|
296564
|
-
for (; ; ) {
|
|
296565
|
-
if (opts?.signal?.aborted) throw new Error("AbortError");
|
|
296566
|
-
if (Date.now() > deadline) {
|
|
296567
|
-
throw new Error(`PSRP command timed out after ${opts?.timeoutMs ?? 12e4}ms`);
|
|
296568
|
-
}
|
|
296569
|
-
const receiveBody = `<rsp:Receive><rsp:DesiredStream CommandId="${commandId}">stdout</rsp:DesiredStream></rsp:Receive>`;
|
|
296570
|
-
const receiveHeaders = `<wsman:OptionSet s:mustUnderstand="true"><wsman:Option Name="WSMAN_CMDSHELL_OPTION_KEEPALIVE">True</wsman:Option></wsman:OptionSet>` + this.shellHeaders(shellId);
|
|
296571
|
-
const received = await this.post(
|
|
296572
|
-
"http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive",
|
|
296573
|
-
receiveBody,
|
|
296574
|
-
receiveHeaders
|
|
296575
|
-
);
|
|
296576
|
-
this.assertOk(received, "Receive");
|
|
296577
|
-
const streamRe = /<\w*:?Stream\b[^>]*\bName="(?:stdout|stderr|pr)"[^>]*>([\s\S]*?)<\/\w*:?Stream>/gi;
|
|
296578
|
-
let m2;
|
|
296579
|
-
while ((m2 = streamRe.exec(received.body)) !== null) {
|
|
296580
|
-
const b64 = m2[1].replace(/\s+/g, "");
|
|
296581
|
-
if (!b64) continue;
|
|
296582
|
-
for (const raw of unfragmentMessages(import_node_buffer2.Buffer.from(b64, "base64"))) {
|
|
296583
|
-
if (raw.length < 40) continue;
|
|
296584
|
-
const messageType = raw.readUInt32LE(4);
|
|
296585
|
-
const payload = raw.subarray(40).toString("utf8").replace(/^\uFEFF/, "");
|
|
296586
|
-
if (messageType === MSG.PIPELINE_OUTPUT) {
|
|
296587
|
-
const sRe = /<S[^>]*>([\s\S]*?)<\/S>/gi;
|
|
296588
|
-
let sm;
|
|
296589
|
-
while ((sm = sRe.exec(payload)) !== null) stdout += sm[1];
|
|
296590
|
-
} else if (messageType === MSG.ERROR_RECORD) {
|
|
296591
|
-
hadErrors = true;
|
|
296592
|
-
const mRe = payload.match(/<S N="Message">([\s\S]*?)<\/S>/i);
|
|
296593
|
-
stderr += (mRe ? mRe[1] : payload) + "\n";
|
|
296594
|
-
} else if (messageType === MSG.PIPELINE_STATE) {
|
|
296595
|
-
pipelineDone = true;
|
|
296596
|
-
const stateMatch = payload.match(/<I32 N="State">(\d+)<\/I32>/i);
|
|
296597
|
-
if (stateMatch) {
|
|
296598
|
-
if (parseInt(stateMatch[1], 10) === 5) hadErrors = true;
|
|
296599
|
-
}
|
|
296600
|
-
const ec = payload.match(/N="ExitCode"[^>]*>(-?\d+)</i);
|
|
296601
|
-
if (ec) exitCode = parseInt(ec[1], 10);
|
|
296602
|
-
}
|
|
296603
|
-
}
|
|
296604
|
-
}
|
|
296605
|
-
const wsExit = received.body.match(/<\w*:?ExitCode>\s*(-?\d+)\s*<\/\w*:?ExitCode>/i);
|
|
296606
|
-
if (wsExit) exitCode = parseInt(wsExit[1], 10);
|
|
296607
|
-
if (pipelineDone) break;
|
|
296608
|
-
if (/CommandState\/Done/i.test(received.body)) break;
|
|
296609
|
-
}
|
|
296610
|
-
if (exitCode !== 0) hadErrors = true;
|
|
296611
|
-
return { stdout, stderr, exitCode, hadErrors };
|
|
296626
|
+
return await this.receivePipeline(shellId, commandId, deadline, opts);
|
|
296612
296627
|
} finally {
|
|
296613
296628
|
await this.deleteShell(shellId);
|
|
296614
296629
|
}
|
|
296615
296630
|
}
|
|
296631
|
+
async receivePipeline(shellId, commandId, deadline, opts) {
|
|
296632
|
+
let stdout = "";
|
|
296633
|
+
let stderr = "";
|
|
296634
|
+
let hadErrors = false;
|
|
296635
|
+
let exitCode = 0;
|
|
296636
|
+
let pipelineDone = false;
|
|
296637
|
+
for (; ; ) {
|
|
296638
|
+
if (opts?.signal?.aborted) throw new Error("AbortError");
|
|
296639
|
+
if (Date.now() > deadline) {
|
|
296640
|
+
throw new Error(`PSRP command timed out after ${opts?.timeoutMs ?? 12e4}ms`);
|
|
296641
|
+
}
|
|
296642
|
+
const receiveBody = `<rsp:Receive><rsp:DesiredStream CommandId="${commandId}">stdout</rsp:DesiredStream></rsp:Receive>`;
|
|
296643
|
+
const receiveHeaders = `<wsman:OptionSet s:mustUnderstand="true"><wsman:Option Name="WSMAN_CMDSHELL_OPTION_KEEPALIVE">True</wsman:Option></wsman:OptionSet>` + this.shellHeaders(shellId);
|
|
296644
|
+
const received = await this.post(
|
|
296645
|
+
"http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive",
|
|
296646
|
+
receiveBody,
|
|
296647
|
+
receiveHeaders
|
|
296648
|
+
);
|
|
296649
|
+
this.assertOk(received, "Receive");
|
|
296650
|
+
const streamRe = /<\w*:?Stream\b[^>]*\bName="(?:stdout|stderr|pr)"[^>]*>([\s\S]*?)<\/\w*:?Stream>/gi;
|
|
296651
|
+
let m2;
|
|
296652
|
+
while ((m2 = streamRe.exec(received.body)) !== null) {
|
|
296653
|
+
const b64 = m2[1].replace(/\s+/g, "");
|
|
296654
|
+
if (!b64) continue;
|
|
296655
|
+
for (const raw of unfragmentMessages(import_node_buffer2.Buffer.from(b64, "base64"))) {
|
|
296656
|
+
if (raw.length < 40) continue;
|
|
296657
|
+
const messageType = raw.readUInt32LE(4);
|
|
296658
|
+
const payload = raw.subarray(40).toString("utf8").replace(/^\uFEFF/, "");
|
|
296659
|
+
if (messageType === MSG.PIPELINE_OUTPUT) {
|
|
296660
|
+
const sRe = /<S[^>]*>([\s\S]*?)<\/S>/gi;
|
|
296661
|
+
let sm;
|
|
296662
|
+
let extracted = false;
|
|
296663
|
+
while ((sm = sRe.exec(payload)) !== null) {
|
|
296664
|
+
stdout += sm[1];
|
|
296665
|
+
extracted = true;
|
|
296666
|
+
}
|
|
296667
|
+
if (!extracted) {
|
|
296668
|
+
const nRe = /<(?:I32|I64|B|ToString)[^>]*>([\s\S]*?)<\/(?:I32|I64|B|ToString)>/i;
|
|
296669
|
+
const nm = payload.match(nRe);
|
|
296670
|
+
if (nm) stdout += nm[1];
|
|
296671
|
+
else stdout += payload.replace(/<[^>]+>/g, "").trim();
|
|
296672
|
+
}
|
|
296673
|
+
} else if (messageType === MSG.ERROR_RECORD) {
|
|
296674
|
+
hadErrors = true;
|
|
296675
|
+
const mRe = payload.match(/<S N="Message">([\s\S]*?)<\/S>/i);
|
|
296676
|
+
stderr += (mRe ? mRe[1] : payload) + "\n";
|
|
296677
|
+
} else if (messageType === MSG.PIPELINE_STATE) {
|
|
296678
|
+
pipelineDone = true;
|
|
296679
|
+
const stateMatch = payload.match(/<I32 N="State">(\d+)<\/I32>/i);
|
|
296680
|
+
if (stateMatch) {
|
|
296681
|
+
if (parseInt(stateMatch[1], 10) === 5) hadErrors = true;
|
|
296682
|
+
}
|
|
296683
|
+
const ec = payload.match(/N="ExitCode"[^>]*>(-?\d+)</i);
|
|
296684
|
+
if (ec) exitCode = parseInt(ec[1], 10);
|
|
296685
|
+
}
|
|
296686
|
+
}
|
|
296687
|
+
}
|
|
296688
|
+
const wsExit = received.body.match(/<\w*:?ExitCode>\s*(-?\d+)\s*<\/\w*:?ExitCode>/i);
|
|
296689
|
+
if (wsExit) exitCode = parseInt(wsExit[1], 10);
|
|
296690
|
+
if (pipelineDone) break;
|
|
296691
|
+
if (/CommandState\/Done/i.test(received.body)) break;
|
|
296692
|
+
}
|
|
296693
|
+
if (exitCode !== 0) hadErrors = true;
|
|
296694
|
+
return { stdout, stderr, exitCode, hadErrors };
|
|
296695
|
+
}
|
|
296616
296696
|
/**
|
|
296617
296697
|
* Drain Receive until RUNSPACEPOOL_STATE reports Opened (state=2).
|
|
296618
296698
|
* Must run after Create and before Command.
|
|
@@ -296817,7 +296897,7 @@ Run commands with exec_command / run_fleet_command. Interactive TUI apps are not
|
|
|
296817
296897
|
\x1B[36m\u276F ${command}\x1B[0m\r
|
|
296818
296898
|
`);
|
|
296819
296899
|
const ps = this.translateCmdToPowerShell(command, instance.cwd);
|
|
296820
|
-
const result = await transport.
|
|
296900
|
+
const result = await transport.runScriptOnPool(ps, {
|
|
296821
296901
|
timeoutMs: options?.timeoutMs ?? DEFAULT_WINRM_TIMEOUT_MS,
|
|
296822
296902
|
signal: options?.signal
|
|
296823
296903
|
});
|
|
@@ -296938,6 +297018,9 @@ Run commands with exec_command / run_fleet_command. Interactive TUI apps are not
|
|
|
296938
297018
|
void instance.winrm.deleteShell(instance.persistentShellId);
|
|
296939
297019
|
instance.persistentShellId = void 0;
|
|
296940
297020
|
}
|
|
297021
|
+
if (instance.psrp) {
|
|
297022
|
+
void instance.psrp.closePool();
|
|
297023
|
+
}
|
|
296941
297024
|
instance.exitCallback?.(0);
|
|
296942
297025
|
}
|
|
296943
297026
|
onData(ptyId, callback) {
|
|
@@ -345277,6 +345360,28 @@ var MessagesZodState = external_exports.object({ messages: withLangGraph(externa
|
|
|
345277
345360
|
// ../../node_modules/@langchain/langgraph/dist/index.js
|
|
345278
345361
|
initializeAsyncLocalStorageSingleton();
|
|
345279
345362
|
|
|
345363
|
+
// ../../packages/backend/src/services/redaction/secretRedact.ts
|
|
345364
|
+
var PATTERNS = [
|
|
345365
|
+
{ re: /\b(gys_at_[A-Za-z0-9_-]{16,})\b/g, label: "RTERM_TOKEN" },
|
|
345366
|
+
{ re: /\b(ghp_[A-Za-z0-9]{20,})\b/g, label: "GITHUB_PAT" },
|
|
345367
|
+
{ re: /\b(sk-[A-Za-z0-9_-]{20,})\b/g, label: "API_KEY" },
|
|
345368
|
+
{ re: /\b(AKIA[0-9A-Z]{16})\b/g, label: "AWS_KEY" },
|
|
345369
|
+
{ re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g, label: "PRIVATE_KEY" },
|
|
345370
|
+
{ re: /(password|passwd|pwd|secret|token)\s*[:=]\s*["']?([^\s"']{6,})/gi, label: "CREDENTIAL" }
|
|
345371
|
+
];
|
|
345372
|
+
function redactText(input, extraValues = []) {
|
|
345373
|
+
let out = String(input ?? "");
|
|
345374
|
+
for (const v of extraValues) {
|
|
345375
|
+
const t = String(v || "");
|
|
345376
|
+
if (t.length < 4) continue;
|
|
345377
|
+
out = out.split(t).join(`[REDACTED]`);
|
|
345378
|
+
}
|
|
345379
|
+
for (const p of PATTERNS) {
|
|
345380
|
+
out = out.replace(p.re, `[REDACTED:${p.label}]`);
|
|
345381
|
+
}
|
|
345382
|
+
return out;
|
|
345383
|
+
}
|
|
345384
|
+
|
|
345280
345385
|
// ../../packages/backend/src/services/agentRunLedger.ts
|
|
345281
345386
|
var import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
345282
345387
|
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
@@ -366951,6 +367056,11 @@ function reconcileToolCalls(toolCalls) {
|
|
|
366951
367056
|
}
|
|
366952
367057
|
return result;
|
|
366953
367058
|
}
|
|
367059
|
+
var TOOL_RESULT_MAX_CHARS = 32768;
|
|
367060
|
+
function stringifyToolResult(result) {
|
|
367061
|
+
const raw = typeof result === "string" ? result : JSON.stringify(result);
|
|
367062
|
+
return redactText(clipTextMiddle(raw, TOOL_RESULT_MAX_CHARS));
|
|
367063
|
+
}
|
|
366954
367064
|
function clipTextMiddle(input, maxChars) {
|
|
366955
367065
|
if (maxChars <= 0) return "";
|
|
366956
367066
|
if (input.length <= maxChars) return input;
|
|
@@ -368873,7 +368983,7 @@ Actually, your intention might be different. Please re-read the description of t
|
|
|
368873
368983
|
try {
|
|
368874
368984
|
const pluginArgs = typeof toolCall.args === "string" ? JSON.parse(toolCall.args) : toolCall.args || {};
|
|
368875
368985
|
const pluginResult = await pluginHandler(pluginArgs);
|
|
368876
|
-
result =
|
|
368986
|
+
result = stringifyToolResult(pluginResult);
|
|
368877
368987
|
} catch (err) {
|
|
368878
368988
|
result = `Plugin tool "${toolCall.name}" error: ${err.message}`;
|
|
368879
368989
|
}
|
|
@@ -369161,7 +369271,7 @@ Actually, your intention might be different. Please re-read the description of t
|
|
|
369161
369271
|
const pluginHandler = this.pluginTools.get(name);
|
|
369162
369272
|
if (pluginHandler) {
|
|
369163
369273
|
const result = await pluginHandler(args);
|
|
369164
|
-
return
|
|
369274
|
+
return stringifyToolResult(result);
|
|
369165
369275
|
}
|
|
369166
369276
|
return `Tool "${name}" is not supported in parallel execution mode.`;
|
|
369167
369277
|
}
|
|
@@ -369242,7 +369352,7 @@ Actually, your intention might be different. Please re-read the description of t
|
|
|
369242
369352
|
args,
|
|
369243
369353
|
signal
|
|
369244
369354
|
);
|
|
369245
|
-
resultText =
|
|
369355
|
+
resultText = stringifyToolResult(result);
|
|
369246
369356
|
} catch (err) {
|
|
369247
369357
|
if (this.helpers.isAbortError(err)) throw err;
|
|
369248
369358
|
resultText = err instanceof Error ? err.message : String(err);
|
|
@@ -373011,6 +373121,26 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
373011
373121
|
|
|
373012
373122
|
// ../../packages/backend/src/services/Gateway/WebSocketGatewayAdapter.ts
|
|
373013
373123
|
var import_node_module4 = require("node:module");
|
|
373124
|
+
|
|
373125
|
+
// ../../packages/backend/src/services/security/tokenScopes.ts
|
|
373126
|
+
var SCOPE_FULL = "*";
|
|
373127
|
+
function methodAllowed(method, scopes) {
|
|
373128
|
+
if (!scopes || scopes.length === 0) return true;
|
|
373129
|
+
if (scopes.includes(SCOPE_FULL)) return true;
|
|
373130
|
+
const m2 = String(method || "");
|
|
373131
|
+
for (const s of scopes) {
|
|
373132
|
+
if (s === m2) return true;
|
|
373133
|
+
if (s.endsWith(":*")) {
|
|
373134
|
+
const prefix = s.slice(0, -1);
|
|
373135
|
+
if (m2.startsWith(prefix)) return true;
|
|
373136
|
+
if (prefix === "terminals:" && m2.startsWith("terminal:")) return true;
|
|
373137
|
+
}
|
|
373138
|
+
if (s.endsWith("*") && m2.startsWith(s.slice(0, -1))) return true;
|
|
373139
|
+
}
|
|
373140
|
+
return false;
|
|
373141
|
+
}
|
|
373142
|
+
|
|
373143
|
+
// ../../packages/backend/src/services/Gateway/WebSocketGatewayAdapter.ts
|
|
373014
373144
|
var import_meta7 = {};
|
|
373015
373145
|
var nodeRequire = (0, import_node_module4.createRequire)(
|
|
373016
373146
|
typeof __filename !== "undefined" ? __filename : import_meta7.url
|
|
@@ -373021,6 +373151,22 @@ var WebSocketRpcError = class extends Error {
|
|
|
373021
373151
|
this.code = code;
|
|
373022
373152
|
}
|
|
373023
373153
|
};
|
|
373154
|
+
function httpRouteMatches(pattern, pathname) {
|
|
373155
|
+
if (pattern === pathname) return true;
|
|
373156
|
+
if (pattern.endsWith("/*")) {
|
|
373157
|
+
const prefix = pattern.slice(0, -1);
|
|
373158
|
+
const base = pattern.slice(0, -2);
|
|
373159
|
+
return pathname === base || pathname.startsWith(prefix) || pathname.startsWith(base + "/");
|
|
373160
|
+
}
|
|
373161
|
+
const a = pattern.split("/").filter(Boolean);
|
|
373162
|
+
const b = pathname.split("/").filter(Boolean);
|
|
373163
|
+
if (a.length !== b.length) return false;
|
|
373164
|
+
for (let i = 0; i < a.length; i++) {
|
|
373165
|
+
if (a[i].startsWith(":")) continue;
|
|
373166
|
+
if (a[i] !== b[i]) return false;
|
|
373167
|
+
}
|
|
373168
|
+
return true;
|
|
373169
|
+
}
|
|
373024
373170
|
function createDefaultWebSocketServerFactory(httpRoutes) {
|
|
373025
373171
|
return ({ host, port }) => {
|
|
373026
373172
|
if (!httpRoutes || httpRoutes.length === 0) {
|
|
@@ -373043,7 +373189,7 @@ function createDefaultWebSocketServerFactory(httpRoutes) {
|
|
|
373043
373189
|
).pathname;
|
|
373044
373190
|
} catch {
|
|
373045
373191
|
}
|
|
373046
|
-
const route = routes.find((r) => r.path
|
|
373192
|
+
const route = routes.find((r) => httpRouteMatches(r.path, pathname));
|
|
373047
373193
|
if (!route) {
|
|
373048
373194
|
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
373049
373195
|
res.end("not found");
|
|
@@ -373099,6 +373245,7 @@ var WebSocketGatewayAdapter = class {
|
|
|
373099
373245
|
server = null;
|
|
373100
373246
|
transportIdBySocket = /* @__PURE__ */ new Map();
|
|
373101
373247
|
isSameMachineBySocket = /* @__PURE__ */ new WeakMap();
|
|
373248
|
+
scopesBySocket = /* @__PURE__ */ new WeakMap();
|
|
373102
373249
|
serverFactory;
|
|
373103
373250
|
logger;
|
|
373104
373251
|
start() {
|
|
@@ -373196,6 +373343,12 @@ var WebSocketGatewayAdapter = class {
|
|
|
373196
373343
|
this.isLoopbackAddress(String(remote))
|
|
373197
373344
|
);
|
|
373198
373345
|
this.gateway.registerTransport(transport);
|
|
373346
|
+
const token = this.extractAccessToken(request);
|
|
373347
|
+
const scopeFn = this.options.accessTokenAuth?.scopesForToken;
|
|
373348
|
+
if (token && scopeFn) {
|
|
373349
|
+
const scopes = await scopeFn(token);
|
|
373350
|
+
if (scopes && scopes.length) this.scopesBySocket.set(socket, scopes);
|
|
373351
|
+
}
|
|
373199
373352
|
state.authorized = true;
|
|
373200
373353
|
this.logger.info(
|
|
373201
373354
|
`[WebSocketGatewayAdapter] Client connected: ${remote} (${transport.id})`
|
|
@@ -373493,6 +373646,13 @@ var WebSocketGatewayAdapter = class {
|
|
|
373493
373646
|
);
|
|
373494
373647
|
}
|
|
373495
373648
|
async executeRequest(request, socket) {
|
|
373649
|
+
const scopes = this.scopesBySocket.get(socket);
|
|
373650
|
+
if (scopes && !methodAllowed(request.method, scopes)) {
|
|
373651
|
+
throw new WebSocketRpcError(
|
|
373652
|
+
"FORBIDDEN",
|
|
373653
|
+
`Token is not allowed to call ${request.method}`
|
|
373654
|
+
);
|
|
373655
|
+
}
|
|
373496
373656
|
const params = request.params ?? {};
|
|
373497
373657
|
if (request.method.startsWith("observability:")) {
|
|
373498
373658
|
const bridge = this.options.observabilityBridge;
|
|
@@ -384954,8 +385114,9 @@ var IdleTimeoutService = class {
|
|
|
384954
385114
|
// ../../packages/backend/src/services/Gateway/restApi.ts
|
|
384955
385115
|
function matchRestRoute(routes, method, path33) {
|
|
384956
385116
|
const normalized = path33.replace(/\/+$/, "") || "/";
|
|
385117
|
+
const verb = method.toUpperCase();
|
|
384957
385118
|
for (const route of routes) {
|
|
384958
|
-
if (route.method !==
|
|
385119
|
+
if (route.method !== verb) continue;
|
|
384959
385120
|
const patternParts = route.path.split("/").filter(Boolean);
|
|
384960
385121
|
const pathParts = normalized.split("/").filter(Boolean);
|
|
384961
385122
|
if (patternParts.length !== pathParts.length) continue;
|
|
@@ -384976,36 +385137,18 @@ function matchRestRoute(routes, method, path33) {
|
|
|
384976
385137
|
}
|
|
384977
385138
|
function defaultRestRoutes() {
|
|
384978
385139
|
return [
|
|
385140
|
+
{ method: "GET", path: "/api/v1/health", gatewayMethod: "gateway:ping", description: "Liveness check" },
|
|
385141
|
+
{ method: "GET", path: "/api/v1/methods", gatewayMethod: "gateway:describe", description: "List all gateway RPC methods" },
|
|
385142
|
+
{ method: "GET", path: "/api/v1/openapi.json", gatewayMethod: "__openapi", description: "OpenAPI 3 document for this REST overlay + RPC escape hatch" },
|
|
385143
|
+
{ method: "GET", path: "/api/v1/terminals", gatewayMethod: "terminal:list", description: "List terminal tabs" },
|
|
385144
|
+
{ method: "GET", path: "/api/v1/sessions", gatewayMethod: "session:list", description: "List chat sessions" },
|
|
384979
385145
|
{
|
|
384980
|
-
method: "
|
|
384981
|
-
path: "/api/v1/health",
|
|
384982
|
-
gatewayMethod: "gateway:ping",
|
|
384983
|
-
description: "Liveness check"
|
|
384984
|
-
},
|
|
384985
|
-
{
|
|
384986
|
-
method: "GET",
|
|
384987
|
-
path: "/api/v1/methods",
|
|
384988
|
-
gatewayMethod: "gateway:describe",
|
|
384989
|
-
description: "List all gateway methods (self-describing)"
|
|
384990
|
-
},
|
|
384991
|
-
{
|
|
384992
|
-
method: "GET",
|
|
384993
|
-
path: "/api/v1/terminals",
|
|
384994
|
-
gatewayMethod: "terminal:list",
|
|
384995
|
-
description: "List terminal tabs"
|
|
384996
|
-
},
|
|
384997
|
-
{
|
|
384998
|
-
method: "GET",
|
|
385146
|
+
method: "POST",
|
|
384999
385147
|
path: "/api/v1/sessions",
|
|
385000
|
-
gatewayMethod: "
|
|
385001
|
-
description: "
|
|
385002
|
-
},
|
|
385003
|
-
{
|
|
385004
|
-
method: "GET",
|
|
385005
|
-
path: "/api/v1/skills",
|
|
385006
|
-
gatewayMethod: "skills:getAll",
|
|
385007
|
-
description: "List loaded skills"
|
|
385148
|
+
gatewayMethod: "gateway:createSession",
|
|
385149
|
+
description: "Create a chat/agent session"
|
|
385008
385150
|
},
|
|
385151
|
+
{ method: "GET", path: "/api/v1/skills", gatewayMethod: "skills:getAll", description: "List loaded skills" },
|
|
385009
385152
|
{
|
|
385010
385153
|
method: "GET",
|
|
385011
385154
|
path: "/api/v1/observability/metrics",
|
|
@@ -385022,13 +385165,13 @@ function defaultRestRoutes() {
|
|
|
385022
385165
|
method: "GET",
|
|
385023
385166
|
path: "/api/v1/observability/apm",
|
|
385024
385167
|
gatewayMethod: "observability:apmSummary",
|
|
385025
|
-
description: "APM summary
|
|
385168
|
+
description: "APM summary"
|
|
385026
385169
|
},
|
|
385027
385170
|
{
|
|
385028
385171
|
method: "GET",
|
|
385029
385172
|
path: "/api/v1/history/search",
|
|
385030
385173
|
gatewayMethod: "history:search",
|
|
385031
|
-
description: "Cross-session history search (?q
|
|
385174
|
+
description: "Cross-session history search (?q=)",
|
|
385032
385175
|
buildParams: (_p, body) => {
|
|
385033
385176
|
const b = body ?? {};
|
|
385034
385177
|
return { query: b.q ?? b.query ?? "" };
|
|
@@ -385048,17 +385191,18 @@ function defaultRestRoutes() {
|
|
|
385048
385191
|
method: "GET",
|
|
385049
385192
|
path: "/api/v1/terminals/:id/buffer",
|
|
385050
385193
|
gatewayMethod: "terminal:getBufferDelta",
|
|
385051
|
-
description: "Read
|
|
385194
|
+
description: "Read terminal output delta (?fromOffset=)",
|
|
385052
385195
|
buildParams: (p, body) => {
|
|
385053
385196
|
const b = body ?? {};
|
|
385054
|
-
|
|
385197
|
+
const n2 = Number(b.fromOffset ?? 0);
|
|
385198
|
+
return { terminalId: p.id, fromOffset: Number.isFinite(n2) ? n2 : 0 };
|
|
385055
385199
|
}
|
|
385056
385200
|
},
|
|
385057
385201
|
{
|
|
385058
385202
|
method: "POST",
|
|
385059
385203
|
path: "/api/v1/sessions/:id/chat",
|
|
385060
385204
|
gatewayMethod: "agent:startTask",
|
|
385061
|
-
description: "Send a message
|
|
385205
|
+
description: "Send a message (blocking until the run finishes \u2014 prefer chat-async)",
|
|
385062
385206
|
buildParams: (p, body) => {
|
|
385063
385207
|
const b = body ?? {};
|
|
385064
385208
|
return { sessionId: p.id, userInput: b.message ?? b.userInput ?? "" };
|
|
@@ -385066,17 +385210,66 @@ function defaultRestRoutes() {
|
|
|
385066
385210
|
},
|
|
385067
385211
|
{
|
|
385068
385212
|
method: "POST",
|
|
385069
|
-
path: "/api/v1/
|
|
385070
|
-
gatewayMethod: "",
|
|
385071
|
-
description: "
|
|
385072
|
-
buildParams: (
|
|
385213
|
+
path: "/api/v1/sessions/:id/chat-async",
|
|
385214
|
+
gatewayMethod: "agent:startTaskAsync",
|
|
385215
|
+
description: "Start an agent turn without waiting; subscribe to WS gateway:event for tokens",
|
|
385216
|
+
buildParams: (p, body) => {
|
|
385073
385217
|
const b = body ?? {};
|
|
385074
|
-
return {
|
|
385218
|
+
return { sessionId: p.id, userInput: b.message ?? b.userInput ?? "" };
|
|
385075
385219
|
}
|
|
385220
|
+
},
|
|
385221
|
+
{
|
|
385222
|
+
method: "POST",
|
|
385223
|
+
path: "/api/v1/rpc",
|
|
385224
|
+
gatewayMethod: "",
|
|
385225
|
+
description: "Escape hatch: any gateway method {method, params}"
|
|
385076
385226
|
}
|
|
385077
385227
|
];
|
|
385078
385228
|
}
|
|
385229
|
+
function buildOpenApiDocument() {
|
|
385230
|
+
const routes = defaultRestRoutes();
|
|
385231
|
+
const paths = {};
|
|
385232
|
+
for (const r of routes) {
|
|
385233
|
+
if (r.gatewayMethod === "__openapi") continue;
|
|
385234
|
+
const item = paths[r.path] || {};
|
|
385235
|
+
item[r.method.toLowerCase()] = {
|
|
385236
|
+
summary: r.description,
|
|
385237
|
+
operationId: `${r.method}_${r.path.replace(/[^a-zA-Z0-9]+/g, "_")}`,
|
|
385238
|
+
tags: ["rest"],
|
|
385239
|
+
...r.method === "POST" ? {
|
|
385240
|
+
requestBody: {
|
|
385241
|
+
content: { "application/json": { schema: { type: "object" } } }
|
|
385242
|
+
}
|
|
385243
|
+
} : {},
|
|
385244
|
+
responses: { "200": { description: "OK" }, "401": { description: "Unauthorized" } }
|
|
385245
|
+
};
|
|
385246
|
+
paths[r.path] = item;
|
|
385247
|
+
}
|
|
385248
|
+
const rpcMethods = [...CORE_METHODS, DESCRIBE_METHOD].map((m2) => m2.name);
|
|
385249
|
+
return {
|
|
385250
|
+
openapi: "3.0.3",
|
|
385251
|
+
info: {
|
|
385252
|
+
title: "RTerm HTTP overlay",
|
|
385253
|
+
version: "1.0.0",
|
|
385254
|
+
description: `Thin REST on the same port as the WebSocket JSON-RPC gateway. Streaming agent/PTY traffic stays on ws://. POST /api/v1/rpc reaches every RPC method. Core RPC methods: ${rpcMethods.length}. Categories: ${METHOD_CATEGORIES.join(", ")}.`
|
|
385255
|
+
},
|
|
385256
|
+
paths,
|
|
385257
|
+
"x-gateway-rpc-methods": rpcMethods
|
|
385258
|
+
};
|
|
385259
|
+
}
|
|
385079
385260
|
async function handleRestRequest(routes, dispatch, req) {
|
|
385261
|
+
const verb = req.method.toUpperCase();
|
|
385262
|
+
if (verb === "OPTIONS") {
|
|
385263
|
+
return {
|
|
385264
|
+
status: 204,
|
|
385265
|
+
body: "",
|
|
385266
|
+
headers: {
|
|
385267
|
+
"access-control-allow-origin": "*",
|
|
385268
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
385269
|
+
"access-control-allow-headers": "Authorization, Content-Type"
|
|
385270
|
+
}
|
|
385271
|
+
};
|
|
385272
|
+
}
|
|
385080
385273
|
const match = matchRestRoute(routes, req.method, req.path);
|
|
385081
385274
|
if (!match) {
|
|
385082
385275
|
return {
|
|
@@ -385084,12 +385277,18 @@ async function handleRestRequest(routes, dispatch, req) {
|
|
|
385084
385277
|
body: { error: "not_found", message: `No REST route for ${req.method} ${req.path}` }
|
|
385085
385278
|
};
|
|
385086
385279
|
}
|
|
385280
|
+
if (match.route.gatewayMethod === "__openapi") {
|
|
385281
|
+
return { status: 200, body: buildOpenApiDocument() };
|
|
385282
|
+
}
|
|
385087
385283
|
let gatewayMethod = match.route.gatewayMethod;
|
|
385088
385284
|
let params;
|
|
385089
385285
|
if (gatewayMethod === "") {
|
|
385090
385286
|
const raw = req.body ?? {};
|
|
385091
385287
|
if (!raw.method) {
|
|
385092
|
-
return {
|
|
385288
|
+
return {
|
|
385289
|
+
status: 400,
|
|
385290
|
+
body: { error: "bad_request", message: 'POST /api/v1/rpc needs {"method": "...", "params": {...}}' }
|
|
385291
|
+
};
|
|
385093
385292
|
}
|
|
385094
385293
|
gatewayMethod = raw.method;
|
|
385095
385294
|
params = raw.params ?? {};
|
|
@@ -385105,6 +385304,52 @@ async function handleRestRequest(routes, dispatch, req) {
|
|
|
385105
385304
|
return { status, body: { error: "gateway_error", message } };
|
|
385106
385305
|
}
|
|
385107
385306
|
}
|
|
385307
|
+
var JSON_HEADERS = { "content-type": "application/json; charset=utf-8" };
|
|
385308
|
+
function readJsonBody(req) {
|
|
385309
|
+
return new Promise((resolve2) => {
|
|
385310
|
+
let data = "";
|
|
385311
|
+
req.on?.("data", (d) => {
|
|
385312
|
+
data += String(d ?? "");
|
|
385313
|
+
if (data.length > 2e6) {
|
|
385314
|
+
resolve2({});
|
|
385315
|
+
}
|
|
385316
|
+
});
|
|
385317
|
+
req.on?.("end", () => {
|
|
385318
|
+
try {
|
|
385319
|
+
resolve2(data ? JSON.parse(data) : {});
|
|
385320
|
+
} catch {
|
|
385321
|
+
resolve2({});
|
|
385322
|
+
}
|
|
385323
|
+
});
|
|
385324
|
+
});
|
|
385325
|
+
}
|
|
385326
|
+
function makeRestCatchAllHandler(opts) {
|
|
385327
|
+
const routes = defaultRestRoutes();
|
|
385328
|
+
return async (req, res) => {
|
|
385329
|
+
const r = req;
|
|
385330
|
+
const s = res;
|
|
385331
|
+
try {
|
|
385332
|
+
if (!await opts.isAuthorized(r)) {
|
|
385333
|
+
s.writeHead?.(401, JSON_HEADERS);
|
|
385334
|
+
s.end?.(JSON.stringify({ error: "unauthorized" }));
|
|
385335
|
+
return;
|
|
385336
|
+
}
|
|
385337
|
+
const url2 = new URL(r.url ?? "/", "http://localhost");
|
|
385338
|
+
const body = r.method === "POST" || r.method === "PUT" ? await readJsonBody(r) : Object.fromEntries(url2.searchParams.entries());
|
|
385339
|
+
const result = await handleRestRequest(routes, opts.dispatch, {
|
|
385340
|
+
method: r.method ?? "GET",
|
|
385341
|
+
path: url2.pathname,
|
|
385342
|
+
body
|
|
385343
|
+
});
|
|
385344
|
+
s.writeHead?.(result.status, { ...JSON_HEADERS, ...result.headers ?? {} });
|
|
385345
|
+
if (result.body === "" || result.body === void 0) s.end?.();
|
|
385346
|
+
else s.end?.(typeof result.body === "string" ? result.body : JSON.stringify(result.body));
|
|
385347
|
+
} catch (e) {
|
|
385348
|
+
s.writeHead?.(500, JSON_HEADERS);
|
|
385349
|
+
s.end?.(JSON.stringify({ error: "internal", message: e instanceof Error ? e.message : String(e) }));
|
|
385350
|
+
}
|
|
385351
|
+
};
|
|
385352
|
+
}
|
|
385108
385353
|
|
|
385109
385354
|
// ../../packages/backend/src/services/Gateway/gatewayRateLimit.ts
|
|
385110
385355
|
var GatewayRateLimiter = class {
|
|
@@ -396172,51 +396417,8 @@ async function startGyBackend() {
|
|
|
396172
396417
|
return await target.handleRequest(method, params);
|
|
396173
396418
|
};
|
|
396174
396419
|
const buildRestHttpRoutesSync = (opts) => {
|
|
396175
|
-
|
|
396176
|
-
const staticRoutes = routes.filter((r) => !r.path.includes(":")).map((route) => ({
|
|
396177
|
-
path: route.path,
|
|
396178
|
-
handler: makeRestHandler(routes, opts)
|
|
396179
|
-
}));
|
|
396180
|
-
return staticRoutes;
|
|
396420
|
+
return [{ path: "/api/v1/*", handler: makeRestCatchAllHandler(opts) }];
|
|
396181
396421
|
};
|
|
396182
|
-
const makeRestHandler = (routes, opts) => {
|
|
396183
|
-
return async (req, res) => {
|
|
396184
|
-
const r = req;
|
|
396185
|
-
const s = res;
|
|
396186
|
-
try {
|
|
396187
|
-
if (!await opts.isAuthorized(r)) {
|
|
396188
|
-
s.writeHead?.(401, { "content-type": "application/json" });
|
|
396189
|
-
s.end?.(JSON.stringify({ error: "unauthorized" }));
|
|
396190
|
-
return;
|
|
396191
|
-
}
|
|
396192
|
-
const url2 = new URL(r.url ?? "/", "http://localhost");
|
|
396193
|
-
const body = r.method === "POST" ? await readJsonBody(r) : Object.fromEntries(url2.searchParams.entries());
|
|
396194
|
-
const result = await handleRestRequest(routes, opts.dispatch, {
|
|
396195
|
-
method: r.method ?? "GET",
|
|
396196
|
-
path: url2.pathname,
|
|
396197
|
-
body
|
|
396198
|
-
});
|
|
396199
|
-
s.writeHead?.(result.status, { "content-type": "application/json" });
|
|
396200
|
-
s.end?.(JSON.stringify(result.body));
|
|
396201
|
-
} catch (e) {
|
|
396202
|
-
s.writeHead?.(500, { "content-type": "application/json" });
|
|
396203
|
-
s.end?.(JSON.stringify({ error: "internal", message: e instanceof Error ? e.message : String(e) }));
|
|
396204
|
-
}
|
|
396205
|
-
};
|
|
396206
|
-
};
|
|
396207
|
-
const readJsonBody = (req) => new Promise((resolve2) => {
|
|
396208
|
-
let data = "";
|
|
396209
|
-
req.on?.("data", (d) => {
|
|
396210
|
-
data += String(d ?? "");
|
|
396211
|
-
});
|
|
396212
|
-
req.on?.("end", () => {
|
|
396213
|
-
try {
|
|
396214
|
-
resolve2(data ? JSON.parse(data) : {});
|
|
396215
|
-
} catch {
|
|
396216
|
-
resolve2({});
|
|
396217
|
-
}
|
|
396218
|
-
});
|
|
396219
|
-
});
|
|
396220
396422
|
const wsGatewayControlService = new WebSocketGatewayControlService({
|
|
396221
396423
|
createAdapter: (host, port, ipFilter) => {
|
|
396222
396424
|
const adapter = new WebSocketGatewayAdapter(gatewayService, {
|
|
@@ -396712,6 +396914,9 @@ async function startGyBackend() {
|
|
|
396712
396914
|
console.log(
|
|
396713
396915
|
`[gybackend] Live dashboard: http://${wsState.host}:${wsState.port}/dashboard`
|
|
396714
396916
|
);
|
|
396917
|
+
console.log(
|
|
396918
|
+
`[gybackend] REST API: http://${wsState.host}:${wsState.port}/api/v1/health (OpenAPI /api/v1/openapi.json)`
|
|
396919
|
+
);
|
|
396715
396920
|
} else {
|
|
396716
396921
|
console.log("[gybackend] WebSocket RPC endpoint: disabled");
|
|
396717
396922
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rterm-backend",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
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; executes over SSH/WinRM/serial under policy with tamper-evident audit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"forward-deployed-engineer",
|