neuralos 3.2.18 → 3.3.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 +1765 -612
- package/package.json +2 -2
package/bin/gybackend.cjs
CHANGED
|
@@ -15957,7 +15957,11 @@ function resolveHistoryStorageDir() {
|
|
|
15957
15957
|
if (overrideDir) {
|
|
15958
15958
|
return import_node_path4.default.resolve(overrideDir);
|
|
15959
15959
|
}
|
|
15960
|
-
|
|
15960
|
+
const gybackendDataDir = (process.env.GYBACKEND_DATA_DIR || "").trim();
|
|
15961
|
+
if (gybackendDataDir) {
|
|
15962
|
+
return import_node_path4.default.resolve(gybackendDataDir);
|
|
15963
|
+
}
|
|
15964
|
+
return import_node_path4.default.join(import_node_os.default.homedir(), ".gybackend-data");
|
|
15961
15965
|
}
|
|
15962
15966
|
function ensureHistoryStorageDir(baseDir = resolveHistoryStorageDir()) {
|
|
15963
15967
|
import_node_fs2.default.mkdirSync(baseDir, { recursive: true });
|
|
@@ -15978,11 +15982,12 @@ function resolveHistoryStoragePaths(baseDir = resolveHistoryStorageDir()) {
|
|
|
15978
15982
|
)
|
|
15979
15983
|
};
|
|
15980
15984
|
}
|
|
15981
|
-
var import_node_fs2, import_node_path4, HISTORY_SQLITE_FILE_NAME, LEGACY_CHAT_HISTORY_FILE_NAME, LEGACY_UI_HISTORY_FILE_NAME;
|
|
15985
|
+
var import_node_fs2, import_node_os, import_node_path4, HISTORY_SQLITE_FILE_NAME, LEGACY_CHAT_HISTORY_FILE_NAME, LEGACY_UI_HISTORY_FILE_NAME;
|
|
15982
15986
|
var init_historyStoragePaths = __esm({
|
|
15983
15987
|
"../../packages/backend/src/services/history/historyStoragePaths.ts"() {
|
|
15984
15988
|
"use strict";
|
|
15985
15989
|
import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
15990
|
+
import_node_os = __toESM(require("node:os"), 1);
|
|
15986
15991
|
import_node_path4 = __toESM(require("node:path"), 1);
|
|
15987
15992
|
HISTORY_SQLITE_FILE_NAME = "gyshell-history.sqlite";
|
|
15988
15993
|
LEGACY_CHAT_HISTORY_FILE_NAME = "gyshell-chat-history.json";
|
|
@@ -27263,11 +27268,11 @@ var require_pdf_worker = __commonJS({
|
|
|
27263
27268
|
var nameTree = new NameTree(nameTreeRef, xref);
|
|
27264
27269
|
var names = nameTree.getAll();
|
|
27265
27270
|
for (var name in names) {
|
|
27266
|
-
var
|
|
27271
|
+
var fs23 = new FileSpec(names[name], xref);
|
|
27267
27272
|
if (!attachments) {
|
|
27268
27273
|
attachments = /* @__PURE__ */ Object.create(null);
|
|
27269
27274
|
}
|
|
27270
|
-
attachments[(0, _util.stringToPDFString)(name)] =
|
|
27275
|
+
attachments[(0, _util.stringToPDFString)(name)] = fs23.serializable;
|
|
27271
27276
|
}
|
|
27272
27277
|
}
|
|
27273
27278
|
return (0, _util.shadow)(this, "attachments", attachments);
|
|
@@ -77119,7 +77124,7 @@ var require_pdf = __commonJS({
|
|
|
77119
77124
|
throw new TypeError("Cannot call a class as a function");
|
|
77120
77125
|
}
|
|
77121
77126
|
}
|
|
77122
|
-
var
|
|
77127
|
+
var fs23 = require("fs");
|
|
77123
77128
|
var http2 = require("http");
|
|
77124
77129
|
var https2 = require("https");
|
|
77125
77130
|
var url2 = require("url");
|
|
@@ -77453,7 +77458,7 @@ var require_pdf = __commonJS({
|
|
|
77453
77458
|
_classCallCheck(this, PDFNodeStreamFsFullReader2);
|
|
77454
77459
|
var _this7 = _possibleConstructorReturn(this, (PDFNodeStreamFsFullReader2.__proto__ || Object.getPrototypeOf(PDFNodeStreamFsFullReader2)).call(this, stream));
|
|
77455
77460
|
var path32 = decodeURI(_this7._url.path);
|
|
77456
|
-
|
|
77461
|
+
fs23.lstat(path32, function(error40, stat) {
|
|
77457
77462
|
if (error40) {
|
|
77458
77463
|
_this7._errored = true;
|
|
77459
77464
|
_this7._reason = error40;
|
|
@@ -77461,7 +77466,7 @@ var require_pdf = __commonJS({
|
|
|
77461
77466
|
return;
|
|
77462
77467
|
}
|
|
77463
77468
|
_this7._contentLength = stat.size;
|
|
77464
|
-
_this7._setReadableStream(
|
|
77469
|
+
_this7._setReadableStream(fs23.createReadStream(path32));
|
|
77465
77470
|
_this7._headersCapability.resolve();
|
|
77466
77471
|
});
|
|
77467
77472
|
return _this7;
|
|
@@ -77473,7 +77478,7 @@ var require_pdf = __commonJS({
|
|
|
77473
77478
|
function PDFNodeStreamFsRangeReader2(stream, start, end) {
|
|
77474
77479
|
_classCallCheck(this, PDFNodeStreamFsRangeReader2);
|
|
77475
77480
|
var _this8 = _possibleConstructorReturn(this, (PDFNodeStreamFsRangeReader2.__proto__ || Object.getPrototypeOf(PDFNodeStreamFsRangeReader2)).call(this, stream));
|
|
77476
|
-
_this8._setReadableStream(
|
|
77481
|
+
_this8._setReadableStream(fs23.createReadStream(decodeURI(_this8._url.path), {
|
|
77477
77482
|
start,
|
|
77478
77483
|
end: end - 1
|
|
77479
77484
|
}));
|
|
@@ -89467,11 +89472,11 @@ var require_pdf_worker2 = __commonJS({
|
|
|
89467
89472
|
var nameTree = new NameTree(nameTreeRef, xref);
|
|
89468
89473
|
var names = nameTree.getAll();
|
|
89469
89474
|
for (var name in names) {
|
|
89470
|
-
var
|
|
89475
|
+
var fs23 = new FileSpec(names[name], xref);
|
|
89471
89476
|
if (!attachments) {
|
|
89472
89477
|
attachments = /* @__PURE__ */ Object.create(null);
|
|
89473
89478
|
}
|
|
89474
|
-
attachments[(0, _util.stringToPDFString)(name)] =
|
|
89479
|
+
attachments[(0, _util.stringToPDFString)(name)] = fs23.serializable;
|
|
89475
89480
|
}
|
|
89476
89481
|
}
|
|
89477
89482
|
return (0, _util.shadow)(this, "attachments", attachments);
|
|
@@ -138955,7 +138960,7 @@ var require_pdf2 = __commonJS({
|
|
|
138955
138960
|
throw new TypeError("Cannot call a class as a function");
|
|
138956
138961
|
}
|
|
138957
138962
|
}
|
|
138958
|
-
var
|
|
138963
|
+
var fs23 = require("fs");
|
|
138959
138964
|
var http2 = require("http");
|
|
138960
138965
|
var https2 = require("https");
|
|
138961
138966
|
var url2 = require("url");
|
|
@@ -139289,7 +139294,7 @@ var require_pdf2 = __commonJS({
|
|
|
139289
139294
|
_classCallCheck(this, PDFNodeStreamFsFullReader2);
|
|
139290
139295
|
var _this7 = _possibleConstructorReturn(this, (PDFNodeStreamFsFullReader2.__proto__ || Object.getPrototypeOf(PDFNodeStreamFsFullReader2)).call(this, stream));
|
|
139291
139296
|
var path32 = decodeURI(_this7._url.path);
|
|
139292
|
-
|
|
139297
|
+
fs23.lstat(path32, function(error40, stat) {
|
|
139293
139298
|
if (error40) {
|
|
139294
139299
|
_this7._errored = true;
|
|
139295
139300
|
_this7._reason = error40;
|
|
@@ -139297,7 +139302,7 @@ var require_pdf2 = __commonJS({
|
|
|
139297
139302
|
return;
|
|
139298
139303
|
}
|
|
139299
139304
|
_this7._contentLength = stat.size;
|
|
139300
|
-
_this7._setReadableStream(
|
|
139305
|
+
_this7._setReadableStream(fs23.createReadStream(path32));
|
|
139301
139306
|
_this7._headersCapability.resolve();
|
|
139302
139307
|
});
|
|
139303
139308
|
return _this7;
|
|
@@ -139309,7 +139314,7 @@ var require_pdf2 = __commonJS({
|
|
|
139309
139314
|
function PDFNodeStreamFsRangeReader2(stream, start, end) {
|
|
139310
139315
|
_classCallCheck(this, PDFNodeStreamFsRangeReader2);
|
|
139311
139316
|
var _this8 = _possibleConstructorReturn(this, (PDFNodeStreamFsRangeReader2.__proto__ || Object.getPrototypeOf(PDFNodeStreamFsRangeReader2)).call(this, stream));
|
|
139312
|
-
_this8._setReadableStream(
|
|
139317
|
+
_this8._setReadableStream(fs23.createReadStream(decodeURI(_this8._url.path), {
|
|
139313
139318
|
start,
|
|
139314
139319
|
end: end - 1
|
|
139315
139320
|
}));
|
|
@@ -161693,11 +161698,11 @@ var require_pdf_worker3 = __commonJS({
|
|
|
161693
161698
|
var nameTree = new NameTree(nameTreeRef, xref);
|
|
161694
161699
|
var names = nameTree.getAll();
|
|
161695
161700
|
for (var name in names) {
|
|
161696
|
-
var
|
|
161701
|
+
var fs23 = new FileSpec(names[name], xref);
|
|
161697
161702
|
if (!attachments) {
|
|
161698
161703
|
attachments = /* @__PURE__ */ Object.create(null);
|
|
161699
161704
|
}
|
|
161700
|
-
attachments[(0, _util.stringToPDFString)(name)] =
|
|
161705
|
+
attachments[(0, _util.stringToPDFString)(name)] = fs23.serializable;
|
|
161701
161706
|
}
|
|
161702
161707
|
}
|
|
161703
161708
|
return (0, _util.shadow)(this, "attachments", attachments);
|
|
@@ -203578,11 +203583,11 @@ var require_pdf_worker4 = __commonJS({
|
|
|
203578
203583
|
var nameTree = new NameTree(nameTreeRef, xref);
|
|
203579
203584
|
var names = nameTree.getAll();
|
|
203580
203585
|
for (var name in names) {
|
|
203581
|
-
var
|
|
203586
|
+
var fs23 = new FileSpec(names[name], xref);
|
|
203582
203587
|
if (!attachments) {
|
|
203583
203588
|
attachments = /* @__PURE__ */ Object.create(null);
|
|
203584
203589
|
}
|
|
203585
|
-
attachments[(0, _util.stringToPDFString)(name)] =
|
|
203590
|
+
attachments[(0, _util.stringToPDFString)(name)] = fs23.serializable;
|
|
203586
203591
|
}
|
|
203587
203592
|
}
|
|
203588
203593
|
return (0, _util.shadow)(this, "attachments", attachments);
|
|
@@ -253965,7 +253970,7 @@ var require_pdf4 = __commonJS({
|
|
|
253965
253970
|
throw new TypeError("Cannot call a class as a function");
|
|
253966
253971
|
}
|
|
253967
253972
|
}
|
|
253968
|
-
var
|
|
253973
|
+
var fs23 = require("fs");
|
|
253969
253974
|
var http2 = require("http");
|
|
253970
253975
|
var https2 = require("https");
|
|
253971
253976
|
var url2 = require("url");
|
|
@@ -254323,7 +254328,7 @@ var require_pdf4 = __commonJS({
|
|
|
254323
254328
|
if (fileUriRegex.test(_this7._url.href)) {
|
|
254324
254329
|
path32 = path32.replace(/^\//, "");
|
|
254325
254330
|
}
|
|
254326
|
-
|
|
254331
|
+
fs23.lstat(path32, function(error40, stat) {
|
|
254327
254332
|
if (error40) {
|
|
254328
254333
|
_this7._errored = true;
|
|
254329
254334
|
_this7._reason = error40;
|
|
@@ -254331,7 +254336,7 @@ var require_pdf4 = __commonJS({
|
|
|
254331
254336
|
return;
|
|
254332
254337
|
}
|
|
254333
254338
|
_this7._contentLength = stat.size;
|
|
254334
|
-
_this7._setReadableStream(
|
|
254339
|
+
_this7._setReadableStream(fs23.createReadStream(path32));
|
|
254335
254340
|
_this7._headersCapability.resolve();
|
|
254336
254341
|
});
|
|
254337
254342
|
return _this7;
|
|
@@ -254347,7 +254352,7 @@ var require_pdf4 = __commonJS({
|
|
|
254347
254352
|
if (fileUriRegex.test(_this8._url.href)) {
|
|
254348
254353
|
path32 = path32.replace(/^\//, "");
|
|
254349
254354
|
}
|
|
254350
|
-
_this8._setReadableStream(
|
|
254355
|
+
_this8._setReadableStream(fs23.createReadStream(path32, {
|
|
254351
254356
|
start,
|
|
254352
254357
|
end: end - 1
|
|
254353
254358
|
}));
|
|
@@ -255452,6 +255457,105 @@ var require_pdf_parse2 = __commonJS({
|
|
|
255452
255457
|
}
|
|
255453
255458
|
});
|
|
255454
255459
|
|
|
255460
|
+
// ../../packages/backend/src/services/AgentHelper/utils/subAgent.ts
|
|
255461
|
+
var subAgent_exports = {};
|
|
255462
|
+
__export(subAgent_exports, {
|
|
255463
|
+
renderSubAgentSummary: () => renderSubAgentSummary,
|
|
255464
|
+
runSubAgents: () => runSubAgents,
|
|
255465
|
+
validateSubAgentSpec: () => validateSubAgentSpec
|
|
255466
|
+
});
|
|
255467
|
+
function validateSubAgentSpec(spec) {
|
|
255468
|
+
if (!spec || typeof spec !== "object") return { ok: false, error: "spec must be an object" };
|
|
255469
|
+
const s = spec;
|
|
255470
|
+
if (!Array.isArray(s.tasks) || s.tasks.length === 0) {
|
|
255471
|
+
return { ok: false, error: "tasks must be a non-empty array" };
|
|
255472
|
+
}
|
|
255473
|
+
if (s.tasks.length > 20) {
|
|
255474
|
+
return { ok: false, error: `too many tasks (${s.tasks.length}); max 20 per fan-out` };
|
|
255475
|
+
}
|
|
255476
|
+
for (let i = 0; i < s.tasks.length; i++) {
|
|
255477
|
+
const t = s.tasks[i];
|
|
255478
|
+
if (!t || typeof t !== "object") return { ok: false, error: `task ${i + 1} must be an object` };
|
|
255479
|
+
if (typeof t.prompt !== "string" || !t.prompt.trim()) {
|
|
255480
|
+
return { ok: false, error: `task ${i + 1} needs a non-empty prompt` };
|
|
255481
|
+
}
|
|
255482
|
+
if (t.label !== void 0 && typeof t.label !== "string") {
|
|
255483
|
+
return { ok: false, error: `task ${i + 1} label must be a string` };
|
|
255484
|
+
}
|
|
255485
|
+
}
|
|
255486
|
+
if (s.maxConcurrent !== void 0 && (!Number.isFinite(s.maxConcurrent) || s.maxConcurrent < 1)) {
|
|
255487
|
+
return { ok: false, error: "maxConcurrent must be >= 1" };
|
|
255488
|
+
}
|
|
255489
|
+
if (s.timeoutMs !== void 0 && (!Number.isFinite(s.timeoutMs) || s.timeoutMs < 1e3)) {
|
|
255490
|
+
return { ok: false, error: "timeoutMs must be >= 1000" };
|
|
255491
|
+
}
|
|
255492
|
+
return { ok: true };
|
|
255493
|
+
}
|
|
255494
|
+
async function runWithTimeout(task2, run, timeoutMs) {
|
|
255495
|
+
const started = Date.now();
|
|
255496
|
+
return await new Promise((resolve2) => {
|
|
255497
|
+
let settled = false;
|
|
255498
|
+
const finish = (r) => {
|
|
255499
|
+
if (!settled) {
|
|
255500
|
+
settled = true;
|
|
255501
|
+
clearTimeout(timer);
|
|
255502
|
+
resolve2(r);
|
|
255503
|
+
}
|
|
255504
|
+
};
|
|
255505
|
+
const timer = setTimeout(() => {
|
|
255506
|
+
finish({ label: task2.label, ok: false, error: "timed out", durationMs: Date.now() - started, timedOut: true });
|
|
255507
|
+
}, timeoutMs);
|
|
255508
|
+
run(task2).then((output) => finish({ label: task2.label, ok: true, output, durationMs: Date.now() - started, timedOut: false })).catch((e) => finish({
|
|
255509
|
+
label: task2.label,
|
|
255510
|
+
ok: false,
|
|
255511
|
+
error: e instanceof Error ? e.message : String(e),
|
|
255512
|
+
durationMs: Date.now() - started,
|
|
255513
|
+
timedOut: false
|
|
255514
|
+
}));
|
|
255515
|
+
});
|
|
255516
|
+
}
|
|
255517
|
+
async function runSubAgents(spec, run) {
|
|
255518
|
+
const maxConcurrent = Math.max(1, spec.maxConcurrent ?? 3);
|
|
255519
|
+
const timeoutMs = spec.timeoutMs ?? 3e5;
|
|
255520
|
+
const results = [];
|
|
255521
|
+
for (let i = 0; i < spec.tasks.length; i += maxConcurrent) {
|
|
255522
|
+
const batch = spec.tasks.slice(i, i + maxConcurrent);
|
|
255523
|
+
const batchResults = await Promise.all(
|
|
255524
|
+
batch.map((t) => runWithTimeout(t, run, timeoutMs))
|
|
255525
|
+
);
|
|
255526
|
+
results.push(...batchResults);
|
|
255527
|
+
}
|
|
255528
|
+
const succeeded = results.filter((r) => r.ok).length;
|
|
255529
|
+
return {
|
|
255530
|
+
ok: succeeded === results.length,
|
|
255531
|
+
total: results.length,
|
|
255532
|
+
succeeded,
|
|
255533
|
+
failed: results.length - succeeded,
|
|
255534
|
+
results
|
|
255535
|
+
};
|
|
255536
|
+
}
|
|
255537
|
+
function renderSubAgentSummary(summary) {
|
|
255538
|
+
const lines = [
|
|
255539
|
+
`Sub-agent fan-out: ${summary.succeeded}/${summary.total} succeeded.`,
|
|
255540
|
+
""
|
|
255541
|
+
];
|
|
255542
|
+
for (const r of summary.results) {
|
|
255543
|
+
const status = r.ok ? "\u2713" : r.timedOut ? "\u23F1" : "\u2717";
|
|
255544
|
+
const detail = r.ok ? (r.output ?? "").slice(0, 400) : `${r.error ?? "failed"}${r.timedOut ? ` (after ${Math.round(r.durationMs / 1e3)}s)` : ""}`;
|
|
255545
|
+
lines.push(`${status} ${r.label}: ${detail}`);
|
|
255546
|
+
lines.push("");
|
|
255547
|
+
}
|
|
255548
|
+
if (summary.failed > 0) {
|
|
255549
|
+
lines.push(`${summary.failed} task(s) failed \u2014 consider retrying those individually.`);
|
|
255550
|
+
}
|
|
255551
|
+
return lines.join("\n");
|
|
255552
|
+
}
|
|
255553
|
+
var init_subAgent = __esm({
|
|
255554
|
+
"../../packages/backend/src/services/AgentHelper/utils/subAgent.ts"() {
|
|
255555
|
+
"use strict";
|
|
255556
|
+
}
|
|
255557
|
+
});
|
|
255558
|
+
|
|
255455
255559
|
// ../../packages/backend/src/services/automation/templateEngine.ts
|
|
255456
255560
|
var templateEngine_exports = {};
|
|
255457
255561
|
__export(templateEngine_exports, {
|
|
@@ -257990,6 +258094,90 @@ var init_etwService = __esm({
|
|
|
257990
258094
|
}
|
|
257991
258095
|
});
|
|
257992
258096
|
|
|
258097
|
+
// ../../packages/backend/src/services/AgentHelper/utils/modelFailover.ts
|
|
258098
|
+
var modelFailover_exports = {};
|
|
258099
|
+
__export(modelFailover_exports, {
|
|
258100
|
+
buildFailoverChain: () => buildFailoverChain,
|
|
258101
|
+
isFailoverEligible: () => isFailoverEligible,
|
|
258102
|
+
withModelFailover: () => withModelFailover
|
|
258103
|
+
});
|
|
258104
|
+
function isFailoverEligible(error40) {
|
|
258105
|
+
const message = error40 instanceof Error ? error40.message : String(error40 ?? "");
|
|
258106
|
+
for (const re of NON_FAILOVER_PATTERNS) {
|
|
258107
|
+
if (re.test(message)) return { eligible: false, reason: "non-failover-error" };
|
|
258108
|
+
}
|
|
258109
|
+
for (const { re: pattern, reason } of FAILOVER_ERROR_PATTERNS) {
|
|
258110
|
+
if (pattern.test(message)) return { eligible: true, reason };
|
|
258111
|
+
}
|
|
258112
|
+
return { eligible: true, reason: "unknown-error" };
|
|
258113
|
+
}
|
|
258114
|
+
function buildFailoverChain(primary, fallbacks) {
|
|
258115
|
+
const chain = [primary];
|
|
258116
|
+
if (!Array.isArray(fallbacks)) return chain;
|
|
258117
|
+
const seen = /* @__PURE__ */ new Set([primary.model]);
|
|
258118
|
+
for (const f of fallbacks) {
|
|
258119
|
+
if (!f || !f.model) continue;
|
|
258120
|
+
if (seen.has(f.model)) continue;
|
|
258121
|
+
seen.add(f.model);
|
|
258122
|
+
chain.push(f);
|
|
258123
|
+
}
|
|
258124
|
+
return chain;
|
|
258125
|
+
}
|
|
258126
|
+
async function withModelFailover(chain, operation, opts = {}) {
|
|
258127
|
+
const attempts = [];
|
|
258128
|
+
let lastError;
|
|
258129
|
+
if (chain.length === 0) {
|
|
258130
|
+
return {
|
|
258131
|
+
error: new Error("model failover chain is empty \u2014 no candidates to try"),
|
|
258132
|
+
attempts
|
|
258133
|
+
};
|
|
258134
|
+
}
|
|
258135
|
+
for (const candidate of chain) {
|
|
258136
|
+
const started = Date.now();
|
|
258137
|
+
try {
|
|
258138
|
+
const value = await operation(candidate);
|
|
258139
|
+
const attempt = { model: candidate.model, ok: true, durationMs: Date.now() - started };
|
|
258140
|
+
attempts.push(attempt);
|
|
258141
|
+
opts.onAttempt?.(attempt);
|
|
258142
|
+
return { value, attempts, usedModel: candidate.model };
|
|
258143
|
+
} catch (error40) {
|
|
258144
|
+
const decision = isFailoverEligible(error40);
|
|
258145
|
+
const attempt = {
|
|
258146
|
+
model: candidate.model,
|
|
258147
|
+
ok: false,
|
|
258148
|
+
reason: decision.reason,
|
|
258149
|
+
durationMs: Date.now() - started
|
|
258150
|
+
};
|
|
258151
|
+
attempts.push(attempt);
|
|
258152
|
+
opts.onAttempt?.(attempt);
|
|
258153
|
+
if (!decision.eligible) {
|
|
258154
|
+
return { error: error40, attempts };
|
|
258155
|
+
}
|
|
258156
|
+
lastError = error40;
|
|
258157
|
+
}
|
|
258158
|
+
}
|
|
258159
|
+
return { error: lastError, attempts };
|
|
258160
|
+
}
|
|
258161
|
+
var FAILOVER_ERROR_PATTERNS, NON_FAILOVER_PATTERNS;
|
|
258162
|
+
var init_modelFailover = __esm({
|
|
258163
|
+
"../../packages/backend/src/services/AgentHelper/utils/modelFailover.ts"() {
|
|
258164
|
+
"use strict";
|
|
258165
|
+
FAILOVER_ERROR_PATTERNS = [
|
|
258166
|
+
{ re: /\b429\b|rate.?limit|too many requests|quota/i, reason: "rate-limited" },
|
|
258167
|
+
{ re: /\b5\d\d\b|internal server error|bad gateway|service unavailable|upstream/i, reason: "provider-5xx" },
|
|
258168
|
+
{ re: /network|fetch failed|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|socket hang up/i, reason: "network" },
|
|
258169
|
+
{ re: /\b401\b|unauthorized|invalid api key|authentication/i, reason: "auth" },
|
|
258170
|
+
{ re: /overloaded|capacity|model .* not found|no such model/i, reason: "model-unavailable" }
|
|
258171
|
+
];
|
|
258172
|
+
NON_FAILOVER_PATTERNS = [
|
|
258173
|
+
/context.?length|token limit|maximum context/i,
|
|
258174
|
+
/invalid request|malformed|bad request|\b400\b/i,
|
|
258175
|
+
/aborted|AbortError/i
|
|
258176
|
+
// user cancelled — never retry
|
|
258177
|
+
];
|
|
258178
|
+
}
|
|
258179
|
+
});
|
|
258180
|
+
|
|
257993
258181
|
// ../../packages/backend/src/services/history/HistorySqliteStore.ts
|
|
257994
258182
|
var HistorySqliteStore_exports = {};
|
|
257995
258183
|
__export(HistorySqliteStore_exports, {
|
|
@@ -261975,6 +262163,136 @@ var require_websocket_server = __commonJS({
|
|
|
261975
262163
|
}
|
|
261976
262164
|
});
|
|
261977
262165
|
|
|
262166
|
+
// ../../packages/backend/src/services/history/historySearch.ts
|
|
262167
|
+
var historySearch_exports = {};
|
|
262168
|
+
__export(historySearch_exports, {
|
|
262169
|
+
buildSnippet: () => buildSnippet,
|
|
262170
|
+
extractMessageText: () => extractMessageText2,
|
|
262171
|
+
findMatches: () => findMatches,
|
|
262172
|
+
normalizeMessages: () => normalizeMessages,
|
|
262173
|
+
searchChatHistory: () => searchChatHistory
|
|
262174
|
+
});
|
|
262175
|
+
function extractMessageText2(data) {
|
|
262176
|
+
if (!data || typeof data !== "object") return "";
|
|
262177
|
+
let obj = data;
|
|
262178
|
+
for (let i = 0; i < 3 && obj.data && typeof obj.data === "object" && !Array.isArray(obj.data); i++) {
|
|
262179
|
+
const inner = obj.data;
|
|
262180
|
+
if (inner.content !== void 0 || inner.data !== void 0) {
|
|
262181
|
+
obj = inner;
|
|
262182
|
+
} else {
|
|
262183
|
+
break;
|
|
262184
|
+
}
|
|
262185
|
+
}
|
|
262186
|
+
const content = obj.content;
|
|
262187
|
+
if (typeof content === "string") return content;
|
|
262188
|
+
if (Array.isArray(content)) {
|
|
262189
|
+
return content.map((part) => {
|
|
262190
|
+
if (typeof part === "string") return part;
|
|
262191
|
+
if (part && typeof part === "object") {
|
|
262192
|
+
const p = part;
|
|
262193
|
+
if (typeof p.text === "string") return p.text;
|
|
262194
|
+
}
|
|
262195
|
+
return "";
|
|
262196
|
+
}).join(" ");
|
|
262197
|
+
}
|
|
262198
|
+
return "";
|
|
262199
|
+
}
|
|
262200
|
+
function buildSnippet(text, offset, length) {
|
|
262201
|
+
const start = Math.max(0, offset - SNIPPET_CONTEXT);
|
|
262202
|
+
const end = Math.min(text.length, offset + length + SNIPPET_CONTEXT);
|
|
262203
|
+
const prefix = start > 0 ? "\u2026" : "";
|
|
262204
|
+
const suffix = end < text.length ? "\u2026" : "";
|
|
262205
|
+
return prefix + text.slice(start, end).replace(/\s+/g, " ").trim() + suffix;
|
|
262206
|
+
}
|
|
262207
|
+
function findMatches(text, query, wholeWord) {
|
|
262208
|
+
if (!text || !query) return [];
|
|
262209
|
+
const haystack = text.toLowerCase();
|
|
262210
|
+
const needle = query.toLowerCase();
|
|
262211
|
+
const out = [];
|
|
262212
|
+
if (wholeWord) {
|
|
262213
|
+
const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "gi");
|
|
262214
|
+
let m2;
|
|
262215
|
+
while ((m2 = re.exec(haystack)) !== null) {
|
|
262216
|
+
out.push({ offset: m2.index, length: m2[0].length });
|
|
262217
|
+
if (m2.index === re.lastIndex) re.lastIndex++;
|
|
262218
|
+
}
|
|
262219
|
+
} else {
|
|
262220
|
+
let idx = haystack.indexOf(needle);
|
|
262221
|
+
while (idx !== -1) {
|
|
262222
|
+
out.push({ offset: idx, length: needle.length });
|
|
262223
|
+
idx = haystack.indexOf(needle, idx + needle.length);
|
|
262224
|
+
}
|
|
262225
|
+
}
|
|
262226
|
+
return out;
|
|
262227
|
+
}
|
|
262228
|
+
function normalizeMessages(messages) {
|
|
262229
|
+
if (!messages) return [];
|
|
262230
|
+
if (Array.isArray(messages)) return messages;
|
|
262231
|
+
if (messages instanceof Map) return [...messages.values()];
|
|
262232
|
+
if (typeof messages === "object") return Object.values(messages);
|
|
262233
|
+
return [];
|
|
262234
|
+
}
|
|
262235
|
+
function searchChatHistory(sessions, query, options = {}) {
|
|
262236
|
+
const sessionLimit = options.sessionLimit ?? DEFAULT_SESSION_LIMIT;
|
|
262237
|
+
const snippetLimit = options.snippetLimit ?? DEFAULT_SNIPPET_LIMIT;
|
|
262238
|
+
const wholeWord = options.wholeWord ?? false;
|
|
262239
|
+
const includeTitles = options.includeTitles ?? true;
|
|
262240
|
+
const trimmed = (query ?? "").trim();
|
|
262241
|
+
if (!trimmed) {
|
|
262242
|
+
return { query: trimmed, totalSessions: 0, totalMatches: 0, sessions: [], truncated: false };
|
|
262243
|
+
}
|
|
262244
|
+
const results = [];
|
|
262245
|
+
let totalMatches = 0;
|
|
262246
|
+
for (const session of sessions) {
|
|
262247
|
+
let matchCount = 0;
|
|
262248
|
+
const matches = [];
|
|
262249
|
+
const titleHit = includeTitles && session.title ? findMatches(session.title, trimmed, wholeWord) : [];
|
|
262250
|
+
for (const message of normalizeMessages(session.messages)) {
|
|
262251
|
+
const text = extractMessageText2(message?.data);
|
|
262252
|
+
if (!text) continue;
|
|
262253
|
+
const hits = findMatches(text, trimmed, wholeWord);
|
|
262254
|
+
for (const hit of hits) {
|
|
262255
|
+
matchCount++;
|
|
262256
|
+
totalMatches++;
|
|
262257
|
+
if (matches.length < snippetLimit) {
|
|
262258
|
+
matches.push({
|
|
262259
|
+
messageId: String(message?.id ?? ""),
|
|
262260
|
+
type: String(message?.type ?? ""),
|
|
262261
|
+
snippet: buildSnippet(text, hit.offset, hit.length),
|
|
262262
|
+
offset: hit.offset
|
|
262263
|
+
});
|
|
262264
|
+
}
|
|
262265
|
+
}
|
|
262266
|
+
}
|
|
262267
|
+
if (matchCount === 0 && titleHit.length === 0) continue;
|
|
262268
|
+
results.push({
|
|
262269
|
+
sessionId: session.id,
|
|
262270
|
+
sessionTitle: session.title,
|
|
262271
|
+
updatedAt: session.updatedAt,
|
|
262272
|
+
matchCount: matchCount + titleHit.length,
|
|
262273
|
+
matches
|
|
262274
|
+
});
|
|
262275
|
+
}
|
|
262276
|
+
results.sort((a, b) => b.matchCount - a.matchCount || b.updatedAt - a.updatedAt);
|
|
262277
|
+
const truncated = results.length > sessionLimit;
|
|
262278
|
+
return {
|
|
262279
|
+
query: trimmed,
|
|
262280
|
+
totalSessions: results.length,
|
|
262281
|
+
totalMatches,
|
|
262282
|
+
sessions: results.slice(0, sessionLimit),
|
|
262283
|
+
truncated
|
|
262284
|
+
};
|
|
262285
|
+
}
|
|
262286
|
+
var SNIPPET_CONTEXT, DEFAULT_SESSION_LIMIT, DEFAULT_SNIPPET_LIMIT;
|
|
262287
|
+
var init_historySearch = __esm({
|
|
262288
|
+
"../../packages/backend/src/services/history/historySearch.ts"() {
|
|
262289
|
+
"use strict";
|
|
262290
|
+
SNIPPET_CONTEXT = 60;
|
|
262291
|
+
DEFAULT_SESSION_LIMIT = 20;
|
|
262292
|
+
DEFAULT_SNIPPET_LIMIT = 3;
|
|
262293
|
+
}
|
|
262294
|
+
});
|
|
262295
|
+
|
|
261978
262296
|
// ../../node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/code.js
|
|
261979
262297
|
var require_code = __commonJS({
|
|
261980
262298
|
"../../node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/code.js"(exports2) {
|
|
@@ -274408,12 +274726,12 @@ var require_dist2 = __commonJS({
|
|
|
274408
274726
|
throw new Error(`Unknown format "${name}"`);
|
|
274409
274727
|
return f;
|
|
274410
274728
|
};
|
|
274411
|
-
function addFormats(ajv, list,
|
|
274729
|
+
function addFormats(ajv, list, fs23, exportName) {
|
|
274412
274730
|
var _a3;
|
|
274413
274731
|
var _b;
|
|
274414
274732
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
274415
274733
|
for (const f of list)
|
|
274416
|
-
ajv.addFormat(f,
|
|
274734
|
+
ajv.addFormat(f, fs23[f]);
|
|
274417
274735
|
}
|
|
274418
274736
|
module2.exports = exports2 = formatsPlugin;
|
|
274419
274737
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -274426,7 +274744,7 @@ var require_windows = __commonJS({
|
|
|
274426
274744
|
"../../node_modules/isexe/windows.js"(exports2, module2) {
|
|
274427
274745
|
module2.exports = isexe;
|
|
274428
274746
|
isexe.sync = sync;
|
|
274429
|
-
var
|
|
274747
|
+
var fs23 = require("fs");
|
|
274430
274748
|
function checkPathExt(path32, options) {
|
|
274431
274749
|
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
274432
274750
|
if (!pathext) {
|
|
@@ -274451,12 +274769,12 @@ var require_windows = __commonJS({
|
|
|
274451
274769
|
return checkPathExt(path32, options);
|
|
274452
274770
|
}
|
|
274453
274771
|
function isexe(path32, options, cb) {
|
|
274454
|
-
|
|
274772
|
+
fs23.stat(path32, function(er, stat) {
|
|
274455
274773
|
cb(er, er ? false : checkStat(stat, path32, options));
|
|
274456
274774
|
});
|
|
274457
274775
|
}
|
|
274458
274776
|
function sync(path32, options) {
|
|
274459
|
-
return checkStat(
|
|
274777
|
+
return checkStat(fs23.statSync(path32), path32, options);
|
|
274460
274778
|
}
|
|
274461
274779
|
}
|
|
274462
274780
|
});
|
|
@@ -274466,14 +274784,14 @@ var require_mode = __commonJS({
|
|
|
274466
274784
|
"../../node_modules/isexe/mode.js"(exports2, module2) {
|
|
274467
274785
|
module2.exports = isexe;
|
|
274468
274786
|
isexe.sync = sync;
|
|
274469
|
-
var
|
|
274787
|
+
var fs23 = require("fs");
|
|
274470
274788
|
function isexe(path32, options, cb) {
|
|
274471
|
-
|
|
274789
|
+
fs23.stat(path32, function(er, stat) {
|
|
274472
274790
|
cb(er, er ? false : checkStat(stat, options));
|
|
274473
274791
|
});
|
|
274474
274792
|
}
|
|
274475
274793
|
function sync(path32, options) {
|
|
274476
|
-
return checkStat(
|
|
274794
|
+
return checkStat(fs23.statSync(path32), options);
|
|
274477
274795
|
}
|
|
274478
274796
|
function checkStat(stat, options) {
|
|
274479
274797
|
return stat.isFile() && checkMode(stat, options);
|
|
@@ -274497,7 +274815,7 @@ var require_mode = __commonJS({
|
|
|
274497
274815
|
// ../../node_modules/isexe/index.js
|
|
274498
274816
|
var require_isexe = __commonJS({
|
|
274499
274817
|
"../../node_modules/isexe/index.js"(exports2, module2) {
|
|
274500
|
-
var
|
|
274818
|
+
var fs23 = require("fs");
|
|
274501
274819
|
var core2;
|
|
274502
274820
|
if (process.platform === "win32" || global.TESTING_WINDOWS) {
|
|
274503
274821
|
core2 = require_windows();
|
|
@@ -274761,16 +275079,16 @@ var require_shebang_command = __commonJS({
|
|
|
274761
275079
|
var require_readShebang = __commonJS({
|
|
274762
275080
|
"../../node_modules/cross-spawn/lib/util/readShebang.js"(exports2, module2) {
|
|
274763
275081
|
"use strict";
|
|
274764
|
-
var
|
|
275082
|
+
var fs23 = require("fs");
|
|
274765
275083
|
var shebangCommand = require_shebang_command();
|
|
274766
275084
|
function readShebang(command) {
|
|
274767
275085
|
const size = 150;
|
|
274768
275086
|
const buffer = Buffer.alloc(size);
|
|
274769
275087
|
let fd;
|
|
274770
275088
|
try {
|
|
274771
|
-
fd =
|
|
274772
|
-
|
|
274773
|
-
|
|
275089
|
+
fd = fs23.openSync(command, "r");
|
|
275090
|
+
fs23.readSync(fd, buffer, 0, size, 0);
|
|
275091
|
+
fs23.closeSync(fd);
|
|
274774
275092
|
} catch (e) {
|
|
274775
275093
|
}
|
|
274776
275094
|
return shebangCommand(buffer.toString());
|
|
@@ -275398,7 +275716,7 @@ var require_has_flag = __commonJS({
|
|
|
275398
275716
|
var require_supports_color = __commonJS({
|
|
275399
275717
|
"../../node_modules/supports-color/index.js"(exports2, module2) {
|
|
275400
275718
|
"use strict";
|
|
275401
|
-
var
|
|
275719
|
+
var os8 = require("os");
|
|
275402
275720
|
var tty = require("tty");
|
|
275403
275721
|
var hasFlag = require_has_flag();
|
|
275404
275722
|
var { env } = process;
|
|
@@ -275446,7 +275764,7 @@ var require_supports_color = __commonJS({
|
|
|
275446
275764
|
return min;
|
|
275447
275765
|
}
|
|
275448
275766
|
if (process.platform === "win32") {
|
|
275449
|
-
const osRelease =
|
|
275767
|
+
const osRelease = os8.release().split(".");
|
|
275450
275768
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
275451
275769
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
275452
275770
|
}
|
|
@@ -286297,11 +286615,11 @@ var require_jsmconsumer_api = __commonJS({
|
|
|
286297
286615
|
consumerName = cfg.name ?? cfg.durable_name ?? "";
|
|
286298
286616
|
}
|
|
286299
286617
|
if (consumerName !== "") {
|
|
286300
|
-
let
|
|
286301
|
-
if (
|
|
286302
|
-
|
|
286618
|
+
let fs23 = cfg.filter_subject ?? void 0;
|
|
286619
|
+
if (fs23 === ">") {
|
|
286620
|
+
fs23 = void 0;
|
|
286303
286621
|
}
|
|
286304
|
-
subj =
|
|
286622
|
+
subj = fs23 !== void 0 ? `${this.prefix}.CONSUMER.CREATE.${stream}.${consumerName}.${fs23}` : `${this.prefix}.CONSUMER.CREATE.${stream}.${consumerName}`;
|
|
286305
286623
|
} else {
|
|
286306
286624
|
subj = cfg.durable_name ? `${this.prefix}.CONSUMER.DURABLE.CREATE.${stream}.${cfg.durable_name}` : `${this.prefix}.CONSUMER.CREATE.${stream}`;
|
|
286307
286625
|
}
|
|
@@ -290852,6 +291170,7 @@ var init_natsEventBus = __esm({
|
|
|
290852
291170
|
|
|
290853
291171
|
// ../../packages/backend/src/runtimes/gybackend/startGyBackend.ts
|
|
290854
291172
|
var import_node_path28 = __toESM(require("node:path"), 1);
|
|
291173
|
+
var import_node_fs12 = __toESM(require("node:fs"), 1);
|
|
290855
291174
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
290856
291175
|
|
|
290857
291176
|
// ../../packages/backend/src/services/TerminalService.ts
|
|
@@ -293693,19 +294012,19 @@ var SSHBackend = class _SSHBackend {
|
|
|
293693
294012
|
),
|
|
293694
294013
|
this.execCollect(client2, "hostname", 8e3)
|
|
293695
294014
|
]);
|
|
293696
|
-
let
|
|
294015
|
+
let os8 = "unix";
|
|
293697
294016
|
const releaseMatch = osRelease.stdout.match(/^ID=(.*)$/m);
|
|
293698
294017
|
if (releaseMatch) {
|
|
293699
|
-
|
|
294018
|
+
os8 = releaseMatch[1].replace(/"/g, "");
|
|
293700
294019
|
} else {
|
|
293701
294020
|
const unameS2 = uname.stdout.split(" ")[0].toLowerCase();
|
|
293702
|
-
|
|
294021
|
+
os8 = unameS2 || "unix";
|
|
293703
294022
|
}
|
|
293704
294023
|
const unameS = uname.stdout.split(" ")[0].toLowerCase();
|
|
293705
294024
|
const platform2 = unameS.includes("darwin") ? "darwin" : unameS.includes("linux") ? "linux" : "unix";
|
|
293706
294025
|
const parts = uname.stdout.split(" ");
|
|
293707
294026
|
const next = {
|
|
293708
|
-
os:
|
|
294027
|
+
os: os8,
|
|
293709
294028
|
platform: platform2,
|
|
293710
294029
|
release: parts[2] || "",
|
|
293711
294030
|
arch: parts[parts.length - 2] || "",
|
|
@@ -300516,6 +300835,9 @@ var TerminalService = class _TerminalService {
|
|
|
300516
300835
|
terminalIdsBeingKilled = /* @__PURE__ */ new Set();
|
|
300517
300836
|
autoReconnect = new AutoReconnect({ maxAttempts: 10 });
|
|
300518
300837
|
terminalClosedListeners = /* @__PURE__ */ new Set();
|
|
300838
|
+
/** v3.2.18: idle-timeout tracking hooks. */
|
|
300839
|
+
terminalCreatedListeners = /* @__PURE__ */ new Set();
|
|
300840
|
+
terminalDataListeners = /* @__PURE__ */ new Set();
|
|
300519
300841
|
/** Optional session logger (records terminal output to disk per session). */
|
|
300520
300842
|
sessionLogger = null;
|
|
300521
300843
|
sessionLogStarted = /* @__PURE__ */ new Set();
|
|
@@ -300565,6 +300887,38 @@ var TerminalService = class _TerminalService {
|
|
|
300565
300887
|
this.terminalClosedListeners.delete(listener);
|
|
300566
300888
|
};
|
|
300567
300889
|
}
|
|
300890
|
+
/** v3.2.18: fires when a terminal tab is created (idle-timeout tracking). */
|
|
300891
|
+
onTerminalCreated(listener) {
|
|
300892
|
+
this.terminalCreatedListeners.add(listener);
|
|
300893
|
+
return () => {
|
|
300894
|
+
this.terminalCreatedListeners.delete(listener);
|
|
300895
|
+
};
|
|
300896
|
+
}
|
|
300897
|
+
/** v3.2.18: fires on any terminal data event (idle-timeout activity). */
|
|
300898
|
+
onTerminalData(listener) {
|
|
300899
|
+
this.terminalDataListeners.add(listener);
|
|
300900
|
+
return () => {
|
|
300901
|
+
this.terminalDataListeners.delete(listener);
|
|
300902
|
+
};
|
|
300903
|
+
}
|
|
300904
|
+
/** v3.2.18: notify created-listeners (called from createTerminal paths). */
|
|
300905
|
+
notifyTerminalCreated(terminalId) {
|
|
300906
|
+
for (const listener of this.terminalCreatedListeners) {
|
|
300907
|
+
try {
|
|
300908
|
+
listener(terminalId);
|
|
300909
|
+
} catch {
|
|
300910
|
+
}
|
|
300911
|
+
}
|
|
300912
|
+
}
|
|
300913
|
+
/** v3.2.18: notify data-listeners (called from the data pump). */
|
|
300914
|
+
notifyTerminalData(terminalId) {
|
|
300915
|
+
for (const listener of this.terminalDataListeners) {
|
|
300916
|
+
try {
|
|
300917
|
+
listener(terminalId);
|
|
300918
|
+
} catch {
|
|
300919
|
+
}
|
|
300920
|
+
}
|
|
300921
|
+
}
|
|
300568
300922
|
listRenderableTerminals() {
|
|
300569
300923
|
return Array.from(this.terminals.values()).map((terminal) => ({
|
|
300570
300924
|
id: terminal.id,
|
|
@@ -300883,6 +301237,7 @@ var TerminalService = class _TerminalService {
|
|
|
300883
301237
|
}
|
|
300884
301238
|
this.publishTerminalTabsChanged();
|
|
300885
301239
|
this.schedulePersistTerminalState();
|
|
301240
|
+
this.notifyTerminalCreated(tab.id);
|
|
300886
301241
|
return tab;
|
|
300887
301242
|
}
|
|
300888
301243
|
async reconnectTerminal(terminalId) {
|
|
@@ -301076,6 +301431,7 @@ var TerminalService = class _TerminalService {
|
|
|
301076
301431
|
}
|
|
301077
301432
|
this.sessionLogger.write(terminalId, captureData);
|
|
301078
301433
|
}
|
|
301434
|
+
this.notifyTerminalData(terminalId);
|
|
301079
301435
|
});
|
|
301080
301436
|
}
|
|
301081
301437
|
}
|
|
@@ -301397,6 +301753,7 @@ ${promptPrefix}`;
|
|
|
301397
301753
|
const backend = this.getBackend(terminal.type);
|
|
301398
301754
|
backend.write(terminal.ptyId, data);
|
|
301399
301755
|
this.pendingWriteRetries.delete(terminalId);
|
|
301756
|
+
this.notifyTerminalData(terminalId);
|
|
301400
301757
|
} else if (terminal) {
|
|
301401
301758
|
if (terminal.runtimeState === "exited") {
|
|
301402
301759
|
this.pendingWrites.delete(terminalId);
|
|
@@ -350077,6 +350434,11 @@ var CLOSE_TERMINAL_TAB_DESCRIPTION = [
|
|
|
350077
350434
|
"Use this to clean up tabs the task no longer needs \u2014 e.g. after a transfer finished, or after a one-off probe on a server. Close tabs you opened yourself when the work is done; do not close tabs the user was actively using unless asked.",
|
|
350078
350435
|
"Guarded: it refuses to close the last remaining terminal tab (at least one tab must stay open) and reports how many tabs remain after the close. If the tab is running a command, the command is terminated by the close."
|
|
350079
350436
|
].join("\n");
|
|
350437
|
+
var SPAWN_SUBAGENTS_DESCRIPTION = [
|
|
350438
|
+
"Fan work out to concurrent child agent sessions. Each task gets its own session running its prompt to completion; results are collected into one summary.",
|
|
350439
|
+
"Use this when you have MULTIPLE INDEPENDENT tasks that would otherwise run one at a time \u2014 e.g. 'check disk usage on these 6 servers', 'analyze these 3 files', or 'research these 4 questions'. Do NOT use it for sequential work where each step depends on the previous one's output.",
|
|
350440
|
+
"Each task needs a self-contained prompt (the child has no context from this conversation) and a short label. Guards: max 20 tasks per fan-out, per-child timeout 5 minutes (configurable), concurrency cap 3 (configurable). One child failing or timing out does not affect the others \u2014 you get a per-task result with \u2713/\u2717 status."
|
|
350441
|
+
].join("\n");
|
|
350080
350442
|
var COPY_BETWEEN_TABS_DESCRIPTION = [
|
|
350081
350443
|
"Start an asynchronous file copy between two different terminal tabs on different machines. Use this only for cross-terminal-tab file transfer; do not use it for copying within one tab or between two tabs connected to the same machine.",
|
|
350082
350444
|
"If either source or target terminal is disconnected or not ready, this tool returns an explicit terminal_status for that side and does not start a transfer.",
|
|
@@ -350214,6 +350576,11 @@ var BUILTIN_TOOL_INFO = [
|
|
|
350214
350576
|
description: CLOSE_TERMINAL_TAB_DESCRIPTION,
|
|
350215
350577
|
shortDescription: "Close a terminal tab"
|
|
350216
350578
|
},
|
|
350579
|
+
{
|
|
350580
|
+
name: "spawn_subagents",
|
|
350581
|
+
description: SPAWN_SUBAGENTS_DESCRIPTION,
|
|
350582
|
+
shortDescription: "Fan out to concurrent child agents"
|
|
350583
|
+
},
|
|
350217
350584
|
{
|
|
350218
350585
|
name: "create_or_edit",
|
|
350219
350586
|
description: CREATE_OR_EDIT_TOOL_DESCRIPTION,
|
|
@@ -351869,6 +352236,69 @@ async function closeTerminalTab(args, context2) {
|
|
|
351869
352236
|
}
|
|
351870
352237
|
}
|
|
351871
352238
|
|
|
352239
|
+
// ../../packages/backend/src/services/AgentHelper/tools/subAgent_tools.ts
|
|
352240
|
+
init_zod();
|
|
352241
|
+
var spawnSubAgentsSchema = external_exports.object({
|
|
352242
|
+
tasks: external_exports.array(
|
|
352243
|
+
external_exports.object({
|
|
352244
|
+
label: external_exports.string().min(1).describe('Short label for reporting, e.g. "server-1"'),
|
|
352245
|
+
prompt: external_exports.string().min(1).describe("The self-contained prompt for the child agent.")
|
|
352246
|
+
})
|
|
352247
|
+
).min(1).max(20).describe("The tasks to fan out. Each child gets its own session. Max 20."),
|
|
352248
|
+
maxConcurrent: external_exports.number().int().min(1).max(10).optional().describe("Max children running concurrently (default 3)."),
|
|
352249
|
+
timeoutMs: external_exports.number().int().min(1e3).optional().describe("Per-child timeout in ms (default 300000 = 5 minutes).")
|
|
352250
|
+
});
|
|
352251
|
+
async function spawnSubAgents(args, context2) {
|
|
352252
|
+
abortIfNeeded(context2.signal);
|
|
352253
|
+
const { sendEvent, sessionId, messageId } = context2;
|
|
352254
|
+
sendEvent(sessionId, {
|
|
352255
|
+
messageId,
|
|
352256
|
+
type: "sub_tool_started",
|
|
352257
|
+
toolName: "spawn_subagents",
|
|
352258
|
+
title: `Fan out ${args.tasks.length} sub-agent(s)`,
|
|
352259
|
+
hint: `concurrency ${args.maxConcurrent ?? 3}`,
|
|
352260
|
+
input: JSON.stringify(args)
|
|
352261
|
+
});
|
|
352262
|
+
const finish = (output) => {
|
|
352263
|
+
sendEvent(sessionId, { messageId, type: "sub_tool_delta", outputDelta: output });
|
|
352264
|
+
sendEvent(sessionId, { messageId, type: "sub_tool_finished" });
|
|
352265
|
+
return output;
|
|
352266
|
+
};
|
|
352267
|
+
try {
|
|
352268
|
+
const { validateSubAgentSpec: validateSubAgentSpec2, runSubAgents: runSubAgents2, renderSubAgentSummary: renderSubAgentSummary2 } = await Promise.resolve().then(() => (init_subAgent(), subAgent_exports));
|
|
352269
|
+
const validation = validateSubAgentSpec2(args);
|
|
352270
|
+
if (!validation.ok) {
|
|
352271
|
+
return finish(`Invalid sub-agent spec: ${validation.error}`);
|
|
352272
|
+
}
|
|
352273
|
+
const agentService = context2.agentService;
|
|
352274
|
+
if (!agentService?.startTask) {
|
|
352275
|
+
return finish(
|
|
352276
|
+
"Sub-agent delegation is not available in this runtime (no agent service bridge)."
|
|
352277
|
+
);
|
|
352278
|
+
}
|
|
352279
|
+
const summary = await runSubAgents2(
|
|
352280
|
+
{
|
|
352281
|
+
tasks: args.tasks.map((t) => ({ label: t.label, prompt: t.prompt })),
|
|
352282
|
+
maxConcurrent: args.maxConcurrent,
|
|
352283
|
+
timeoutMs: args.timeoutMs
|
|
352284
|
+
},
|
|
352285
|
+
async (task2) => {
|
|
352286
|
+
const childSession = await agentService.createSession();
|
|
352287
|
+
const result = await agentService.startTask({
|
|
352288
|
+
sessionId: childSession.sessionId,
|
|
352289
|
+
userInput: task2.prompt
|
|
352290
|
+
});
|
|
352291
|
+
const text = typeof result === "string" ? result : String(result?.answer ?? result?.output ?? result);
|
|
352292
|
+
return text;
|
|
352293
|
+
}
|
|
352294
|
+
);
|
|
352295
|
+
return finish(renderSubAgentSummary2(summary));
|
|
352296
|
+
} catch (error40) {
|
|
352297
|
+
if (error40 instanceof Error && error40.name === "AbortError") throw error40;
|
|
352298
|
+
return finish(`Sub-agent fan-out failed: ${error40 instanceof Error ? error40.message : String(error40)}`);
|
|
352299
|
+
}
|
|
352300
|
+
}
|
|
352301
|
+
|
|
351872
352302
|
// ../../packages/backend/src/services/AgentHelper/tools/wait_tools.ts
|
|
351873
352303
|
init_zod();
|
|
351874
352304
|
var waitSchema = external_exports.object({
|
|
@@ -354243,6 +354673,11 @@ var CORE_METHODS = [
|
|
|
354243
354673
|
m("gateway:isSameMachine", "gateway", "Whether the caller is on the same machine as the backend.", "1.0.0"),
|
|
354244
354674
|
m("gateway:createSession", "session", "Create an agent session (returns sessionId).", "1.0.0"),
|
|
354245
354675
|
m("session:list", "session", "List chat sessions.", "1.0.0"),
|
|
354676
|
+
m("history:search", "session", "Full-text search across ALL stored chat sessions (v3.2.18). Params: {query, wholeWord?, includeTitles?, sessionLimit?, snippetLimit?}. Returns matching sessions ranked by match count with snippets.", "3.2.18"),
|
|
354677
|
+
m("settings:listBackups", "settings", "List timestamped settings backups (v3.2.18). Returns [{name, at, size}] newest-first.", "3.2.18"),
|
|
354678
|
+
m("settings:restoreBackup", "settings", "Restore settings from a named backup (v3.2.18). Takes a SAFETY backup of the current state first. Params: {name}.", "3.2.18"),
|
|
354679
|
+
m("settings:export", "settings", "Export the current settings.json content as a portable JSON string (v3.2.18).", "3.2.18"),
|
|
354680
|
+
m("settings:import", "settings", "Import settings JSON (v3.2.18). Validates it is a JSON object; takes a SAFETY backup before replacing. Params: {content}.", "3.2.18"),
|
|
354246
354681
|
m("session:get", "session", "Get one chat session by id.", "1.0.0", { sessionId: { type: "string" } }),
|
|
354247
354682
|
m("agent:startTask", "agent", "Start an AI agent task (blocking) and return the final answer.", "1.0.0", { sessionId: { type: "string" }, userInput: { type: "string" } }),
|
|
354248
354683
|
m("agent:startTaskAsync", "agent", "Start an AI agent task (async; watch events for progress).", "1.0.0", { sessionId: { type: "string" }, userInput: { type: "string" } }),
|
|
@@ -355212,6 +355647,11 @@ function buildToolsForModel(readFileSupport) {
|
|
|
355212
355647
|
description: BUILTIN_TOOL_INFO.find((t) => t.name === "close_terminal_tab")?.description ?? "",
|
|
355213
355648
|
schema: closeTerminalTabSchema
|
|
355214
355649
|
},
|
|
355650
|
+
{
|
|
355651
|
+
name: "spawn_subagents",
|
|
355652
|
+
description: BUILTIN_TOOL_INFO.find((t) => t.name === "spawn_subagents")?.description ?? "",
|
|
355653
|
+
schema: spawnSubAgentsSchema
|
|
355654
|
+
},
|
|
355215
355655
|
{
|
|
355216
355656
|
name: WRITE_FILE_TOOL_NAME,
|
|
355217
355657
|
description: WRITE_FILE_TOOL_DESCRIPTION,
|
|
@@ -355450,6 +355890,7 @@ var toolImplementations = {
|
|
|
355450
355890
|
reconnectTerminalTab,
|
|
355451
355891
|
openTerminalTab,
|
|
355452
355892
|
closeTerminalTab,
|
|
355893
|
+
spawnSubAgents,
|
|
355453
355894
|
wait,
|
|
355454
355895
|
waitTerminalIdle,
|
|
355455
355896
|
copyBetweenTabs,
|
|
@@ -368370,7 +368811,7 @@ Missing image attachments (kept as references only): ${missingNames.join(", ")}`
|
|
|
368370
368811
|
// ../../packages/backend/src/services/PassChatTempExportService.ts
|
|
368371
368812
|
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
368372
368813
|
var import_promises6 = __toESM(require("node:fs/promises"), 1);
|
|
368373
|
-
var
|
|
368814
|
+
var import_node_os2 = __toESM(require("node:os"), 1);
|
|
368374
368815
|
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
368375
368816
|
var import_node_crypto15 = __toESM(require("node:crypto"), 1);
|
|
368376
368817
|
var DEFAULT_MAX_FILES = 50;
|
|
@@ -368379,7 +368820,7 @@ var PassChatTempExportService = class {
|
|
|
368379
368820
|
maxFiles;
|
|
368380
368821
|
groupBySession;
|
|
368381
368822
|
constructor(options) {
|
|
368382
|
-
this.baseDir = options?.baseDir || import_node_path6.default.join(
|
|
368823
|
+
this.baseDir = options?.baseDir || import_node_path6.default.join(import_node_os2.default.tmpdir(), "gyshell-pass-chats");
|
|
368383
368824
|
this.maxFiles = options?.maxFiles === null ? null : Number.isInteger(options?.maxFiles) && Number(options?.maxFiles) > 0 ? Number(options?.maxFiles) : DEFAULT_MAX_FILES;
|
|
368384
368825
|
this.groupBySession = options?.groupBySession === true;
|
|
368385
368826
|
}
|
|
@@ -369002,6 +369443,20 @@ var AgentService_v2 = class {
|
|
|
369002
369443
|
isAbortError(error40) {
|
|
369003
369444
|
return this.helpers.isAbortError(error40);
|
|
369004
369445
|
}
|
|
369446
|
+
/**
|
|
369447
|
+
* v3.2.18: resolve a fallback model id to its ModelDefinition from the
|
|
369448
|
+
* session's settings. Returns null when the model isn't configured.
|
|
369449
|
+
*/
|
|
369450
|
+
resolveFallbackModelItem(modelId) {
|
|
369451
|
+
try {
|
|
369452
|
+
const settings = this.options?.getSettings?.() ?? null;
|
|
369453
|
+
const items = settings?.models?.items ?? [];
|
|
369454
|
+
const hit = items.find((m2) => m2.model === modelId || m2.id === modelId || m2.name === modelId);
|
|
369455
|
+
return hit ?? null;
|
|
369456
|
+
} catch {
|
|
369457
|
+
return null;
|
|
369458
|
+
}
|
|
369459
|
+
}
|
|
369005
369460
|
/**
|
|
369006
369461
|
* v3.2.12: true when the given run's final_output node already emitted the
|
|
369007
369462
|
* `done` event. GatewayService uses this to avoid broadcasting a duplicate
|
|
@@ -369142,6 +369597,22 @@ var AgentService_v2 = class {
|
|
|
369142
369597
|
const actionModel = actionItem?.apiKey ? this.helpers.createChatModel(actionItem, 0.1, profile) : model;
|
|
369143
369598
|
const thinkingModel = thinkingItem?.apiKey ? this.helpers.createChatModel(thinkingItem, 0.2, profile) : model;
|
|
369144
369599
|
const compactionModel = compactionItem?.apiKey ? this.helpers.createChatModel(compactionItem, 0.2, profile) : thinkingItem?.apiKey ? thinkingModel : model;
|
|
369600
|
+
const fallbackModels = [];
|
|
369601
|
+
const seenFallbackIds = /* @__PURE__ */ new Set([profile.globalModelId]);
|
|
369602
|
+
for (const fallbackId of Array.isArray(profile.fallbackModels) ? profile.fallbackModels : []) {
|
|
369603
|
+
if (typeof fallbackId !== "string" || !fallbackId.trim()) continue;
|
|
369604
|
+
if (seenFallbackIds.has(fallbackId)) continue;
|
|
369605
|
+
const item = settings.models.items.find((m2) => m2.id === fallbackId);
|
|
369606
|
+
if (!item || !item.apiKey) {
|
|
369607
|
+
console.warn(
|
|
369608
|
+
"[AgentService_v2] Fallback model is invalid or has no API key \u2014 skipping:",
|
|
369609
|
+
{ profileId, fallbackId }
|
|
369610
|
+
);
|
|
369611
|
+
continue;
|
|
369612
|
+
}
|
|
369613
|
+
seenFallbackIds.add(fallbackId);
|
|
369614
|
+
fallbackModels.push({ model: item.model, label: item.name ?? item.model });
|
|
369615
|
+
}
|
|
369145
369616
|
const actionModelSupportsStructuredOutput = actionItem?.apiKey ? actionItem.supportsStructuredOutput === true : globalItem.supportsStructuredOutput === true;
|
|
369146
369617
|
const actionModelSupportsObjectToolChoice = actionItem?.apiKey ? actionItem.supportsObjectToolChoice === true : globalItem.supportsObjectToolChoice === true;
|
|
369147
369618
|
const thinkingModelSupportsStructuredOutput = thinkingItem?.apiKey ? thinkingItem.supportsStructuredOutput === true : globalItem.supportsStructuredOutput === true;
|
|
@@ -369162,6 +369633,7 @@ var AgentService_v2 = class {
|
|
|
369162
369633
|
actionModel,
|
|
369163
369634
|
thinkingModel,
|
|
369164
369635
|
compactionModel,
|
|
369636
|
+
fallbackModels,
|
|
369165
369637
|
actionModelSupportsStructuredOutput,
|
|
369166
369638
|
actionModelSupportsObjectToolChoice,
|
|
369167
369639
|
thinkingModelSupportsStructuredOutput,
|
|
@@ -369549,172 +370021,231 @@ var AgentService_v2 = class {
|
|
|
369549
370021
|
let reasoningContent = "";
|
|
369550
370022
|
let debugRawChunks = [];
|
|
369551
370023
|
const llmTraceStart = Date.now();
|
|
369552
|
-
const
|
|
369553
|
-
|
|
369554
|
-
|
|
369555
|
-
|
|
369556
|
-
|
|
369557
|
-
|
|
369558
|
-
|
|
369559
|
-
|
|
370024
|
+
const fallbacks = sessionBinding.fallbackModels;
|
|
370025
|
+
let fullResponse;
|
|
370026
|
+
if (Array.isArray(fallbacks) && fallbacks.length > 0) {
|
|
370027
|
+
const { withModelFailover: withModelFailover2 } = await Promise.resolve().then(() => (init_modelFailover(), modelFailover_exports));
|
|
370028
|
+
const primaryModel = baseModel?.model ?? baseModel?.modelName ?? "unknown";
|
|
370029
|
+
const chain = [{ model: primaryModel }, ...fallbacks.map((f) => ({ model: f.model, label: f.label }))];
|
|
370030
|
+
const failover = await withModelFailover2(chain, async (candidate) => {
|
|
370031
|
+
let modelToUse = modelWithTools;
|
|
370032
|
+
if (candidate.model !== primaryModel) {
|
|
370033
|
+
const fallbackItem = this.resolveFallbackModelItem(candidate.model);
|
|
370034
|
+
if (!fallbackItem) {
|
|
370035
|
+
throw new Error(`fallback model "${candidate.model}" is not configured`);
|
|
370036
|
+
}
|
|
370037
|
+
const fallbackChat = this.helpers.createChatModel(
|
|
370038
|
+
fallbackItem,
|
|
370039
|
+
shouldUseThinkingModelOnThisPass ? 0.2 : 0.7,
|
|
370040
|
+
null
|
|
370041
|
+
);
|
|
370042
|
+
modelToUse = fallbackChat.bindTools([...builtInTools, ...mcpTools]);
|
|
370043
|
+
}
|
|
370044
|
+
return await invokeWithRetryAndSanitizedInput({
|
|
370045
|
+
helpers: this.helpers,
|
|
370046
|
+
messages: modelInputMessages,
|
|
370047
|
+
modelSupportsImage: sessionBinding.readFileSupport.image,
|
|
370048
|
+
signal: config2?.signal,
|
|
370049
|
+
operation: async (streamInputMessages) => {
|
|
370050
|
+
const stream = await modelToUse.stream(streamInputMessages, { signal: config2?.signal });
|
|
370051
|
+
let response = null;
|
|
370052
|
+
for await (const chunk2 of stream) {
|
|
370053
|
+
response = response ? response.concat(chunk2) : chunk2;
|
|
370054
|
+
}
|
|
370055
|
+
if (!response) throw new Error("Model stream ended without a usable response.");
|
|
370056
|
+
return response;
|
|
370057
|
+
},
|
|
370058
|
+
onRetry: (attempt) => {
|
|
370059
|
+
this.helpers.sendEvent(sessionId, {
|
|
370060
|
+
type: "alert",
|
|
370061
|
+
message: `Retrying (${attempt}/${MODEL_RETRY_MAX})...`,
|
|
370062
|
+
level: "info",
|
|
370063
|
+
messageId: `retry-${messageId}-${attempt}`
|
|
370064
|
+
});
|
|
370065
|
+
},
|
|
370066
|
+
maxRetries: MODEL_RETRY_MAX,
|
|
370067
|
+
delaysMs: MODEL_RETRY_DELAYS_MS
|
|
369560
370068
|
});
|
|
369561
|
-
|
|
369562
|
-
|
|
369563
|
-
|
|
369564
|
-
|
|
369565
|
-
|
|
369566
|
-
|
|
369567
|
-
|
|
369568
|
-
|
|
369569
|
-
|
|
369570
|
-
|
|
369571
|
-
|
|
369572
|
-
|
|
369573
|
-
|
|
369574
|
-
|
|
369575
|
-
|
|
369576
|
-
|
|
369577
|
-
|
|
369578
|
-
|
|
369579
|
-
|
|
369580
|
-
|
|
369581
|
-
|
|
369582
|
-
outputDelta: delta
|
|
369583
|
-
});
|
|
369584
|
-
};
|
|
369585
|
-
const finishReasoningBanner = () => {
|
|
369586
|
-
if (!activeReasoningBannerId) return;
|
|
369587
|
-
this.helpers.sendEvent(sessionId, {
|
|
369588
|
-
messageId: activeReasoningBannerId,
|
|
369589
|
-
type: "sub_tool_finished"
|
|
370069
|
+
}, {
|
|
370070
|
+
onAttempt: (a) => {
|
|
370071
|
+
if (!a.ok) {
|
|
370072
|
+
console.warn(`[AgentService_v2] model failover: ${a.model} failed (${a.reason}) after ${a.durationMs}ms`);
|
|
370073
|
+
}
|
|
370074
|
+
}
|
|
370075
|
+
});
|
|
370076
|
+
if (failover.value !== void 0) {
|
|
370077
|
+
fullResponse = failover.value;
|
|
370078
|
+
} else {
|
|
370079
|
+
throw failover.error ?? new Error("model failover exhausted all candidates");
|
|
370080
|
+
}
|
|
370081
|
+
} else {
|
|
370082
|
+
fullResponse = await invokeWithRetryAndSanitizedInput({
|
|
370083
|
+
helpers: this.helpers,
|
|
370084
|
+
messages: modelInputMessages,
|
|
370085
|
+
modelSupportsImage: sessionBinding.readFileSupport.image,
|
|
370086
|
+
signal: config2?.signal,
|
|
370087
|
+
operation: async (streamInputMessages) => {
|
|
370088
|
+
const stream = await modelWithTools.stream(streamInputMessages, {
|
|
370089
|
+
signal: config2?.signal
|
|
369590
370090
|
});
|
|
369591
|
-
|
|
369592
|
-
|
|
369593
|
-
|
|
369594
|
-
|
|
369595
|
-
|
|
369596
|
-
|
|
369597
|
-
|
|
369598
|
-
);
|
|
369599
|
-
|
|
369600
|
-
|
|
369601
|
-
|
|
369602
|
-
|
|
369603
|
-
|
|
369604
|
-
|
|
369605
|
-
|
|
369606
|
-
|
|
369607
|
-
);
|
|
369608
|
-
|
|
369609
|
-
|
|
369610
|
-
|
|
369611
|
-
|
|
369612
|
-
|
|
369613
|
-
|
|
369614
|
-
|
|
369615
|
-
|
|
369616
|
-
if (
|
|
369617
|
-
|
|
369618
|
-
|
|
369619
|
-
|
|
370091
|
+
let response = null;
|
|
370092
|
+
let skippedEmptyGenericChunks = 0;
|
|
370093
|
+
const streamReasoningExtractor = createStreamReasoningExtractor();
|
|
370094
|
+
const attemptDebugRawChunks = [];
|
|
370095
|
+
let activeReasoningBannerId = null;
|
|
370096
|
+
const startReasoningBanner = () => {
|
|
370097
|
+
if (activeReasoningBannerId) return;
|
|
370098
|
+
activeReasoningBannerId = v4_default();
|
|
370099
|
+
this.helpers.sendEvent(sessionId, {
|
|
370100
|
+
messageId: activeReasoningBannerId,
|
|
370101
|
+
type: "sub_tool_started",
|
|
370102
|
+
title: "Reasoning...",
|
|
370103
|
+
hint: ""
|
|
370104
|
+
});
|
|
370105
|
+
};
|
|
370106
|
+
const appendReasoningDelta = (delta) => {
|
|
370107
|
+
if (!delta) return;
|
|
370108
|
+
startReasoningBanner();
|
|
370109
|
+
this.helpers.sendEvent(sessionId, {
|
|
370110
|
+
messageId: activeReasoningBannerId,
|
|
370111
|
+
type: "sub_tool_delta",
|
|
370112
|
+
outputDelta: delta
|
|
370113
|
+
});
|
|
370114
|
+
};
|
|
370115
|
+
const finishReasoningBanner = () => {
|
|
370116
|
+
if (!activeReasoningBannerId) return;
|
|
370117
|
+
this.helpers.sendEvent(sessionId, {
|
|
370118
|
+
messageId: activeReasoningBannerId,
|
|
370119
|
+
type: "sub_tool_finished"
|
|
370120
|
+
});
|
|
370121
|
+
activeReasoningBannerId = null;
|
|
370122
|
+
};
|
|
370123
|
+
try {
|
|
370124
|
+
for await (const chunk2 of stream) {
|
|
370125
|
+
const rawChunk = captureRawResponseChunk(
|
|
370126
|
+
chunk2,
|
|
370127
|
+
attemptDebugRawChunks
|
|
370128
|
+
);
|
|
370129
|
+
const extracted = streamReasoningExtractor.processChunk(
|
|
370130
|
+
chunk2,
|
|
370131
|
+
rawChunk
|
|
370132
|
+
);
|
|
370133
|
+
const appendResult = appendStreamedModelResponseChunk(
|
|
370134
|
+
response,
|
|
370135
|
+
chunk2,
|
|
370136
|
+
rawChunk
|
|
370137
|
+
);
|
|
370138
|
+
response = appendResult.response;
|
|
370139
|
+
if (appendResult.skippedEmptyGenericChunk) {
|
|
370140
|
+
skippedEmptyGenericChunks += 1;
|
|
370141
|
+
}
|
|
370142
|
+
const rawDelta = this.helpers.extractText(chunk2.content);
|
|
370143
|
+
if (rawDelta) {
|
|
370144
|
+
partialText += rawDelta;
|
|
370145
|
+
}
|
|
370146
|
+
if (extracted.reasoning) {
|
|
370147
|
+
appendReasoningDelta(extracted.reasoning);
|
|
370148
|
+
} else {
|
|
370149
|
+
finishReasoningBanner();
|
|
370150
|
+
}
|
|
370151
|
+
if (extracted.content) {
|
|
370152
|
+
this.helpers.sendEvent(sessionId, {
|
|
370153
|
+
messageId,
|
|
370154
|
+
type: "say",
|
|
370155
|
+
content: extracted.content
|
|
370156
|
+
});
|
|
370157
|
+
}
|
|
369620
370158
|
}
|
|
369621
|
-
|
|
370159
|
+
const pendingContent = streamReasoningExtractor.flushPendingContent();
|
|
370160
|
+
if (pendingContent) {
|
|
369622
370161
|
this.helpers.sendEvent(sessionId, {
|
|
369623
370162
|
messageId,
|
|
369624
370163
|
type: "say",
|
|
369625
|
-
content:
|
|
370164
|
+
content: pendingContent
|
|
369626
370165
|
});
|
|
369627
370166
|
}
|
|
370167
|
+
finishReasoningBanner();
|
|
370168
|
+
} catch (err) {
|
|
370169
|
+
finishReasoningBanner();
|
|
370170
|
+
if (partialText.trim()) {
|
|
370171
|
+
this.lastAbortedMessage = new AIMessage({
|
|
370172
|
+
content: partialText,
|
|
370173
|
+
additional_kwargs: {
|
|
370174
|
+
_gyshellMessageId: messageId,
|
|
370175
|
+
_gyshellAborted: true
|
|
370176
|
+
}
|
|
370177
|
+
});
|
|
370178
|
+
console.log(
|
|
370179
|
+
"[AgentService_v2] Captured partial message from error/abort in instance variable."
|
|
370180
|
+
);
|
|
370181
|
+
}
|
|
370182
|
+
throw err;
|
|
369628
370183
|
}
|
|
369629
|
-
|
|
369630
|
-
|
|
369631
|
-
this.helpers.sendEvent(sessionId, {
|
|
369632
|
-
messageId,
|
|
369633
|
-
type: "say",
|
|
369634
|
-
content: pendingContent
|
|
369635
|
-
});
|
|
370184
|
+
if (!response) {
|
|
370185
|
+
throw new Error("Model stream ended without a usable response.");
|
|
369636
370186
|
}
|
|
369637
|
-
|
|
369638
|
-
|
|
369639
|
-
|
|
369640
|
-
|
|
369641
|
-
|
|
369642
|
-
|
|
369643
|
-
|
|
369644
|
-
|
|
369645
|
-
|
|
370187
|
+
if (skippedEmptyGenericChunks > 0) {
|
|
370188
|
+
response.additional_kwargs = {
|
|
370189
|
+
...response.additional_kwargs || {},
|
|
370190
|
+
[SKIPPED_EMPTY_GENERIC_CHUNKS_KEY]: skippedEmptyGenericChunks
|
|
370191
|
+
};
|
|
370192
|
+
}
|
|
370193
|
+
reasoningContent = streamReasoningExtractor.getReasoningContent();
|
|
370194
|
+
if (isEmptyMalformedToolCallFinish(response, attemptDebugRawChunks) && typeof modelWithTools.invoke === "function") {
|
|
370195
|
+
console.warn(
|
|
370196
|
+
`[AgentService_v2] Stream ended with malformed empty tool-call finish; retrying same request with non-stream invoke (sessionId=${sessionId}).`
|
|
370197
|
+
);
|
|
370198
|
+
const invokeResponse = await modelWithTools.invoke(
|
|
370199
|
+
streamInputMessages,
|
|
370200
|
+
{
|
|
370201
|
+
signal: config2?.signal
|
|
369646
370202
|
}
|
|
369647
|
-
});
|
|
369648
|
-
console.log(
|
|
369649
|
-
"[AgentService_v2] Captured partial message from error/abort in instance variable."
|
|
369650
370203
|
);
|
|
369651
|
-
|
|
369652
|
-
|
|
369653
|
-
|
|
369654
|
-
if (!response) {
|
|
369655
|
-
throw new Error("Model stream ended without a usable response.");
|
|
369656
|
-
}
|
|
369657
|
-
if (skippedEmptyGenericChunks > 0) {
|
|
369658
|
-
response.additional_kwargs = {
|
|
369659
|
-
...response.additional_kwargs || {},
|
|
369660
|
-
[SKIPPED_EMPTY_GENERIC_CHUNKS_KEY]: skippedEmptyGenericChunks
|
|
369661
|
-
};
|
|
369662
|
-
}
|
|
369663
|
-
reasoningContent = streamReasoningExtractor.getReasoningContent();
|
|
369664
|
-
if (isEmptyMalformedToolCallFinish(response, attemptDebugRawChunks) && typeof modelWithTools.invoke === "function") {
|
|
369665
|
-
console.warn(
|
|
369666
|
-
`[AgentService_v2] Stream ended with malformed empty tool-call finish; retrying same request with non-stream invoke (sessionId=${sessionId}).`
|
|
369667
|
-
);
|
|
369668
|
-
const invokeResponse = await modelWithTools.invoke(
|
|
369669
|
-
streamInputMessages,
|
|
369670
|
-
{
|
|
369671
|
-
signal: config2?.signal
|
|
369672
|
-
}
|
|
369673
|
-
);
|
|
369674
|
-
captureRawResponseChunk(
|
|
369675
|
-
invokeResponse,
|
|
369676
|
-
attemptDebugRawChunks
|
|
369677
|
-
);
|
|
369678
|
-
if (!isEmptyMalformedToolCallFinish(
|
|
369679
|
-
invokeResponse,
|
|
369680
|
-
attemptDebugRawChunks
|
|
369681
|
-
)) {
|
|
369682
|
-
const invokeText = this.helpers.extractText(
|
|
369683
|
-
invokeResponse.content
|
|
370204
|
+
captureRawResponseChunk(
|
|
370205
|
+
invokeResponse,
|
|
370206
|
+
attemptDebugRawChunks
|
|
369684
370207
|
);
|
|
369685
|
-
if (
|
|
369686
|
-
|
|
369687
|
-
|
|
369688
|
-
|
|
369689
|
-
|
|
369690
|
-
|
|
370208
|
+
if (!isEmptyMalformedToolCallFinish(
|
|
370209
|
+
invokeResponse,
|
|
370210
|
+
attemptDebugRawChunks
|
|
370211
|
+
)) {
|
|
370212
|
+
const invokeText = this.helpers.extractText(
|
|
370213
|
+
invokeResponse.content
|
|
370214
|
+
);
|
|
370215
|
+
if (invokeText) {
|
|
370216
|
+
this.helpers.sendEvent(sessionId, {
|
|
370217
|
+
messageId,
|
|
370218
|
+
type: "say",
|
|
370219
|
+
content: invokeText
|
|
370220
|
+
});
|
|
370221
|
+
}
|
|
370222
|
+
response = invokeResponse;
|
|
369691
370223
|
}
|
|
369692
|
-
response = invokeResponse;
|
|
369693
370224
|
}
|
|
369694
|
-
|
|
369695
|
-
|
|
369696
|
-
|
|
369697
|
-
|
|
369698
|
-
|
|
369699
|
-
|
|
369700
|
-
|
|
369701
|
-
|
|
369702
|
-
|
|
369703
|
-
|
|
369704
|
-
|
|
369705
|
-
|
|
369706
|
-
|
|
369707
|
-
|
|
369708
|
-
|
|
369709
|
-
|
|
369710
|
-
|
|
369711
|
-
|
|
369712
|
-
|
|
369713
|
-
}
|
|
369714
|
-
|
|
369715
|
-
|
|
369716
|
-
|
|
369717
|
-
}
|
|
370225
|
+
if (!isEmptyMalformedToolCallFinish(response, attemptDebugRawChunks) && isEmptyUnusableModelResponse(response, attemptDebugRawChunks)) {
|
|
370226
|
+
const finishReason = describeStreamedResponseFinish(
|
|
370227
|
+
response,
|
|
370228
|
+
attemptDebugRawChunks
|
|
370229
|
+
);
|
|
370230
|
+
throw new Error(
|
|
370231
|
+
`Model stream ended with an empty unusable response (finish_reason=${finishReason}).`
|
|
370232
|
+
);
|
|
370233
|
+
}
|
|
370234
|
+
debugRawChunks = attemptDebugRawChunks;
|
|
370235
|
+
return response;
|
|
370236
|
+
},
|
|
370237
|
+
onRetry: (attempt) => {
|
|
370238
|
+
this.helpers.sendEvent(sessionId, {
|
|
370239
|
+
type: "alert",
|
|
370240
|
+
message: `Retrying (${attempt}/${MODEL_RETRY_MAX})...`,
|
|
370241
|
+
level: "info",
|
|
370242
|
+
messageId: `retry-${messageId}-${attempt}`
|
|
370243
|
+
});
|
|
370244
|
+
},
|
|
370245
|
+
maxRetries: MODEL_RETRY_MAX,
|
|
370246
|
+
delaysMs: MODEL_RETRY_DELAYS_MS
|
|
370247
|
+
});
|
|
370248
|
+
}
|
|
369718
370249
|
fullResponse.additional_kwargs = {
|
|
369719
370250
|
...fullResponse.additional_kwargs || {},
|
|
369720
370251
|
_gyshellMessageId: messageId
|
|
@@ -370110,6 +370641,22 @@ Actually, your intention might be different. Please re-read the description of t
|
|
|
370110
370641
|
}
|
|
370111
370642
|
break;
|
|
370112
370643
|
}
|
|
370644
|
+
case "spawn_subagents": {
|
|
370645
|
+
try {
|
|
370646
|
+
const validatedArgs = spawnSubAgentsSchema.parse(
|
|
370647
|
+
toolCall.args || {}
|
|
370648
|
+
);
|
|
370649
|
+
result = await toolImplementations.spawnSubAgents(
|
|
370650
|
+
validatedArgs,
|
|
370651
|
+
// The sub-agent tool needs the agent service to create child
|
|
370652
|
+
// sessions — inject it into the execution context.
|
|
370653
|
+
{ ...executionContext, agentService: this }
|
|
370654
|
+
);
|
|
370655
|
+
} catch (err) {
|
|
370656
|
+
result = `Parameter validation error for spawn_subagents: ${err.message}`;
|
|
370657
|
+
}
|
|
370658
|
+
break;
|
|
370659
|
+
}
|
|
370113
370660
|
case "manage_ssh_connection": {
|
|
370114
370661
|
try {
|
|
370115
370662
|
const validatedArgs = manageSshConnectionSchema.parse(
|
|
@@ -374652,13 +375199,16 @@ var WebSocketGatewayAdapter = class {
|
|
|
374652
375199
|
}
|
|
374653
375200
|
const token = this.extractAccessToken(request);
|
|
374654
375201
|
if (!token) {
|
|
375202
|
+
this.options.rateLimiter?.recordAuthFailure(this.clientKeyForRequest(request));
|
|
374655
375203
|
return "missing access token";
|
|
374656
375204
|
}
|
|
374657
375205
|
try {
|
|
374658
375206
|
const valid = await auth2.verifyToken(token);
|
|
374659
375207
|
if (!valid) {
|
|
375208
|
+
this.options.rateLimiter?.recordAuthFailure(this.clientKeyForRequest(request));
|
|
374660
375209
|
return "invalid access token";
|
|
374661
375210
|
}
|
|
375211
|
+
this.options.rateLimiter?.recordAuthSuccess(this.clientKeyForRequest(request));
|
|
374662
375212
|
return null;
|
|
374663
375213
|
} catch (error40) {
|
|
374664
375214
|
this.logger.warn(
|
|
@@ -374668,6 +375218,15 @@ var WebSocketGatewayAdapter = class {
|
|
|
374668
375218
|
return "token verification failed";
|
|
374669
375219
|
}
|
|
374670
375220
|
}
|
|
375221
|
+
/** v3.2.18: a stable per-client key for rate limiting (IP or token id). */
|
|
375222
|
+
clientKeyForRequest(request) {
|
|
375223
|
+
return String(request?.socket?.remoteAddress || "unknown");
|
|
375224
|
+
}
|
|
375225
|
+
/** v3.2.18: per-client key from a live socket connection. */
|
|
375226
|
+
clientKeyForSocket(socket) {
|
|
375227
|
+
const remote = socket.remoteAddress;
|
|
375228
|
+
return String(remote || "unknown");
|
|
375229
|
+
}
|
|
374671
375230
|
extractAccessToken(request) {
|
|
374672
375231
|
const fromAuthHeader = this.readBearerToken(
|
|
374673
375232
|
request?.headers?.authorization
|
|
@@ -374828,6 +375387,17 @@ var WebSocketGatewayAdapter = class {
|
|
|
374828
375387
|
try {
|
|
374829
375388
|
const parsed = this.parseRequest(raw);
|
|
374830
375389
|
requestId = parsed.id !== void 0 ? String(parsed.id) : void 0;
|
|
375390
|
+
const limiter = this.options.rateLimiter;
|
|
375391
|
+
if (limiter) {
|
|
375392
|
+
const clientKey = this.clientKeyForSocket(socket);
|
|
375393
|
+
const decision = limiter.check(clientKey);
|
|
375394
|
+
if (!decision.allowed) {
|
|
375395
|
+
throw new WebSocketRpcError(
|
|
375396
|
+
"RATE_LIMITED",
|
|
375397
|
+
decision.reason === "locked-out" ? `locked out; retry in ${Math.ceil((decision.retryAfterMs ?? 0) / 1e3)}s` : `rate limited; retry in ${Math.ceil((decision.retryAfterMs ?? 1e3) / 1e3)}s`
|
|
375398
|
+
);
|
|
375399
|
+
}
|
|
375400
|
+
}
|
|
374831
375401
|
const result = await this.executeRequest(parsed, socket);
|
|
374832
375402
|
if (requestId) {
|
|
374833
375403
|
this.sendRpcSuccess(socket, requestId, result);
|
|
@@ -374885,6 +375455,26 @@ var WebSocketGatewayAdapter = class {
|
|
|
374885
375455
|
"Unsupported websocket payload type."
|
|
374886
375456
|
);
|
|
374887
375457
|
}
|
|
375458
|
+
/**
|
|
375459
|
+
* v3.2.18: public dispatch for non-WebSocket callers (the REST API layer).
|
|
375460
|
+
* Executes a gateway method with a synthetic loopback socket — same
|
|
375461
|
+
* validation and error mapping as a WS request, no socket required.
|
|
375462
|
+
*/
|
|
375463
|
+
async handleRequest(method, params) {
|
|
375464
|
+
const syntheticSocket = {
|
|
375465
|
+
send: () => {
|
|
375466
|
+
},
|
|
375467
|
+
close: () => {
|
|
375468
|
+
},
|
|
375469
|
+
on: () => {
|
|
375470
|
+
},
|
|
375471
|
+
readyState: 1
|
|
375472
|
+
};
|
|
375473
|
+
return await this.executeRequest(
|
|
375474
|
+
{ id: `rest-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, method, params },
|
|
375475
|
+
syntheticSocket
|
|
375476
|
+
);
|
|
375477
|
+
}
|
|
374888
375478
|
async executeRequest(request, socket) {
|
|
374889
375479
|
const params = request.params ?? {};
|
|
374890
375480
|
if (request.method.startsWith("observability:")) {
|
|
@@ -374961,6 +375551,61 @@ var WebSocketGatewayAdapter = class {
|
|
|
374961
375551
|
}
|
|
374962
375552
|
return { session };
|
|
374963
375553
|
}
|
|
375554
|
+
case "history:search": {
|
|
375555
|
+
const bridge = this.options.historyBridge;
|
|
375556
|
+
if (!bridge?.getAllSessions) {
|
|
375557
|
+
throw new WebSocketRpcError(
|
|
375558
|
+
"METHOD_NOT_FOUND",
|
|
375559
|
+
"history:search is not available on this gateway (no history bridge)."
|
|
375560
|
+
);
|
|
375561
|
+
}
|
|
375562
|
+
const { searchChatHistory: searchChatHistory2 } = await Promise.resolve().then(() => (init_historySearch(), historySearch_exports));
|
|
375563
|
+
const sessions = await bridge.getAllSessions();
|
|
375564
|
+
const query = this.readStringParam(params, "query");
|
|
375565
|
+
const wholeWord = params?.wholeWord === true;
|
|
375566
|
+
const includeTitles = params?.includeTitles !== false;
|
|
375567
|
+
const sessionLimit = typeof params?.sessionLimit === "number" ? params.sessionLimit : void 0;
|
|
375568
|
+
const snippetLimit = typeof params?.snippetLimit === "number" ? params.snippetLimit : void 0;
|
|
375569
|
+
return searchChatHistory2(sessions, query, { wholeWord, includeTitles, sessionLimit, snippetLimit });
|
|
375570
|
+
}
|
|
375571
|
+
case "settings:listBackups": {
|
|
375572
|
+
const bridge = this.options.settingsBridge;
|
|
375573
|
+
if (!bridge?.listBackups) {
|
|
375574
|
+
throw new WebSocketRpcError("METHOD_NOT_FOUND", "settings backups are not available on this gateway.");
|
|
375575
|
+
}
|
|
375576
|
+
return { backups: await bridge.listBackups() };
|
|
375577
|
+
}
|
|
375578
|
+
case "settings:restoreBackup": {
|
|
375579
|
+
const bridge = this.options.settingsBridge;
|
|
375580
|
+
if (!bridge?.restoreBackup) {
|
|
375581
|
+
throw new WebSocketRpcError("METHOD_NOT_FOUND", "settings backups are not available on this gateway.");
|
|
375582
|
+
}
|
|
375583
|
+
const name = this.readStringParam(params, "name");
|
|
375584
|
+
const result = await bridge.restoreBackup(name);
|
|
375585
|
+
if (result && typeof result === "object" && "ok" in result && !result.ok) {
|
|
375586
|
+
throw new WebSocketRpcError("BAD_REQUEST", String(result.error ?? "restore failed"));
|
|
375587
|
+
}
|
|
375588
|
+
return { restored: name };
|
|
375589
|
+
}
|
|
375590
|
+
case "settings:export": {
|
|
375591
|
+
const bridge = this.options.settingsBridge;
|
|
375592
|
+
if (!bridge?.export) {
|
|
375593
|
+
throw new WebSocketRpcError("METHOD_NOT_FOUND", "settings export is not available on this gateway.");
|
|
375594
|
+
}
|
|
375595
|
+
return { content: await bridge.export() };
|
|
375596
|
+
}
|
|
375597
|
+
case "settings:import": {
|
|
375598
|
+
const bridge = this.options.settingsBridge;
|
|
375599
|
+
if (!bridge?.import) {
|
|
375600
|
+
throw new WebSocketRpcError("METHOD_NOT_FOUND", "settings import is not available on this gateway.");
|
|
375601
|
+
}
|
|
375602
|
+
const content = this.readStringParam(params, "content");
|
|
375603
|
+
const result = await bridge.import(content);
|
|
375604
|
+
if (result && typeof result === "object" && "ok" in result && !result.ok) {
|
|
375605
|
+
throw new WebSocketRpcError("BAD_REQUEST", String(result.error ?? "import failed"));
|
|
375606
|
+
}
|
|
375607
|
+
return { imported: true };
|
|
375608
|
+
}
|
|
374964
375609
|
case "agent:exportHistory": {
|
|
374965
375610
|
const bridge = this.options.agentBridge;
|
|
374966
375611
|
if (!bridge?.exportHistory) {
|
|
@@ -383611,7 +384256,7 @@ var NodeMcpToolService = class extends McpRuntimeCore {
|
|
|
383611
384256
|
|
|
383612
384257
|
// ../../packages/backend/src/adapters/node/NodeSkillService.ts
|
|
383613
384258
|
var import_node_path16 = __toESM(require("node:path"), 1);
|
|
383614
|
-
var
|
|
384259
|
+
var import_node_os4 = __toESM(require("node:os"), 1);
|
|
383615
384260
|
|
|
383616
384261
|
// ../../packages/backend/src/skills/FileSkillStore.ts
|
|
383617
384262
|
var import_promises9 = __toESM(require("node:fs/promises"), 1);
|
|
@@ -383916,10 +384561,10 @@ ${files}`;
|
|
|
383916
384561
|
|
|
383917
384562
|
// ../../packages/backend/src/skills/scanRoots.ts
|
|
383918
384563
|
var import_node_path15 = __toESM(require("node:path"), 1);
|
|
383919
|
-
var
|
|
384564
|
+
var import_node_os3 = __toESM(require("node:os"), 1);
|
|
383920
384565
|
function resolveDefaultSkillScanRoots(options) {
|
|
383921
384566
|
const primaryRoot = import_node_path15.default.resolve(options.primaryRoot);
|
|
383922
|
-
const homeDir = (options.homeDir ||
|
|
384567
|
+
const homeDir = (options.homeDir || import_node_os3.default.homedir() || "").trim();
|
|
383923
384568
|
const platform2 = options.platform || process.platform;
|
|
383924
384569
|
const appData = (options.appData || process.env.APPDATA || "").trim();
|
|
383925
384570
|
const codexHome = (options.codexHome || process.env.CODEX_HOME || "").trim();
|
|
@@ -383991,7 +384636,7 @@ var NodeSkillService = class {
|
|
|
383991
384636
|
resolveScanRoots() {
|
|
383992
384637
|
return resolveDefaultSkillScanRoots({
|
|
383993
384638
|
primaryRoot: this.skillsDir,
|
|
383994
|
-
homeDir:
|
|
384639
|
+
homeDir: import_node_os4.default.homedir(),
|
|
383995
384640
|
platform: process.platform,
|
|
383996
384641
|
appData: process.env.APPDATA,
|
|
383997
384642
|
codexHome: process.env.CODEX_HOME
|
|
@@ -386114,6 +386759,367 @@ var SessionLogService = class _SessionLogService {
|
|
|
386114
386759
|
|
|
386115
386760
|
// ../../packages/backend/src/runtimes/gybackend/startGyBackend.ts
|
|
386116
386761
|
init_schedulerService();
|
|
386762
|
+
|
|
386763
|
+
// ../../packages/backend/src/services/settings/settingsBackup.ts
|
|
386764
|
+
var BACKUP_PREFIX = "settings-backup-";
|
|
386765
|
+
var BACKUP_SUFFIX = ".json";
|
|
386766
|
+
function backupNameFor(date6) {
|
|
386767
|
+
const pad = (n2, w = 2) => String(n2).padStart(w, "0");
|
|
386768
|
+
return `${BACKUP_PREFIX}${date6.getUTCFullYear()}${pad(date6.getUTCMonth() + 1)}${pad(date6.getUTCDate())}-${pad(date6.getUTCHours())}${pad(date6.getUTCMinutes())}${pad(date6.getUTCSeconds())}${BACKUP_SUFFIX}`;
|
|
386769
|
+
}
|
|
386770
|
+
function parseBackupName(name) {
|
|
386771
|
+
if (!name.startsWith(BACKUP_PREFIX) || !name.endsWith(BACKUP_SUFFIX)) return null;
|
|
386772
|
+
const stem = name.slice(BACKUP_PREFIX.length, -BACKUP_SUFFIX.length);
|
|
386773
|
+
const m2 = stem.match(/^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/);
|
|
386774
|
+
if (!m2) return null;
|
|
386775
|
+
const [, y, mo, d, h, mi, s] = m2.map(Number);
|
|
386776
|
+
const date6 = new Date(Date.UTC(y, mo - 1, d, h, mi, s));
|
|
386777
|
+
return Number.isNaN(date6.getTime()) ? null : date6;
|
|
386778
|
+
}
|
|
386779
|
+
var SettingsBackupService = class {
|
|
386780
|
+
constructor(io, opts = {}) {
|
|
386781
|
+
this.io = io;
|
|
386782
|
+
this.keep = opts.keep ?? 20;
|
|
386783
|
+
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
386784
|
+
}
|
|
386785
|
+
keep;
|
|
386786
|
+
now;
|
|
386787
|
+
/** Take a backup of the CURRENT settings content, then rotate. */
|
|
386788
|
+
backup() {
|
|
386789
|
+
const content = this.io.read();
|
|
386790
|
+
const name = backupNameFor(this.now());
|
|
386791
|
+
this.io.writeBackup(name, content);
|
|
386792
|
+
this.rotate();
|
|
386793
|
+
return { name, at: this.now(), size: content.length };
|
|
386794
|
+
}
|
|
386795
|
+
/** Delete oldest backups beyond the keep limit. Returns names removed. */
|
|
386796
|
+
rotate() {
|
|
386797
|
+
const names = this.io.listBackups().map((n2) => ({ n: n2, at: parseBackupName(n2) })).filter((x) => x.at !== null).sort((a, b) => a.at.getTime() - b.at.getTime());
|
|
386798
|
+
const excess = names.slice(0, Math.max(0, names.length - this.keep));
|
|
386799
|
+
for (const { n: n2 } of excess) this.io.deleteBackup(n2);
|
|
386800
|
+
return excess.map((x) => x.n);
|
|
386801
|
+
}
|
|
386802
|
+
/** List backups, newest first. */
|
|
386803
|
+
list() {
|
|
386804
|
+
return this.io.listBackups().map((n2) => ({ n: n2, at: parseBackupName(n2) })).filter((x) => x.at !== null).sort((a, b) => b.at.getTime() - a.at.getTime()).map(({ n: n2, at }) => ({ name: n2, at, size: this.io.readBackup(n2).length }));
|
|
386805
|
+
}
|
|
386806
|
+
/** Restore a backup by name (writes it to the live settings path). */
|
|
386807
|
+
restore(name) {
|
|
386808
|
+
if (!this.io.listBackups().includes(name)) {
|
|
386809
|
+
return { ok: false, error: `no backup named "${name}"` };
|
|
386810
|
+
}
|
|
386811
|
+
this.backup();
|
|
386812
|
+
const content = this.io.readBackup(name);
|
|
386813
|
+
this.io.write(content);
|
|
386814
|
+
return { ok: true };
|
|
386815
|
+
}
|
|
386816
|
+
/** Export the current settings as a portable JSON string. */
|
|
386817
|
+
export() {
|
|
386818
|
+
return this.io.read();
|
|
386819
|
+
}
|
|
386820
|
+
/** Validate + import settings JSON. Refuses non-object / empty content. */
|
|
386821
|
+
import(content) {
|
|
386822
|
+
const trimmed = (content ?? "").trim();
|
|
386823
|
+
if (!trimmed) return { ok: false, error: "empty settings content" };
|
|
386824
|
+
let parsed;
|
|
386825
|
+
try {
|
|
386826
|
+
parsed = JSON.parse(trimmed);
|
|
386827
|
+
} catch (e) {
|
|
386828
|
+
return { ok: false, error: `invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
386829
|
+
}
|
|
386830
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
386831
|
+
return { ok: false, error: "settings content must be a JSON object" };
|
|
386832
|
+
}
|
|
386833
|
+
this.backup();
|
|
386834
|
+
this.io.write(trimmed);
|
|
386835
|
+
return { ok: true };
|
|
386836
|
+
}
|
|
386837
|
+
};
|
|
386838
|
+
|
|
386839
|
+
// ../../packages/backend/src/services/terminal/idleTimeout.ts
|
|
386840
|
+
var IdleTimeoutService = class {
|
|
386841
|
+
idleMs;
|
|
386842
|
+
protectedIds;
|
|
386843
|
+
now;
|
|
386844
|
+
lastActivity = /* @__PURE__ */ new Map();
|
|
386845
|
+
constructor(opts = {}) {
|
|
386846
|
+
this.idleMs = Math.max(0, (opts.idleMinutes ?? 30) * 6e4);
|
|
386847
|
+
this.protectedIds = new Set(opts.protectedIds ?? []);
|
|
386848
|
+
this.now = opts.now ?? (() => Date.now());
|
|
386849
|
+
}
|
|
386850
|
+
/** Record activity for a terminal. */
|
|
386851
|
+
touch(terminalId) {
|
|
386852
|
+
this.lastActivity.set(terminalId, this.now());
|
|
386853
|
+
}
|
|
386854
|
+
/** Register a terminal (starts its idle clock). */
|
|
386855
|
+
register(terminalId) {
|
|
386856
|
+
if (!this.lastActivity.has(terminalId)) {
|
|
386857
|
+
this.lastActivity.set(terminalId, this.now());
|
|
386858
|
+
}
|
|
386859
|
+
}
|
|
386860
|
+
/** Forget a terminal (on close). */
|
|
386861
|
+
forget(terminalId) {
|
|
386862
|
+
this.lastActivity.delete(terminalId);
|
|
386863
|
+
}
|
|
386864
|
+
/** Is this terminal idle past the threshold? */
|
|
386865
|
+
isIdle(terminalId) {
|
|
386866
|
+
if (this.protectedIds.has(terminalId)) return false;
|
|
386867
|
+
const last = this.lastActivity.get(terminalId);
|
|
386868
|
+
if (last === void 0) return false;
|
|
386869
|
+
return this.now() - last >= this.idleMs;
|
|
386870
|
+
}
|
|
386871
|
+
/** All currently-idle terminals (among the known ones). */
|
|
386872
|
+
idleTerminals(knownIds) {
|
|
386873
|
+
const t = this.now();
|
|
386874
|
+
const out = [];
|
|
386875
|
+
for (const id of knownIds) {
|
|
386876
|
+
if (this.protectedIds.has(id)) continue;
|
|
386877
|
+
const last = this.lastActivity.get(id);
|
|
386878
|
+
if (last === void 0) continue;
|
|
386879
|
+
const idleMin = (t - last) / 6e4;
|
|
386880
|
+
if (idleMin * 6e4 >= this.idleMs) {
|
|
386881
|
+
out.push({ terminalId: id, idleMinutes: Math.round(idleMin * 10) / 10 });
|
|
386882
|
+
}
|
|
386883
|
+
}
|
|
386884
|
+
return out;
|
|
386885
|
+
}
|
|
386886
|
+
/** Configure the threshold at runtime. */
|
|
386887
|
+
setIdleMinutes(minutes) {
|
|
386888
|
+
this.idleMs = Math.max(0, minutes * 6e4);
|
|
386889
|
+
}
|
|
386890
|
+
get idleMinutes() {
|
|
386891
|
+
return this.idleMs / 6e4;
|
|
386892
|
+
}
|
|
386893
|
+
};
|
|
386894
|
+
|
|
386895
|
+
// ../../packages/backend/src/services/Gateway/restApi.ts
|
|
386896
|
+
function matchRestRoute(routes, method, path32) {
|
|
386897
|
+
const normalized = path32.replace(/\/+$/, "") || "/";
|
|
386898
|
+
for (const route of routes) {
|
|
386899
|
+
if (route.method !== method.toUpperCase()) continue;
|
|
386900
|
+
const patternParts = route.path.split("/").filter(Boolean);
|
|
386901
|
+
const pathParts = normalized.split("/").filter(Boolean);
|
|
386902
|
+
if (patternParts.length !== pathParts.length) continue;
|
|
386903
|
+
const params = {};
|
|
386904
|
+
let matched = true;
|
|
386905
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
386906
|
+
const p = patternParts[i];
|
|
386907
|
+
if (p.startsWith(":")) {
|
|
386908
|
+
params[p.slice(1)] = decodeURIComponent(pathParts[i]);
|
|
386909
|
+
} else if (p !== pathParts[i]) {
|
|
386910
|
+
matched = false;
|
|
386911
|
+
break;
|
|
386912
|
+
}
|
|
386913
|
+
}
|
|
386914
|
+
if (matched) return { route, pathParams: params };
|
|
386915
|
+
}
|
|
386916
|
+
return null;
|
|
386917
|
+
}
|
|
386918
|
+
function defaultRestRoutes() {
|
|
386919
|
+
return [
|
|
386920
|
+
{
|
|
386921
|
+
method: "GET",
|
|
386922
|
+
path: "/api/v1/health",
|
|
386923
|
+
gatewayMethod: "gateway:ping",
|
|
386924
|
+
description: "Liveness check"
|
|
386925
|
+
},
|
|
386926
|
+
{
|
|
386927
|
+
method: "GET",
|
|
386928
|
+
path: "/api/v1/methods",
|
|
386929
|
+
gatewayMethod: "gateway:describe",
|
|
386930
|
+
description: "List all gateway methods (self-describing)"
|
|
386931
|
+
},
|
|
386932
|
+
{
|
|
386933
|
+
method: "GET",
|
|
386934
|
+
path: "/api/v1/terminals",
|
|
386935
|
+
gatewayMethod: "terminal:list",
|
|
386936
|
+
description: "List terminal tabs"
|
|
386937
|
+
},
|
|
386938
|
+
{
|
|
386939
|
+
method: "GET",
|
|
386940
|
+
path: "/api/v1/sessions",
|
|
386941
|
+
gatewayMethod: "session:list",
|
|
386942
|
+
description: "List chat sessions"
|
|
386943
|
+
},
|
|
386944
|
+
{
|
|
386945
|
+
method: "GET",
|
|
386946
|
+
path: "/api/v1/skills",
|
|
386947
|
+
gatewayMethod: "skills:getAll",
|
|
386948
|
+
description: "List loaded skills"
|
|
386949
|
+
},
|
|
386950
|
+
{
|
|
386951
|
+
method: "GET",
|
|
386952
|
+
path: "/api/v1/observability/metrics",
|
|
386953
|
+
gatewayMethod: "observability:metricsPrometheus",
|
|
386954
|
+
description: "Host metrics (Prometheus text or summary)"
|
|
386955
|
+
},
|
|
386956
|
+
{
|
|
386957
|
+
method: "GET",
|
|
386958
|
+
path: "/api/v1/observability/dashboard",
|
|
386959
|
+
gatewayMethod: "observability:liveDashboardState",
|
|
386960
|
+
description: "Live dashboard state"
|
|
386961
|
+
},
|
|
386962
|
+
{
|
|
386963
|
+
method: "GET",
|
|
386964
|
+
path: "/api/v1/observability/apm",
|
|
386965
|
+
gatewayMethod: "observability:apmSummary",
|
|
386966
|
+
description: "APM summary (LLM + app traces)"
|
|
386967
|
+
},
|
|
386968
|
+
{
|
|
386969
|
+
method: "GET",
|
|
386970
|
+
path: "/api/v1/history/search",
|
|
386971
|
+
gatewayMethod: "history:search",
|
|
386972
|
+
description: "Cross-session history search (?q=...)",
|
|
386973
|
+
buildParams: (_p, body) => {
|
|
386974
|
+
const b = body ?? {};
|
|
386975
|
+
return { query: b.q ?? b.query ?? "" };
|
|
386976
|
+
}
|
|
386977
|
+
},
|
|
386978
|
+
{
|
|
386979
|
+
method: "POST",
|
|
386980
|
+
path: "/api/v1/terminals/:id/write",
|
|
386981
|
+
gatewayMethod: "terminal:write",
|
|
386982
|
+
description: "Write data to a terminal tab",
|
|
386983
|
+
buildParams: (p, body) => {
|
|
386984
|
+
const b = body ?? {};
|
|
386985
|
+
return { terminalId: p.id, data: b.data ?? "" };
|
|
386986
|
+
}
|
|
386987
|
+
},
|
|
386988
|
+
{
|
|
386989
|
+
method: "GET",
|
|
386990
|
+
path: "/api/v1/terminals/:id/buffer",
|
|
386991
|
+
gatewayMethod: "terminal:getBufferDelta",
|
|
386992
|
+
description: "Read a terminal tab output delta",
|
|
386993
|
+
buildParams: (p, body) => {
|
|
386994
|
+
const b = body ?? {};
|
|
386995
|
+
return { terminalId: p.id, fromOffset: b.fromOffset ?? 0 };
|
|
386996
|
+
}
|
|
386997
|
+
},
|
|
386998
|
+
{
|
|
386999
|
+
method: "POST",
|
|
387000
|
+
path: "/api/v1/sessions/:id/chat",
|
|
387001
|
+
gatewayMethod: "agent:startTask",
|
|
387002
|
+
description: "Send a message to the agent (blocking)",
|
|
387003
|
+
buildParams: (p, body) => {
|
|
387004
|
+
const b = body ?? {};
|
|
387005
|
+
return { sessionId: p.id, userInput: b.message ?? b.userInput ?? "" };
|
|
387006
|
+
}
|
|
387007
|
+
},
|
|
387008
|
+
{
|
|
387009
|
+
method: "POST",
|
|
387010
|
+
path: "/api/v1/rpc",
|
|
387011
|
+
gatewayMethod: "",
|
|
387012
|
+
description: "Escape hatch: dispatch any gateway method",
|
|
387013
|
+
buildParams: (_p, body) => {
|
|
387014
|
+
const b = body ?? {};
|
|
387015
|
+
return { __rpcMethod: b.method ?? "", ...b.params ?? {} };
|
|
387016
|
+
}
|
|
387017
|
+
}
|
|
387018
|
+
];
|
|
387019
|
+
}
|
|
387020
|
+
async function handleRestRequest(routes, dispatch, req) {
|
|
387021
|
+
const match = matchRestRoute(routes, req.method, req.path);
|
|
387022
|
+
if (!match) {
|
|
387023
|
+
return {
|
|
387024
|
+
status: 404,
|
|
387025
|
+
body: { error: "not_found", message: `No REST route for ${req.method} ${req.path}` }
|
|
387026
|
+
};
|
|
387027
|
+
}
|
|
387028
|
+
let gatewayMethod = match.route.gatewayMethod;
|
|
387029
|
+
let params;
|
|
387030
|
+
if (gatewayMethod === "") {
|
|
387031
|
+
const raw = req.body ?? {};
|
|
387032
|
+
if (!raw.method) {
|
|
387033
|
+
return { status: 400, body: { error: "bad_request", message: 'POST /api/v1/rpc needs {"method": "...", "params": {...}}' } };
|
|
387034
|
+
}
|
|
387035
|
+
gatewayMethod = raw.method;
|
|
387036
|
+
params = raw.params ?? {};
|
|
387037
|
+
} else {
|
|
387038
|
+
params = match.route.buildParams?.(match.pathParams, req.body) ?? match.pathParams;
|
|
387039
|
+
}
|
|
387040
|
+
try {
|
|
387041
|
+
const result = await dispatch(gatewayMethod, params);
|
|
387042
|
+
return { status: 200, body: result ?? { ok: true } };
|
|
387043
|
+
} catch (error40) {
|
|
387044
|
+
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
387045
|
+
const status = /not found|no terminal|no session|no playbook/i.test(message) ? 404 : /invalid|bad request|must be|requires/i.test(message) ? 400 : 500;
|
|
387046
|
+
return { status, body: { error: "gateway_error", message } };
|
|
387047
|
+
}
|
|
387048
|
+
}
|
|
387049
|
+
|
|
387050
|
+
// ../../packages/backend/src/services/Gateway/gatewayRateLimit.ts
|
|
387051
|
+
var GatewayRateLimiter = class {
|
|
387052
|
+
capacity;
|
|
387053
|
+
refillPerSecond;
|
|
387054
|
+
authFailureLimit;
|
|
387055
|
+
authLockoutMs;
|
|
387056
|
+
now;
|
|
387057
|
+
buckets = /* @__PURE__ */ new Map();
|
|
387058
|
+
authTrackers = /* @__PURE__ */ new Map();
|
|
387059
|
+
constructor(opts = {}) {
|
|
387060
|
+
this.capacity = opts.capacity ?? 60;
|
|
387061
|
+
this.refillPerSecond = opts.refillPerSecond ?? 1;
|
|
387062
|
+
this.authFailureLimit = opts.authFailureLimit ?? 5;
|
|
387063
|
+
this.authLockoutMs = opts.authLockoutMs ?? 6e4;
|
|
387064
|
+
this.now = opts.now ?? (() => Date.now());
|
|
387065
|
+
}
|
|
387066
|
+
refill(bucket) {
|
|
387067
|
+
const t = this.now();
|
|
387068
|
+
const elapsed2 = (t - bucket.lastRefill) / 1e3;
|
|
387069
|
+
if (elapsed2 <= 0) return;
|
|
387070
|
+
bucket.tokens = Math.min(this.capacity, bucket.tokens + elapsed2 * this.refillPerSecond);
|
|
387071
|
+
bucket.lastRefill = t;
|
|
387072
|
+
}
|
|
387073
|
+
/** Check (and consume) one token for a client. */
|
|
387074
|
+
check(clientKey) {
|
|
387075
|
+
const t = this.now();
|
|
387076
|
+
const bucket = this.buckets.get(clientKey) ?? { tokens: this.capacity, lastRefill: t };
|
|
387077
|
+
this.refill(bucket);
|
|
387078
|
+
this.buckets.set(clientKey, bucket);
|
|
387079
|
+
if (bucket.tokens >= 1) {
|
|
387080
|
+
bucket.tokens -= 1;
|
|
387081
|
+
return { allowed: true, remaining: Math.floor(bucket.tokens) };
|
|
387082
|
+
}
|
|
387083
|
+
const msToNextToken = Math.ceil((1 - bucket.tokens) / this.refillPerSecond * 1e3);
|
|
387084
|
+
return { allowed: false, remaining: 0, retryAfterMs: msToNextToken, reason: "rate-limited" };
|
|
387085
|
+
}
|
|
387086
|
+
/** Record a failed authentication attempt; returns the lockout decision. */
|
|
387087
|
+
recordAuthFailure(clientKey) {
|
|
387088
|
+
const t = this.now();
|
|
387089
|
+
const tracker = this.authTrackers.get(clientKey) ?? { failures: 0, lockedUntil: 0 };
|
|
387090
|
+
tracker.failures += 1;
|
|
387091
|
+
if (tracker.failures >= this.authFailureLimit) {
|
|
387092
|
+
tracker.lockedUntil = t + this.authLockoutMs;
|
|
387093
|
+
tracker.failures = 0;
|
|
387094
|
+
}
|
|
387095
|
+
this.authTrackers.set(clientKey, tracker);
|
|
387096
|
+
if (t < tracker.lockedUntil) {
|
|
387097
|
+
return { allowed: false, remaining: 0, retryAfterMs: tracker.lockedUntil - t, reason: "locked-out" };
|
|
387098
|
+
}
|
|
387099
|
+
return { allowed: true, remaining: this.authFailureLimit - tracker.failures };
|
|
387100
|
+
}
|
|
387101
|
+
/** Clear auth failures on successful authentication. */
|
|
387102
|
+
recordAuthSuccess(clientKey) {
|
|
387103
|
+
this.authTrackers.delete(clientKey);
|
|
387104
|
+
}
|
|
387105
|
+
/** Is this client currently locked out? */
|
|
387106
|
+
isLockedOut(clientKey) {
|
|
387107
|
+
const tracker = this.authTrackers.get(clientKey);
|
|
387108
|
+
if (!tracker) return false;
|
|
387109
|
+
return this.now() < tracker.lockedUntil;
|
|
387110
|
+
}
|
|
387111
|
+
/** Drop state for a client (on disconnect). */
|
|
387112
|
+
forget(clientKey) {
|
|
387113
|
+
this.buckets.delete(clientKey);
|
|
387114
|
+
this.authTrackers.delete(clientKey);
|
|
387115
|
+
}
|
|
387116
|
+
/** Number of tracked clients (for monitoring/tests). */
|
|
387117
|
+
get clientCount() {
|
|
387118
|
+
return this.buckets.size;
|
|
387119
|
+
}
|
|
387120
|
+
};
|
|
387121
|
+
|
|
387122
|
+
// ../../packages/backend/src/runtimes/gybackend/startGyBackend.ts
|
|
386117
387123
|
init_scheduledTaskRunner();
|
|
386118
387124
|
|
|
386119
387125
|
// ../../packages/backend/src/services/history/HistoryStorageMigration.ts
|
|
@@ -393608,11 +394614,11 @@ function createObservability(deps) {
|
|
|
393608
394614
|
const policyEngine = new AgtPolicyEngine({
|
|
393609
394615
|
loadPolicy: async () => {
|
|
393610
394616
|
const req = (0, import_node_module5.createRequire)(typeof __filename !== "undefined" ? __filename : import_meta7.url);
|
|
393611
|
-
const
|
|
394617
|
+
const fs23 = req("node:fs");
|
|
393612
394618
|
const path32 = req("node:path");
|
|
393613
394619
|
const policyPath = path32.join(".", "policy.yaml");
|
|
393614
|
-
if (
|
|
393615
|
-
return parsePolicyYaml(
|
|
394620
|
+
if (fs23.existsSync(policyPath)) {
|
|
394621
|
+
return parsePolicyYaml(fs23.readFileSync(policyPath, "utf8"));
|
|
393616
394622
|
}
|
|
393617
394623
|
return {
|
|
393618
394624
|
name: "rterm-default",
|
|
@@ -393654,18 +394660,18 @@ function createObservability(deps) {
|
|
|
393654
394660
|
const scanRoots = [pluginScanRoot, "./plugins"];
|
|
393655
394661
|
try {
|
|
393656
394662
|
const req = (0, import_node_module5.createRequire)(typeof __filename !== "undefined" ? __filename : import_meta7.url);
|
|
393657
|
-
const
|
|
393658
|
-
if (
|
|
394663
|
+
const fs23 = req("node:fs");
|
|
394664
|
+
if (fs23.existsSync(bundlePluginRoot)) scanRoots.push(bundlePluginRoot);
|
|
393659
394665
|
} catch {
|
|
393660
394666
|
}
|
|
393661
394667
|
try {
|
|
393662
394668
|
const req = (0, import_node_module5.createRequire)(typeof __filename !== "undefined" ? __filename : import_meta7.url);
|
|
393663
|
-
const
|
|
394669
|
+
const fs23 = req("node:fs");
|
|
393664
394670
|
const path32 = req("node:path");
|
|
393665
394671
|
const resourcesPath = process.resourcesPath;
|
|
393666
394672
|
if (resourcesPath) {
|
|
393667
394673
|
const resourcesPlugins = path32.join(resourcesPath, "plugins");
|
|
393668
|
-
if (
|
|
394674
|
+
if (fs23.existsSync(resourcesPlugins)) scanRoots.push(resourcesPlugins);
|
|
393669
394675
|
}
|
|
393670
394676
|
} catch {
|
|
393671
394677
|
}
|
|
@@ -396631,434 +397637,581 @@ async function startGyBackend() {
|
|
|
396631
397637
|
gatewayService.broadcastRaw("skills:updated", result.skills);
|
|
396632
397638
|
gatewayService.broadcastRaw("memory:updated", result.memory);
|
|
396633
397639
|
};
|
|
396634
|
-
const
|
|
396635
|
-
|
|
396636
|
-
|
|
396637
|
-
|
|
396638
|
-
|
|
396639
|
-
|
|
396640
|
-
|
|
397640
|
+
const gatewayRateLimiter = new GatewayRateLimiter({
|
|
397641
|
+
capacity: Number(import_node_process2.default.env.RTERM_RATE_LIMIT_BURST ?? 60) || 60,
|
|
397642
|
+
refillPerSecond: Number(import_node_process2.default.env.RTERM_RATE_LIMIT_REFILL ?? 1) || 1,
|
|
397643
|
+
authFailureLimit: 5,
|
|
397644
|
+
authLockoutMs: 6e4
|
|
397645
|
+
});
|
|
397646
|
+
const settingsDir = import_node_path28.default.dirname(settingsService.getSettingsPath?.() ?? "");
|
|
397647
|
+
const backupsDir = settingsDir ? import_node_path28.default.join(settingsDir, "backups") : "";
|
|
397648
|
+
const settingsBackupService = new SettingsBackupService(
|
|
397649
|
+
backupsDir ? {
|
|
397650
|
+
read: () => import_node_fs12.default.readFileSync(settingsService.getSettingsPath(), "utf8"),
|
|
397651
|
+
write: (c) => import_node_fs12.default.writeFileSync(settingsService.getSettingsPath(), c),
|
|
397652
|
+
listBackups: () => import_node_fs12.default.existsSync(backupsDir) ? import_node_fs12.default.readdirSync(backupsDir) : [],
|
|
397653
|
+
readBackup: (n2) => import_node_fs12.default.readFileSync(import_node_path28.default.join(backupsDir, n2), "utf8"),
|
|
397654
|
+
writeBackup: (n2, c) => {
|
|
397655
|
+
import_node_fs12.default.mkdirSync(backupsDir, { recursive: true });
|
|
397656
|
+
import_node_fs12.default.writeFileSync(import_node_path28.default.join(backupsDir, n2), c);
|
|
396641
397657
|
},
|
|
396642
|
-
|
|
396643
|
-
|
|
396644
|
-
|
|
396645
|
-
|
|
396646
|
-
|
|
396647
|
-
|
|
396648
|
-
|
|
396649
|
-
|
|
396650
|
-
|
|
396651
|
-
|
|
396652
|
-
|
|
396653
|
-
|
|
396654
|
-
|
|
397658
|
+
deleteBackup: (n2) => {
|
|
397659
|
+
const p = import_node_path28.default.join(backupsDir, n2);
|
|
397660
|
+
if (import_node_fs12.default.existsSync(p)) import_node_fs12.default.unlinkSync(p);
|
|
397661
|
+
}
|
|
397662
|
+
} : { read: () => "", write: () => {
|
|
397663
|
+
}, listBackups: () => [], readBackup: () => "", writeBackup: () => {
|
|
397664
|
+
}, deleteBackup: () => {
|
|
397665
|
+
} },
|
|
397666
|
+
{ keep: 20 }
|
|
397667
|
+
);
|
|
397668
|
+
const originalSetSettings = settingsService.setSettings.bind(settingsService);
|
|
397669
|
+
settingsService.setSettings = ((next) => {
|
|
397670
|
+
try {
|
|
397671
|
+
settingsBackupService.backup();
|
|
397672
|
+
} catch {
|
|
397673
|
+
}
|
|
397674
|
+
return originalSetSettings(next);
|
|
397675
|
+
});
|
|
397676
|
+
const idleTimeoutService = new IdleTimeoutService({
|
|
397677
|
+
idleMinutes: Number(import_node_process2.default.env.RTERM_IDLE_TIMEOUT_MINUTES ?? 30) || 30,
|
|
397678
|
+
protectedIds: [import_node_process2.default.env.GYBACKEND_TERMINAL_ID || "local-main"]
|
|
397679
|
+
});
|
|
397680
|
+
for (const t of terminalService.getAllTerminals()) idleTimeoutService.register(t.id);
|
|
397681
|
+
terminalService.onTerminalCreated?.((id) => idleTimeoutService.register(id));
|
|
397682
|
+
terminalService.onTerminalData?.((id) => idleTimeoutService.touch(id));
|
|
397683
|
+
terminalService.onTerminalClosed?.((id) => idleTimeoutService.forget(id));
|
|
397684
|
+
const idleSweeper = setInterval(() => {
|
|
397685
|
+
try {
|
|
397686
|
+
const idle = idleTimeoutService.idleTerminals(terminalService.getAllTerminals().map((t) => t.id));
|
|
397687
|
+
for (const { terminalId, idleMinutes } of idle) {
|
|
397688
|
+
console.log(`[gybackend] closing idle terminal ${terminalId} (${idleMinutes}min idle)`);
|
|
397689
|
+
try {
|
|
397690
|
+
terminalService.kill(terminalId);
|
|
397691
|
+
} catch {
|
|
397692
|
+
}
|
|
397693
|
+
idleTimeoutService.forget(terminalId);
|
|
397694
|
+
}
|
|
397695
|
+
} catch {
|
|
397696
|
+
}
|
|
397697
|
+
}, 6e4);
|
|
397698
|
+
if (typeof idleSweeper.unref === "function") idleSweeper.unref();
|
|
397699
|
+
let restDispatchTarget = null;
|
|
397700
|
+
const restDispatch = async (method, params) => {
|
|
397701
|
+
const target = restDispatchTarget;
|
|
397702
|
+
if (!target?.handleRequest) {
|
|
397703
|
+
throw new Error("REST API is not ready yet (gateway still starting)");
|
|
397704
|
+
}
|
|
397705
|
+
return await target.handleRequest(method, params);
|
|
397706
|
+
};
|
|
397707
|
+
const buildRestHttpRoutesSync = (opts) => {
|
|
397708
|
+
const routes = defaultRestRoutes();
|
|
397709
|
+
const staticRoutes = routes.filter((r) => !r.path.includes(":")).map((route) => ({
|
|
397710
|
+
path: route.path,
|
|
397711
|
+
handler: makeRestHandler(routes, opts)
|
|
397712
|
+
}));
|
|
397713
|
+
return staticRoutes;
|
|
397714
|
+
};
|
|
397715
|
+
const makeRestHandler = (routes, opts) => {
|
|
397716
|
+
return async (req, res) => {
|
|
397717
|
+
const r = req;
|
|
397718
|
+
const s = res;
|
|
397719
|
+
try {
|
|
397720
|
+
if (!await opts.isAuthorized(r)) {
|
|
397721
|
+
s.writeHead?.(401, { "content-type": "application/json" });
|
|
397722
|
+
s.end?.(JSON.stringify({ error: "unauthorized" }));
|
|
397723
|
+
return;
|
|
397724
|
+
}
|
|
397725
|
+
const url2 = new URL(r.url ?? "/", "http://localhost");
|
|
397726
|
+
const body = r.method === "POST" ? await readJsonBody(r) : Object.fromEntries(url2.searchParams.entries());
|
|
397727
|
+
const result = await handleRestRequest(routes, opts.dispatch, {
|
|
397728
|
+
method: r.method ?? "GET",
|
|
397729
|
+
path: url2.pathname,
|
|
397730
|
+
body
|
|
397731
|
+
});
|
|
397732
|
+
s.writeHead?.(result.status, { "content-type": "application/json" });
|
|
397733
|
+
s.end?.(JSON.stringify(result.body));
|
|
397734
|
+
} catch (e) {
|
|
397735
|
+
s.writeHead?.(500, { "content-type": "application/json" });
|
|
397736
|
+
s.end?.(JSON.stringify({ error: "internal", message: e instanceof Error ? e.message : String(e) }));
|
|
397737
|
+
}
|
|
397738
|
+
};
|
|
397739
|
+
};
|
|
397740
|
+
const readJsonBody = (req) => new Promise((resolve2) => {
|
|
397741
|
+
let data = "";
|
|
397742
|
+
req.on?.("data", (d) => {
|
|
397743
|
+
data += String(d ?? "");
|
|
397744
|
+
});
|
|
397745
|
+
req.on?.("end", () => {
|
|
397746
|
+
try {
|
|
397747
|
+
resolve2(data ? JSON.parse(data) : {});
|
|
397748
|
+
} catch {
|
|
397749
|
+
resolve2({});
|
|
397750
|
+
}
|
|
397751
|
+
});
|
|
397752
|
+
});
|
|
397753
|
+
const wsGatewayControlService = new WebSocketGatewayControlService({
|
|
397754
|
+
createAdapter: (host, port, ipFilter) => {
|
|
397755
|
+
const adapter = new WebSocketGatewayAdapter(gatewayService, {
|
|
397756
|
+
host,
|
|
397757
|
+
port,
|
|
397758
|
+
accessTokenAuth: {
|
|
397759
|
+
verifyToken: (token) => accessTokenService.verifyToken(token),
|
|
397760
|
+
allowLocalhostWithoutToken: true
|
|
397761
|
+
},
|
|
397762
|
+
// v3.2.18: per-client rate limiting (token bucket + auth lockout).
|
|
397763
|
+
rateLimiter: gatewayRateLimiter,
|
|
397764
|
+
ipFilter,
|
|
397765
|
+
// Browser dashboard on the SAME port as the WS gateway: /dashboard serves
|
|
397766
|
+
// a live page (WS push via observability:liveDashboardSubscribe, with a
|
|
397767
|
+
// /dashboard/json polling fallback). Auth mirrors the WS gateway —
|
|
397768
|
+
// loopback open, remote needs a valid access token.
|
|
397769
|
+
// v3.2.18: REST API routes (/api/v1/*) are prepended so curl/CI can
|
|
397770
|
+
// drive the gateway without a WS client.
|
|
397771
|
+
httpRoutes: [
|
|
397772
|
+
...buildRestHttpRoutesSync({
|
|
397773
|
+
isAuthorized: (req) => dashboardHttpAuthorized(req, (t) => accessTokenService.verifyToken(t)),
|
|
397774
|
+
dispatch: restDispatch
|
|
397775
|
+
}),
|
|
397776
|
+
{
|
|
397777
|
+
path: "/dashboard",
|
|
397778
|
+
handler: async (req, res) => {
|
|
397779
|
+
if (!await dashboardHttpAuthorized(req, (t) => accessTokenService.verifyToken(t))) {
|
|
397780
|
+
res.writeHead(401, { "content-type": "text/plain; charset=utf-8" });
|
|
397781
|
+
res.end("missing/invalid access token");
|
|
397782
|
+
return;
|
|
397783
|
+
}
|
|
397784
|
+
const state = await observability.dashboard.state();
|
|
397785
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
397786
|
+
res.end(
|
|
397787
|
+
renderLiveDashboardHtml(state, {
|
|
397788
|
+
title: "RTerm \xB7 Unified Dashboard",
|
|
397789
|
+
dataUrl: "/dashboard/json"
|
|
397790
|
+
})
|
|
397791
|
+
);
|
|
397792
|
+
}
|
|
397793
|
+
},
|
|
397794
|
+
{
|
|
397795
|
+
path: "/dashboard/json",
|
|
397796
|
+
handler: async (req, res) => {
|
|
397797
|
+
if (!await dashboardHttpAuthorized(req, (t) => accessTokenService.verifyToken(t))) {
|
|
397798
|
+
res.writeHead(401, { "content-type": "text/plain; charset=utf-8" });
|
|
397799
|
+
res.end("missing/invalid access token");
|
|
397800
|
+
return;
|
|
397801
|
+
}
|
|
397802
|
+
const state = await observability.dashboard.state();
|
|
397803
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
397804
|
+
res.end(JSON.stringify(state));
|
|
396655
397805
|
}
|
|
396656
|
-
const state = await observability.dashboard.state();
|
|
396657
|
-
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
396658
|
-
res.end(
|
|
396659
|
-
renderLiveDashboardHtml(state, {
|
|
396660
|
-
title: "RTerm \xB7 Unified Dashboard",
|
|
396661
|
-
dataUrl: "/dashboard/json"
|
|
396662
|
-
})
|
|
396663
|
-
);
|
|
396664
397806
|
}
|
|
396665
|
-
|
|
396666
|
-
{
|
|
396667
|
-
|
|
396668
|
-
|
|
396669
|
-
|
|
396670
|
-
|
|
396671
|
-
|
|
396672
|
-
|
|
397807
|
+
],
|
|
397808
|
+
terminalBridge: {
|
|
397809
|
+
listTerminals: () => terminalService.getDisplayTerminals().map((terminal) => ({
|
|
397810
|
+
id: terminal.id,
|
|
397811
|
+
title: terminal.title,
|
|
397812
|
+
type: terminal.type,
|
|
397813
|
+
cols: terminal.cols,
|
|
397814
|
+
rows: terminal.rows,
|
|
397815
|
+
runtimeState: terminal.runtimeState,
|
|
397816
|
+
lastExitCode: terminal.lastExitCode,
|
|
397817
|
+
monitorIdentity: terminalService.getMonitorIdentity(terminal.id) ?? void 0
|
|
397818
|
+
})),
|
|
397819
|
+
createTab: async (config2) => {
|
|
397820
|
+
const snapshot = terminalService.getDisplayTerminals();
|
|
397821
|
+
const normalized = createAutoTerminalConfig(snapshot, config2);
|
|
397822
|
+
const tab = await terminalService.createTerminal(normalized);
|
|
397823
|
+
return { id: tab.id };
|
|
397824
|
+
},
|
|
397825
|
+
write: async (terminalId, data) => {
|
|
397826
|
+
terminalService.write(terminalId, data);
|
|
397827
|
+
},
|
|
397828
|
+
writePaths: async (terminalId, paths) => {
|
|
397829
|
+
terminalService.writePaths(terminalId, paths);
|
|
397830
|
+
},
|
|
397831
|
+
resize: async (terminalId, cols, rows) => {
|
|
397832
|
+
terminalService.resize(terminalId, cols, rows);
|
|
397833
|
+
},
|
|
397834
|
+
kill: async (terminalId) => {
|
|
397835
|
+
if (terminalService.getDisplayTerminals().length <= 1) {
|
|
397836
|
+
throw new Error("Cannot close the last terminal tab.");
|
|
396673
397837
|
}
|
|
396674
|
-
|
|
396675
|
-
|
|
396676
|
-
|
|
397838
|
+
terminalService.kill(terminalId);
|
|
397839
|
+
},
|
|
397840
|
+
reconnect: async (terminalId) => {
|
|
397841
|
+
const tab = await terminalService.reconnectTerminal(terminalId);
|
|
397842
|
+
return { id: tab.id };
|
|
397843
|
+
},
|
|
397844
|
+
setTitle: async (terminalId, title) => {
|
|
397845
|
+
terminalService.setTitle(terminalId, title);
|
|
397846
|
+
},
|
|
397847
|
+
setSelection: async (terminalId, selectionText) => {
|
|
397848
|
+
terminalService.setSelection(terminalId, selectionText);
|
|
397849
|
+
},
|
|
397850
|
+
getBufferDelta: async (terminalId, fromOffset) => {
|
|
397851
|
+
const data = terminalService.getBufferDelta(terminalId, fromOffset);
|
|
397852
|
+
const offset = terminalService.getCurrentOffset(terminalId);
|
|
397853
|
+
return { data, offset };
|
|
397854
|
+
},
|
|
397855
|
+
generateCommandDraft: async (terminalId, prompt, profileId) => {
|
|
397856
|
+
return await terminalCommandDraftService.generateCommandDraft({
|
|
397857
|
+
terminalId,
|
|
397858
|
+
prompt,
|
|
397859
|
+
profileId
|
|
397860
|
+
});
|
|
396677
397861
|
}
|
|
396678
|
-
}
|
|
396679
|
-
],
|
|
396680
|
-
terminalBridge: {
|
|
396681
|
-
listTerminals: () => terminalService.getDisplayTerminals().map((terminal) => ({
|
|
396682
|
-
id: terminal.id,
|
|
396683
|
-
title: terminal.title,
|
|
396684
|
-
type: terminal.type,
|
|
396685
|
-
cols: terminal.cols,
|
|
396686
|
-
rows: terminal.rows,
|
|
396687
|
-
runtimeState: terminal.runtimeState,
|
|
396688
|
-
lastExitCode: terminal.lastExitCode,
|
|
396689
|
-
monitorIdentity: terminalService.getMonitorIdentity(terminal.id) ?? void 0
|
|
396690
|
-
})),
|
|
396691
|
-
createTab: async (config2) => {
|
|
396692
|
-
const snapshot = terminalService.getDisplayTerminals();
|
|
396693
|
-
const normalized = createAutoTerminalConfig(snapshot, config2);
|
|
396694
|
-
const tab = await terminalService.createTerminal(normalized);
|
|
396695
|
-
return { id: tab.id };
|
|
396696
|
-
},
|
|
396697
|
-
write: async (terminalId, data) => {
|
|
396698
|
-
terminalService.write(terminalId, data);
|
|
396699
397862
|
},
|
|
396700
|
-
|
|
396701
|
-
|
|
396702
|
-
|
|
396703
|
-
|
|
396704
|
-
|
|
396705
|
-
|
|
396706
|
-
|
|
396707
|
-
|
|
396708
|
-
|
|
397863
|
+
filesystemBridge: {
|
|
397864
|
+
listDirectory: async (terminalId, dirPath) => {
|
|
397865
|
+
return await fileSystemService.listDirectory(terminalId, dirPath);
|
|
397866
|
+
},
|
|
397867
|
+
readTextFile: async (terminalId, filePath, options) => {
|
|
397868
|
+
return await fileSystemService.readTextFile(
|
|
397869
|
+
terminalId,
|
|
397870
|
+
filePath,
|
|
397871
|
+
options
|
|
397872
|
+
);
|
|
397873
|
+
},
|
|
397874
|
+
readFileBase64: async (terminalId, filePath, options) => {
|
|
397875
|
+
return await fileSystemService.readFileBase64(
|
|
397876
|
+
terminalId,
|
|
397877
|
+
filePath,
|
|
397878
|
+
options
|
|
397879
|
+
);
|
|
397880
|
+
},
|
|
397881
|
+
writeTextFile: async (terminalId, filePath, content) => {
|
|
397882
|
+
await fileSystemService.writeTextFile(
|
|
397883
|
+
terminalId,
|
|
397884
|
+
filePath,
|
|
397885
|
+
content
|
|
397886
|
+
);
|
|
397887
|
+
},
|
|
397888
|
+
writeFileBase64: async (terminalId, filePath, contentBase64, options) => {
|
|
397889
|
+
await fileSystemService.writeFileBase64(
|
|
397890
|
+
terminalId,
|
|
397891
|
+
filePath,
|
|
397892
|
+
contentBase64,
|
|
397893
|
+
options
|
|
397894
|
+
);
|
|
397895
|
+
},
|
|
397896
|
+
transferEntries: async (sourceTerminalId, sourcePaths, targetTerminalId, targetDirPath, options) => {
|
|
397897
|
+
return await fileSystemService.transferEntries(
|
|
397898
|
+
sourceTerminalId,
|
|
397899
|
+
sourcePaths,
|
|
397900
|
+
targetTerminalId,
|
|
397901
|
+
targetDirPath,
|
|
397902
|
+
options
|
|
397903
|
+
);
|
|
397904
|
+
},
|
|
397905
|
+
startTransfer: async (input) => {
|
|
397906
|
+
return fileTransferService.startTransfer(input);
|
|
397907
|
+
},
|
|
397908
|
+
getTransfer: async (transferId) => {
|
|
397909
|
+
return fileTransferService.getTransfer(transferId);
|
|
397910
|
+
},
|
|
397911
|
+
listTransfers: async (options) => {
|
|
397912
|
+
return fileTransferService.listTransfers(options);
|
|
397913
|
+
},
|
|
397914
|
+
cancelTransfer: async (transferId) => {
|
|
397915
|
+
return fileTransferService.cancelTransfer(transferId);
|
|
397916
|
+
},
|
|
397917
|
+
cancelTransferTask: async (transferId) => {
|
|
397918
|
+
return fileTransferService.cancelTransfer(transferId);
|
|
397919
|
+
},
|
|
397920
|
+
createDirectory: async (terminalId, dirPath) => {
|
|
397921
|
+
await fileSystemService.createDirectory(terminalId, dirPath);
|
|
397922
|
+
},
|
|
397923
|
+
createFile: async (terminalId, filePath) => {
|
|
397924
|
+
await fileSystemService.createFile(terminalId, filePath);
|
|
397925
|
+
},
|
|
397926
|
+
deletePath: async (terminalId, targetPath, options) => {
|
|
397927
|
+
await fileSystemService.deletePath(terminalId, targetPath, options);
|
|
397928
|
+
},
|
|
397929
|
+
renamePath: async (terminalId, sourcePath, targetPath) => {
|
|
397930
|
+
await fileSystemService.renamePath(
|
|
397931
|
+
terminalId,
|
|
397932
|
+
sourcePath,
|
|
397933
|
+
targetPath
|
|
397934
|
+
);
|
|
396709
397935
|
}
|
|
396710
|
-
terminalService.kill(terminalId);
|
|
396711
|
-
},
|
|
396712
|
-
reconnect: async (terminalId) => {
|
|
396713
|
-
const tab = await terminalService.reconnectTerminal(terminalId);
|
|
396714
|
-
return { id: tab.id };
|
|
396715
|
-
},
|
|
396716
|
-
setTitle: async (terminalId, title) => {
|
|
396717
|
-
terminalService.setTitle(terminalId, title);
|
|
396718
|
-
},
|
|
396719
|
-
setSelection: async (terminalId, selectionText) => {
|
|
396720
|
-
terminalService.setSelection(terminalId, selectionText);
|
|
396721
|
-
},
|
|
396722
|
-
getBufferDelta: async (terminalId, fromOffset) => {
|
|
396723
|
-
const data = terminalService.getBufferDelta(terminalId, fromOffset);
|
|
396724
|
-
const offset = terminalService.getCurrentOffset(terminalId);
|
|
396725
|
-
return { data, offset };
|
|
396726
|
-
},
|
|
396727
|
-
generateCommandDraft: async (terminalId, prompt, profileId) => {
|
|
396728
|
-
return await terminalCommandDraftService.generateCommandDraft({
|
|
396729
|
-
terminalId,
|
|
396730
|
-
prompt,
|
|
396731
|
-
profileId
|
|
396732
|
-
});
|
|
396733
|
-
}
|
|
396734
|
-
},
|
|
396735
|
-
filesystemBridge: {
|
|
396736
|
-
listDirectory: async (terminalId, dirPath) => {
|
|
396737
|
-
return await fileSystemService.listDirectory(terminalId, dirPath);
|
|
396738
|
-
},
|
|
396739
|
-
readTextFile: async (terminalId, filePath, options) => {
|
|
396740
|
-
return await fileSystemService.readTextFile(
|
|
396741
|
-
terminalId,
|
|
396742
|
-
filePath,
|
|
396743
|
-
options
|
|
396744
|
-
);
|
|
396745
|
-
},
|
|
396746
|
-
readFileBase64: async (terminalId, filePath, options) => {
|
|
396747
|
-
return await fileSystemService.readFileBase64(
|
|
396748
|
-
terminalId,
|
|
396749
|
-
filePath,
|
|
396750
|
-
options
|
|
396751
|
-
);
|
|
396752
|
-
},
|
|
396753
|
-
writeTextFile: async (terminalId, filePath, content) => {
|
|
396754
|
-
await fileSystemService.writeTextFile(
|
|
396755
|
-
terminalId,
|
|
396756
|
-
filePath,
|
|
396757
|
-
content
|
|
396758
|
-
);
|
|
396759
|
-
},
|
|
396760
|
-
writeFileBase64: async (terminalId, filePath, contentBase64, options) => {
|
|
396761
|
-
await fileSystemService.writeFileBase64(
|
|
396762
|
-
terminalId,
|
|
396763
|
-
filePath,
|
|
396764
|
-
contentBase64,
|
|
396765
|
-
options
|
|
396766
|
-
);
|
|
396767
|
-
},
|
|
396768
|
-
transferEntries: async (sourceTerminalId, sourcePaths, targetTerminalId, targetDirPath, options) => {
|
|
396769
|
-
return await fileSystemService.transferEntries(
|
|
396770
|
-
sourceTerminalId,
|
|
396771
|
-
sourcePaths,
|
|
396772
|
-
targetTerminalId,
|
|
396773
|
-
targetDirPath,
|
|
396774
|
-
options
|
|
396775
|
-
);
|
|
396776
|
-
},
|
|
396777
|
-
startTransfer: async (input) => {
|
|
396778
|
-
return fileTransferService.startTransfer(input);
|
|
396779
|
-
},
|
|
396780
|
-
getTransfer: async (transferId) => {
|
|
396781
|
-
return fileTransferService.getTransfer(transferId);
|
|
396782
|
-
},
|
|
396783
|
-
listTransfers: async (options) => {
|
|
396784
|
-
return fileTransferService.listTransfers(options);
|
|
396785
|
-
},
|
|
396786
|
-
cancelTransfer: async (transferId) => {
|
|
396787
|
-
return fileTransferService.cancelTransfer(transferId);
|
|
396788
|
-
},
|
|
396789
|
-
cancelTransferTask: async (transferId) => {
|
|
396790
|
-
return fileTransferService.cancelTransfer(transferId);
|
|
396791
|
-
},
|
|
396792
|
-
createDirectory: async (terminalId, dirPath) => {
|
|
396793
|
-
await fileSystemService.createDirectory(terminalId, dirPath);
|
|
396794
397936
|
},
|
|
396795
|
-
|
|
396796
|
-
|
|
396797
|
-
|
|
396798
|
-
|
|
396799
|
-
|
|
396800
|
-
|
|
396801
|
-
|
|
396802
|
-
|
|
396803
|
-
|
|
396804
|
-
|
|
396805
|
-
|
|
396806
|
-
|
|
396807
|
-
|
|
396808
|
-
|
|
396809
|
-
|
|
396810
|
-
|
|
396811
|
-
|
|
396812
|
-
|
|
396813
|
-
snapshot.models.
|
|
396814
|
-
|
|
396815
|
-
|
|
396816
|
-
|
|
396817
|
-
|
|
396818
|
-
id: profile.id,
|
|
396819
|
-
name: profile.name,
|
|
396820
|
-
globalModelId: profile.globalModelId,
|
|
396821
|
-
modelName: modelNameById.get(profile.globalModelId)
|
|
396822
|
-
}))
|
|
396823
|
-
};
|
|
396824
|
-
},
|
|
396825
|
-
setActiveProfile: (profileId) => {
|
|
396826
|
-
const snapshot = settingsService.getSettings();
|
|
396827
|
-
const exists = snapshot.models.profiles.some(
|
|
396828
|
-
(profile) => profile.id === profileId
|
|
396829
|
-
);
|
|
396830
|
-
if (!exists) {
|
|
396831
|
-
throw new Error(`Profile not found: ${profileId}`);
|
|
396832
|
-
}
|
|
396833
|
-
settingsService.setSettings({
|
|
396834
|
-
models: {
|
|
396835
|
-
items: snapshot.models.items,
|
|
396836
|
-
profiles: snapshot.models.profiles,
|
|
396837
|
-
activeProfileId: profileId
|
|
397937
|
+
profileBridge: {
|
|
397938
|
+
getProfiles: () => {
|
|
397939
|
+
const snapshot = settingsService.getSettings();
|
|
397940
|
+
const modelNameById = new Map(
|
|
397941
|
+
snapshot.models.items.map((item) => [item.id, item.model])
|
|
397942
|
+
);
|
|
397943
|
+
return {
|
|
397944
|
+
activeProfileId: snapshot.models.activeProfileId,
|
|
397945
|
+
profiles: snapshot.models.profiles.map((profile) => ({
|
|
397946
|
+
id: profile.id,
|
|
397947
|
+
name: profile.name,
|
|
397948
|
+
globalModelId: profile.globalModelId,
|
|
397949
|
+
modelName: modelNameById.get(profile.globalModelId)
|
|
397950
|
+
}))
|
|
397951
|
+
};
|
|
397952
|
+
},
|
|
397953
|
+
setActiveProfile: (profileId) => {
|
|
397954
|
+
const snapshot = settingsService.getSettings();
|
|
397955
|
+
const exists = snapshot.models.profiles.some(
|
|
397956
|
+
(profile) => profile.id === profileId
|
|
397957
|
+
);
|
|
397958
|
+
if (!exists) {
|
|
397959
|
+
throw new Error(`Profile not found: ${profileId}`);
|
|
396838
397960
|
}
|
|
396839
|
-
|
|
396840
|
-
|
|
396841
|
-
|
|
396842
|
-
|
|
396843
|
-
|
|
396844
|
-
|
|
396845
|
-
|
|
396846
|
-
|
|
396847
|
-
|
|
396848
|
-
|
|
396849
|
-
|
|
396850
|
-
globalModelId: profile.globalModelId,
|
|
396851
|
-
modelName: modelNameById.get(profile.globalModelId)
|
|
396852
|
-
}))
|
|
396853
|
-
};
|
|
396854
|
-
},
|
|
396855
|
-
probeModel: async (model) => {
|
|
396856
|
-
return await modelCapabilityService.probe(model);
|
|
396857
|
-
}
|
|
396858
|
-
},
|
|
396859
|
-
agentBridge: {
|
|
396860
|
-
exportHistory: async (sessionId, mode) => {
|
|
396861
|
-
await gatewayService.waitForRunCompletion(sessionId);
|
|
396862
|
-
const backendSession = agentService.exportChatSession(sessionId);
|
|
396863
|
-
if (!backendSession) {
|
|
396864
|
-
throw new Error(`Session with ID ${sessionId} not found`);
|
|
396865
|
-
}
|
|
396866
|
-
const uiSession = uiHistoryService.getSession(sessionId);
|
|
396867
|
-
if (mode === "simple") {
|
|
396868
|
-
const markdown = uiHistoryService.toReadableMarkdown(
|
|
396869
|
-
uiSession?.messages || [],
|
|
396870
|
-
uiSession?.title || backendSession.title
|
|
397961
|
+
settingsService.setSettings({
|
|
397962
|
+
models: {
|
|
397963
|
+
items: snapshot.models.items,
|
|
397964
|
+
profiles: snapshot.models.profiles,
|
|
397965
|
+
activeProfileId: profileId
|
|
397966
|
+
}
|
|
397967
|
+
});
|
|
397968
|
+
const next = settingsService.getSettings();
|
|
397969
|
+
agentService.updateSettings(next);
|
|
397970
|
+
const modelNameById = new Map(
|
|
397971
|
+
next.models.items.map((item) => [item.id, item.model])
|
|
396871
397972
|
);
|
|
396872
397973
|
return {
|
|
396873
|
-
|
|
396874
|
-
|
|
396875
|
-
|
|
396876
|
-
|
|
397974
|
+
activeProfileId: next.models.activeProfileId,
|
|
397975
|
+
profiles: next.models.profiles.map((profile) => ({
|
|
397976
|
+
id: profile.id,
|
|
397977
|
+
name: profile.name,
|
|
397978
|
+
globalModelId: profile.globalModelId,
|
|
397979
|
+
modelName: modelNameById.get(profile.globalModelId)
|
|
397980
|
+
}))
|
|
396877
397981
|
};
|
|
397982
|
+
},
|
|
397983
|
+
probeModel: async (model) => {
|
|
397984
|
+
return await modelCapabilityService.probe(model);
|
|
396878
397985
|
}
|
|
396879
|
-
return {
|
|
396880
|
-
sessionId: backendSession.id,
|
|
396881
|
-
mode,
|
|
396882
|
-
title: uiSession?.title || backendSession.title,
|
|
396883
|
-
lastCheckpointOffset: backendSession.lastCheckpointOffset,
|
|
396884
|
-
createdAt: new Date(backendSession.createdAt).toISOString(),
|
|
396885
|
-
updatedAt: new Date(backendSession.updatedAt).toISOString(),
|
|
396886
|
-
frontendMessages: uiSession?.messages || [],
|
|
396887
|
-
backendMessages: backendSession.messages.map((msg) => ({
|
|
396888
|
-
messageId: msg.id,
|
|
396889
|
-
messageType: msg.type,
|
|
396890
|
-
messageData: msg.data
|
|
396891
|
-
}))
|
|
396892
|
-
};
|
|
396893
|
-
},
|
|
396894
|
-
getAllChatHistory: () => agentService.getAllChatHistory(),
|
|
396895
|
-
loadChatSession: (sessionId) => agentService.loadChatSession(sessionId),
|
|
396896
|
-
getUiMessages: (sessionId) => uiHistoryService.getMessages(sessionId)
|
|
396897
|
-
},
|
|
396898
|
-
systemBridge: {
|
|
396899
|
-
saveImageAttachment: async (payload) => {
|
|
396900
|
-
return await imageAttachmentService.saveImageAttachment(payload);
|
|
396901
|
-
}
|
|
396902
|
-
},
|
|
396903
|
-
skillBridge: {
|
|
396904
|
-
reload: async () => {
|
|
396905
|
-
return await skillService.reload();
|
|
396906
|
-
},
|
|
396907
|
-
getAll: async () => {
|
|
396908
|
-
return await skillService.getAll();
|
|
396909
|
-
},
|
|
396910
|
-
getEnabled: async () => {
|
|
396911
|
-
return await skillService.getEnabledSkills();
|
|
396912
397986
|
},
|
|
396913
|
-
|
|
396914
|
-
|
|
396915
|
-
|
|
396916
|
-
|
|
396917
|
-
|
|
396918
|
-
|
|
396919
|
-
|
|
396920
|
-
|
|
396921
|
-
|
|
396922
|
-
|
|
396923
|
-
|
|
396924
|
-
|
|
396925
|
-
|
|
396926
|
-
|
|
396927
|
-
|
|
396928
|
-
|
|
396929
|
-
|
|
396930
|
-
|
|
396931
|
-
|
|
396932
|
-
const nextSkills = { ...snapshot.tools?.skills ?? {} };
|
|
396933
|
-
nextSkills[name] = enabled;
|
|
396934
|
-
settingsService.setSettings({
|
|
396935
|
-
tools: {
|
|
396936
|
-
builtIn: snapshot.tools?.builtIn ?? {},
|
|
396937
|
-
skills: nextSkills
|
|
397987
|
+
agentBridge: {
|
|
397988
|
+
exportHistory: async (sessionId, mode) => {
|
|
397989
|
+
await gatewayService.waitForRunCompletion(sessionId);
|
|
397990
|
+
const backendSession = agentService.exportChatSession(sessionId);
|
|
397991
|
+
if (!backendSession) {
|
|
397992
|
+
throw new Error(`Session with ID ${sessionId} not found`);
|
|
397993
|
+
}
|
|
397994
|
+
const uiSession = uiHistoryService.getSession(sessionId);
|
|
397995
|
+
if (mode === "simple") {
|
|
397996
|
+
const markdown = uiHistoryService.toReadableMarkdown(
|
|
397997
|
+
uiSession?.messages || [],
|
|
397998
|
+
uiSession?.title || backendSession.title
|
|
397999
|
+
);
|
|
398000
|
+
return {
|
|
398001
|
+
sessionId,
|
|
398002
|
+
mode,
|
|
398003
|
+
title: uiSession?.title || backendSession.title,
|
|
398004
|
+
content: markdown
|
|
398005
|
+
};
|
|
396938
398006
|
}
|
|
396939
|
-
|
|
396940
|
-
|
|
396941
|
-
|
|
396942
|
-
|
|
396943
|
-
|
|
396944
|
-
|
|
396945
|
-
|
|
396946
|
-
|
|
396947
|
-
|
|
396948
|
-
|
|
396949
|
-
|
|
396950
|
-
|
|
396951
|
-
|
|
396952
|
-
|
|
396953
|
-
|
|
396954
|
-
|
|
396955
|
-
|
|
396956
|
-
|
|
396957
|
-
settingsService.getSettings().agentSettings?.activeProfileId || null
|
|
396958
|
-
);
|
|
396959
|
-
gatewayService.broadcastRaw("memory:updated", snapshot);
|
|
396960
|
-
return snapshot;
|
|
396961
|
-
},
|
|
396962
|
-
search: async (query, limit2) => {
|
|
396963
|
-
const { content } = await memoryService.getMemorySnapshot(
|
|
396964
|
-
settingsService.getSettings().agentSettings?.activeProfileId || null
|
|
396965
|
-
);
|
|
396966
|
-
return searchMemory(content, query, limit2 ?? 10);
|
|
396967
|
-
},
|
|
396968
|
-
append: async (note) => {
|
|
396969
|
-
const profileId = settingsService.getSettings().agentSettings?.activeProfileId || null;
|
|
396970
|
-
const { content } = await memoryService.getMemorySnapshot(profileId);
|
|
396971
|
-
const next = appendMemoryNote(content, note);
|
|
396972
|
-
const snapshot = await memoryService.writeMemory(next, profileId);
|
|
396973
|
-
gatewayService.broadcastRaw("memory:updated", snapshot);
|
|
396974
|
-
return snapshot;
|
|
396975
|
-
}
|
|
396976
|
-
},
|
|
396977
|
-
agentSettingsBridge: {
|
|
396978
|
-
get: () => agentSettingProfileService.getState(),
|
|
396979
|
-
saveCurrent: async () => {
|
|
396980
|
-
const result = await agentSettingProfileService.saveCurrent();
|
|
396981
|
-
broadcastAgentSettingResult(result);
|
|
396982
|
-
return result;
|
|
398007
|
+
return {
|
|
398008
|
+
sessionId: backendSession.id,
|
|
398009
|
+
mode,
|
|
398010
|
+
title: uiSession?.title || backendSession.title,
|
|
398011
|
+
lastCheckpointOffset: backendSession.lastCheckpointOffset,
|
|
398012
|
+
createdAt: new Date(backendSession.createdAt).toISOString(),
|
|
398013
|
+
updatedAt: new Date(backendSession.updatedAt).toISOString(),
|
|
398014
|
+
frontendMessages: uiSession?.messages || [],
|
|
398015
|
+
backendMessages: backendSession.messages.map((msg) => ({
|
|
398016
|
+
messageId: msg.id,
|
|
398017
|
+
messageType: msg.type,
|
|
398018
|
+
messageData: msg.data
|
|
398019
|
+
}))
|
|
398020
|
+
};
|
|
398021
|
+
},
|
|
398022
|
+
getAllChatHistory: () => agentService.getAllChatHistory(),
|
|
398023
|
+
loadChatSession: (sessionId) => agentService.loadChatSession(sessionId),
|
|
398024
|
+
getUiMessages: (sessionId) => uiHistoryService.getMessages(sessionId)
|
|
396983
398025
|
},
|
|
396984
|
-
|
|
396985
|
-
|
|
396986
|
-
|
|
396987
|
-
|
|
398026
|
+
systemBridge: {
|
|
398027
|
+
saveImageAttachment: async (payload) => {
|
|
398028
|
+
return await imageAttachmentService.saveImageAttachment(payload);
|
|
398029
|
+
}
|
|
396988
398030
|
},
|
|
396989
|
-
|
|
396990
|
-
|
|
396991
|
-
|
|
396992
|
-
|
|
398031
|
+
skillBridge: {
|
|
398032
|
+
reload: async () => {
|
|
398033
|
+
return await skillService.reload();
|
|
398034
|
+
},
|
|
398035
|
+
getAll: async () => {
|
|
398036
|
+
return await skillService.getAll();
|
|
398037
|
+
},
|
|
398038
|
+
getEnabled: async () => {
|
|
398039
|
+
return await skillService.getEnabledSkills();
|
|
398040
|
+
},
|
|
398041
|
+
create: async () => {
|
|
398042
|
+
return await skillService.createSkillFromTemplate();
|
|
398043
|
+
},
|
|
398044
|
+
delete: async (fileName) => {
|
|
398045
|
+
await skillService.deleteSkillFile(fileName);
|
|
398046
|
+
return await skillService.getAll();
|
|
398047
|
+
},
|
|
398048
|
+
listSkills: async () => {
|
|
398049
|
+
const snapshot = settingsService.getSettings();
|
|
398050
|
+
const enabledMap = snapshot.tools?.skills ?? {};
|
|
398051
|
+
const skills = await skillService.getAll();
|
|
398052
|
+
return skills.map((skill) => ({
|
|
398053
|
+
name: skill.name,
|
|
398054
|
+
description: skill.description,
|
|
398055
|
+
enabled: enabledMap[skill.name] !== false
|
|
398056
|
+
}));
|
|
398057
|
+
},
|
|
398058
|
+
setSkillEnabled: async (name, enabled) => {
|
|
398059
|
+
const snapshot = settingsService.getSettings();
|
|
398060
|
+
const nextSkills = { ...snapshot.tools?.skills ?? {} };
|
|
398061
|
+
nextSkills[name] = enabled;
|
|
398062
|
+
settingsService.setSettings({
|
|
398063
|
+
tools: {
|
|
398064
|
+
builtIn: snapshot.tools?.builtIn ?? {},
|
|
398065
|
+
skills: nextSkills
|
|
398066
|
+
}
|
|
398067
|
+
});
|
|
398068
|
+
const next = settingsService.getSettings();
|
|
398069
|
+
agentService.updateSettings(next);
|
|
398070
|
+
const skills = await skillService.getAll();
|
|
398071
|
+
const summary = buildSkillStatusSummary(skills, next.tools?.skills);
|
|
398072
|
+
gatewayService.broadcastRaw("skills:updated", summary);
|
|
398073
|
+
return summary;
|
|
398074
|
+
}
|
|
396993
398075
|
},
|
|
396994
|
-
|
|
396995
|
-
|
|
396996
|
-
|
|
396997
|
-
|
|
396998
|
-
}
|
|
396999
|
-
},
|
|
397000
|
-
settingsBridge: {
|
|
397001
|
-
getSettings: () => settingsService.getSettings(),
|
|
397002
|
-
setSettings: async (patch) => {
|
|
397003
|
-
if (patch?.gateway?.ws) {
|
|
397004
|
-
throw new Error(
|
|
397005
|
-
"settings.gateway.ws is not configurable via websocket RPC."
|
|
398076
|
+
memoryBridge: {
|
|
398077
|
+
get: async () => {
|
|
398078
|
+
return await memoryService.getMemorySnapshot(
|
|
398079
|
+
settingsService.getSettings().agentSettings?.activeProfileId || null
|
|
397006
398080
|
);
|
|
398081
|
+
},
|
|
398082
|
+
setContent: async (content) => {
|
|
398083
|
+
const snapshot = await memoryService.writeMemory(
|
|
398084
|
+
content,
|
|
398085
|
+
settingsService.getSettings().agentSettings?.activeProfileId || null
|
|
398086
|
+
);
|
|
398087
|
+
gatewayService.broadcastRaw("memory:updated", snapshot);
|
|
398088
|
+
return snapshot;
|
|
398089
|
+
},
|
|
398090
|
+
search: async (query, limit2) => {
|
|
398091
|
+
const { content } = await memoryService.getMemorySnapshot(
|
|
398092
|
+
settingsService.getSettings().agentSettings?.activeProfileId || null
|
|
398093
|
+
);
|
|
398094
|
+
return searchMemory(content, query, limit2 ?? 10);
|
|
398095
|
+
},
|
|
398096
|
+
append: async (note) => {
|
|
398097
|
+
const profileId = settingsService.getSettings().agentSettings?.activeProfileId || null;
|
|
398098
|
+
const { content } = await memoryService.getMemorySnapshot(profileId);
|
|
398099
|
+
const next = appendMemoryNote(content, note);
|
|
398100
|
+
const snapshot = await memoryService.writeMemory(next, profileId);
|
|
398101
|
+
gatewayService.broadcastRaw("memory:updated", snapshot);
|
|
398102
|
+
return snapshot;
|
|
397007
398103
|
}
|
|
397008
|
-
settingsService.setSettings(patch);
|
|
397009
|
-
const next = settingsService.getSettings();
|
|
397010
|
-
agentService.updateSettings(next);
|
|
397011
|
-
return next;
|
|
397012
|
-
}
|
|
397013
|
-
},
|
|
397014
|
-
commandPolicyBridge: {
|
|
397015
|
-
getLists: async () => {
|
|
397016
|
-
return await commandPolicyService.getLists();
|
|
397017
398104
|
},
|
|
397018
|
-
|
|
397019
|
-
|
|
398105
|
+
agentSettingsBridge: {
|
|
398106
|
+
get: () => agentSettingProfileService.getState(),
|
|
398107
|
+
saveCurrent: async () => {
|
|
398108
|
+
const result = await agentSettingProfileService.saveCurrent();
|
|
398109
|
+
broadcastAgentSettingResult(result);
|
|
398110
|
+
return result;
|
|
398111
|
+
},
|
|
398112
|
+
apply: async (profileId) => {
|
|
398113
|
+
const result = await agentSettingProfileService.apply(profileId);
|
|
398114
|
+
broadcastAgentSettingResult(result);
|
|
398115
|
+
return result;
|
|
398116
|
+
},
|
|
398117
|
+
overwrite: async (profileId) => {
|
|
398118
|
+
const result = await agentSettingProfileService.overwrite(profileId);
|
|
398119
|
+
broadcastAgentSettingResult(result);
|
|
398120
|
+
return result;
|
|
398121
|
+
},
|
|
398122
|
+
delete: async (profileId) => {
|
|
398123
|
+
const result = await agentSettingProfileService.delete(profileId);
|
|
398124
|
+
broadcastAgentSettingResult(result);
|
|
398125
|
+
return result;
|
|
398126
|
+
}
|
|
397020
398127
|
},
|
|
397021
|
-
|
|
397022
|
-
|
|
397023
|
-
|
|
397024
|
-
|
|
397025
|
-
|
|
397026
|
-
|
|
397027
|
-
|
|
398128
|
+
settingsBridge: {
|
|
398129
|
+
getSettings: () => settingsService.getSettings(),
|
|
398130
|
+
setSettings: async (patch) => {
|
|
398131
|
+
if (patch?.gateway?.ws) {
|
|
398132
|
+
throw new Error(
|
|
398133
|
+
"settings.gateway.ws is not configurable via websocket RPC."
|
|
398134
|
+
);
|
|
398135
|
+
}
|
|
398136
|
+
settingsService.setSettings(patch);
|
|
398137
|
+
const next = settingsService.getSettings();
|
|
398138
|
+
agentService.updateSettings(next);
|
|
398139
|
+
return next;
|
|
398140
|
+
},
|
|
398141
|
+
// v3.2.18: settings backup/restore/export/import.
|
|
398142
|
+
listBackups: () => settingsBackupService.list(),
|
|
398143
|
+
restoreBackup: async (name) => settingsBackupService.restore(name),
|
|
398144
|
+
export: () => settingsBackupService.export(),
|
|
398145
|
+
import: async (content) => settingsBackupService.import(content)
|
|
397028
398146
|
},
|
|
397029
|
-
|
|
397030
|
-
|
|
397031
|
-
|
|
398147
|
+
// v3.2.18: cross-session history search.
|
|
398148
|
+
historyBridge: {
|
|
398149
|
+
getAllSessions: async () => {
|
|
398150
|
+
try {
|
|
398151
|
+
const all = historyStore.listChatSessions();
|
|
398152
|
+
if (Array.isArray(all) && all.length > 0) {
|
|
398153
|
+
return all;
|
|
398154
|
+
}
|
|
398155
|
+
} catch (e) {
|
|
398156
|
+
console.warn(
|
|
398157
|
+
"[history:search] store load failed, falling back to metadata:",
|
|
398158
|
+
e instanceof Error ? e.message : e
|
|
398159
|
+
);
|
|
398160
|
+
}
|
|
398161
|
+
return agentService.getAllChatHistory() ?? [];
|
|
398162
|
+
}
|
|
397032
398163
|
},
|
|
397033
|
-
|
|
397034
|
-
|
|
397035
|
-
|
|
398164
|
+
commandPolicyBridge: {
|
|
398165
|
+
getLists: async () => {
|
|
398166
|
+
return await commandPolicyService.getLists();
|
|
398167
|
+
},
|
|
398168
|
+
addRule: async (listName, rule) => {
|
|
398169
|
+
return await commandPolicyService.addRule(listName, rule);
|
|
398170
|
+
},
|
|
398171
|
+
deleteRule: async (listName, rule) => {
|
|
398172
|
+
return await commandPolicyService.deleteRule(listName, rule);
|
|
398173
|
+
}
|
|
397036
398174
|
},
|
|
397037
|
-
|
|
397038
|
-
|
|
397039
|
-
|
|
397040
|
-
|
|
397041
|
-
|
|
397042
|
-
|
|
397043
|
-
|
|
397044
|
-
|
|
397045
|
-
|
|
397046
|
-
|
|
397047
|
-
|
|
397048
|
-
|
|
397049
|
-
|
|
397050
|
-
|
|
397051
|
-
|
|
397052
|
-
|
|
397053
|
-
|
|
397054
|
-
|
|
397055
|
-
|
|
397056
|
-
|
|
397057
|
-
|
|
397058
|
-
|
|
397059
|
-
|
|
397060
|
-
|
|
397061
|
-
|
|
398175
|
+
toolsBridge: {
|
|
398176
|
+
reloadMcp: async () => {
|
|
398177
|
+
return await mcpToolService.reloadAll();
|
|
398178
|
+
},
|
|
398179
|
+
getMcp: () => mcpToolService.getSummaries(),
|
|
398180
|
+
setMcpEnabled: async (name, enabled) => {
|
|
398181
|
+
return await mcpToolService.setServerEnabled(name, enabled);
|
|
398182
|
+
},
|
|
398183
|
+
getBuiltIn: () => {
|
|
398184
|
+
const settings = settingsService.getSettings();
|
|
398185
|
+
return buildBuiltInToolStatusSummary(settings.tools?.builtIn);
|
|
398186
|
+
},
|
|
398187
|
+
setBuiltInEnabled: async (name, enabled) => {
|
|
398188
|
+
const settings = settingsService.getSettings();
|
|
398189
|
+
const nextBuiltIn = { ...settings.tools?.builtIn ?? {} };
|
|
398190
|
+
nextBuiltIn[name] = enabled;
|
|
398191
|
+
settingsService.setSettings({
|
|
398192
|
+
tools: {
|
|
398193
|
+
builtIn: nextBuiltIn,
|
|
398194
|
+
skills: settings.tools?.skills ?? {}
|
|
398195
|
+
}
|
|
398196
|
+
});
|
|
398197
|
+
const next = settingsService.getSettings();
|
|
398198
|
+
agentService.updateSettings(next);
|
|
398199
|
+
const summary = buildBuiltInToolStatusSummary(next.tools?.builtIn);
|
|
398200
|
+
gatewayService.broadcastRaw("tools:builtInUpdated", summary);
|
|
398201
|
+
return summary;
|
|
398202
|
+
}
|
|
398203
|
+
},
|
|
398204
|
+
// Observability bridge (v2.9.x): exposes the 9 platform capabilities
|
|
398205
|
+
// (metrics export, secrets, on-call, cost, recording, gitops, playbooks,
|
|
398206
|
+
// cloud, live dashboard) as observability:* RPC methods.
|
|
398207
|
+
observabilityBridge: createObservabilityBridge({
|
|
398208
|
+
observability: () => observability,
|
|
398209
|
+
terminalService: () => terminalService
|
|
398210
|
+
})
|
|
398211
|
+
});
|
|
398212
|
+
restDispatchTarget = adapter;
|
|
398213
|
+
return adapter;
|
|
398214
|
+
}
|
|
397062
398215
|
});
|
|
397063
398216
|
await wsGatewayControlService.applyPolicy(startupPolicy);
|
|
397064
398217
|
const shutdown = async (signal) => {
|