neuralos 3.2.17 → 3.2.19
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 +1739 -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,84 @@ 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
|
+
for (const candidate of chain) {
|
|
258130
|
+
const started = Date.now();
|
|
258131
|
+
try {
|
|
258132
|
+
const value = await operation(candidate);
|
|
258133
|
+
const attempt = { model: candidate.model, ok: true, durationMs: Date.now() - started };
|
|
258134
|
+
attempts.push(attempt);
|
|
258135
|
+
opts.onAttempt?.(attempt);
|
|
258136
|
+
return { value, attempts, usedModel: candidate.model };
|
|
258137
|
+
} catch (error40) {
|
|
258138
|
+
const decision = isFailoverEligible(error40);
|
|
258139
|
+
const attempt = {
|
|
258140
|
+
model: candidate.model,
|
|
258141
|
+
ok: false,
|
|
258142
|
+
reason: decision.reason,
|
|
258143
|
+
durationMs: Date.now() - started
|
|
258144
|
+
};
|
|
258145
|
+
attempts.push(attempt);
|
|
258146
|
+
opts.onAttempt?.(attempt);
|
|
258147
|
+
if (!decision.eligible) {
|
|
258148
|
+
return { error: error40, attempts };
|
|
258149
|
+
}
|
|
258150
|
+
lastError = error40;
|
|
258151
|
+
}
|
|
258152
|
+
}
|
|
258153
|
+
return { error: lastError, attempts };
|
|
258154
|
+
}
|
|
258155
|
+
var FAILOVER_ERROR_PATTERNS, NON_FAILOVER_PATTERNS;
|
|
258156
|
+
var init_modelFailover = __esm({
|
|
258157
|
+
"../../packages/backend/src/services/AgentHelper/utils/modelFailover.ts"() {
|
|
258158
|
+
"use strict";
|
|
258159
|
+
FAILOVER_ERROR_PATTERNS = [
|
|
258160
|
+
{ re: /\b429\b|rate.?limit|too many requests|quota/i, reason: "rate-limited" },
|
|
258161
|
+
{ re: /\b5\d\d\b|internal server error|bad gateway|service unavailable|upstream/i, reason: "provider-5xx" },
|
|
258162
|
+
{ re: /network|fetch failed|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|socket hang up/i, reason: "network" },
|
|
258163
|
+
{ re: /\b401\b|unauthorized|invalid api key|authentication/i, reason: "auth" },
|
|
258164
|
+
{ re: /overloaded|capacity|model .* not found|no such model/i, reason: "model-unavailable" }
|
|
258165
|
+
];
|
|
258166
|
+
NON_FAILOVER_PATTERNS = [
|
|
258167
|
+
/context.?length|token limit|maximum context/i,
|
|
258168
|
+
/invalid request|malformed|bad request|\b400\b/i,
|
|
258169
|
+
/aborted|AbortError/i
|
|
258170
|
+
// user cancelled — never retry
|
|
258171
|
+
];
|
|
258172
|
+
}
|
|
258173
|
+
});
|
|
258174
|
+
|
|
257993
258175
|
// ../../packages/backend/src/services/history/HistorySqliteStore.ts
|
|
257994
258176
|
var HistorySqliteStore_exports = {};
|
|
257995
258177
|
__export(HistorySqliteStore_exports, {
|
|
@@ -261975,6 +262157,136 @@ var require_websocket_server = __commonJS({
|
|
|
261975
262157
|
}
|
|
261976
262158
|
});
|
|
261977
262159
|
|
|
262160
|
+
// ../../packages/backend/src/services/history/historySearch.ts
|
|
262161
|
+
var historySearch_exports = {};
|
|
262162
|
+
__export(historySearch_exports, {
|
|
262163
|
+
buildSnippet: () => buildSnippet,
|
|
262164
|
+
extractMessageText: () => extractMessageText2,
|
|
262165
|
+
findMatches: () => findMatches,
|
|
262166
|
+
normalizeMessages: () => normalizeMessages,
|
|
262167
|
+
searchChatHistory: () => searchChatHistory
|
|
262168
|
+
});
|
|
262169
|
+
function extractMessageText2(data) {
|
|
262170
|
+
if (!data || typeof data !== "object") return "";
|
|
262171
|
+
let obj = data;
|
|
262172
|
+
for (let i = 0; i < 3 && obj.data && typeof obj.data === "object" && !Array.isArray(obj.data); i++) {
|
|
262173
|
+
const inner = obj.data;
|
|
262174
|
+
if (inner.content !== void 0 || inner.data !== void 0) {
|
|
262175
|
+
obj = inner;
|
|
262176
|
+
} else {
|
|
262177
|
+
break;
|
|
262178
|
+
}
|
|
262179
|
+
}
|
|
262180
|
+
const content = obj.content;
|
|
262181
|
+
if (typeof content === "string") return content;
|
|
262182
|
+
if (Array.isArray(content)) {
|
|
262183
|
+
return content.map((part) => {
|
|
262184
|
+
if (typeof part === "string") return part;
|
|
262185
|
+
if (part && typeof part === "object") {
|
|
262186
|
+
const p = part;
|
|
262187
|
+
if (typeof p.text === "string") return p.text;
|
|
262188
|
+
}
|
|
262189
|
+
return "";
|
|
262190
|
+
}).join(" ");
|
|
262191
|
+
}
|
|
262192
|
+
return "";
|
|
262193
|
+
}
|
|
262194
|
+
function buildSnippet(text, offset, length) {
|
|
262195
|
+
const start = Math.max(0, offset - SNIPPET_CONTEXT);
|
|
262196
|
+
const end = Math.min(text.length, offset + length + SNIPPET_CONTEXT);
|
|
262197
|
+
const prefix = start > 0 ? "\u2026" : "";
|
|
262198
|
+
const suffix = end < text.length ? "\u2026" : "";
|
|
262199
|
+
return prefix + text.slice(start, end).replace(/\s+/g, " ").trim() + suffix;
|
|
262200
|
+
}
|
|
262201
|
+
function findMatches(text, query, wholeWord) {
|
|
262202
|
+
if (!text || !query) return [];
|
|
262203
|
+
const haystack = text.toLowerCase();
|
|
262204
|
+
const needle = query.toLowerCase();
|
|
262205
|
+
const out = [];
|
|
262206
|
+
if (wholeWord) {
|
|
262207
|
+
const re = new RegExp(`\\b${needle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "gi");
|
|
262208
|
+
let m2;
|
|
262209
|
+
while ((m2 = re.exec(haystack)) !== null) {
|
|
262210
|
+
out.push({ offset: m2.index, length: m2[0].length });
|
|
262211
|
+
if (m2.index === re.lastIndex) re.lastIndex++;
|
|
262212
|
+
}
|
|
262213
|
+
} else {
|
|
262214
|
+
let idx = haystack.indexOf(needle);
|
|
262215
|
+
while (idx !== -1) {
|
|
262216
|
+
out.push({ offset: idx, length: needle.length });
|
|
262217
|
+
idx = haystack.indexOf(needle, idx + needle.length);
|
|
262218
|
+
}
|
|
262219
|
+
}
|
|
262220
|
+
return out;
|
|
262221
|
+
}
|
|
262222
|
+
function normalizeMessages(messages) {
|
|
262223
|
+
if (!messages) return [];
|
|
262224
|
+
if (Array.isArray(messages)) return messages;
|
|
262225
|
+
if (messages instanceof Map) return [...messages.values()];
|
|
262226
|
+
if (typeof messages === "object") return Object.values(messages);
|
|
262227
|
+
return [];
|
|
262228
|
+
}
|
|
262229
|
+
function searchChatHistory(sessions, query, options = {}) {
|
|
262230
|
+
const sessionLimit = options.sessionLimit ?? DEFAULT_SESSION_LIMIT;
|
|
262231
|
+
const snippetLimit = options.snippetLimit ?? DEFAULT_SNIPPET_LIMIT;
|
|
262232
|
+
const wholeWord = options.wholeWord ?? false;
|
|
262233
|
+
const includeTitles = options.includeTitles ?? true;
|
|
262234
|
+
const trimmed = (query ?? "").trim();
|
|
262235
|
+
if (!trimmed) {
|
|
262236
|
+
return { query: trimmed, totalSessions: 0, totalMatches: 0, sessions: [], truncated: false };
|
|
262237
|
+
}
|
|
262238
|
+
const results = [];
|
|
262239
|
+
let totalMatches = 0;
|
|
262240
|
+
for (const session of sessions) {
|
|
262241
|
+
let matchCount = 0;
|
|
262242
|
+
const matches = [];
|
|
262243
|
+
const titleHit = includeTitles && session.title ? findMatches(session.title, trimmed, wholeWord) : [];
|
|
262244
|
+
for (const message of normalizeMessages(session.messages)) {
|
|
262245
|
+
const text = extractMessageText2(message?.data);
|
|
262246
|
+
if (!text) continue;
|
|
262247
|
+
const hits = findMatches(text, trimmed, wholeWord);
|
|
262248
|
+
for (const hit of hits) {
|
|
262249
|
+
matchCount++;
|
|
262250
|
+
totalMatches++;
|
|
262251
|
+
if (matches.length < snippetLimit) {
|
|
262252
|
+
matches.push({
|
|
262253
|
+
messageId: String(message?.id ?? ""),
|
|
262254
|
+
type: String(message?.type ?? ""),
|
|
262255
|
+
snippet: buildSnippet(text, hit.offset, hit.length),
|
|
262256
|
+
offset: hit.offset
|
|
262257
|
+
});
|
|
262258
|
+
}
|
|
262259
|
+
}
|
|
262260
|
+
}
|
|
262261
|
+
if (matchCount === 0 && titleHit.length === 0) continue;
|
|
262262
|
+
results.push({
|
|
262263
|
+
sessionId: session.id,
|
|
262264
|
+
sessionTitle: session.title,
|
|
262265
|
+
updatedAt: session.updatedAt,
|
|
262266
|
+
matchCount: matchCount + titleHit.length,
|
|
262267
|
+
matches
|
|
262268
|
+
});
|
|
262269
|
+
}
|
|
262270
|
+
results.sort((a, b) => b.matchCount - a.matchCount || b.updatedAt - a.updatedAt);
|
|
262271
|
+
const truncated = results.length > sessionLimit;
|
|
262272
|
+
return {
|
|
262273
|
+
query: trimmed,
|
|
262274
|
+
totalSessions: results.length,
|
|
262275
|
+
totalMatches,
|
|
262276
|
+
sessions: results.slice(0, sessionLimit),
|
|
262277
|
+
truncated
|
|
262278
|
+
};
|
|
262279
|
+
}
|
|
262280
|
+
var SNIPPET_CONTEXT, DEFAULT_SESSION_LIMIT, DEFAULT_SNIPPET_LIMIT;
|
|
262281
|
+
var init_historySearch = __esm({
|
|
262282
|
+
"../../packages/backend/src/services/history/historySearch.ts"() {
|
|
262283
|
+
"use strict";
|
|
262284
|
+
SNIPPET_CONTEXT = 60;
|
|
262285
|
+
DEFAULT_SESSION_LIMIT = 20;
|
|
262286
|
+
DEFAULT_SNIPPET_LIMIT = 3;
|
|
262287
|
+
}
|
|
262288
|
+
});
|
|
262289
|
+
|
|
261978
262290
|
// ../../node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/code.js
|
|
261979
262291
|
var require_code = __commonJS({
|
|
261980
262292
|
"../../node_modules/@modelcontextprotocol/sdk/node_modules/ajv/dist/compile/codegen/code.js"(exports2) {
|
|
@@ -274408,12 +274720,12 @@ var require_dist2 = __commonJS({
|
|
|
274408
274720
|
throw new Error(`Unknown format "${name}"`);
|
|
274409
274721
|
return f;
|
|
274410
274722
|
};
|
|
274411
|
-
function addFormats(ajv, list,
|
|
274723
|
+
function addFormats(ajv, list, fs23, exportName) {
|
|
274412
274724
|
var _a3;
|
|
274413
274725
|
var _b;
|
|
274414
274726
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
274415
274727
|
for (const f of list)
|
|
274416
|
-
ajv.addFormat(f,
|
|
274728
|
+
ajv.addFormat(f, fs23[f]);
|
|
274417
274729
|
}
|
|
274418
274730
|
module2.exports = exports2 = formatsPlugin;
|
|
274419
274731
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -274426,7 +274738,7 @@ var require_windows = __commonJS({
|
|
|
274426
274738
|
"../../node_modules/isexe/windows.js"(exports2, module2) {
|
|
274427
274739
|
module2.exports = isexe;
|
|
274428
274740
|
isexe.sync = sync;
|
|
274429
|
-
var
|
|
274741
|
+
var fs23 = require("fs");
|
|
274430
274742
|
function checkPathExt(path32, options) {
|
|
274431
274743
|
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
274432
274744
|
if (!pathext) {
|
|
@@ -274451,12 +274763,12 @@ var require_windows = __commonJS({
|
|
|
274451
274763
|
return checkPathExt(path32, options);
|
|
274452
274764
|
}
|
|
274453
274765
|
function isexe(path32, options, cb) {
|
|
274454
|
-
|
|
274766
|
+
fs23.stat(path32, function(er, stat) {
|
|
274455
274767
|
cb(er, er ? false : checkStat(stat, path32, options));
|
|
274456
274768
|
});
|
|
274457
274769
|
}
|
|
274458
274770
|
function sync(path32, options) {
|
|
274459
|
-
return checkStat(
|
|
274771
|
+
return checkStat(fs23.statSync(path32), path32, options);
|
|
274460
274772
|
}
|
|
274461
274773
|
}
|
|
274462
274774
|
});
|
|
@@ -274466,14 +274778,14 @@ var require_mode = __commonJS({
|
|
|
274466
274778
|
"../../node_modules/isexe/mode.js"(exports2, module2) {
|
|
274467
274779
|
module2.exports = isexe;
|
|
274468
274780
|
isexe.sync = sync;
|
|
274469
|
-
var
|
|
274781
|
+
var fs23 = require("fs");
|
|
274470
274782
|
function isexe(path32, options, cb) {
|
|
274471
|
-
|
|
274783
|
+
fs23.stat(path32, function(er, stat) {
|
|
274472
274784
|
cb(er, er ? false : checkStat(stat, options));
|
|
274473
274785
|
});
|
|
274474
274786
|
}
|
|
274475
274787
|
function sync(path32, options) {
|
|
274476
|
-
return checkStat(
|
|
274788
|
+
return checkStat(fs23.statSync(path32), options);
|
|
274477
274789
|
}
|
|
274478
274790
|
function checkStat(stat, options) {
|
|
274479
274791
|
return stat.isFile() && checkMode(stat, options);
|
|
@@ -274497,7 +274809,7 @@ var require_mode = __commonJS({
|
|
|
274497
274809
|
// ../../node_modules/isexe/index.js
|
|
274498
274810
|
var require_isexe = __commonJS({
|
|
274499
274811
|
"../../node_modules/isexe/index.js"(exports2, module2) {
|
|
274500
|
-
var
|
|
274812
|
+
var fs23 = require("fs");
|
|
274501
274813
|
var core2;
|
|
274502
274814
|
if (process.platform === "win32" || global.TESTING_WINDOWS) {
|
|
274503
274815
|
core2 = require_windows();
|
|
@@ -274761,16 +275073,16 @@ var require_shebang_command = __commonJS({
|
|
|
274761
275073
|
var require_readShebang = __commonJS({
|
|
274762
275074
|
"../../node_modules/cross-spawn/lib/util/readShebang.js"(exports2, module2) {
|
|
274763
275075
|
"use strict";
|
|
274764
|
-
var
|
|
275076
|
+
var fs23 = require("fs");
|
|
274765
275077
|
var shebangCommand = require_shebang_command();
|
|
274766
275078
|
function readShebang(command) {
|
|
274767
275079
|
const size = 150;
|
|
274768
275080
|
const buffer = Buffer.alloc(size);
|
|
274769
275081
|
let fd;
|
|
274770
275082
|
try {
|
|
274771
|
-
fd =
|
|
274772
|
-
|
|
274773
|
-
|
|
275083
|
+
fd = fs23.openSync(command, "r");
|
|
275084
|
+
fs23.readSync(fd, buffer, 0, size, 0);
|
|
275085
|
+
fs23.closeSync(fd);
|
|
274774
275086
|
} catch (e) {
|
|
274775
275087
|
}
|
|
274776
275088
|
return shebangCommand(buffer.toString());
|
|
@@ -275398,7 +275710,7 @@ var require_has_flag = __commonJS({
|
|
|
275398
275710
|
var require_supports_color = __commonJS({
|
|
275399
275711
|
"../../node_modules/supports-color/index.js"(exports2, module2) {
|
|
275400
275712
|
"use strict";
|
|
275401
|
-
var
|
|
275713
|
+
var os8 = require("os");
|
|
275402
275714
|
var tty = require("tty");
|
|
275403
275715
|
var hasFlag = require_has_flag();
|
|
275404
275716
|
var { env } = process;
|
|
@@ -275446,7 +275758,7 @@ var require_supports_color = __commonJS({
|
|
|
275446
275758
|
return min;
|
|
275447
275759
|
}
|
|
275448
275760
|
if (process.platform === "win32") {
|
|
275449
|
-
const osRelease =
|
|
275761
|
+
const osRelease = os8.release().split(".");
|
|
275450
275762
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
275451
275763
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
275452
275764
|
}
|
|
@@ -286297,11 +286609,11 @@ var require_jsmconsumer_api = __commonJS({
|
|
|
286297
286609
|
consumerName = cfg.name ?? cfg.durable_name ?? "";
|
|
286298
286610
|
}
|
|
286299
286611
|
if (consumerName !== "") {
|
|
286300
|
-
let
|
|
286301
|
-
if (
|
|
286302
|
-
|
|
286612
|
+
let fs23 = cfg.filter_subject ?? void 0;
|
|
286613
|
+
if (fs23 === ">") {
|
|
286614
|
+
fs23 = void 0;
|
|
286303
286615
|
}
|
|
286304
|
-
subj =
|
|
286616
|
+
subj = fs23 !== void 0 ? `${this.prefix}.CONSUMER.CREATE.${stream}.${consumerName}.${fs23}` : `${this.prefix}.CONSUMER.CREATE.${stream}.${consumerName}`;
|
|
286305
286617
|
} else {
|
|
286306
286618
|
subj = cfg.durable_name ? `${this.prefix}.CONSUMER.DURABLE.CREATE.${stream}.${cfg.durable_name}` : `${this.prefix}.CONSUMER.CREATE.${stream}`;
|
|
286307
286619
|
}
|
|
@@ -290852,6 +291164,7 @@ var init_natsEventBus = __esm({
|
|
|
290852
291164
|
|
|
290853
291165
|
// ../../packages/backend/src/runtimes/gybackend/startGyBackend.ts
|
|
290854
291166
|
var import_node_path28 = __toESM(require("node:path"), 1);
|
|
291167
|
+
var import_node_fs12 = __toESM(require("node:fs"), 1);
|
|
290855
291168
|
var import_node_process2 = __toESM(require("node:process"), 1);
|
|
290856
291169
|
|
|
290857
291170
|
// ../../packages/backend/src/services/TerminalService.ts
|
|
@@ -293693,19 +294006,19 @@ var SSHBackend = class _SSHBackend {
|
|
|
293693
294006
|
),
|
|
293694
294007
|
this.execCollect(client2, "hostname", 8e3)
|
|
293695
294008
|
]);
|
|
293696
|
-
let
|
|
294009
|
+
let os8 = "unix";
|
|
293697
294010
|
const releaseMatch = osRelease.stdout.match(/^ID=(.*)$/m);
|
|
293698
294011
|
if (releaseMatch) {
|
|
293699
|
-
|
|
294012
|
+
os8 = releaseMatch[1].replace(/"/g, "");
|
|
293700
294013
|
} else {
|
|
293701
294014
|
const unameS2 = uname.stdout.split(" ")[0].toLowerCase();
|
|
293702
|
-
|
|
294015
|
+
os8 = unameS2 || "unix";
|
|
293703
294016
|
}
|
|
293704
294017
|
const unameS = uname.stdout.split(" ")[0].toLowerCase();
|
|
293705
294018
|
const platform2 = unameS.includes("darwin") ? "darwin" : unameS.includes("linux") ? "linux" : "unix";
|
|
293706
294019
|
const parts = uname.stdout.split(" ");
|
|
293707
294020
|
const next = {
|
|
293708
|
-
os:
|
|
294021
|
+
os: os8,
|
|
293709
294022
|
platform: platform2,
|
|
293710
294023
|
release: parts[2] || "",
|
|
293711
294024
|
arch: parts[parts.length - 2] || "",
|
|
@@ -300516,6 +300829,9 @@ var TerminalService = class _TerminalService {
|
|
|
300516
300829
|
terminalIdsBeingKilled = /* @__PURE__ */ new Set();
|
|
300517
300830
|
autoReconnect = new AutoReconnect({ maxAttempts: 10 });
|
|
300518
300831
|
terminalClosedListeners = /* @__PURE__ */ new Set();
|
|
300832
|
+
/** v3.2.18: idle-timeout tracking hooks. */
|
|
300833
|
+
terminalCreatedListeners = /* @__PURE__ */ new Set();
|
|
300834
|
+
terminalDataListeners = /* @__PURE__ */ new Set();
|
|
300519
300835
|
/** Optional session logger (records terminal output to disk per session). */
|
|
300520
300836
|
sessionLogger = null;
|
|
300521
300837
|
sessionLogStarted = /* @__PURE__ */ new Set();
|
|
@@ -300565,6 +300881,38 @@ var TerminalService = class _TerminalService {
|
|
|
300565
300881
|
this.terminalClosedListeners.delete(listener);
|
|
300566
300882
|
};
|
|
300567
300883
|
}
|
|
300884
|
+
/** v3.2.18: fires when a terminal tab is created (idle-timeout tracking). */
|
|
300885
|
+
onTerminalCreated(listener) {
|
|
300886
|
+
this.terminalCreatedListeners.add(listener);
|
|
300887
|
+
return () => {
|
|
300888
|
+
this.terminalCreatedListeners.delete(listener);
|
|
300889
|
+
};
|
|
300890
|
+
}
|
|
300891
|
+
/** v3.2.18: fires on any terminal data event (idle-timeout activity). */
|
|
300892
|
+
onTerminalData(listener) {
|
|
300893
|
+
this.terminalDataListeners.add(listener);
|
|
300894
|
+
return () => {
|
|
300895
|
+
this.terminalDataListeners.delete(listener);
|
|
300896
|
+
};
|
|
300897
|
+
}
|
|
300898
|
+
/** v3.2.18: notify created-listeners (called from createTerminal paths). */
|
|
300899
|
+
notifyTerminalCreated(terminalId) {
|
|
300900
|
+
for (const listener of this.terminalCreatedListeners) {
|
|
300901
|
+
try {
|
|
300902
|
+
listener(terminalId);
|
|
300903
|
+
} catch {
|
|
300904
|
+
}
|
|
300905
|
+
}
|
|
300906
|
+
}
|
|
300907
|
+
/** v3.2.18: notify data-listeners (called from the data pump). */
|
|
300908
|
+
notifyTerminalData(terminalId) {
|
|
300909
|
+
for (const listener of this.terminalDataListeners) {
|
|
300910
|
+
try {
|
|
300911
|
+
listener(terminalId);
|
|
300912
|
+
} catch {
|
|
300913
|
+
}
|
|
300914
|
+
}
|
|
300915
|
+
}
|
|
300568
300916
|
listRenderableTerminals() {
|
|
300569
300917
|
return Array.from(this.terminals.values()).map((terminal) => ({
|
|
300570
300918
|
id: terminal.id,
|
|
@@ -300883,6 +301231,7 @@ var TerminalService = class _TerminalService {
|
|
|
300883
301231
|
}
|
|
300884
301232
|
this.publishTerminalTabsChanged();
|
|
300885
301233
|
this.schedulePersistTerminalState();
|
|
301234
|
+
this.notifyTerminalCreated(tab.id);
|
|
300886
301235
|
return tab;
|
|
300887
301236
|
}
|
|
300888
301237
|
async reconnectTerminal(terminalId) {
|
|
@@ -301076,6 +301425,7 @@ var TerminalService = class _TerminalService {
|
|
|
301076
301425
|
}
|
|
301077
301426
|
this.sessionLogger.write(terminalId, captureData);
|
|
301078
301427
|
}
|
|
301428
|
+
this.notifyTerminalData(terminalId);
|
|
301079
301429
|
});
|
|
301080
301430
|
}
|
|
301081
301431
|
}
|
|
@@ -301397,6 +301747,7 @@ ${promptPrefix}`;
|
|
|
301397
301747
|
const backend = this.getBackend(terminal.type);
|
|
301398
301748
|
backend.write(terminal.ptyId, data);
|
|
301399
301749
|
this.pendingWriteRetries.delete(terminalId);
|
|
301750
|
+
this.notifyTerminalData(terminalId);
|
|
301400
301751
|
} else if (terminal) {
|
|
301401
301752
|
if (terminal.runtimeState === "exited") {
|
|
301402
301753
|
this.pendingWrites.delete(terminalId);
|
|
@@ -350077,6 +350428,11 @@ var CLOSE_TERMINAL_TAB_DESCRIPTION = [
|
|
|
350077
350428
|
"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
350429
|
"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
350430
|
].join("\n");
|
|
350431
|
+
var SPAWN_SUBAGENTS_DESCRIPTION = [
|
|
350432
|
+
"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.",
|
|
350433
|
+
"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.",
|
|
350434
|
+
"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."
|
|
350435
|
+
].join("\n");
|
|
350080
350436
|
var COPY_BETWEEN_TABS_DESCRIPTION = [
|
|
350081
350437
|
"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
350438
|
"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 +350570,11 @@ var BUILTIN_TOOL_INFO = [
|
|
|
350214
350570
|
description: CLOSE_TERMINAL_TAB_DESCRIPTION,
|
|
350215
350571
|
shortDescription: "Close a terminal tab"
|
|
350216
350572
|
},
|
|
350573
|
+
{
|
|
350574
|
+
name: "spawn_subagents",
|
|
350575
|
+
description: SPAWN_SUBAGENTS_DESCRIPTION,
|
|
350576
|
+
shortDescription: "Fan out to concurrent child agents"
|
|
350577
|
+
},
|
|
350217
350578
|
{
|
|
350218
350579
|
name: "create_or_edit",
|
|
350219
350580
|
description: CREATE_OR_EDIT_TOOL_DESCRIPTION,
|
|
@@ -351869,6 +352230,69 @@ async function closeTerminalTab(args, context2) {
|
|
|
351869
352230
|
}
|
|
351870
352231
|
}
|
|
351871
352232
|
|
|
352233
|
+
// ../../packages/backend/src/services/AgentHelper/tools/subAgent_tools.ts
|
|
352234
|
+
init_zod();
|
|
352235
|
+
var spawnSubAgentsSchema = external_exports.object({
|
|
352236
|
+
tasks: external_exports.array(
|
|
352237
|
+
external_exports.object({
|
|
352238
|
+
label: external_exports.string().min(1).describe('Short label for reporting, e.g. "server-1"'),
|
|
352239
|
+
prompt: external_exports.string().min(1).describe("The self-contained prompt for the child agent.")
|
|
352240
|
+
})
|
|
352241
|
+
).min(1).max(20).describe("The tasks to fan out. Each child gets its own session. Max 20."),
|
|
352242
|
+
maxConcurrent: external_exports.number().int().min(1).max(10).optional().describe("Max children running concurrently (default 3)."),
|
|
352243
|
+
timeoutMs: external_exports.number().int().min(1e3).optional().describe("Per-child timeout in ms (default 300000 = 5 minutes).")
|
|
352244
|
+
});
|
|
352245
|
+
async function spawnSubAgents(args, context2) {
|
|
352246
|
+
abortIfNeeded(context2.signal);
|
|
352247
|
+
const { sendEvent, sessionId, messageId } = context2;
|
|
352248
|
+
sendEvent(sessionId, {
|
|
352249
|
+
messageId,
|
|
352250
|
+
type: "sub_tool_started",
|
|
352251
|
+
toolName: "spawn_subagents",
|
|
352252
|
+
title: `Fan out ${args.tasks.length} sub-agent(s)`,
|
|
352253
|
+
hint: `concurrency ${args.maxConcurrent ?? 3}`,
|
|
352254
|
+
input: JSON.stringify(args)
|
|
352255
|
+
});
|
|
352256
|
+
const finish = (output) => {
|
|
352257
|
+
sendEvent(sessionId, { messageId, type: "sub_tool_delta", outputDelta: output });
|
|
352258
|
+
sendEvent(sessionId, { messageId, type: "sub_tool_finished" });
|
|
352259
|
+
return output;
|
|
352260
|
+
};
|
|
352261
|
+
try {
|
|
352262
|
+
const { validateSubAgentSpec: validateSubAgentSpec2, runSubAgents: runSubAgents2, renderSubAgentSummary: renderSubAgentSummary2 } = await Promise.resolve().then(() => (init_subAgent(), subAgent_exports));
|
|
352263
|
+
const validation = validateSubAgentSpec2(args);
|
|
352264
|
+
if (!validation.ok) {
|
|
352265
|
+
return finish(`Invalid sub-agent spec: ${validation.error}`);
|
|
352266
|
+
}
|
|
352267
|
+
const agentService = context2.agentService;
|
|
352268
|
+
if (!agentService?.startTask) {
|
|
352269
|
+
return finish(
|
|
352270
|
+
"Sub-agent delegation is not available in this runtime (no agent service bridge)."
|
|
352271
|
+
);
|
|
352272
|
+
}
|
|
352273
|
+
const summary = await runSubAgents2(
|
|
352274
|
+
{
|
|
352275
|
+
tasks: args.tasks.map((t) => ({ label: t.label, prompt: t.prompt })),
|
|
352276
|
+
maxConcurrent: args.maxConcurrent,
|
|
352277
|
+
timeoutMs: args.timeoutMs
|
|
352278
|
+
},
|
|
352279
|
+
async (task2) => {
|
|
352280
|
+
const childSession = await agentService.createSession();
|
|
352281
|
+
const result = await agentService.startTask({
|
|
352282
|
+
sessionId: childSession.sessionId,
|
|
352283
|
+
userInput: task2.prompt
|
|
352284
|
+
});
|
|
352285
|
+
const text = typeof result === "string" ? result : String(result?.answer ?? result?.output ?? result);
|
|
352286
|
+
return text;
|
|
352287
|
+
}
|
|
352288
|
+
);
|
|
352289
|
+
return finish(renderSubAgentSummary2(summary));
|
|
352290
|
+
} catch (error40) {
|
|
352291
|
+
if (error40 instanceof Error && error40.name === "AbortError") throw error40;
|
|
352292
|
+
return finish(`Sub-agent fan-out failed: ${error40 instanceof Error ? error40.message : String(error40)}`);
|
|
352293
|
+
}
|
|
352294
|
+
}
|
|
352295
|
+
|
|
351872
352296
|
// ../../packages/backend/src/services/AgentHelper/tools/wait_tools.ts
|
|
351873
352297
|
init_zod();
|
|
351874
352298
|
var waitSchema = external_exports.object({
|
|
@@ -354243,6 +354667,11 @@ var CORE_METHODS = [
|
|
|
354243
354667
|
m("gateway:isSameMachine", "gateway", "Whether the caller is on the same machine as the backend.", "1.0.0"),
|
|
354244
354668
|
m("gateway:createSession", "session", "Create an agent session (returns sessionId).", "1.0.0"),
|
|
354245
354669
|
m("session:list", "session", "List chat sessions.", "1.0.0"),
|
|
354670
|
+
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"),
|
|
354671
|
+
m("settings:listBackups", "settings", "List timestamped settings backups (v3.2.18). Returns [{name, at, size}] newest-first.", "3.2.18"),
|
|
354672
|
+
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"),
|
|
354673
|
+
m("settings:export", "settings", "Export the current settings.json content as a portable JSON string (v3.2.18).", "3.2.18"),
|
|
354674
|
+
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
354675
|
m("session:get", "session", "Get one chat session by id.", "1.0.0", { sessionId: { type: "string" } }),
|
|
354247
354676
|
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
354677
|
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 +355641,11 @@ function buildToolsForModel(readFileSupport) {
|
|
|
355212
355641
|
description: BUILTIN_TOOL_INFO.find((t) => t.name === "close_terminal_tab")?.description ?? "",
|
|
355213
355642
|
schema: closeTerminalTabSchema
|
|
355214
355643
|
},
|
|
355644
|
+
{
|
|
355645
|
+
name: "spawn_subagents",
|
|
355646
|
+
description: BUILTIN_TOOL_INFO.find((t) => t.name === "spawn_subagents")?.description ?? "",
|
|
355647
|
+
schema: spawnSubAgentsSchema
|
|
355648
|
+
},
|
|
355215
355649
|
{
|
|
355216
355650
|
name: WRITE_FILE_TOOL_NAME,
|
|
355217
355651
|
description: WRITE_FILE_TOOL_DESCRIPTION,
|
|
@@ -355450,6 +355884,7 @@ var toolImplementations = {
|
|
|
355450
355884
|
reconnectTerminalTab,
|
|
355451
355885
|
openTerminalTab,
|
|
355452
355886
|
closeTerminalTab,
|
|
355887
|
+
spawnSubAgents,
|
|
355453
355888
|
wait,
|
|
355454
355889
|
waitTerminalIdle,
|
|
355455
355890
|
copyBetweenTabs,
|
|
@@ -368370,7 +368805,7 @@ Missing image attachments (kept as references only): ${missingNames.join(", ")}`
|
|
|
368370
368805
|
// ../../packages/backend/src/services/PassChatTempExportService.ts
|
|
368371
368806
|
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
368372
368807
|
var import_promises6 = __toESM(require("node:fs/promises"), 1);
|
|
368373
|
-
var
|
|
368808
|
+
var import_node_os2 = __toESM(require("node:os"), 1);
|
|
368374
368809
|
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
368375
368810
|
var import_node_crypto15 = __toESM(require("node:crypto"), 1);
|
|
368376
368811
|
var DEFAULT_MAX_FILES = 50;
|
|
@@ -368379,7 +368814,7 @@ var PassChatTempExportService = class {
|
|
|
368379
368814
|
maxFiles;
|
|
368380
368815
|
groupBySession;
|
|
368381
368816
|
constructor(options) {
|
|
368382
|
-
this.baseDir = options?.baseDir || import_node_path6.default.join(
|
|
368817
|
+
this.baseDir = options?.baseDir || import_node_path6.default.join(import_node_os2.default.tmpdir(), "gyshell-pass-chats");
|
|
368383
368818
|
this.maxFiles = options?.maxFiles === null ? null : Number.isInteger(options?.maxFiles) && Number(options?.maxFiles) > 0 ? Number(options?.maxFiles) : DEFAULT_MAX_FILES;
|
|
368384
368819
|
this.groupBySession = options?.groupBySession === true;
|
|
368385
368820
|
}
|
|
@@ -369002,6 +369437,20 @@ var AgentService_v2 = class {
|
|
|
369002
369437
|
isAbortError(error40) {
|
|
369003
369438
|
return this.helpers.isAbortError(error40);
|
|
369004
369439
|
}
|
|
369440
|
+
/**
|
|
369441
|
+
* v3.2.18: resolve a fallback model id to its ModelDefinition from the
|
|
369442
|
+
* session's settings. Returns null when the model isn't configured.
|
|
369443
|
+
*/
|
|
369444
|
+
resolveFallbackModelItem(modelId) {
|
|
369445
|
+
try {
|
|
369446
|
+
const settings = this.options?.getSettings?.() ?? null;
|
|
369447
|
+
const items = settings?.models?.items ?? [];
|
|
369448
|
+
const hit = items.find((m2) => m2.model === modelId || m2.id === modelId || m2.name === modelId);
|
|
369449
|
+
return hit ?? null;
|
|
369450
|
+
} catch {
|
|
369451
|
+
return null;
|
|
369452
|
+
}
|
|
369453
|
+
}
|
|
369005
369454
|
/**
|
|
369006
369455
|
* v3.2.12: true when the given run's final_output node already emitted the
|
|
369007
369456
|
* `done` event. GatewayService uses this to avoid broadcasting a duplicate
|
|
@@ -369549,172 +369998,228 @@ var AgentService_v2 = class {
|
|
|
369549
369998
|
let reasoningContent = "";
|
|
369550
369999
|
let debugRawChunks = [];
|
|
369551
370000
|
const llmTraceStart = Date.now();
|
|
369552
|
-
const
|
|
369553
|
-
|
|
369554
|
-
|
|
369555
|
-
|
|
369556
|
-
|
|
369557
|
-
|
|
369558
|
-
|
|
369559
|
-
|
|
370001
|
+
const fallbacks = sessionBinding.fallbackModels;
|
|
370002
|
+
let fullResponse;
|
|
370003
|
+
if (Array.isArray(fallbacks) && fallbacks.length > 0) {
|
|
370004
|
+
const { withModelFailover: withModelFailover2 } = await Promise.resolve().then(() => (init_modelFailover(), modelFailover_exports));
|
|
370005
|
+
const primaryModel = baseModel?.model ?? baseModel?.modelName ?? "unknown";
|
|
370006
|
+
const chain = [{ model: primaryModel }, ...fallbacks.map((f) => ({ model: f.model, label: f.label }))];
|
|
370007
|
+
const failover = await withModelFailover2(chain, async (candidate) => {
|
|
370008
|
+
let modelToUse = modelWithTools;
|
|
370009
|
+
if (candidate.model !== primaryModel) {
|
|
370010
|
+
const fallbackItem = this.resolveFallbackModelItem(candidate.model);
|
|
370011
|
+
if (!fallbackItem) {
|
|
370012
|
+
throw new Error(`fallback model "${candidate.model}" is not configured`);
|
|
370013
|
+
}
|
|
370014
|
+
const fallbackBinding = this.getSessionModelBinding(sessionId);
|
|
370015
|
+
const fallbackChat = this.helpers.createChatModel(fallbackItem, 0.7, fallbackBinding?.profile ?? null);
|
|
370016
|
+
modelToUse = fallbackChat;
|
|
370017
|
+
}
|
|
370018
|
+
return await invokeWithRetryAndSanitizedInput({
|
|
370019
|
+
helpers: this.helpers,
|
|
370020
|
+
messages: modelInputMessages,
|
|
370021
|
+
modelSupportsImage: sessionBinding.readFileSupport.image,
|
|
370022
|
+
signal: config2?.signal,
|
|
370023
|
+
operation: async (streamInputMessages) => {
|
|
370024
|
+
const stream = await modelToUse.stream(streamInputMessages, { signal: config2?.signal });
|
|
370025
|
+
let response = null;
|
|
370026
|
+
for await (const chunk2 of stream) {
|
|
370027
|
+
response = response ? response.concat(chunk2) : chunk2;
|
|
370028
|
+
}
|
|
370029
|
+
if (!response) throw new Error("Model stream ended without a usable response.");
|
|
370030
|
+
return response;
|
|
370031
|
+
},
|
|
370032
|
+
onRetry: (attempt) => {
|
|
370033
|
+
this.helpers.sendEvent(sessionId, {
|
|
370034
|
+
type: "alert",
|
|
370035
|
+
message: `Retrying (${attempt}/${MODEL_RETRY_MAX})...`,
|
|
370036
|
+
level: "info",
|
|
370037
|
+
messageId: `retry-${messageId}-${attempt}`
|
|
370038
|
+
});
|
|
370039
|
+
},
|
|
370040
|
+
maxRetries: MODEL_RETRY_MAX,
|
|
370041
|
+
delaysMs: MODEL_RETRY_DELAYS_MS
|
|
369560
370042
|
});
|
|
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"
|
|
370043
|
+
}, {
|
|
370044
|
+
onAttempt: (a) => {
|
|
370045
|
+
if (!a.ok) {
|
|
370046
|
+
console.warn(`[AgentService_v2] model failover: ${a.model} failed (${a.reason}) after ${a.durationMs}ms`);
|
|
370047
|
+
}
|
|
370048
|
+
}
|
|
370049
|
+
});
|
|
370050
|
+
if (failover.value !== void 0) {
|
|
370051
|
+
fullResponse = failover.value;
|
|
370052
|
+
} else {
|
|
370053
|
+
throw failover.error ?? new Error("model failover exhausted all candidates");
|
|
370054
|
+
}
|
|
370055
|
+
} else {
|
|
370056
|
+
fullResponse = await invokeWithRetryAndSanitizedInput({
|
|
370057
|
+
helpers: this.helpers,
|
|
370058
|
+
messages: modelInputMessages,
|
|
370059
|
+
modelSupportsImage: sessionBinding.readFileSupport.image,
|
|
370060
|
+
signal: config2?.signal,
|
|
370061
|
+
operation: async (streamInputMessages) => {
|
|
370062
|
+
const stream = await modelWithTools.stream(streamInputMessages, {
|
|
370063
|
+
signal: config2?.signal
|
|
369590
370064
|
});
|
|
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
|
-
|
|
370065
|
+
let response = null;
|
|
370066
|
+
let skippedEmptyGenericChunks = 0;
|
|
370067
|
+
const streamReasoningExtractor = createStreamReasoningExtractor();
|
|
370068
|
+
const attemptDebugRawChunks = [];
|
|
370069
|
+
let activeReasoningBannerId = null;
|
|
370070
|
+
const startReasoningBanner = () => {
|
|
370071
|
+
if (activeReasoningBannerId) return;
|
|
370072
|
+
activeReasoningBannerId = v4_default();
|
|
370073
|
+
this.helpers.sendEvent(sessionId, {
|
|
370074
|
+
messageId: activeReasoningBannerId,
|
|
370075
|
+
type: "sub_tool_started",
|
|
370076
|
+
title: "Reasoning...",
|
|
370077
|
+
hint: ""
|
|
370078
|
+
});
|
|
370079
|
+
};
|
|
370080
|
+
const appendReasoningDelta = (delta) => {
|
|
370081
|
+
if (!delta) return;
|
|
370082
|
+
startReasoningBanner();
|
|
370083
|
+
this.helpers.sendEvent(sessionId, {
|
|
370084
|
+
messageId: activeReasoningBannerId,
|
|
370085
|
+
type: "sub_tool_delta",
|
|
370086
|
+
outputDelta: delta
|
|
370087
|
+
});
|
|
370088
|
+
};
|
|
370089
|
+
const finishReasoningBanner = () => {
|
|
370090
|
+
if (!activeReasoningBannerId) return;
|
|
370091
|
+
this.helpers.sendEvent(sessionId, {
|
|
370092
|
+
messageId: activeReasoningBannerId,
|
|
370093
|
+
type: "sub_tool_finished"
|
|
370094
|
+
});
|
|
370095
|
+
activeReasoningBannerId = null;
|
|
370096
|
+
};
|
|
370097
|
+
try {
|
|
370098
|
+
for await (const chunk2 of stream) {
|
|
370099
|
+
const rawChunk = captureRawResponseChunk(
|
|
370100
|
+
chunk2,
|
|
370101
|
+
attemptDebugRawChunks
|
|
370102
|
+
);
|
|
370103
|
+
const extracted = streamReasoningExtractor.processChunk(
|
|
370104
|
+
chunk2,
|
|
370105
|
+
rawChunk
|
|
370106
|
+
);
|
|
370107
|
+
const appendResult = appendStreamedModelResponseChunk(
|
|
370108
|
+
response,
|
|
370109
|
+
chunk2,
|
|
370110
|
+
rawChunk
|
|
370111
|
+
);
|
|
370112
|
+
response = appendResult.response;
|
|
370113
|
+
if (appendResult.skippedEmptyGenericChunk) {
|
|
370114
|
+
skippedEmptyGenericChunks += 1;
|
|
370115
|
+
}
|
|
370116
|
+
const rawDelta = this.helpers.extractText(chunk2.content);
|
|
370117
|
+
if (rawDelta) {
|
|
370118
|
+
partialText += rawDelta;
|
|
370119
|
+
}
|
|
370120
|
+
if (extracted.reasoning) {
|
|
370121
|
+
appendReasoningDelta(extracted.reasoning);
|
|
370122
|
+
} else {
|
|
370123
|
+
finishReasoningBanner();
|
|
370124
|
+
}
|
|
370125
|
+
if (extracted.content) {
|
|
370126
|
+
this.helpers.sendEvent(sessionId, {
|
|
370127
|
+
messageId,
|
|
370128
|
+
type: "say",
|
|
370129
|
+
content: extracted.content
|
|
370130
|
+
});
|
|
370131
|
+
}
|
|
369620
370132
|
}
|
|
369621
|
-
|
|
370133
|
+
const pendingContent = streamReasoningExtractor.flushPendingContent();
|
|
370134
|
+
if (pendingContent) {
|
|
369622
370135
|
this.helpers.sendEvent(sessionId, {
|
|
369623
370136
|
messageId,
|
|
369624
370137
|
type: "say",
|
|
369625
|
-
content:
|
|
370138
|
+
content: pendingContent
|
|
369626
370139
|
});
|
|
369627
370140
|
}
|
|
370141
|
+
finishReasoningBanner();
|
|
370142
|
+
} catch (err) {
|
|
370143
|
+
finishReasoningBanner();
|
|
370144
|
+
if (partialText.trim()) {
|
|
370145
|
+
this.lastAbortedMessage = new AIMessage({
|
|
370146
|
+
content: partialText,
|
|
370147
|
+
additional_kwargs: {
|
|
370148
|
+
_gyshellMessageId: messageId,
|
|
370149
|
+
_gyshellAborted: true
|
|
370150
|
+
}
|
|
370151
|
+
});
|
|
370152
|
+
console.log(
|
|
370153
|
+
"[AgentService_v2] Captured partial message from error/abort in instance variable."
|
|
370154
|
+
);
|
|
370155
|
+
}
|
|
370156
|
+
throw err;
|
|
369628
370157
|
}
|
|
369629
|
-
|
|
369630
|
-
|
|
369631
|
-
this.helpers.sendEvent(sessionId, {
|
|
369632
|
-
messageId,
|
|
369633
|
-
type: "say",
|
|
369634
|
-
content: pendingContent
|
|
369635
|
-
});
|
|
370158
|
+
if (!response) {
|
|
370159
|
+
throw new Error("Model stream ended without a usable response.");
|
|
369636
370160
|
}
|
|
369637
|
-
|
|
369638
|
-
|
|
369639
|
-
|
|
369640
|
-
|
|
369641
|
-
|
|
369642
|
-
|
|
369643
|
-
|
|
369644
|
-
|
|
369645
|
-
|
|
370161
|
+
if (skippedEmptyGenericChunks > 0) {
|
|
370162
|
+
response.additional_kwargs = {
|
|
370163
|
+
...response.additional_kwargs || {},
|
|
370164
|
+
[SKIPPED_EMPTY_GENERIC_CHUNKS_KEY]: skippedEmptyGenericChunks
|
|
370165
|
+
};
|
|
370166
|
+
}
|
|
370167
|
+
reasoningContent = streamReasoningExtractor.getReasoningContent();
|
|
370168
|
+
if (isEmptyMalformedToolCallFinish(response, attemptDebugRawChunks) && typeof modelWithTools.invoke === "function") {
|
|
370169
|
+
console.warn(
|
|
370170
|
+
`[AgentService_v2] Stream ended with malformed empty tool-call finish; retrying same request with non-stream invoke (sessionId=${sessionId}).`
|
|
370171
|
+
);
|
|
370172
|
+
const invokeResponse = await modelWithTools.invoke(
|
|
370173
|
+
streamInputMessages,
|
|
370174
|
+
{
|
|
370175
|
+
signal: config2?.signal
|
|
369646
370176
|
}
|
|
369647
|
-
});
|
|
369648
|
-
console.log(
|
|
369649
|
-
"[AgentService_v2] Captured partial message from error/abort in instance variable."
|
|
369650
370177
|
);
|
|
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
|
|
370178
|
+
captureRawResponseChunk(
|
|
370179
|
+
invokeResponse,
|
|
370180
|
+
attemptDebugRawChunks
|
|
369684
370181
|
);
|
|
369685
|
-
if (
|
|
369686
|
-
|
|
369687
|
-
|
|
369688
|
-
|
|
369689
|
-
|
|
369690
|
-
|
|
370182
|
+
if (!isEmptyMalformedToolCallFinish(
|
|
370183
|
+
invokeResponse,
|
|
370184
|
+
attemptDebugRawChunks
|
|
370185
|
+
)) {
|
|
370186
|
+
const invokeText = this.helpers.extractText(
|
|
370187
|
+
invokeResponse.content
|
|
370188
|
+
);
|
|
370189
|
+
if (invokeText) {
|
|
370190
|
+
this.helpers.sendEvent(sessionId, {
|
|
370191
|
+
messageId,
|
|
370192
|
+
type: "say",
|
|
370193
|
+
content: invokeText
|
|
370194
|
+
});
|
|
370195
|
+
}
|
|
370196
|
+
response = invokeResponse;
|
|
369691
370197
|
}
|
|
369692
|
-
response = invokeResponse;
|
|
369693
370198
|
}
|
|
369694
|
-
|
|
369695
|
-
|
|
369696
|
-
|
|
369697
|
-
|
|
369698
|
-
|
|
369699
|
-
|
|
369700
|
-
|
|
369701
|
-
|
|
369702
|
-
|
|
369703
|
-
|
|
369704
|
-
|
|
369705
|
-
|
|
369706
|
-
|
|
369707
|
-
|
|
369708
|
-
|
|
369709
|
-
|
|
369710
|
-
|
|
369711
|
-
|
|
369712
|
-
|
|
369713
|
-
}
|
|
369714
|
-
|
|
369715
|
-
|
|
369716
|
-
|
|
369717
|
-
}
|
|
370199
|
+
if (!isEmptyMalformedToolCallFinish(response, attemptDebugRawChunks) && isEmptyUnusableModelResponse(response, attemptDebugRawChunks)) {
|
|
370200
|
+
const finishReason = describeStreamedResponseFinish(
|
|
370201
|
+
response,
|
|
370202
|
+
attemptDebugRawChunks
|
|
370203
|
+
);
|
|
370204
|
+
throw new Error(
|
|
370205
|
+
`Model stream ended with an empty unusable response (finish_reason=${finishReason}).`
|
|
370206
|
+
);
|
|
370207
|
+
}
|
|
370208
|
+
debugRawChunks = attemptDebugRawChunks;
|
|
370209
|
+
return response;
|
|
370210
|
+
},
|
|
370211
|
+
onRetry: (attempt) => {
|
|
370212
|
+
this.helpers.sendEvent(sessionId, {
|
|
370213
|
+
type: "alert",
|
|
370214
|
+
message: `Retrying (${attempt}/${MODEL_RETRY_MAX})...`,
|
|
370215
|
+
level: "info",
|
|
370216
|
+
messageId: `retry-${messageId}-${attempt}`
|
|
370217
|
+
});
|
|
370218
|
+
},
|
|
370219
|
+
maxRetries: MODEL_RETRY_MAX,
|
|
370220
|
+
delaysMs: MODEL_RETRY_DELAYS_MS
|
|
370221
|
+
});
|
|
370222
|
+
}
|
|
369718
370223
|
fullResponse.additional_kwargs = {
|
|
369719
370224
|
...fullResponse.additional_kwargs || {},
|
|
369720
370225
|
_gyshellMessageId: messageId
|
|
@@ -370110,6 +370615,22 @@ Actually, your intention might be different. Please re-read the description of t
|
|
|
370110
370615
|
}
|
|
370111
370616
|
break;
|
|
370112
370617
|
}
|
|
370618
|
+
case "spawn_subagents": {
|
|
370619
|
+
try {
|
|
370620
|
+
const validatedArgs = spawnSubAgentsSchema.parse(
|
|
370621
|
+
toolCall.args || {}
|
|
370622
|
+
);
|
|
370623
|
+
result = await toolImplementations.spawnSubAgents(
|
|
370624
|
+
validatedArgs,
|
|
370625
|
+
// The sub-agent tool needs the agent service to create child
|
|
370626
|
+
// sessions — inject it into the execution context.
|
|
370627
|
+
{ ...executionContext, agentService: this }
|
|
370628
|
+
);
|
|
370629
|
+
} catch (err) {
|
|
370630
|
+
result = `Parameter validation error for spawn_subagents: ${err.message}`;
|
|
370631
|
+
}
|
|
370632
|
+
break;
|
|
370633
|
+
}
|
|
370113
370634
|
case "manage_ssh_connection": {
|
|
370114
370635
|
try {
|
|
370115
370636
|
const validatedArgs = manageSshConnectionSchema.parse(
|
|
@@ -374652,13 +375173,16 @@ var WebSocketGatewayAdapter = class {
|
|
|
374652
375173
|
}
|
|
374653
375174
|
const token = this.extractAccessToken(request);
|
|
374654
375175
|
if (!token) {
|
|
375176
|
+
this.options.rateLimiter?.recordAuthFailure(this.clientKeyForRequest(request));
|
|
374655
375177
|
return "missing access token";
|
|
374656
375178
|
}
|
|
374657
375179
|
try {
|
|
374658
375180
|
const valid = await auth2.verifyToken(token);
|
|
374659
375181
|
if (!valid) {
|
|
375182
|
+
this.options.rateLimiter?.recordAuthFailure(this.clientKeyForRequest(request));
|
|
374660
375183
|
return "invalid access token";
|
|
374661
375184
|
}
|
|
375185
|
+
this.options.rateLimiter?.recordAuthSuccess(this.clientKeyForRequest(request));
|
|
374662
375186
|
return null;
|
|
374663
375187
|
} catch (error40) {
|
|
374664
375188
|
this.logger.warn(
|
|
@@ -374668,6 +375192,15 @@ var WebSocketGatewayAdapter = class {
|
|
|
374668
375192
|
return "token verification failed";
|
|
374669
375193
|
}
|
|
374670
375194
|
}
|
|
375195
|
+
/** v3.2.18: a stable per-client key for rate limiting (IP or token id). */
|
|
375196
|
+
clientKeyForRequest(request) {
|
|
375197
|
+
return String(request?.socket?.remoteAddress || "unknown");
|
|
375198
|
+
}
|
|
375199
|
+
/** v3.2.18: per-client key from a live socket connection. */
|
|
375200
|
+
clientKeyForSocket(socket) {
|
|
375201
|
+
const remote = socket.remoteAddress;
|
|
375202
|
+
return String(remote || "unknown");
|
|
375203
|
+
}
|
|
374671
375204
|
extractAccessToken(request) {
|
|
374672
375205
|
const fromAuthHeader = this.readBearerToken(
|
|
374673
375206
|
request?.headers?.authorization
|
|
@@ -374828,6 +375361,17 @@ var WebSocketGatewayAdapter = class {
|
|
|
374828
375361
|
try {
|
|
374829
375362
|
const parsed = this.parseRequest(raw);
|
|
374830
375363
|
requestId = parsed.id !== void 0 ? String(parsed.id) : void 0;
|
|
375364
|
+
const limiter = this.options.rateLimiter;
|
|
375365
|
+
if (limiter) {
|
|
375366
|
+
const clientKey = this.clientKeyForSocket(socket);
|
|
375367
|
+
const decision = limiter.check(clientKey);
|
|
375368
|
+
if (!decision.allowed) {
|
|
375369
|
+
throw new WebSocketRpcError(
|
|
375370
|
+
"RATE_LIMITED",
|
|
375371
|
+
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`
|
|
375372
|
+
);
|
|
375373
|
+
}
|
|
375374
|
+
}
|
|
374831
375375
|
const result = await this.executeRequest(parsed, socket);
|
|
374832
375376
|
if (requestId) {
|
|
374833
375377
|
this.sendRpcSuccess(socket, requestId, result);
|
|
@@ -374885,6 +375429,26 @@ var WebSocketGatewayAdapter = class {
|
|
|
374885
375429
|
"Unsupported websocket payload type."
|
|
374886
375430
|
);
|
|
374887
375431
|
}
|
|
375432
|
+
/**
|
|
375433
|
+
* v3.2.18: public dispatch for non-WebSocket callers (the REST API layer).
|
|
375434
|
+
* Executes a gateway method with a synthetic loopback socket — same
|
|
375435
|
+
* validation and error mapping as a WS request, no socket required.
|
|
375436
|
+
*/
|
|
375437
|
+
async handleRequest(method, params) {
|
|
375438
|
+
const syntheticSocket = {
|
|
375439
|
+
send: () => {
|
|
375440
|
+
},
|
|
375441
|
+
close: () => {
|
|
375442
|
+
},
|
|
375443
|
+
on: () => {
|
|
375444
|
+
},
|
|
375445
|
+
readyState: 1
|
|
375446
|
+
};
|
|
375447
|
+
return await this.executeRequest(
|
|
375448
|
+
{ id: `rest-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, method, params },
|
|
375449
|
+
syntheticSocket
|
|
375450
|
+
);
|
|
375451
|
+
}
|
|
374888
375452
|
async executeRequest(request, socket) {
|
|
374889
375453
|
const params = request.params ?? {};
|
|
374890
375454
|
if (request.method.startsWith("observability:")) {
|
|
@@ -374961,6 +375525,61 @@ var WebSocketGatewayAdapter = class {
|
|
|
374961
375525
|
}
|
|
374962
375526
|
return { session };
|
|
374963
375527
|
}
|
|
375528
|
+
case "history:search": {
|
|
375529
|
+
const bridge = this.options.historyBridge;
|
|
375530
|
+
if (!bridge?.getAllSessions) {
|
|
375531
|
+
throw new WebSocketRpcError(
|
|
375532
|
+
"METHOD_NOT_FOUND",
|
|
375533
|
+
"history:search is not available on this gateway (no history bridge)."
|
|
375534
|
+
);
|
|
375535
|
+
}
|
|
375536
|
+
const { searchChatHistory: searchChatHistory2 } = await Promise.resolve().then(() => (init_historySearch(), historySearch_exports));
|
|
375537
|
+
const sessions = await bridge.getAllSessions();
|
|
375538
|
+
const query = this.readStringParam(params, "query");
|
|
375539
|
+
const wholeWord = params?.wholeWord === true;
|
|
375540
|
+
const includeTitles = params?.includeTitles !== false;
|
|
375541
|
+
const sessionLimit = typeof params?.sessionLimit === "number" ? params.sessionLimit : void 0;
|
|
375542
|
+
const snippetLimit = typeof params?.snippetLimit === "number" ? params.snippetLimit : void 0;
|
|
375543
|
+
return searchChatHistory2(sessions, query, { wholeWord, includeTitles, sessionLimit, snippetLimit });
|
|
375544
|
+
}
|
|
375545
|
+
case "settings:listBackups": {
|
|
375546
|
+
const bridge = this.options.settingsBridge;
|
|
375547
|
+
if (!bridge?.listBackups) {
|
|
375548
|
+
throw new WebSocketRpcError("METHOD_NOT_FOUND", "settings backups are not available on this gateway.");
|
|
375549
|
+
}
|
|
375550
|
+
return { backups: await bridge.listBackups() };
|
|
375551
|
+
}
|
|
375552
|
+
case "settings:restoreBackup": {
|
|
375553
|
+
const bridge = this.options.settingsBridge;
|
|
375554
|
+
if (!bridge?.restoreBackup) {
|
|
375555
|
+
throw new WebSocketRpcError("METHOD_NOT_FOUND", "settings backups are not available on this gateway.");
|
|
375556
|
+
}
|
|
375557
|
+
const name = this.readStringParam(params, "name");
|
|
375558
|
+
const result = await bridge.restoreBackup(name);
|
|
375559
|
+
if (result && typeof result === "object" && "ok" in result && !result.ok) {
|
|
375560
|
+
throw new WebSocketRpcError("BAD_REQUEST", String(result.error ?? "restore failed"));
|
|
375561
|
+
}
|
|
375562
|
+
return { restored: name };
|
|
375563
|
+
}
|
|
375564
|
+
case "settings:export": {
|
|
375565
|
+
const bridge = this.options.settingsBridge;
|
|
375566
|
+
if (!bridge?.export) {
|
|
375567
|
+
throw new WebSocketRpcError("METHOD_NOT_FOUND", "settings export is not available on this gateway.");
|
|
375568
|
+
}
|
|
375569
|
+
return { content: await bridge.export() };
|
|
375570
|
+
}
|
|
375571
|
+
case "settings:import": {
|
|
375572
|
+
const bridge = this.options.settingsBridge;
|
|
375573
|
+
if (!bridge?.import) {
|
|
375574
|
+
throw new WebSocketRpcError("METHOD_NOT_FOUND", "settings import is not available on this gateway.");
|
|
375575
|
+
}
|
|
375576
|
+
const content = this.readStringParam(params, "content");
|
|
375577
|
+
const result = await bridge.import(content);
|
|
375578
|
+
if (result && typeof result === "object" && "ok" in result && !result.ok) {
|
|
375579
|
+
throw new WebSocketRpcError("BAD_REQUEST", String(result.error ?? "import failed"));
|
|
375580
|
+
}
|
|
375581
|
+
return { imported: true };
|
|
375582
|
+
}
|
|
374964
375583
|
case "agent:exportHistory": {
|
|
374965
375584
|
const bridge = this.options.agentBridge;
|
|
374966
375585
|
if (!bridge?.exportHistory) {
|
|
@@ -383611,7 +384230,7 @@ var NodeMcpToolService = class extends McpRuntimeCore {
|
|
|
383611
384230
|
|
|
383612
384231
|
// ../../packages/backend/src/adapters/node/NodeSkillService.ts
|
|
383613
384232
|
var import_node_path16 = __toESM(require("node:path"), 1);
|
|
383614
|
-
var
|
|
384233
|
+
var import_node_os4 = __toESM(require("node:os"), 1);
|
|
383615
384234
|
|
|
383616
384235
|
// ../../packages/backend/src/skills/FileSkillStore.ts
|
|
383617
384236
|
var import_promises9 = __toESM(require("node:fs/promises"), 1);
|
|
@@ -383916,10 +384535,10 @@ ${files}`;
|
|
|
383916
384535
|
|
|
383917
384536
|
// ../../packages/backend/src/skills/scanRoots.ts
|
|
383918
384537
|
var import_node_path15 = __toESM(require("node:path"), 1);
|
|
383919
|
-
var
|
|
384538
|
+
var import_node_os3 = __toESM(require("node:os"), 1);
|
|
383920
384539
|
function resolveDefaultSkillScanRoots(options) {
|
|
383921
384540
|
const primaryRoot = import_node_path15.default.resolve(options.primaryRoot);
|
|
383922
|
-
const homeDir = (options.homeDir ||
|
|
384541
|
+
const homeDir = (options.homeDir || import_node_os3.default.homedir() || "").trim();
|
|
383923
384542
|
const platform2 = options.platform || process.platform;
|
|
383924
384543
|
const appData = (options.appData || process.env.APPDATA || "").trim();
|
|
383925
384544
|
const codexHome = (options.codexHome || process.env.CODEX_HOME || "").trim();
|
|
@@ -383991,7 +384610,7 @@ var NodeSkillService = class {
|
|
|
383991
384610
|
resolveScanRoots() {
|
|
383992
384611
|
return resolveDefaultSkillScanRoots({
|
|
383993
384612
|
primaryRoot: this.skillsDir,
|
|
383994
|
-
homeDir:
|
|
384613
|
+
homeDir: import_node_os4.default.homedir(),
|
|
383995
384614
|
platform: process.platform,
|
|
383996
384615
|
appData: process.env.APPDATA,
|
|
383997
384616
|
codexHome: process.env.CODEX_HOME
|
|
@@ -386114,6 +386733,367 @@ var SessionLogService = class _SessionLogService {
|
|
|
386114
386733
|
|
|
386115
386734
|
// ../../packages/backend/src/runtimes/gybackend/startGyBackend.ts
|
|
386116
386735
|
init_schedulerService();
|
|
386736
|
+
|
|
386737
|
+
// ../../packages/backend/src/services/settings/settingsBackup.ts
|
|
386738
|
+
var BACKUP_PREFIX = "settings-backup-";
|
|
386739
|
+
var BACKUP_SUFFIX = ".json";
|
|
386740
|
+
function backupNameFor(date6) {
|
|
386741
|
+
const pad = (n2, w = 2) => String(n2).padStart(w, "0");
|
|
386742
|
+
return `${BACKUP_PREFIX}${date6.getUTCFullYear()}${pad(date6.getUTCMonth() + 1)}${pad(date6.getUTCDate())}-${pad(date6.getUTCHours())}${pad(date6.getUTCMinutes())}${pad(date6.getUTCSeconds())}${BACKUP_SUFFIX}`;
|
|
386743
|
+
}
|
|
386744
|
+
function parseBackupName(name) {
|
|
386745
|
+
if (!name.startsWith(BACKUP_PREFIX) || !name.endsWith(BACKUP_SUFFIX)) return null;
|
|
386746
|
+
const stem = name.slice(BACKUP_PREFIX.length, -BACKUP_SUFFIX.length);
|
|
386747
|
+
const m2 = stem.match(/^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/);
|
|
386748
|
+
if (!m2) return null;
|
|
386749
|
+
const [, y, mo, d, h, mi, s] = m2.map(Number);
|
|
386750
|
+
const date6 = new Date(Date.UTC(y, mo - 1, d, h, mi, s));
|
|
386751
|
+
return Number.isNaN(date6.getTime()) ? null : date6;
|
|
386752
|
+
}
|
|
386753
|
+
var SettingsBackupService = class {
|
|
386754
|
+
constructor(io, opts = {}) {
|
|
386755
|
+
this.io = io;
|
|
386756
|
+
this.keep = opts.keep ?? 20;
|
|
386757
|
+
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
386758
|
+
}
|
|
386759
|
+
keep;
|
|
386760
|
+
now;
|
|
386761
|
+
/** Take a backup of the CURRENT settings content, then rotate. */
|
|
386762
|
+
backup() {
|
|
386763
|
+
const content = this.io.read();
|
|
386764
|
+
const name = backupNameFor(this.now());
|
|
386765
|
+
this.io.writeBackup(name, content);
|
|
386766
|
+
this.rotate();
|
|
386767
|
+
return { name, at: this.now(), size: content.length };
|
|
386768
|
+
}
|
|
386769
|
+
/** Delete oldest backups beyond the keep limit. Returns names removed. */
|
|
386770
|
+
rotate() {
|
|
386771
|
+
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());
|
|
386772
|
+
const excess = names.slice(0, Math.max(0, names.length - this.keep));
|
|
386773
|
+
for (const { n: n2 } of excess) this.io.deleteBackup(n2);
|
|
386774
|
+
return excess.map((x) => x.n);
|
|
386775
|
+
}
|
|
386776
|
+
/** List backups, newest first. */
|
|
386777
|
+
list() {
|
|
386778
|
+
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 }));
|
|
386779
|
+
}
|
|
386780
|
+
/** Restore a backup by name (writes it to the live settings path). */
|
|
386781
|
+
restore(name) {
|
|
386782
|
+
if (!this.io.listBackups().includes(name)) {
|
|
386783
|
+
return { ok: false, error: `no backup named "${name}"` };
|
|
386784
|
+
}
|
|
386785
|
+
this.backup();
|
|
386786
|
+
const content = this.io.readBackup(name);
|
|
386787
|
+
this.io.write(content);
|
|
386788
|
+
return { ok: true };
|
|
386789
|
+
}
|
|
386790
|
+
/** Export the current settings as a portable JSON string. */
|
|
386791
|
+
export() {
|
|
386792
|
+
return this.io.read();
|
|
386793
|
+
}
|
|
386794
|
+
/** Validate + import settings JSON. Refuses non-object / empty content. */
|
|
386795
|
+
import(content) {
|
|
386796
|
+
const trimmed = (content ?? "").trim();
|
|
386797
|
+
if (!trimmed) return { ok: false, error: "empty settings content" };
|
|
386798
|
+
let parsed;
|
|
386799
|
+
try {
|
|
386800
|
+
parsed = JSON.parse(trimmed);
|
|
386801
|
+
} catch (e) {
|
|
386802
|
+
return { ok: false, error: `invalid JSON: ${e instanceof Error ? e.message : String(e)}` };
|
|
386803
|
+
}
|
|
386804
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
386805
|
+
return { ok: false, error: "settings content must be a JSON object" };
|
|
386806
|
+
}
|
|
386807
|
+
this.backup();
|
|
386808
|
+
this.io.write(trimmed);
|
|
386809
|
+
return { ok: true };
|
|
386810
|
+
}
|
|
386811
|
+
};
|
|
386812
|
+
|
|
386813
|
+
// ../../packages/backend/src/services/terminal/idleTimeout.ts
|
|
386814
|
+
var IdleTimeoutService = class {
|
|
386815
|
+
idleMs;
|
|
386816
|
+
protectedIds;
|
|
386817
|
+
now;
|
|
386818
|
+
lastActivity = /* @__PURE__ */ new Map();
|
|
386819
|
+
constructor(opts = {}) {
|
|
386820
|
+
this.idleMs = Math.max(0, (opts.idleMinutes ?? 30) * 6e4);
|
|
386821
|
+
this.protectedIds = new Set(opts.protectedIds ?? []);
|
|
386822
|
+
this.now = opts.now ?? (() => Date.now());
|
|
386823
|
+
}
|
|
386824
|
+
/** Record activity for a terminal. */
|
|
386825
|
+
touch(terminalId) {
|
|
386826
|
+
this.lastActivity.set(terminalId, this.now());
|
|
386827
|
+
}
|
|
386828
|
+
/** Register a terminal (starts its idle clock). */
|
|
386829
|
+
register(terminalId) {
|
|
386830
|
+
if (!this.lastActivity.has(terminalId)) {
|
|
386831
|
+
this.lastActivity.set(terminalId, this.now());
|
|
386832
|
+
}
|
|
386833
|
+
}
|
|
386834
|
+
/** Forget a terminal (on close). */
|
|
386835
|
+
forget(terminalId) {
|
|
386836
|
+
this.lastActivity.delete(terminalId);
|
|
386837
|
+
}
|
|
386838
|
+
/** Is this terminal idle past the threshold? */
|
|
386839
|
+
isIdle(terminalId) {
|
|
386840
|
+
if (this.protectedIds.has(terminalId)) return false;
|
|
386841
|
+
const last = this.lastActivity.get(terminalId);
|
|
386842
|
+
if (last === void 0) return false;
|
|
386843
|
+
return this.now() - last >= this.idleMs;
|
|
386844
|
+
}
|
|
386845
|
+
/** All currently-idle terminals (among the known ones). */
|
|
386846
|
+
idleTerminals(knownIds) {
|
|
386847
|
+
const t = this.now();
|
|
386848
|
+
const out = [];
|
|
386849
|
+
for (const id of knownIds) {
|
|
386850
|
+
if (this.protectedIds.has(id)) continue;
|
|
386851
|
+
const last = this.lastActivity.get(id);
|
|
386852
|
+
if (last === void 0) continue;
|
|
386853
|
+
const idleMin = (t - last) / 6e4;
|
|
386854
|
+
if (idleMin * 6e4 >= this.idleMs) {
|
|
386855
|
+
out.push({ terminalId: id, idleMinutes: Math.round(idleMin * 10) / 10 });
|
|
386856
|
+
}
|
|
386857
|
+
}
|
|
386858
|
+
return out;
|
|
386859
|
+
}
|
|
386860
|
+
/** Configure the threshold at runtime. */
|
|
386861
|
+
setIdleMinutes(minutes) {
|
|
386862
|
+
this.idleMs = Math.max(0, minutes * 6e4);
|
|
386863
|
+
}
|
|
386864
|
+
get idleMinutes() {
|
|
386865
|
+
return this.idleMs / 6e4;
|
|
386866
|
+
}
|
|
386867
|
+
};
|
|
386868
|
+
|
|
386869
|
+
// ../../packages/backend/src/services/Gateway/restApi.ts
|
|
386870
|
+
function matchRestRoute(routes, method, path32) {
|
|
386871
|
+
const normalized = path32.replace(/\/+$/, "") || "/";
|
|
386872
|
+
for (const route of routes) {
|
|
386873
|
+
if (route.method !== method.toUpperCase()) continue;
|
|
386874
|
+
const patternParts = route.path.split("/").filter(Boolean);
|
|
386875
|
+
const pathParts = normalized.split("/").filter(Boolean);
|
|
386876
|
+
if (patternParts.length !== pathParts.length) continue;
|
|
386877
|
+
const params = {};
|
|
386878
|
+
let matched = true;
|
|
386879
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
386880
|
+
const p = patternParts[i];
|
|
386881
|
+
if (p.startsWith(":")) {
|
|
386882
|
+
params[p.slice(1)] = decodeURIComponent(pathParts[i]);
|
|
386883
|
+
} else if (p !== pathParts[i]) {
|
|
386884
|
+
matched = false;
|
|
386885
|
+
break;
|
|
386886
|
+
}
|
|
386887
|
+
}
|
|
386888
|
+
if (matched) return { route, pathParams: params };
|
|
386889
|
+
}
|
|
386890
|
+
return null;
|
|
386891
|
+
}
|
|
386892
|
+
function defaultRestRoutes() {
|
|
386893
|
+
return [
|
|
386894
|
+
{
|
|
386895
|
+
method: "GET",
|
|
386896
|
+
path: "/api/v1/health",
|
|
386897
|
+
gatewayMethod: "gateway:ping",
|
|
386898
|
+
description: "Liveness check"
|
|
386899
|
+
},
|
|
386900
|
+
{
|
|
386901
|
+
method: "GET",
|
|
386902
|
+
path: "/api/v1/methods",
|
|
386903
|
+
gatewayMethod: "gateway:describe",
|
|
386904
|
+
description: "List all gateway methods (self-describing)"
|
|
386905
|
+
},
|
|
386906
|
+
{
|
|
386907
|
+
method: "GET",
|
|
386908
|
+
path: "/api/v1/terminals",
|
|
386909
|
+
gatewayMethod: "terminal:list",
|
|
386910
|
+
description: "List terminal tabs"
|
|
386911
|
+
},
|
|
386912
|
+
{
|
|
386913
|
+
method: "GET",
|
|
386914
|
+
path: "/api/v1/sessions",
|
|
386915
|
+
gatewayMethod: "session:list",
|
|
386916
|
+
description: "List chat sessions"
|
|
386917
|
+
},
|
|
386918
|
+
{
|
|
386919
|
+
method: "GET",
|
|
386920
|
+
path: "/api/v1/skills",
|
|
386921
|
+
gatewayMethod: "skills:getAll",
|
|
386922
|
+
description: "List loaded skills"
|
|
386923
|
+
},
|
|
386924
|
+
{
|
|
386925
|
+
method: "GET",
|
|
386926
|
+
path: "/api/v1/observability/metrics",
|
|
386927
|
+
gatewayMethod: "observability:metricsPrometheus",
|
|
386928
|
+
description: "Host metrics (Prometheus text or summary)"
|
|
386929
|
+
},
|
|
386930
|
+
{
|
|
386931
|
+
method: "GET",
|
|
386932
|
+
path: "/api/v1/observability/dashboard",
|
|
386933
|
+
gatewayMethod: "observability:liveDashboardState",
|
|
386934
|
+
description: "Live dashboard state"
|
|
386935
|
+
},
|
|
386936
|
+
{
|
|
386937
|
+
method: "GET",
|
|
386938
|
+
path: "/api/v1/observability/apm",
|
|
386939
|
+
gatewayMethod: "observability:apmSummary",
|
|
386940
|
+
description: "APM summary (LLM + app traces)"
|
|
386941
|
+
},
|
|
386942
|
+
{
|
|
386943
|
+
method: "GET",
|
|
386944
|
+
path: "/api/v1/history/search",
|
|
386945
|
+
gatewayMethod: "history:search",
|
|
386946
|
+
description: "Cross-session history search (?q=...)",
|
|
386947
|
+
buildParams: (_p, body) => {
|
|
386948
|
+
const b = body ?? {};
|
|
386949
|
+
return { query: b.q ?? b.query ?? "" };
|
|
386950
|
+
}
|
|
386951
|
+
},
|
|
386952
|
+
{
|
|
386953
|
+
method: "POST",
|
|
386954
|
+
path: "/api/v1/terminals/:id/write",
|
|
386955
|
+
gatewayMethod: "terminal:write",
|
|
386956
|
+
description: "Write data to a terminal tab",
|
|
386957
|
+
buildParams: (p, body) => {
|
|
386958
|
+
const b = body ?? {};
|
|
386959
|
+
return { terminalId: p.id, data: b.data ?? "" };
|
|
386960
|
+
}
|
|
386961
|
+
},
|
|
386962
|
+
{
|
|
386963
|
+
method: "GET",
|
|
386964
|
+
path: "/api/v1/terminals/:id/buffer",
|
|
386965
|
+
gatewayMethod: "terminal:getBufferDelta",
|
|
386966
|
+
description: "Read a terminal tab output delta",
|
|
386967
|
+
buildParams: (p, body) => {
|
|
386968
|
+
const b = body ?? {};
|
|
386969
|
+
return { terminalId: p.id, fromOffset: b.fromOffset ?? 0 };
|
|
386970
|
+
}
|
|
386971
|
+
},
|
|
386972
|
+
{
|
|
386973
|
+
method: "POST",
|
|
386974
|
+
path: "/api/v1/sessions/:id/chat",
|
|
386975
|
+
gatewayMethod: "agent:startTask",
|
|
386976
|
+
description: "Send a message to the agent (blocking)",
|
|
386977
|
+
buildParams: (p, body) => {
|
|
386978
|
+
const b = body ?? {};
|
|
386979
|
+
return { sessionId: p.id, userInput: b.message ?? b.userInput ?? "" };
|
|
386980
|
+
}
|
|
386981
|
+
},
|
|
386982
|
+
{
|
|
386983
|
+
method: "POST",
|
|
386984
|
+
path: "/api/v1/rpc",
|
|
386985
|
+
gatewayMethod: "",
|
|
386986
|
+
description: "Escape hatch: dispatch any gateway method",
|
|
386987
|
+
buildParams: (_p, body) => {
|
|
386988
|
+
const b = body ?? {};
|
|
386989
|
+
return { __rpcMethod: b.method ?? "", ...b.params ?? {} };
|
|
386990
|
+
}
|
|
386991
|
+
}
|
|
386992
|
+
];
|
|
386993
|
+
}
|
|
386994
|
+
async function handleRestRequest(routes, dispatch, req) {
|
|
386995
|
+
const match = matchRestRoute(routes, req.method, req.path);
|
|
386996
|
+
if (!match) {
|
|
386997
|
+
return {
|
|
386998
|
+
status: 404,
|
|
386999
|
+
body: { error: "not_found", message: `No REST route for ${req.method} ${req.path}` }
|
|
387000
|
+
};
|
|
387001
|
+
}
|
|
387002
|
+
let gatewayMethod = match.route.gatewayMethod;
|
|
387003
|
+
let params;
|
|
387004
|
+
if (gatewayMethod === "") {
|
|
387005
|
+
const raw = req.body ?? {};
|
|
387006
|
+
if (!raw.method) {
|
|
387007
|
+
return { status: 400, body: { error: "bad_request", message: 'POST /api/v1/rpc needs {"method": "...", "params": {...}}' } };
|
|
387008
|
+
}
|
|
387009
|
+
gatewayMethod = raw.method;
|
|
387010
|
+
params = raw.params ?? {};
|
|
387011
|
+
} else {
|
|
387012
|
+
params = match.route.buildParams?.(match.pathParams, req.body) ?? match.pathParams;
|
|
387013
|
+
}
|
|
387014
|
+
try {
|
|
387015
|
+
const result = await dispatch(gatewayMethod, params);
|
|
387016
|
+
return { status: 200, body: result ?? { ok: true } };
|
|
387017
|
+
} catch (error40) {
|
|
387018
|
+
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
387019
|
+
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;
|
|
387020
|
+
return { status, body: { error: "gateway_error", message } };
|
|
387021
|
+
}
|
|
387022
|
+
}
|
|
387023
|
+
|
|
387024
|
+
// ../../packages/backend/src/services/Gateway/gatewayRateLimit.ts
|
|
387025
|
+
var GatewayRateLimiter = class {
|
|
387026
|
+
capacity;
|
|
387027
|
+
refillPerSecond;
|
|
387028
|
+
authFailureLimit;
|
|
387029
|
+
authLockoutMs;
|
|
387030
|
+
now;
|
|
387031
|
+
buckets = /* @__PURE__ */ new Map();
|
|
387032
|
+
authTrackers = /* @__PURE__ */ new Map();
|
|
387033
|
+
constructor(opts = {}) {
|
|
387034
|
+
this.capacity = opts.capacity ?? 60;
|
|
387035
|
+
this.refillPerSecond = opts.refillPerSecond ?? 1;
|
|
387036
|
+
this.authFailureLimit = opts.authFailureLimit ?? 5;
|
|
387037
|
+
this.authLockoutMs = opts.authLockoutMs ?? 6e4;
|
|
387038
|
+
this.now = opts.now ?? (() => Date.now());
|
|
387039
|
+
}
|
|
387040
|
+
refill(bucket) {
|
|
387041
|
+
const t = this.now();
|
|
387042
|
+
const elapsed2 = (t - bucket.lastRefill) / 1e3;
|
|
387043
|
+
if (elapsed2 <= 0) return;
|
|
387044
|
+
bucket.tokens = Math.min(this.capacity, bucket.tokens + elapsed2 * this.refillPerSecond);
|
|
387045
|
+
bucket.lastRefill = t;
|
|
387046
|
+
}
|
|
387047
|
+
/** Check (and consume) one token for a client. */
|
|
387048
|
+
check(clientKey) {
|
|
387049
|
+
const t = this.now();
|
|
387050
|
+
const bucket = this.buckets.get(clientKey) ?? { tokens: this.capacity, lastRefill: t };
|
|
387051
|
+
this.refill(bucket);
|
|
387052
|
+
this.buckets.set(clientKey, bucket);
|
|
387053
|
+
if (bucket.tokens >= 1) {
|
|
387054
|
+
bucket.tokens -= 1;
|
|
387055
|
+
return { allowed: true, remaining: Math.floor(bucket.tokens) };
|
|
387056
|
+
}
|
|
387057
|
+
const msToNextToken = Math.ceil((1 - bucket.tokens) / this.refillPerSecond * 1e3);
|
|
387058
|
+
return { allowed: false, remaining: 0, retryAfterMs: msToNextToken, reason: "rate-limited" };
|
|
387059
|
+
}
|
|
387060
|
+
/** Record a failed authentication attempt; returns the lockout decision. */
|
|
387061
|
+
recordAuthFailure(clientKey) {
|
|
387062
|
+
const t = this.now();
|
|
387063
|
+
const tracker = this.authTrackers.get(clientKey) ?? { failures: 0, lockedUntil: 0 };
|
|
387064
|
+
tracker.failures += 1;
|
|
387065
|
+
if (tracker.failures >= this.authFailureLimit) {
|
|
387066
|
+
tracker.lockedUntil = t + this.authLockoutMs;
|
|
387067
|
+
tracker.failures = 0;
|
|
387068
|
+
}
|
|
387069
|
+
this.authTrackers.set(clientKey, tracker);
|
|
387070
|
+
if (t < tracker.lockedUntil) {
|
|
387071
|
+
return { allowed: false, remaining: 0, retryAfterMs: tracker.lockedUntil - t, reason: "locked-out" };
|
|
387072
|
+
}
|
|
387073
|
+
return { allowed: true, remaining: this.authFailureLimit - tracker.failures };
|
|
387074
|
+
}
|
|
387075
|
+
/** Clear auth failures on successful authentication. */
|
|
387076
|
+
recordAuthSuccess(clientKey) {
|
|
387077
|
+
this.authTrackers.delete(clientKey);
|
|
387078
|
+
}
|
|
387079
|
+
/** Is this client currently locked out? */
|
|
387080
|
+
isLockedOut(clientKey) {
|
|
387081
|
+
const tracker = this.authTrackers.get(clientKey);
|
|
387082
|
+
if (!tracker) return false;
|
|
387083
|
+
return this.now() < tracker.lockedUntil;
|
|
387084
|
+
}
|
|
387085
|
+
/** Drop state for a client (on disconnect). */
|
|
387086
|
+
forget(clientKey) {
|
|
387087
|
+
this.buckets.delete(clientKey);
|
|
387088
|
+
this.authTrackers.delete(clientKey);
|
|
387089
|
+
}
|
|
387090
|
+
/** Number of tracked clients (for monitoring/tests). */
|
|
387091
|
+
get clientCount() {
|
|
387092
|
+
return this.buckets.size;
|
|
387093
|
+
}
|
|
387094
|
+
};
|
|
387095
|
+
|
|
387096
|
+
// ../../packages/backend/src/runtimes/gybackend/startGyBackend.ts
|
|
386117
387097
|
init_scheduledTaskRunner();
|
|
386118
387098
|
|
|
386119
387099
|
// ../../packages/backend/src/services/history/HistoryStorageMigration.ts
|
|
@@ -393608,11 +394588,11 @@ function createObservability(deps) {
|
|
|
393608
394588
|
const policyEngine = new AgtPolicyEngine({
|
|
393609
394589
|
loadPolicy: async () => {
|
|
393610
394590
|
const req = (0, import_node_module5.createRequire)(typeof __filename !== "undefined" ? __filename : import_meta7.url);
|
|
393611
|
-
const
|
|
394591
|
+
const fs23 = req("node:fs");
|
|
393612
394592
|
const path32 = req("node:path");
|
|
393613
394593
|
const policyPath = path32.join(".", "policy.yaml");
|
|
393614
|
-
if (
|
|
393615
|
-
return parsePolicyYaml(
|
|
394594
|
+
if (fs23.existsSync(policyPath)) {
|
|
394595
|
+
return parsePolicyYaml(fs23.readFileSync(policyPath, "utf8"));
|
|
393616
394596
|
}
|
|
393617
394597
|
return {
|
|
393618
394598
|
name: "rterm-default",
|
|
@@ -393654,18 +394634,18 @@ function createObservability(deps) {
|
|
|
393654
394634
|
const scanRoots = [pluginScanRoot, "./plugins"];
|
|
393655
394635
|
try {
|
|
393656
394636
|
const req = (0, import_node_module5.createRequire)(typeof __filename !== "undefined" ? __filename : import_meta7.url);
|
|
393657
|
-
const
|
|
393658
|
-
if (
|
|
394637
|
+
const fs23 = req("node:fs");
|
|
394638
|
+
if (fs23.existsSync(bundlePluginRoot)) scanRoots.push(bundlePluginRoot);
|
|
393659
394639
|
} catch {
|
|
393660
394640
|
}
|
|
393661
394641
|
try {
|
|
393662
394642
|
const req = (0, import_node_module5.createRequire)(typeof __filename !== "undefined" ? __filename : import_meta7.url);
|
|
393663
|
-
const
|
|
394643
|
+
const fs23 = req("node:fs");
|
|
393664
394644
|
const path32 = req("node:path");
|
|
393665
394645
|
const resourcesPath = process.resourcesPath;
|
|
393666
394646
|
if (resourcesPath) {
|
|
393667
394647
|
const resourcesPlugins = path32.join(resourcesPath, "plugins");
|
|
393668
|
-
if (
|
|
394648
|
+
if (fs23.existsSync(resourcesPlugins)) scanRoots.push(resourcesPlugins);
|
|
393669
394649
|
}
|
|
393670
394650
|
} catch {
|
|
393671
394651
|
}
|
|
@@ -396631,434 +397611,581 @@ async function startGyBackend() {
|
|
|
396631
397611
|
gatewayService.broadcastRaw("skills:updated", result.skills);
|
|
396632
397612
|
gatewayService.broadcastRaw("memory:updated", result.memory);
|
|
396633
397613
|
};
|
|
396634
|
-
const
|
|
396635
|
-
|
|
396636
|
-
|
|
396637
|
-
|
|
396638
|
-
|
|
396639
|
-
|
|
396640
|
-
|
|
397614
|
+
const gatewayRateLimiter = new GatewayRateLimiter({
|
|
397615
|
+
capacity: Number(import_node_process2.default.env.RTERM_RATE_LIMIT_BURST ?? 60) || 60,
|
|
397616
|
+
refillPerSecond: Number(import_node_process2.default.env.RTERM_RATE_LIMIT_REFILL ?? 1) || 1,
|
|
397617
|
+
authFailureLimit: 5,
|
|
397618
|
+
authLockoutMs: 6e4
|
|
397619
|
+
});
|
|
397620
|
+
const settingsDir = import_node_path28.default.dirname(settingsService.getSettingsPath?.() ?? "");
|
|
397621
|
+
const backupsDir = settingsDir ? import_node_path28.default.join(settingsDir, "backups") : "";
|
|
397622
|
+
const settingsBackupService = new SettingsBackupService(
|
|
397623
|
+
backupsDir ? {
|
|
397624
|
+
read: () => import_node_fs12.default.readFileSync(settingsService.getSettingsPath(), "utf8"),
|
|
397625
|
+
write: (c) => import_node_fs12.default.writeFileSync(settingsService.getSettingsPath(), c),
|
|
397626
|
+
listBackups: () => import_node_fs12.default.existsSync(backupsDir) ? import_node_fs12.default.readdirSync(backupsDir) : [],
|
|
397627
|
+
readBackup: (n2) => import_node_fs12.default.readFileSync(import_node_path28.default.join(backupsDir, n2), "utf8"),
|
|
397628
|
+
writeBackup: (n2, c) => {
|
|
397629
|
+
import_node_fs12.default.mkdirSync(backupsDir, { recursive: true });
|
|
397630
|
+
import_node_fs12.default.writeFileSync(import_node_path28.default.join(backupsDir, n2), c);
|
|
396641
397631
|
},
|
|
396642
|
-
|
|
396643
|
-
|
|
396644
|
-
|
|
396645
|
-
|
|
396646
|
-
|
|
396647
|
-
|
|
396648
|
-
|
|
396649
|
-
|
|
396650
|
-
|
|
396651
|
-
|
|
396652
|
-
|
|
396653
|
-
|
|
396654
|
-
|
|
397632
|
+
deleteBackup: (n2) => {
|
|
397633
|
+
const p = import_node_path28.default.join(backupsDir, n2);
|
|
397634
|
+
if (import_node_fs12.default.existsSync(p)) import_node_fs12.default.unlinkSync(p);
|
|
397635
|
+
}
|
|
397636
|
+
} : { read: () => "", write: () => {
|
|
397637
|
+
}, listBackups: () => [], readBackup: () => "", writeBackup: () => {
|
|
397638
|
+
}, deleteBackup: () => {
|
|
397639
|
+
} },
|
|
397640
|
+
{ keep: 20 }
|
|
397641
|
+
);
|
|
397642
|
+
const originalSetSettings = settingsService.setSettings.bind(settingsService);
|
|
397643
|
+
settingsService.setSettings = ((next) => {
|
|
397644
|
+
try {
|
|
397645
|
+
settingsBackupService.backup();
|
|
397646
|
+
} catch {
|
|
397647
|
+
}
|
|
397648
|
+
return originalSetSettings(next);
|
|
397649
|
+
});
|
|
397650
|
+
const idleTimeoutService = new IdleTimeoutService({
|
|
397651
|
+
idleMinutes: Number(import_node_process2.default.env.RTERM_IDLE_TIMEOUT_MINUTES ?? 30) || 30,
|
|
397652
|
+
protectedIds: [import_node_process2.default.env.GYBACKEND_TERMINAL_ID || "local-main"]
|
|
397653
|
+
});
|
|
397654
|
+
for (const t of terminalService.getAllTerminals()) idleTimeoutService.register(t.id);
|
|
397655
|
+
terminalService.onTerminalCreated?.((id) => idleTimeoutService.register(id));
|
|
397656
|
+
terminalService.onTerminalData?.((id) => idleTimeoutService.touch(id));
|
|
397657
|
+
terminalService.onTerminalClosed?.((id) => idleTimeoutService.forget(id));
|
|
397658
|
+
const idleSweeper = setInterval(() => {
|
|
397659
|
+
try {
|
|
397660
|
+
const idle = idleTimeoutService.idleTerminals(terminalService.getAllTerminals().map((t) => t.id));
|
|
397661
|
+
for (const { terminalId, idleMinutes } of idle) {
|
|
397662
|
+
console.log(`[gybackend] closing idle terminal ${terminalId} (${idleMinutes}min idle)`);
|
|
397663
|
+
try {
|
|
397664
|
+
terminalService.kill(terminalId);
|
|
397665
|
+
} catch {
|
|
397666
|
+
}
|
|
397667
|
+
idleTimeoutService.forget(terminalId);
|
|
397668
|
+
}
|
|
397669
|
+
} catch {
|
|
397670
|
+
}
|
|
397671
|
+
}, 6e4);
|
|
397672
|
+
if (typeof idleSweeper.unref === "function") idleSweeper.unref();
|
|
397673
|
+
let restDispatchTarget = null;
|
|
397674
|
+
const restDispatch = async (method, params) => {
|
|
397675
|
+
const target = restDispatchTarget;
|
|
397676
|
+
if (!target?.handleRequest) {
|
|
397677
|
+
throw new Error("REST API is not ready yet (gateway still starting)");
|
|
397678
|
+
}
|
|
397679
|
+
return await target.handleRequest(method, params);
|
|
397680
|
+
};
|
|
397681
|
+
const buildRestHttpRoutesSync = (opts) => {
|
|
397682
|
+
const routes = defaultRestRoutes();
|
|
397683
|
+
const staticRoutes = routes.filter((r) => !r.path.includes(":")).map((route) => ({
|
|
397684
|
+
path: route.path,
|
|
397685
|
+
handler: makeRestHandler(routes, opts)
|
|
397686
|
+
}));
|
|
397687
|
+
return staticRoutes;
|
|
397688
|
+
};
|
|
397689
|
+
const makeRestHandler = (routes, opts) => {
|
|
397690
|
+
return async (req, res) => {
|
|
397691
|
+
const r = req;
|
|
397692
|
+
const s = res;
|
|
397693
|
+
try {
|
|
397694
|
+
if (!await opts.isAuthorized(r)) {
|
|
397695
|
+
s.writeHead?.(401, { "content-type": "application/json" });
|
|
397696
|
+
s.end?.(JSON.stringify({ error: "unauthorized" }));
|
|
397697
|
+
return;
|
|
397698
|
+
}
|
|
397699
|
+
const url2 = new URL(r.url ?? "/", "http://localhost");
|
|
397700
|
+
const body = r.method === "POST" ? await readJsonBody(r) : Object.fromEntries(url2.searchParams.entries());
|
|
397701
|
+
const result = await handleRestRequest(routes, opts.dispatch, {
|
|
397702
|
+
method: r.method ?? "GET",
|
|
397703
|
+
path: url2.pathname,
|
|
397704
|
+
body
|
|
397705
|
+
});
|
|
397706
|
+
s.writeHead?.(result.status, { "content-type": "application/json" });
|
|
397707
|
+
s.end?.(JSON.stringify(result.body));
|
|
397708
|
+
} catch (e) {
|
|
397709
|
+
s.writeHead?.(500, { "content-type": "application/json" });
|
|
397710
|
+
s.end?.(JSON.stringify({ error: "internal", message: e instanceof Error ? e.message : String(e) }));
|
|
397711
|
+
}
|
|
397712
|
+
};
|
|
397713
|
+
};
|
|
397714
|
+
const readJsonBody = (req) => new Promise((resolve2) => {
|
|
397715
|
+
let data = "";
|
|
397716
|
+
req.on?.("data", (d) => {
|
|
397717
|
+
data += String(d ?? "");
|
|
397718
|
+
});
|
|
397719
|
+
req.on?.("end", () => {
|
|
397720
|
+
try {
|
|
397721
|
+
resolve2(data ? JSON.parse(data) : {});
|
|
397722
|
+
} catch {
|
|
397723
|
+
resolve2({});
|
|
397724
|
+
}
|
|
397725
|
+
});
|
|
397726
|
+
});
|
|
397727
|
+
const wsGatewayControlService = new WebSocketGatewayControlService({
|
|
397728
|
+
createAdapter: (host, port, ipFilter) => {
|
|
397729
|
+
const adapter = new WebSocketGatewayAdapter(gatewayService, {
|
|
397730
|
+
host,
|
|
397731
|
+
port,
|
|
397732
|
+
accessTokenAuth: {
|
|
397733
|
+
verifyToken: (token) => accessTokenService.verifyToken(token),
|
|
397734
|
+
allowLocalhostWithoutToken: true
|
|
397735
|
+
},
|
|
397736
|
+
// v3.2.18: per-client rate limiting (token bucket + auth lockout).
|
|
397737
|
+
rateLimiter: gatewayRateLimiter,
|
|
397738
|
+
ipFilter,
|
|
397739
|
+
// Browser dashboard on the SAME port as the WS gateway: /dashboard serves
|
|
397740
|
+
// a live page (WS push via observability:liveDashboardSubscribe, with a
|
|
397741
|
+
// /dashboard/json polling fallback). Auth mirrors the WS gateway —
|
|
397742
|
+
// loopback open, remote needs a valid access token.
|
|
397743
|
+
// v3.2.18: REST API routes (/api/v1/*) are prepended so curl/CI can
|
|
397744
|
+
// drive the gateway without a WS client.
|
|
397745
|
+
httpRoutes: [
|
|
397746
|
+
...buildRestHttpRoutesSync({
|
|
397747
|
+
isAuthorized: (req) => dashboardHttpAuthorized(req, (t) => accessTokenService.verifyToken(t)),
|
|
397748
|
+
dispatch: restDispatch
|
|
397749
|
+
}),
|
|
397750
|
+
{
|
|
397751
|
+
path: "/dashboard",
|
|
397752
|
+
handler: async (req, res) => {
|
|
397753
|
+
if (!await dashboardHttpAuthorized(req, (t) => accessTokenService.verifyToken(t))) {
|
|
397754
|
+
res.writeHead(401, { "content-type": "text/plain; charset=utf-8" });
|
|
397755
|
+
res.end("missing/invalid access token");
|
|
397756
|
+
return;
|
|
397757
|
+
}
|
|
397758
|
+
const state = await observability.dashboard.state();
|
|
397759
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
397760
|
+
res.end(
|
|
397761
|
+
renderLiveDashboardHtml(state, {
|
|
397762
|
+
title: "RTerm \xB7 Unified Dashboard",
|
|
397763
|
+
dataUrl: "/dashboard/json"
|
|
397764
|
+
})
|
|
397765
|
+
);
|
|
397766
|
+
}
|
|
397767
|
+
},
|
|
397768
|
+
{
|
|
397769
|
+
path: "/dashboard/json",
|
|
397770
|
+
handler: async (req, res) => {
|
|
397771
|
+
if (!await dashboardHttpAuthorized(req, (t) => accessTokenService.verifyToken(t))) {
|
|
397772
|
+
res.writeHead(401, { "content-type": "text/plain; charset=utf-8" });
|
|
397773
|
+
res.end("missing/invalid access token");
|
|
397774
|
+
return;
|
|
397775
|
+
}
|
|
397776
|
+
const state = await observability.dashboard.state();
|
|
397777
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
397778
|
+
res.end(JSON.stringify(state));
|
|
396655
397779
|
}
|
|
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
397780
|
}
|
|
396665
|
-
|
|
396666
|
-
{
|
|
396667
|
-
|
|
396668
|
-
|
|
396669
|
-
|
|
396670
|
-
|
|
396671
|
-
|
|
396672
|
-
|
|
397781
|
+
],
|
|
397782
|
+
terminalBridge: {
|
|
397783
|
+
listTerminals: () => terminalService.getDisplayTerminals().map((terminal) => ({
|
|
397784
|
+
id: terminal.id,
|
|
397785
|
+
title: terminal.title,
|
|
397786
|
+
type: terminal.type,
|
|
397787
|
+
cols: terminal.cols,
|
|
397788
|
+
rows: terminal.rows,
|
|
397789
|
+
runtimeState: terminal.runtimeState,
|
|
397790
|
+
lastExitCode: terminal.lastExitCode,
|
|
397791
|
+
monitorIdentity: terminalService.getMonitorIdentity(terminal.id) ?? void 0
|
|
397792
|
+
})),
|
|
397793
|
+
createTab: async (config2) => {
|
|
397794
|
+
const snapshot = terminalService.getDisplayTerminals();
|
|
397795
|
+
const normalized = createAutoTerminalConfig(snapshot, config2);
|
|
397796
|
+
const tab = await terminalService.createTerminal(normalized);
|
|
397797
|
+
return { id: tab.id };
|
|
397798
|
+
},
|
|
397799
|
+
write: async (terminalId, data) => {
|
|
397800
|
+
terminalService.write(terminalId, data);
|
|
397801
|
+
},
|
|
397802
|
+
writePaths: async (terminalId, paths) => {
|
|
397803
|
+
terminalService.writePaths(terminalId, paths);
|
|
397804
|
+
},
|
|
397805
|
+
resize: async (terminalId, cols, rows) => {
|
|
397806
|
+
terminalService.resize(terminalId, cols, rows);
|
|
397807
|
+
},
|
|
397808
|
+
kill: async (terminalId) => {
|
|
397809
|
+
if (terminalService.getDisplayTerminals().length <= 1) {
|
|
397810
|
+
throw new Error("Cannot close the last terminal tab.");
|
|
396673
397811
|
}
|
|
396674
|
-
|
|
396675
|
-
|
|
396676
|
-
|
|
397812
|
+
terminalService.kill(terminalId);
|
|
397813
|
+
},
|
|
397814
|
+
reconnect: async (terminalId) => {
|
|
397815
|
+
const tab = await terminalService.reconnectTerminal(terminalId);
|
|
397816
|
+
return { id: tab.id };
|
|
397817
|
+
},
|
|
397818
|
+
setTitle: async (terminalId, title) => {
|
|
397819
|
+
terminalService.setTitle(terminalId, title);
|
|
397820
|
+
},
|
|
397821
|
+
setSelection: async (terminalId, selectionText) => {
|
|
397822
|
+
terminalService.setSelection(terminalId, selectionText);
|
|
397823
|
+
},
|
|
397824
|
+
getBufferDelta: async (terminalId, fromOffset) => {
|
|
397825
|
+
const data = terminalService.getBufferDelta(terminalId, fromOffset);
|
|
397826
|
+
const offset = terminalService.getCurrentOffset(terminalId);
|
|
397827
|
+
return { data, offset };
|
|
397828
|
+
},
|
|
397829
|
+
generateCommandDraft: async (terminalId, prompt, profileId) => {
|
|
397830
|
+
return await terminalCommandDraftService.generateCommandDraft({
|
|
397831
|
+
terminalId,
|
|
397832
|
+
prompt,
|
|
397833
|
+
profileId
|
|
397834
|
+
});
|
|
396677
397835
|
}
|
|
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
397836
|
},
|
|
396700
|
-
|
|
396701
|
-
|
|
396702
|
-
|
|
396703
|
-
|
|
396704
|
-
|
|
396705
|
-
|
|
396706
|
-
|
|
396707
|
-
|
|
396708
|
-
|
|
397837
|
+
filesystemBridge: {
|
|
397838
|
+
listDirectory: async (terminalId, dirPath) => {
|
|
397839
|
+
return await fileSystemService.listDirectory(terminalId, dirPath);
|
|
397840
|
+
},
|
|
397841
|
+
readTextFile: async (terminalId, filePath, options) => {
|
|
397842
|
+
return await fileSystemService.readTextFile(
|
|
397843
|
+
terminalId,
|
|
397844
|
+
filePath,
|
|
397845
|
+
options
|
|
397846
|
+
);
|
|
397847
|
+
},
|
|
397848
|
+
readFileBase64: async (terminalId, filePath, options) => {
|
|
397849
|
+
return await fileSystemService.readFileBase64(
|
|
397850
|
+
terminalId,
|
|
397851
|
+
filePath,
|
|
397852
|
+
options
|
|
397853
|
+
);
|
|
397854
|
+
},
|
|
397855
|
+
writeTextFile: async (terminalId, filePath, content) => {
|
|
397856
|
+
await fileSystemService.writeTextFile(
|
|
397857
|
+
terminalId,
|
|
397858
|
+
filePath,
|
|
397859
|
+
content
|
|
397860
|
+
);
|
|
397861
|
+
},
|
|
397862
|
+
writeFileBase64: async (terminalId, filePath, contentBase64, options) => {
|
|
397863
|
+
await fileSystemService.writeFileBase64(
|
|
397864
|
+
terminalId,
|
|
397865
|
+
filePath,
|
|
397866
|
+
contentBase64,
|
|
397867
|
+
options
|
|
397868
|
+
);
|
|
397869
|
+
},
|
|
397870
|
+
transferEntries: async (sourceTerminalId, sourcePaths, targetTerminalId, targetDirPath, options) => {
|
|
397871
|
+
return await fileSystemService.transferEntries(
|
|
397872
|
+
sourceTerminalId,
|
|
397873
|
+
sourcePaths,
|
|
397874
|
+
targetTerminalId,
|
|
397875
|
+
targetDirPath,
|
|
397876
|
+
options
|
|
397877
|
+
);
|
|
397878
|
+
},
|
|
397879
|
+
startTransfer: async (input) => {
|
|
397880
|
+
return fileTransferService.startTransfer(input);
|
|
397881
|
+
},
|
|
397882
|
+
getTransfer: async (transferId) => {
|
|
397883
|
+
return fileTransferService.getTransfer(transferId);
|
|
397884
|
+
},
|
|
397885
|
+
listTransfers: async (options) => {
|
|
397886
|
+
return fileTransferService.listTransfers(options);
|
|
397887
|
+
},
|
|
397888
|
+
cancelTransfer: async (transferId) => {
|
|
397889
|
+
return fileTransferService.cancelTransfer(transferId);
|
|
397890
|
+
},
|
|
397891
|
+
cancelTransferTask: async (transferId) => {
|
|
397892
|
+
return fileTransferService.cancelTransfer(transferId);
|
|
397893
|
+
},
|
|
397894
|
+
createDirectory: async (terminalId, dirPath) => {
|
|
397895
|
+
await fileSystemService.createDirectory(terminalId, dirPath);
|
|
397896
|
+
},
|
|
397897
|
+
createFile: async (terminalId, filePath) => {
|
|
397898
|
+
await fileSystemService.createFile(terminalId, filePath);
|
|
397899
|
+
},
|
|
397900
|
+
deletePath: async (terminalId, targetPath, options) => {
|
|
397901
|
+
await fileSystemService.deletePath(terminalId, targetPath, options);
|
|
397902
|
+
},
|
|
397903
|
+
renamePath: async (terminalId, sourcePath, targetPath) => {
|
|
397904
|
+
await fileSystemService.renamePath(
|
|
397905
|
+
terminalId,
|
|
397906
|
+
sourcePath,
|
|
397907
|
+
targetPath
|
|
397908
|
+
);
|
|
396709
397909
|
}
|
|
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
397910
|
},
|
|
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
|
|
397911
|
+
profileBridge: {
|
|
397912
|
+
getProfiles: () => {
|
|
397913
|
+
const snapshot = settingsService.getSettings();
|
|
397914
|
+
const modelNameById = new Map(
|
|
397915
|
+
snapshot.models.items.map((item) => [item.id, item.model])
|
|
397916
|
+
);
|
|
397917
|
+
return {
|
|
397918
|
+
activeProfileId: snapshot.models.activeProfileId,
|
|
397919
|
+
profiles: snapshot.models.profiles.map((profile) => ({
|
|
397920
|
+
id: profile.id,
|
|
397921
|
+
name: profile.name,
|
|
397922
|
+
globalModelId: profile.globalModelId,
|
|
397923
|
+
modelName: modelNameById.get(profile.globalModelId)
|
|
397924
|
+
}))
|
|
397925
|
+
};
|
|
397926
|
+
},
|
|
397927
|
+
setActiveProfile: (profileId) => {
|
|
397928
|
+
const snapshot = settingsService.getSettings();
|
|
397929
|
+
const exists = snapshot.models.profiles.some(
|
|
397930
|
+
(profile) => profile.id === profileId
|
|
397931
|
+
);
|
|
397932
|
+
if (!exists) {
|
|
397933
|
+
throw new Error(`Profile not found: ${profileId}`);
|
|
396838
397934
|
}
|
|
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
|
|
397935
|
+
settingsService.setSettings({
|
|
397936
|
+
models: {
|
|
397937
|
+
items: snapshot.models.items,
|
|
397938
|
+
profiles: snapshot.models.profiles,
|
|
397939
|
+
activeProfileId: profileId
|
|
397940
|
+
}
|
|
397941
|
+
});
|
|
397942
|
+
const next = settingsService.getSettings();
|
|
397943
|
+
agentService.updateSettings(next);
|
|
397944
|
+
const modelNameById = new Map(
|
|
397945
|
+
next.models.items.map((item) => [item.id, item.model])
|
|
396871
397946
|
);
|
|
396872
397947
|
return {
|
|
396873
|
-
|
|
396874
|
-
|
|
396875
|
-
|
|
396876
|
-
|
|
397948
|
+
activeProfileId: next.models.activeProfileId,
|
|
397949
|
+
profiles: next.models.profiles.map((profile) => ({
|
|
397950
|
+
id: profile.id,
|
|
397951
|
+
name: profile.name,
|
|
397952
|
+
globalModelId: profile.globalModelId,
|
|
397953
|
+
modelName: modelNameById.get(profile.globalModelId)
|
|
397954
|
+
}))
|
|
396877
397955
|
};
|
|
397956
|
+
},
|
|
397957
|
+
probeModel: async (model) => {
|
|
397958
|
+
return await modelCapabilityService.probe(model);
|
|
396878
397959
|
}
|
|
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
397960
|
},
|
|
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
|
|
397961
|
+
agentBridge: {
|
|
397962
|
+
exportHistory: async (sessionId, mode) => {
|
|
397963
|
+
await gatewayService.waitForRunCompletion(sessionId);
|
|
397964
|
+
const backendSession = agentService.exportChatSession(sessionId);
|
|
397965
|
+
if (!backendSession) {
|
|
397966
|
+
throw new Error(`Session with ID ${sessionId} not found`);
|
|
397967
|
+
}
|
|
397968
|
+
const uiSession = uiHistoryService.getSession(sessionId);
|
|
397969
|
+
if (mode === "simple") {
|
|
397970
|
+
const markdown = uiHistoryService.toReadableMarkdown(
|
|
397971
|
+
uiSession?.messages || [],
|
|
397972
|
+
uiSession?.title || backendSession.title
|
|
397973
|
+
);
|
|
397974
|
+
return {
|
|
397975
|
+
sessionId,
|
|
397976
|
+
mode,
|
|
397977
|
+
title: uiSession?.title || backendSession.title,
|
|
397978
|
+
content: markdown
|
|
397979
|
+
};
|
|
396938
397980
|
}
|
|
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;
|
|
397981
|
+
return {
|
|
397982
|
+
sessionId: backendSession.id,
|
|
397983
|
+
mode,
|
|
397984
|
+
title: uiSession?.title || backendSession.title,
|
|
397985
|
+
lastCheckpointOffset: backendSession.lastCheckpointOffset,
|
|
397986
|
+
createdAt: new Date(backendSession.createdAt).toISOString(),
|
|
397987
|
+
updatedAt: new Date(backendSession.updatedAt).toISOString(),
|
|
397988
|
+
frontendMessages: uiSession?.messages || [],
|
|
397989
|
+
backendMessages: backendSession.messages.map((msg) => ({
|
|
397990
|
+
messageId: msg.id,
|
|
397991
|
+
messageType: msg.type,
|
|
397992
|
+
messageData: msg.data
|
|
397993
|
+
}))
|
|
397994
|
+
};
|
|
397995
|
+
},
|
|
397996
|
+
getAllChatHistory: () => agentService.getAllChatHistory(),
|
|
397997
|
+
loadChatSession: (sessionId) => agentService.loadChatSession(sessionId),
|
|
397998
|
+
getUiMessages: (sessionId) => uiHistoryService.getMessages(sessionId)
|
|
396983
397999
|
},
|
|
396984
|
-
|
|
396985
|
-
|
|
396986
|
-
|
|
396987
|
-
|
|
398000
|
+
systemBridge: {
|
|
398001
|
+
saveImageAttachment: async (payload) => {
|
|
398002
|
+
return await imageAttachmentService.saveImageAttachment(payload);
|
|
398003
|
+
}
|
|
396988
398004
|
},
|
|
396989
|
-
|
|
396990
|
-
|
|
396991
|
-
|
|
396992
|
-
|
|
398005
|
+
skillBridge: {
|
|
398006
|
+
reload: async () => {
|
|
398007
|
+
return await skillService.reload();
|
|
398008
|
+
},
|
|
398009
|
+
getAll: async () => {
|
|
398010
|
+
return await skillService.getAll();
|
|
398011
|
+
},
|
|
398012
|
+
getEnabled: async () => {
|
|
398013
|
+
return await skillService.getEnabledSkills();
|
|
398014
|
+
},
|
|
398015
|
+
create: async () => {
|
|
398016
|
+
return await skillService.createSkillFromTemplate();
|
|
398017
|
+
},
|
|
398018
|
+
delete: async (fileName) => {
|
|
398019
|
+
await skillService.deleteSkillFile(fileName);
|
|
398020
|
+
return await skillService.getAll();
|
|
398021
|
+
},
|
|
398022
|
+
listSkills: async () => {
|
|
398023
|
+
const snapshot = settingsService.getSettings();
|
|
398024
|
+
const enabledMap = snapshot.tools?.skills ?? {};
|
|
398025
|
+
const skills = await skillService.getAll();
|
|
398026
|
+
return skills.map((skill) => ({
|
|
398027
|
+
name: skill.name,
|
|
398028
|
+
description: skill.description,
|
|
398029
|
+
enabled: enabledMap[skill.name] !== false
|
|
398030
|
+
}));
|
|
398031
|
+
},
|
|
398032
|
+
setSkillEnabled: async (name, enabled) => {
|
|
398033
|
+
const snapshot = settingsService.getSettings();
|
|
398034
|
+
const nextSkills = { ...snapshot.tools?.skills ?? {} };
|
|
398035
|
+
nextSkills[name] = enabled;
|
|
398036
|
+
settingsService.setSettings({
|
|
398037
|
+
tools: {
|
|
398038
|
+
builtIn: snapshot.tools?.builtIn ?? {},
|
|
398039
|
+
skills: nextSkills
|
|
398040
|
+
}
|
|
398041
|
+
});
|
|
398042
|
+
const next = settingsService.getSettings();
|
|
398043
|
+
agentService.updateSettings(next);
|
|
398044
|
+
const skills = await skillService.getAll();
|
|
398045
|
+
const summary = buildSkillStatusSummary(skills, next.tools?.skills);
|
|
398046
|
+
gatewayService.broadcastRaw("skills:updated", summary);
|
|
398047
|
+
return summary;
|
|
398048
|
+
}
|
|
396993
398049
|
},
|
|
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."
|
|
398050
|
+
memoryBridge: {
|
|
398051
|
+
get: async () => {
|
|
398052
|
+
return await memoryService.getMemorySnapshot(
|
|
398053
|
+
settingsService.getSettings().agentSettings?.activeProfileId || null
|
|
397006
398054
|
);
|
|
398055
|
+
},
|
|
398056
|
+
setContent: async (content) => {
|
|
398057
|
+
const snapshot = await memoryService.writeMemory(
|
|
398058
|
+
content,
|
|
398059
|
+
settingsService.getSettings().agentSettings?.activeProfileId || null
|
|
398060
|
+
);
|
|
398061
|
+
gatewayService.broadcastRaw("memory:updated", snapshot);
|
|
398062
|
+
return snapshot;
|
|
398063
|
+
},
|
|
398064
|
+
search: async (query, limit2) => {
|
|
398065
|
+
const { content } = await memoryService.getMemorySnapshot(
|
|
398066
|
+
settingsService.getSettings().agentSettings?.activeProfileId || null
|
|
398067
|
+
);
|
|
398068
|
+
return searchMemory(content, query, limit2 ?? 10);
|
|
398069
|
+
},
|
|
398070
|
+
append: async (note) => {
|
|
398071
|
+
const profileId = settingsService.getSettings().agentSettings?.activeProfileId || null;
|
|
398072
|
+
const { content } = await memoryService.getMemorySnapshot(profileId);
|
|
398073
|
+
const next = appendMemoryNote(content, note);
|
|
398074
|
+
const snapshot = await memoryService.writeMemory(next, profileId);
|
|
398075
|
+
gatewayService.broadcastRaw("memory:updated", snapshot);
|
|
398076
|
+
return snapshot;
|
|
397007
398077
|
}
|
|
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
398078
|
},
|
|
397018
|
-
|
|
397019
|
-
|
|
398079
|
+
agentSettingsBridge: {
|
|
398080
|
+
get: () => agentSettingProfileService.getState(),
|
|
398081
|
+
saveCurrent: async () => {
|
|
398082
|
+
const result = await agentSettingProfileService.saveCurrent();
|
|
398083
|
+
broadcastAgentSettingResult(result);
|
|
398084
|
+
return result;
|
|
398085
|
+
},
|
|
398086
|
+
apply: async (profileId) => {
|
|
398087
|
+
const result = await agentSettingProfileService.apply(profileId);
|
|
398088
|
+
broadcastAgentSettingResult(result);
|
|
398089
|
+
return result;
|
|
398090
|
+
},
|
|
398091
|
+
overwrite: async (profileId) => {
|
|
398092
|
+
const result = await agentSettingProfileService.overwrite(profileId);
|
|
398093
|
+
broadcastAgentSettingResult(result);
|
|
398094
|
+
return result;
|
|
398095
|
+
},
|
|
398096
|
+
delete: async (profileId) => {
|
|
398097
|
+
const result = await agentSettingProfileService.delete(profileId);
|
|
398098
|
+
broadcastAgentSettingResult(result);
|
|
398099
|
+
return result;
|
|
398100
|
+
}
|
|
397020
398101
|
},
|
|
397021
|
-
|
|
397022
|
-
|
|
397023
|
-
|
|
397024
|
-
|
|
397025
|
-
|
|
397026
|
-
|
|
397027
|
-
|
|
398102
|
+
settingsBridge: {
|
|
398103
|
+
getSettings: () => settingsService.getSettings(),
|
|
398104
|
+
setSettings: async (patch) => {
|
|
398105
|
+
if (patch?.gateway?.ws) {
|
|
398106
|
+
throw new Error(
|
|
398107
|
+
"settings.gateway.ws is not configurable via websocket RPC."
|
|
398108
|
+
);
|
|
398109
|
+
}
|
|
398110
|
+
settingsService.setSettings(patch);
|
|
398111
|
+
const next = settingsService.getSettings();
|
|
398112
|
+
agentService.updateSettings(next);
|
|
398113
|
+
return next;
|
|
398114
|
+
},
|
|
398115
|
+
// v3.2.18: settings backup/restore/export/import.
|
|
398116
|
+
listBackups: () => settingsBackupService.list(),
|
|
398117
|
+
restoreBackup: async (name) => settingsBackupService.restore(name),
|
|
398118
|
+
export: () => settingsBackupService.export(),
|
|
398119
|
+
import: async (content) => settingsBackupService.import(content)
|
|
397028
398120
|
},
|
|
397029
|
-
|
|
397030
|
-
|
|
397031
|
-
|
|
398121
|
+
// v3.2.18: cross-session history search.
|
|
398122
|
+
historyBridge: {
|
|
398123
|
+
getAllSessions: async () => {
|
|
398124
|
+
try {
|
|
398125
|
+
const all = historyStore.listChatSessions();
|
|
398126
|
+
if (Array.isArray(all) && all.length > 0) {
|
|
398127
|
+
return all;
|
|
398128
|
+
}
|
|
398129
|
+
} catch (e) {
|
|
398130
|
+
console.warn(
|
|
398131
|
+
"[history:search] store load failed, falling back to metadata:",
|
|
398132
|
+
e instanceof Error ? e.message : e
|
|
398133
|
+
);
|
|
398134
|
+
}
|
|
398135
|
+
return agentService.getAllChatHistory() ?? [];
|
|
398136
|
+
}
|
|
397032
398137
|
},
|
|
397033
|
-
|
|
397034
|
-
|
|
397035
|
-
|
|
398138
|
+
commandPolicyBridge: {
|
|
398139
|
+
getLists: async () => {
|
|
398140
|
+
return await commandPolicyService.getLists();
|
|
398141
|
+
},
|
|
398142
|
+
addRule: async (listName, rule) => {
|
|
398143
|
+
return await commandPolicyService.addRule(listName, rule);
|
|
398144
|
+
},
|
|
398145
|
+
deleteRule: async (listName, rule) => {
|
|
398146
|
+
return await commandPolicyService.deleteRule(listName, rule);
|
|
398147
|
+
}
|
|
397036
398148
|
},
|
|
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
|
-
|
|
398149
|
+
toolsBridge: {
|
|
398150
|
+
reloadMcp: async () => {
|
|
398151
|
+
return await mcpToolService.reloadAll();
|
|
398152
|
+
},
|
|
398153
|
+
getMcp: () => mcpToolService.getSummaries(),
|
|
398154
|
+
setMcpEnabled: async (name, enabled) => {
|
|
398155
|
+
return await mcpToolService.setServerEnabled(name, enabled);
|
|
398156
|
+
},
|
|
398157
|
+
getBuiltIn: () => {
|
|
398158
|
+
const settings = settingsService.getSettings();
|
|
398159
|
+
return buildBuiltInToolStatusSummary(settings.tools?.builtIn);
|
|
398160
|
+
},
|
|
398161
|
+
setBuiltInEnabled: async (name, enabled) => {
|
|
398162
|
+
const settings = settingsService.getSettings();
|
|
398163
|
+
const nextBuiltIn = { ...settings.tools?.builtIn ?? {} };
|
|
398164
|
+
nextBuiltIn[name] = enabled;
|
|
398165
|
+
settingsService.setSettings({
|
|
398166
|
+
tools: {
|
|
398167
|
+
builtIn: nextBuiltIn,
|
|
398168
|
+
skills: settings.tools?.skills ?? {}
|
|
398169
|
+
}
|
|
398170
|
+
});
|
|
398171
|
+
const next = settingsService.getSettings();
|
|
398172
|
+
agentService.updateSettings(next);
|
|
398173
|
+
const summary = buildBuiltInToolStatusSummary(next.tools?.builtIn);
|
|
398174
|
+
gatewayService.broadcastRaw("tools:builtInUpdated", summary);
|
|
398175
|
+
return summary;
|
|
398176
|
+
}
|
|
398177
|
+
},
|
|
398178
|
+
// Observability bridge (v2.9.x): exposes the 9 platform capabilities
|
|
398179
|
+
// (metrics export, secrets, on-call, cost, recording, gitops, playbooks,
|
|
398180
|
+
// cloud, live dashboard) as observability:* RPC methods.
|
|
398181
|
+
observabilityBridge: createObservabilityBridge({
|
|
398182
|
+
observability: () => observability,
|
|
398183
|
+
terminalService: () => terminalService
|
|
398184
|
+
})
|
|
398185
|
+
});
|
|
398186
|
+
restDispatchTarget = adapter;
|
|
398187
|
+
return adapter;
|
|
398188
|
+
}
|
|
397062
398189
|
});
|
|
397063
398190
|
await wsGatewayControlService.applyPolicy(startupPolicy);
|
|
397064
398191
|
const shutdown = async (signal) => {
|