@moikapy/lich 0.7.0 → 0.7.1
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/CHANGELOG.md +60 -5
- package/README.md +38 -9
- package/dist/{chunk-JYURFAGB.js → chunk-CVX7LZWC.js} +2 -2
- package/dist/{chunk-SAEB3QL3.js → chunk-PZNYVGD4.js} +2 -2
- package/dist/{chunk-QVJCIZIF.js → chunk-WNFBIX4E.js} +629 -169
- package/dist/chunk-WNFBIX4E.js.map +1 -0
- package/dist/cli.js +8 -6
- package/dist/cli.js.map +1 -1
- package/dist/{gateway-5BG3YCZF.js → gateway-44QTIJTJ.js} +108 -49
- package/dist/gateway-44QTIJTJ.js.map +1 -0
- package/dist/index.d.ts +29 -1
- package/dist/index.js +2 -2
- package/dist/{tui-L6RABP2J.js → tui-MEJGEILU.js} +25 -10
- package/dist/tui-MEJGEILU.js.map +1 -0
- package/docs/architecture/overview.md +2 -3
- package/docs/architecture/tools.md +30 -3
- package/docs/getting-started.md +3 -3
- package/docs/index.md +1 -1
- package/docs/user-guide/cli.md +5 -4
- package/docs/user-guide/gateway.md +70 -16
- package/docs/user-guide/library.md +1 -1
- package/docs/user-guide/redot.md +2 -2
- package/docs/user-guide/tui.md +2 -2
- package/package.json +1 -1
- package/dist/chunk-QVJCIZIF.js.map +0 -1
- package/dist/gateway-5BG3YCZF.js.map +0 -1
- package/dist/tui-L6RABP2J.js.map +0 -1
- /package/dist/{chunk-JYURFAGB.js.map → chunk-CVX7LZWC.js.map} +0 -0
- /package/dist/{chunk-SAEB3QL3.js.map → chunk-PZNYVGD4.js.map} +0 -0
|
@@ -4,6 +4,7 @@ import { readdir } from "fs/promises";
|
|
|
4
4
|
import path2 from "path";
|
|
5
5
|
|
|
6
6
|
// src/tools/guard.ts
|
|
7
|
+
import fs from "fs";
|
|
7
8
|
import path from "path";
|
|
8
9
|
|
|
9
10
|
// src/util/json.ts
|
|
@@ -33,14 +34,73 @@ function truncate_text(text, max_chars) {
|
|
|
33
34
|
// src/tools/guard.ts
|
|
34
35
|
var DEFAULT_MAX_OUTPUT_CHARS = 2e4;
|
|
35
36
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e4;
|
|
36
|
-
function
|
|
37
|
+
function is_inside(base, candidate) {
|
|
38
|
+
const relative = path.relative(base, candidate);
|
|
39
|
+
return relative.startsWith("..") === false && path.isAbsolute(relative) === false;
|
|
40
|
+
}
|
|
41
|
+
function deepest_existing(target) {
|
|
42
|
+
let current = target;
|
|
43
|
+
while (fs.existsSync(current) === false) {
|
|
44
|
+
const parent = path.dirname(current);
|
|
45
|
+
if (parent === current) {
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
current = parent;
|
|
49
|
+
}
|
|
50
|
+
return current;
|
|
51
|
+
}
|
|
52
|
+
function resolve_safe_path(base_dir, target, for_write = false) {
|
|
37
53
|
const base = path.resolve(base_dir);
|
|
38
54
|
const resolved = path.resolve(base, target);
|
|
39
|
-
|
|
40
|
-
|
|
55
|
+
if (is_inside(base, resolved) === false) {
|
|
56
|
+
throw new Error(`path_escape: ${target} escapes ${base_dir}`);
|
|
57
|
+
}
|
|
58
|
+
const real_base = fs.realpathSync(base);
|
|
59
|
+
const existing = deepest_existing(resolved);
|
|
60
|
+
const real_existing = fs.realpathSync(existing);
|
|
61
|
+
const suffix = path.relative(existing, resolved);
|
|
62
|
+
const real_resolved = suffix.length === 0 ? real_existing : path.resolve(real_existing, suffix);
|
|
63
|
+
if (is_inside(real_base, real_resolved) === false) {
|
|
64
|
+
throw new Error(`path_escape: ${target} escapes ${base_dir}`);
|
|
65
|
+
}
|
|
66
|
+
if (for_write === true) {
|
|
67
|
+
reject_symlink_leaf(resolved, target, base_dir);
|
|
68
|
+
}
|
|
69
|
+
return real_resolved;
|
|
70
|
+
}
|
|
71
|
+
function reject_symlink_leaf(resolved, target, base_dir) {
|
|
72
|
+
let info;
|
|
73
|
+
try {
|
|
74
|
+
info = fs.lstatSync(resolved);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (is_enoent(err) === true) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
if (info.isSymbolicLink() === true) {
|
|
41
82
|
throw new Error(`path_escape: ${target} escapes ${base_dir}`);
|
|
42
83
|
}
|
|
43
|
-
|
|
84
|
+
}
|
|
85
|
+
function assert_file_tool_access(work_dir, resolved, mode) {
|
|
86
|
+
const base = fs.realpathSync(path.resolve(work_dir));
|
|
87
|
+
const rel = path.relative(base, resolved);
|
|
88
|
+
const parts = rel.split(path.sep).filter((part) => part.length > 0);
|
|
89
|
+
if (parts[0] === ".lich" && parts[1] === "config.json" && parts.length === 2) {
|
|
90
|
+
throw new Error("forbidden_path: .lich/config.json");
|
|
91
|
+
}
|
|
92
|
+
if (mode === "write" && parts[0] === ".lich") {
|
|
93
|
+
const allowed = parts[1] === "skills" || parts[1] === "plugins";
|
|
94
|
+
if (allowed === false) {
|
|
95
|
+
throw new Error("forbidden_path: .lich writes limited to skills/ and plugins/");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (mode === "write") {
|
|
99
|
+
const leaf = path.basename(resolved);
|
|
100
|
+
if (leaf === ".env" || leaf.startsWith(".env.")) {
|
|
101
|
+
throw new Error("forbidden_path: .env*");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
44
104
|
}
|
|
45
105
|
function require_string_arg(args, key) {
|
|
46
106
|
const value = args[key];
|
|
@@ -116,6 +176,233 @@ async function capture_errors(run) {
|
|
|
116
176
|
}
|
|
117
177
|
}
|
|
118
178
|
|
|
179
|
+
// src/tools/url_guard.ts
|
|
180
|
+
import dns from "dns/promises";
|
|
181
|
+
import http from "http";
|
|
182
|
+
import https from "https";
|
|
183
|
+
import net from "net";
|
|
184
|
+
var MAX_REDIRECTS = 5;
|
|
185
|
+
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
186
|
+
var fetch_override;
|
|
187
|
+
function private_urls_allowed() {
|
|
188
|
+
return process.env["LICH_ALLOW_PRIVATE_URLS"] === "1";
|
|
189
|
+
}
|
|
190
|
+
function is_blocked_ipv4(address) {
|
|
191
|
+
const parts = address.split(".").map((part) => Number(part));
|
|
192
|
+
if (parts.length !== 4 || parts.some((n) => Number.isFinite(n) === false)) {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
const [a, b] = parts;
|
|
196
|
+
if (a === 0 || a === 10 || a === 127) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (a === 169 && b === 254) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (a === 172 && b >= 16 && b <= 31) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
if (a === 192 && b === 168) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
if (a === 100 && b >= 64 && b <= 127) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
return a >= 224;
|
|
212
|
+
}
|
|
213
|
+
function is_blocked_ipv6(address) {
|
|
214
|
+
const normalized = address.toLowerCase();
|
|
215
|
+
if (normalized === "::" || normalized === "::1") {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
if (normalized.startsWith("::ffff:")) {
|
|
219
|
+
const mapped = normalized.slice("::ffff:".length);
|
|
220
|
+
return net.isIPv4(mapped) === true ? is_blocked_ipv4(mapped) : true;
|
|
221
|
+
}
|
|
222
|
+
const head = Number.parseInt(normalized.split(":")[0] ?? "", 16);
|
|
223
|
+
if (Number.isFinite(head) === false) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
if ((head & 65472) === 65152) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if ((head & 65024) === 64512) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
function is_blocked_ip(address) {
|
|
235
|
+
if (net.isIPv4(address) === true) {
|
|
236
|
+
return is_blocked_ipv4(address);
|
|
237
|
+
}
|
|
238
|
+
if (net.isIPv6(address) === true) {
|
|
239
|
+
return is_blocked_ipv6(address);
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
function parse_http_url(raw) {
|
|
244
|
+
let parsed;
|
|
245
|
+
try {
|
|
246
|
+
parsed = new URL(raw);
|
|
247
|
+
} catch {
|
|
248
|
+
throw new Error(`invalid_url: ${raw}`);
|
|
249
|
+
}
|
|
250
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
251
|
+
throw new Error(`invalid_url: unsupported protocol ${parsed.protocol}`);
|
|
252
|
+
}
|
|
253
|
+
return parsed;
|
|
254
|
+
}
|
|
255
|
+
function normalize_hostname(hostname) {
|
|
256
|
+
if (hostname.startsWith("[") === true && hostname.endsWith("]") === true) {
|
|
257
|
+
return hostname.slice(1, -1);
|
|
258
|
+
}
|
|
259
|
+
return hostname;
|
|
260
|
+
}
|
|
261
|
+
async function resolve_public_ip(raw_hostname) {
|
|
262
|
+
const hostname = normalize_hostname(raw_hostname);
|
|
263
|
+
if (private_urls_allowed() === true) {
|
|
264
|
+
if (net.isIP(hostname) !== 0) {
|
|
265
|
+
return hostname;
|
|
266
|
+
}
|
|
267
|
+
const hit = await dns.lookup(hostname);
|
|
268
|
+
return hit.address;
|
|
269
|
+
}
|
|
270
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local")) {
|
|
271
|
+
throw new Error(`blocked_url: ${hostname}`);
|
|
272
|
+
}
|
|
273
|
+
if (net.isIP(hostname) !== 0) {
|
|
274
|
+
if (is_blocked_ip(hostname) === true) {
|
|
275
|
+
throw new Error(`blocked_url: ${hostname}`);
|
|
276
|
+
}
|
|
277
|
+
return hostname;
|
|
278
|
+
}
|
|
279
|
+
const records = await dns.lookup(hostname, { all: true, verbatim: true });
|
|
280
|
+
if (records.length === 0) {
|
|
281
|
+
throw new Error(`blocked_url: ${hostname}`);
|
|
282
|
+
}
|
|
283
|
+
for (const record of records) {
|
|
284
|
+
if (is_blocked_ip(record.address) === true) {
|
|
285
|
+
throw new Error(`blocked_url: ${hostname}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return records[0]?.address ?? hostname;
|
|
289
|
+
}
|
|
290
|
+
function pinned_lookup(ip) {
|
|
291
|
+
const family = net.isIPv6(ip) === true ? 6 : 4;
|
|
292
|
+
return ((_hostname, options, callback) => {
|
|
293
|
+
const cb = typeof options === "function" ? options : callback;
|
|
294
|
+
const opts = typeof options === "function" ? void 0 : options;
|
|
295
|
+
if (typeof cb !== "function") {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (opts !== void 0 && opts.all === true) {
|
|
299
|
+
cb(null, [{ address: ip, family }]);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
cb(null, ip, family);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function request_headers(init) {
|
|
306
|
+
const headers = {};
|
|
307
|
+
new Headers(init?.headers).forEach((value, key) => {
|
|
308
|
+
headers[key] = value;
|
|
309
|
+
});
|
|
310
|
+
return headers;
|
|
311
|
+
}
|
|
312
|
+
function request_body(init) {
|
|
313
|
+
const body = init?.body;
|
|
314
|
+
if (body === void 0 || body === null) {
|
|
315
|
+
return void 0;
|
|
316
|
+
}
|
|
317
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
318
|
+
return body;
|
|
319
|
+
}
|
|
320
|
+
throw new Error("blocked_url: unsupported_body");
|
|
321
|
+
}
|
|
322
|
+
function pinned_http_fetch(url, ip, init) {
|
|
323
|
+
const lib = url.protocol === "https:" ? https : http;
|
|
324
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
325
|
+
const headers = request_headers(init);
|
|
326
|
+
const body = request_body(init);
|
|
327
|
+
return new Promise((resolve, reject) => {
|
|
328
|
+
const req = lib.request(
|
|
329
|
+
{
|
|
330
|
+
protocol: url.protocol,
|
|
331
|
+
hostname: url.hostname,
|
|
332
|
+
port: url.port.length > 0 ? Number(url.port) : void 0,
|
|
333
|
+
path: `${url.pathname}${url.search}`,
|
|
334
|
+
method,
|
|
335
|
+
headers,
|
|
336
|
+
lookup: pinned_lookup(ip)
|
|
337
|
+
},
|
|
338
|
+
(incoming) => {
|
|
339
|
+
const chunks = [];
|
|
340
|
+
incoming.on("data", (chunk) => {
|
|
341
|
+
chunks.push(chunk);
|
|
342
|
+
});
|
|
343
|
+
incoming.on("end", () => {
|
|
344
|
+
const status = incoming.statusCode ?? 0;
|
|
345
|
+
const response_headers = new Headers();
|
|
346
|
+
for (const [key, value] of Object.entries(incoming.headers)) {
|
|
347
|
+
if (typeof value === "string") {
|
|
348
|
+
response_headers.set(key, value);
|
|
349
|
+
} else if (Array.isArray(value) === true) {
|
|
350
|
+
for (const part of value) {
|
|
351
|
+
response_headers.append(key, part);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
resolve(new Response(Buffer.concat(chunks), { status, headers: response_headers }));
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
req.on("error", reject);
|
|
360
|
+
const signal = init.signal;
|
|
361
|
+
if (signal !== void 0 && signal !== null) {
|
|
362
|
+
if (signal.aborted === true) {
|
|
363
|
+
req.destroy(new Error("aborted"));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
signal.addEventListener(
|
|
367
|
+
"abort",
|
|
368
|
+
() => {
|
|
369
|
+
req.destroy(new Error("aborted"));
|
|
370
|
+
},
|
|
371
|
+
{ once: true }
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (body !== void 0) {
|
|
375
|
+
req.write(body);
|
|
376
|
+
}
|
|
377
|
+
req.end();
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function redirect_target(current, response) {
|
|
381
|
+
if (REDIRECT_STATUSES.has(response.status) === false) {
|
|
382
|
+
return void 0;
|
|
383
|
+
}
|
|
384
|
+
const location = response.headers.get("location");
|
|
385
|
+
if (location === null || location.length === 0) {
|
|
386
|
+
return void 0;
|
|
387
|
+
}
|
|
388
|
+
return new URL(location, current);
|
|
389
|
+
}
|
|
390
|
+
async function safe_fetch(raw_url, init) {
|
|
391
|
+
let current = parse_http_url(raw_url);
|
|
392
|
+
let request_init = { ...init ?? {}, redirect: "manual" };
|
|
393
|
+
for (let hop = 0; hop < MAX_REDIRECTS; hop += 1) {
|
|
394
|
+
const ip = await resolve_public_ip(current.hostname);
|
|
395
|
+
const response = fetch_override !== void 0 ? await fetch_override(current.href, { ...request_init, redirect: "manual" }) : await pinned_http_fetch(current, ip, request_init);
|
|
396
|
+
const next = redirect_target(current, response);
|
|
397
|
+
if (next === void 0) {
|
|
398
|
+
return response;
|
|
399
|
+
}
|
|
400
|
+
current = next;
|
|
401
|
+
request_init = { ...request_init, method: "GET", body: void 0 };
|
|
402
|
+
}
|
|
403
|
+
throw new Error("blocked_url: too_many_redirects");
|
|
404
|
+
}
|
|
405
|
+
|
|
119
406
|
// src/tools/builtin/fetch_url.ts
|
|
120
407
|
var DEFAULT_MAX_CHARS = 2e4;
|
|
121
408
|
var MAX_MAX_CHARS = 1e5;
|
|
@@ -136,15 +423,7 @@ function clamp_int_arg(args, key, fallback, max) {
|
|
|
136
423
|
return Math.min(max, Math.max(1, Math.floor(optional_number_arg(args, key, fallback))));
|
|
137
424
|
}
|
|
138
425
|
function valid_http_url(raw) {
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
parsed = new URL(raw);
|
|
142
|
-
} catch {
|
|
143
|
-
throw new Error(`invalid_url: ${raw}`);
|
|
144
|
-
}
|
|
145
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
146
|
-
throw new Error(`invalid_url: unsupported protocol ${parsed.protocol}`);
|
|
147
|
-
}
|
|
426
|
+
parse_http_url(raw);
|
|
148
427
|
}
|
|
149
428
|
function compose_abort_signal(timeout_ms, external) {
|
|
150
429
|
const timeout_signal = AbortSignal.timeout(timeout_ms);
|
|
@@ -155,8 +434,7 @@ async function run_fetch_url(args, external) {
|
|
|
155
434
|
valid_http_url(url);
|
|
156
435
|
const max_chars = clamp_int_arg(args, "max_chars", DEFAULT_MAX_CHARS, MAX_MAX_CHARS);
|
|
157
436
|
const timeout_ms = clamp_int_arg(args, "timeout_ms", DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
158
|
-
const response = await
|
|
159
|
-
redirect: "follow",
|
|
437
|
+
const response = await safe_fetch(url, {
|
|
160
438
|
headers: { "user-agent": USER_AGENT },
|
|
161
439
|
signal: compose_abort_signal(timeout_ms, external)
|
|
162
440
|
});
|
|
@@ -618,10 +896,11 @@ function replacement_for(content, old_string, new_string, replace_all) {
|
|
|
618
896
|
if (replace_all === true) {
|
|
619
897
|
return content.split(old_string).join(new_string);
|
|
620
898
|
}
|
|
621
|
-
return content.replace(old_string, new_string);
|
|
899
|
+
return content.replace(old_string, () => new_string);
|
|
622
900
|
}
|
|
623
901
|
async function apply_edit(work_dir, target, old_string, new_string, replace_all) {
|
|
624
|
-
const file_path = resolve_safe_path(work_dir, target);
|
|
902
|
+
const file_path = resolve_safe_path(work_dir, target, true);
|
|
903
|
+
assert_file_tool_access(work_dir, file_path, "write");
|
|
625
904
|
let content;
|
|
626
905
|
try {
|
|
627
906
|
content = await readFile(file_path, "utf8");
|
|
@@ -650,9 +929,12 @@ var edit_file_tool = {
|
|
|
650
929
|
execute: async (args, context) => capture_errors(async () => {
|
|
651
930
|
const target = require_string_arg(args, "path");
|
|
652
931
|
const old_string = require_string_arg(args, "old_string");
|
|
653
|
-
const
|
|
932
|
+
const new_raw = args["new_string"];
|
|
933
|
+
if (typeof new_raw !== "string") {
|
|
934
|
+
throw new Error("missing_arg: new_string");
|
|
935
|
+
}
|
|
654
936
|
const replace_all = optional_boolean_arg(args, "replace_all", false);
|
|
655
|
-
const result = await apply_edit(context.work_dir, target, old_string,
|
|
937
|
+
const result = await apply_edit(context.work_dir, target, old_string, new_raw, replace_all);
|
|
656
938
|
return { ok: true, output: result.output };
|
|
657
939
|
})
|
|
658
940
|
};
|
|
@@ -812,7 +1094,7 @@ async function scan_dir(frame, matcher) {
|
|
|
812
1094
|
return { files, dirs };
|
|
813
1095
|
}
|
|
814
1096
|
for (const entry of entries) {
|
|
815
|
-
if (SKIP_DIRS.has(entry.name) === true) {
|
|
1097
|
+
if (SKIP_DIRS.has(entry.name) === true || entry.isSymbolicLink() === true) {
|
|
816
1098
|
continue;
|
|
817
1099
|
}
|
|
818
1100
|
const full = path5.join(frame.dir, entry.name);
|
|
@@ -824,13 +1106,25 @@ async function scan_dir(frame, matcher) {
|
|
|
824
1106
|
}
|
|
825
1107
|
return { files, dirs };
|
|
826
1108
|
}
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
const
|
|
1109
|
+
function guard_grep_target(work_dir, absolute) {
|
|
1110
|
+
const relative = path5.relative(work_dir, absolute);
|
|
1111
|
+
const safe = resolve_safe_path(work_dir, relative);
|
|
1112
|
+
assert_file_tool_access(work_dir, safe, "read");
|
|
1113
|
+
return safe;
|
|
1114
|
+
}
|
|
1115
|
+
async function search_file(frame, work_dir, relative_root, regex, collected, max_results) {
|
|
1116
|
+
let safe;
|
|
1117
|
+
try {
|
|
1118
|
+
safe = guard_grep_target(work_dir, frame.dir);
|
|
1119
|
+
} catch {
|
|
1120
|
+
return false;
|
|
1121
|
+
}
|
|
1122
|
+
const size = await file_size(safe);
|
|
1123
|
+
const lines = await read_if_text(safe, size);
|
|
830
1124
|
if (lines === void 0) {
|
|
831
1125
|
return false;
|
|
832
1126
|
}
|
|
833
|
-
const relative = path5.relative(relative_root,
|
|
1127
|
+
const relative = path5.relative(relative_root, safe);
|
|
834
1128
|
for (const hit of match_lines(lines, regex)) {
|
|
835
1129
|
collected.push(`${relative}:${hit.line_no}: ${hit.text}`);
|
|
836
1130
|
if (collected.length >= max_results) {
|
|
@@ -839,7 +1133,7 @@ async function search_file(frame, relative_root, regex, collected, max_results)
|
|
|
839
1133
|
}
|
|
840
1134
|
return false;
|
|
841
1135
|
}
|
|
842
|
-
async function search_tree(root, regex, matcher, max_results) {
|
|
1136
|
+
async function search_tree(root, work_dir, regex, matcher, max_results) {
|
|
843
1137
|
const collected = [];
|
|
844
1138
|
const stack = [{ dir: root, name: root }];
|
|
845
1139
|
while (stack.length > 0 && collected.length < max_results) {
|
|
@@ -849,7 +1143,7 @@ async function search_tree(root, regex, matcher, max_results) {
|
|
|
849
1143
|
}
|
|
850
1144
|
const found = await scan_dir(frame, matcher);
|
|
851
1145
|
for (const file of found.files) {
|
|
852
|
-
const hit_cap = await search_file(file, root, regex, collected, max_results);
|
|
1146
|
+
const hit_cap = await search_file(file, work_dir, root, regex, collected, max_results);
|
|
853
1147
|
if (hit_cap === true) {
|
|
854
1148
|
break;
|
|
855
1149
|
}
|
|
@@ -869,13 +1163,20 @@ function finalize_output(matches, max_results) {
|
|
|
869
1163
|
}
|
|
870
1164
|
return matches.join("\n");
|
|
871
1165
|
}
|
|
872
|
-
function search_file_direct(file_path, regex, collected, max_results) {
|
|
873
|
-
return search_file(
|
|
1166
|
+
function search_file_direct(file_path, work_dir, regex, collected, max_results) {
|
|
1167
|
+
return search_file(
|
|
1168
|
+
{ dir: file_path, name: path5.basename(file_path) },
|
|
1169
|
+
work_dir,
|
|
1170
|
+
path5.dirname(file_path),
|
|
1171
|
+
regex,
|
|
1172
|
+
collected,
|
|
1173
|
+
max_results
|
|
1174
|
+
);
|
|
874
1175
|
}
|
|
875
|
-
async function collect_file_matches(root, regex, matcher, max_results) {
|
|
1176
|
+
async function collect_file_matches(root, work_dir, regex, matcher, max_results) {
|
|
876
1177
|
const collected = [];
|
|
877
1178
|
if (matcher(path5.basename(root)) === true) {
|
|
878
|
-
await search_file_direct(root, regex, collected, max_results);
|
|
1179
|
+
await search_file_direct(root, work_dir, regex, collected, max_results);
|
|
879
1180
|
}
|
|
880
1181
|
return collected;
|
|
881
1182
|
}
|
|
@@ -892,9 +1193,10 @@ async function run_grep(args, work_dir) {
|
|
|
892
1193
|
}
|
|
893
1194
|
const matcher = glob.length > 0 ? glob_matcher(glob) : () => true;
|
|
894
1195
|
const root = resolve_safe_path(work_dir, target);
|
|
1196
|
+
assert_file_tool_access(work_dir, root, "read");
|
|
895
1197
|
const root_stat = await stat(root);
|
|
896
1198
|
const cap = max_results + 1;
|
|
897
|
-
const matches = root_stat.isDirectory() === true ? await search_tree(root, regex, matcher, cap) : await collect_file_matches(root, regex, matcher, cap);
|
|
1199
|
+
const matches = root_stat.isDirectory() === true ? await search_tree(root, work_dir, regex, matcher, cap) : await collect_file_matches(root, work_dir, regex, matcher, cap);
|
|
898
1200
|
return finalize_output(matches, max_results);
|
|
899
1201
|
}
|
|
900
1202
|
var grep_files_tool = {
|
|
@@ -966,7 +1268,6 @@ async function run_http(args, external) {
|
|
|
966
1268
|
const init = {
|
|
967
1269
|
method,
|
|
968
1270
|
headers: read_headers(args),
|
|
969
|
-
redirect: "follow",
|
|
970
1271
|
signal: compose_abort_signal(timeout_ms, external)
|
|
971
1272
|
};
|
|
972
1273
|
if (method !== "GET" && method !== "HEAD") {
|
|
@@ -975,7 +1276,7 @@ async function run_http(args, external) {
|
|
|
975
1276
|
init.body = body;
|
|
976
1277
|
}
|
|
977
1278
|
}
|
|
978
|
-
const response = await
|
|
1279
|
+
const response = await safe_fetch(url, init);
|
|
979
1280
|
const content_type = response.headers.get("content-type") ?? "unknown";
|
|
980
1281
|
const text = await response.text();
|
|
981
1282
|
const sections = [
|
|
@@ -1187,6 +1488,7 @@ function slice_lines2(content, offset, limit) {
|
|
|
1187
1488
|
}
|
|
1188
1489
|
async function read_target(args, work_dir, target) {
|
|
1189
1490
|
const file_path = resolve_safe_path(work_dir, target);
|
|
1491
|
+
assert_file_tool_access(work_dir, file_path, "read");
|
|
1190
1492
|
const content = await readFile3(file_path, "utf8");
|
|
1191
1493
|
const offset = optional_number_arg(args, "offset", 1);
|
|
1192
1494
|
const limit = optional_number_arg(args, "limit", Number.MAX_SAFE_INTEGER);
|
|
@@ -1211,78 +1513,47 @@ var read_file_tool = {
|
|
|
1211
1513
|
};
|
|
1212
1514
|
|
|
1213
1515
|
// src/tools/builtin/run_tests.ts
|
|
1516
|
+
import { spawn as spawn2 } from "child_process";
|
|
1517
|
+
|
|
1518
|
+
// src/tools/builtin/terminal.ts
|
|
1214
1519
|
import { spawn } from "child_process";
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
var
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
description: "Optional test-file filter appended to the test command (e.g. a vitest file filter)"
|
|
1223
|
-
}
|
|
1224
|
-
},
|
|
1225
|
-
additionalProperties: false
|
|
1520
|
+
|
|
1521
|
+
// src/gateway/token_env.ts
|
|
1522
|
+
var DEFAULT_GATEWAY_TOKEN_ENVS = {
|
|
1523
|
+
webhook: "LICH_GATEWAY_TOKEN",
|
|
1524
|
+
telegram: "LICH_TELEGRAM_BOT_TOKEN",
|
|
1525
|
+
discord: "LICH_DISCORD_BOT_TOKEN",
|
|
1526
|
+
twitch: "LICH_TWITCH_OAUTH_TOKEN"
|
|
1226
1527
|
};
|
|
1227
|
-
var
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
const child = spawn("bash", ["-lc", command], {
|
|
1231
|
-
cwd,
|
|
1232
|
-
env: process.env,
|
|
1233
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1234
|
-
});
|
|
1235
|
-
child.stdout?.on("data", (chunk) => on_chunk("stdout", chunk));
|
|
1236
|
-
child.stderr?.on("data", (chunk) => on_chunk("stderr", chunk));
|
|
1237
|
-
return new Promise((resolve) => {
|
|
1238
|
-
child.on("close", (code) => resolve({ exit_code: code ?? -1 }));
|
|
1239
|
-
child.on("error", () => resolve({ exit_code: -1 }));
|
|
1240
|
-
});
|
|
1528
|
+
var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1529
|
+
function is_env_var_name(value) {
|
|
1530
|
+
return ENV_VAR_NAME.test(value) === true;
|
|
1241
1531
|
}
|
|
1242
|
-
function
|
|
1243
|
-
|
|
1532
|
+
function platform_token_env(config, platform) {
|
|
1533
|
+
const named = config.gateway?.token_envs[platform];
|
|
1534
|
+
if (named !== void 0 && named.length > 0) {
|
|
1535
|
+
return is_env_var_name(named) === true ? named : "";
|
|
1536
|
+
}
|
|
1537
|
+
return DEFAULT_GATEWAY_TOKEN_ENVS[platform] ?? "";
|
|
1244
1538
|
}
|
|
1245
|
-
function
|
|
1246
|
-
const
|
|
1247
|
-
|
|
1539
|
+
function read_platform_token(config, platform) {
|
|
1540
|
+
const key = platform_token_env(config, platform);
|
|
1541
|
+
if (is_env_var_name(key) === false) {
|
|
1542
|
+
return void 0;
|
|
1543
|
+
}
|
|
1544
|
+
const value = process.env[key];
|
|
1545
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1546
|
+
return void 0;
|
|
1547
|
+
}
|
|
1548
|
+
return value;
|
|
1248
1549
|
}
|
|
1249
|
-
var run_tests_tool = {
|
|
1250
|
-
name: "run_tests",
|
|
1251
|
-
description: "Run the project's test suite via LICH_TEST_COMMAND (default: vitest) in work_dir and report a structured pass/fail result with clamped output.",
|
|
1252
|
-
parameters: parameters12,
|
|
1253
|
-
timeout_ms: 6e5,
|
|
1254
|
-
execute: async (args, context) => capture_errors(async () => {
|
|
1255
|
-
if (busy === true) {
|
|
1256
|
-
return { ok: false, output: "", error: "run_tests_busy" };
|
|
1257
|
-
}
|
|
1258
|
-
busy = true;
|
|
1259
|
-
try {
|
|
1260
|
-
const filter = optional_string_arg(args, "filter", "");
|
|
1261
|
-
const command = build_command(filter === "" ? void 0 : filter, context.env);
|
|
1262
|
-
const streams = { stdout: "", stderr: "" };
|
|
1263
|
-
const on_chunk = (stream, chunk) => {
|
|
1264
|
-
streams[stream] = streams[stream] + chunk.toString("utf8");
|
|
1265
|
-
};
|
|
1266
|
-
const outcome = await run_test_command(command, context.work_dir, on_chunk);
|
|
1267
|
-
const ok = outcome.exit_code === 0;
|
|
1268
|
-
return {
|
|
1269
|
-
ok,
|
|
1270
|
-
output: clamp_output(`${streams.stdout}${streams.stderr}
|
|
1271
|
-
[exit ${outcome.exit_code}]`, MAX_OUTPUT_CHARS),
|
|
1272
|
-
...ok ? {} : { error: "tests_failed" }
|
|
1273
|
-
};
|
|
1274
|
-
} finally {
|
|
1275
|
-
busy = false;
|
|
1276
|
-
}
|
|
1277
|
-
})
|
|
1278
|
-
};
|
|
1279
1550
|
|
|
1280
1551
|
// src/tools/builtin/terminal.ts
|
|
1281
|
-
import { spawn as spawn2 } from "child_process";
|
|
1282
1552
|
var MAX_STREAM_CHARS = 5e4;
|
|
1283
1553
|
var DEFAULT_TIMEOUT_MS3 = 6e4;
|
|
1284
1554
|
var MAX_TIMEOUT_MS3 = 3e5;
|
|
1285
|
-
var
|
|
1555
|
+
var PROVIDER_KEY_ENVS = ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "LICH_API_KEY"];
|
|
1556
|
+
var parameters12 = {
|
|
1286
1557
|
type: "object",
|
|
1287
1558
|
properties: {
|
|
1288
1559
|
command: { type: "string", description: "Shell command to run via bash -lc" },
|
|
@@ -1303,6 +1574,21 @@ function stream_chunk(current, chunk) {
|
|
|
1303
1574
|
function clamp_timeout(raw) {
|
|
1304
1575
|
return Math.min(MAX_TIMEOUT_MS3, Math.max(1, Math.floor(raw)));
|
|
1305
1576
|
}
|
|
1577
|
+
function scrub_spawn_env(process_env, context_env) {
|
|
1578
|
+
const drop = /* @__PURE__ */ new Set([...Object.values(DEFAULT_GATEWAY_TOKEN_ENVS), ...PROVIDER_KEY_ENVS]);
|
|
1579
|
+
const merged = { ...process_env, ...context_env };
|
|
1580
|
+
const scrubbed = {};
|
|
1581
|
+
for (const [name, value] of Object.entries(merged)) {
|
|
1582
|
+
if (value === void 0) {
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
if (drop.has(name) === true || SECRET_PATTERN.test(name) === true) {
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
scrubbed[name] = value;
|
|
1589
|
+
}
|
|
1590
|
+
return scrubbed;
|
|
1591
|
+
}
|
|
1306
1592
|
function wire_kill(child, timeout_signal, external) {
|
|
1307
1593
|
timeout_signal.addEventListener("abort", () => child.kill("SIGKILL"), { once: true });
|
|
1308
1594
|
external?.addEventListener("abort", () => child.kill("SIGKILL"), { once: true });
|
|
@@ -1316,7 +1602,10 @@ function wait_close(child) {
|
|
|
1316
1602
|
async function run_command(command, work_dir, env, timeout_ms, external) {
|
|
1317
1603
|
const stdout = { text: "" };
|
|
1318
1604
|
const stderr = { text: "" };
|
|
1319
|
-
const child =
|
|
1605
|
+
const child = spawn("bash", ["-lc", command], {
|
|
1606
|
+
cwd: work_dir,
|
|
1607
|
+
env: scrub_spawn_env(process.env, env)
|
|
1608
|
+
});
|
|
1320
1609
|
child.stdout.on("data", (chunk) => stream_chunk(stdout, chunk));
|
|
1321
1610
|
child.stderr.on("data", (chunk) => stream_chunk(stderr, chunk));
|
|
1322
1611
|
const close_promise = wait_close(child);
|
|
@@ -1354,7 +1643,7 @@ function terminal_result(outcome) {
|
|
|
1354
1643
|
var terminal_tool = {
|
|
1355
1644
|
name: "terminal",
|
|
1356
1645
|
description: "Run a shell command with bash -lc and capture combined stdout/stderr plus the exit code.",
|
|
1357
|
-
parameters:
|
|
1646
|
+
parameters: parameters12,
|
|
1358
1647
|
timeout_ms: MAX_TIMEOUT_MS3,
|
|
1359
1648
|
execute: async (args, context) => capture_errors(async () => {
|
|
1360
1649
|
const command = require_string_arg(args, "command");
|
|
@@ -1364,6 +1653,75 @@ var terminal_tool = {
|
|
|
1364
1653
|
})
|
|
1365
1654
|
};
|
|
1366
1655
|
|
|
1656
|
+
// src/tools/builtin/run_tests.ts
|
|
1657
|
+
var MAX_OUTPUT_CHARS = 2e3;
|
|
1658
|
+
var DEFAULT_TEST_COMMAND = "node node_modules/vitest/vitest.mjs run";
|
|
1659
|
+
var parameters13 = {
|
|
1660
|
+
type: "object",
|
|
1661
|
+
properties: {
|
|
1662
|
+
filter: {
|
|
1663
|
+
type: "string",
|
|
1664
|
+
description: "Optional test-file filter appended to the test command (e.g. a vitest file filter)"
|
|
1665
|
+
}
|
|
1666
|
+
},
|
|
1667
|
+
additionalProperties: false
|
|
1668
|
+
};
|
|
1669
|
+
var busy = false;
|
|
1670
|
+
var run_test_command = default_runner;
|
|
1671
|
+
function default_runner(command, cwd, on_chunk) {
|
|
1672
|
+
const child = spawn2("bash", ["-lc", command], {
|
|
1673
|
+
cwd,
|
|
1674
|
+
env: scrub_spawn_env(process.env, {}),
|
|
1675
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1676
|
+
});
|
|
1677
|
+
child.stdout?.on("data", (chunk) => on_chunk("stdout", chunk));
|
|
1678
|
+
child.stderr?.on("data", (chunk) => on_chunk("stderr", chunk));
|
|
1679
|
+
return new Promise((resolve) => {
|
|
1680
|
+
child.on("close", (code) => resolve({ exit_code: code ?? -1 }));
|
|
1681
|
+
child.on("error", () => resolve({ exit_code: -1 }));
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
function shell_quote(token) {
|
|
1685
|
+
return `'${token.replaceAll("'", "'\\''")}'`;
|
|
1686
|
+
}
|
|
1687
|
+
function build_command(filter, env) {
|
|
1688
|
+
const base = optional_string_arg(env, "LICH_TEST_COMMAND", DEFAULT_TEST_COMMAND);
|
|
1689
|
+
return filter === void 0 ? base : `${base} ${shell_quote(filter)}`;
|
|
1690
|
+
}
|
|
1691
|
+
var run_tests_tool = {
|
|
1692
|
+
name: "run_tests",
|
|
1693
|
+
description: "Run the project's test suite via LICH_TEST_COMMAND (default: vitest) in work_dir and report a structured pass/fail result with clamped output.",
|
|
1694
|
+
parameters: parameters13,
|
|
1695
|
+
timeout_ms: 6e5,
|
|
1696
|
+
execute: async (args, context) => capture_errors(async () => {
|
|
1697
|
+
if (busy === true) {
|
|
1698
|
+
return { ok: false, output: "", error: "run_tests_busy" };
|
|
1699
|
+
}
|
|
1700
|
+
busy = true;
|
|
1701
|
+
try {
|
|
1702
|
+
const filter = optional_string_arg(args, "filter", "");
|
|
1703
|
+
if (filter.startsWith("-") === true) {
|
|
1704
|
+
return { ok: false, output: "", error: "invalid_filter: must not start with -" };
|
|
1705
|
+
}
|
|
1706
|
+
const command = build_command(filter === "" ? void 0 : filter, context.env);
|
|
1707
|
+
const streams = { stdout: "", stderr: "" };
|
|
1708
|
+
const on_chunk = (stream, chunk) => {
|
|
1709
|
+
streams[stream] = streams[stream] + chunk.toString("utf8");
|
|
1710
|
+
};
|
|
1711
|
+
const outcome = await run_test_command(command, context.work_dir, on_chunk);
|
|
1712
|
+
const ok = outcome.exit_code === 0;
|
|
1713
|
+
return {
|
|
1714
|
+
ok,
|
|
1715
|
+
output: clamp_output(`${streams.stdout}${streams.stderr}
|
|
1716
|
+
[exit ${outcome.exit_code}]`, MAX_OUTPUT_CHARS),
|
|
1717
|
+
...ok ? {} : { error: "tests_failed" }
|
|
1718
|
+
};
|
|
1719
|
+
} finally {
|
|
1720
|
+
busy = false;
|
|
1721
|
+
}
|
|
1722
|
+
})
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1367
1725
|
// src/tools/builtin/web_search.ts
|
|
1368
1726
|
var DEFAULT_MAX_RESULTS4 = 8;
|
|
1369
1727
|
var MAX_RESULTS = 20;
|
|
@@ -1490,7 +1848,8 @@ function read_content_arg(args) {
|
|
|
1490
1848
|
return content;
|
|
1491
1849
|
}
|
|
1492
1850
|
async function write_target(work_dir, target, content) {
|
|
1493
|
-
const file_path = resolve_safe_path(work_dir, target);
|
|
1851
|
+
const file_path = resolve_safe_path(work_dir, target, true);
|
|
1852
|
+
assert_file_tool_access(work_dir, file_path, "write");
|
|
1494
1853
|
await mkdir(path7.dirname(file_path), { recursive: true });
|
|
1495
1854
|
await writeFile2(file_path, content, "utf8");
|
|
1496
1855
|
return `wrote ${content.length} chars to ${target}`;
|
|
@@ -1882,34 +2241,39 @@ var ProviderError = class extends Error {
|
|
|
1882
2241
|
// src/agent/config.ts
|
|
1883
2242
|
import { z } from "zod";
|
|
1884
2243
|
|
|
1885
|
-
// src/gateway/
|
|
1886
|
-
var
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2244
|
+
// src/gateway/access.ts
|
|
2245
|
+
var PUBLIC_PLATFORMS = /* @__PURE__ */ new Set(["telegram", "discord", "twitch"]);
|
|
2246
|
+
var DEFAULT_GATEWAY_TOOLS_ENABLED = [
|
|
2247
|
+
"read_file",
|
|
2248
|
+
"list_dir",
|
|
2249
|
+
"grep_files",
|
|
2250
|
+
"fetch_url",
|
|
2251
|
+
"web_search",
|
|
2252
|
+
"docs_read",
|
|
2253
|
+
"docs_search"
|
|
2254
|
+
];
|
|
2255
|
+
function gateway_tools_enabled(config) {
|
|
2256
|
+
return config.gateway?.tools_enabled ?? DEFAULT_GATEWAY_TOOLS_ENABLED;
|
|
1895
2257
|
}
|
|
1896
|
-
function
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
return is_env_var_name(named) === true ? named : "";
|
|
2258
|
+
function is_gateway_sender_allowed(config, platform, chat_id, user_id) {
|
|
2259
|
+
if (PUBLIC_PLATFORMS.has(platform) === false) {
|
|
2260
|
+
return true;
|
|
1900
2261
|
}
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
if (is_env_var_name(key) === false) {
|
|
1906
|
-
return void 0;
|
|
2262
|
+
const users = config.gateway?.allowed_users?.[platform] ?? [];
|
|
2263
|
+
const chats = config.gateway?.allowed_chats?.[platform] ?? [];
|
|
2264
|
+
if (users.length === 0 && chats.length === 0) {
|
|
2265
|
+
return false;
|
|
1907
2266
|
}
|
|
1908
|
-
const
|
|
1909
|
-
|
|
1910
|
-
|
|
2267
|
+
const user_ok = users.length === 0 || users.includes(user_id);
|
|
2268
|
+
const chat_ok = chats.length === 0 || chats.includes(chat_id);
|
|
2269
|
+
return user_ok && chat_ok;
|
|
2270
|
+
}
|
|
2271
|
+
function check_gateway_sender(config, platform, chat_id, user_id) {
|
|
2272
|
+
if (is_gateway_sender_allowed(config, platform, chat_id, user_id) === true) {
|
|
2273
|
+
return true;
|
|
1911
2274
|
}
|
|
1912
|
-
|
|
2275
|
+
logger.warn(`gateway denied ${platform} chat=${chat_id} user=${user_id}`);
|
|
2276
|
+
return false;
|
|
1913
2277
|
}
|
|
1914
2278
|
|
|
1915
2279
|
// src/mcp/mcp_pin.ts
|
|
@@ -2052,10 +2416,17 @@ function refuse_mcp_entry(name, entry) {
|
|
|
2052
2416
|
}
|
|
2053
2417
|
|
|
2054
2418
|
// src/agent/config.ts
|
|
2419
|
+
var gateway_allowlist = z.record(z.string(), z.array(z.string())).default({});
|
|
2055
2420
|
var gateway_schema = z.object({
|
|
2056
2421
|
platforms: z.array(z.enum(["webhook", "telegram", "discord", "twitch"])).default([]),
|
|
2057
2422
|
/** Env-var names that hold tokens. Never store the secrets themselves. */
|
|
2058
|
-
token_envs: z.record(z.string(), z.string().regex(ENV_VAR_NAME, "invalid env var name")).default({})
|
|
2423
|
+
token_envs: z.record(z.string(), z.string().regex(ENV_VAR_NAME, "invalid env var name")).default({}),
|
|
2424
|
+
/** Per-platform user ids allowed to talk to the bot (default-deny on public platforms). */
|
|
2425
|
+
allowed_users: gateway_allowlist,
|
|
2426
|
+
/** Per-platform chat/channel ids allowed (default-deny on public platforms). */
|
|
2427
|
+
allowed_chats: gateway_allowlist,
|
|
2428
|
+
/** Tool allowlist for the gateway agent; defaults to a read-only safe subset. */
|
|
2429
|
+
tools_enabled: z.union([z.literal("all"), z.array(z.string())]).default([...DEFAULT_GATEWAY_TOOLS_ENABLED])
|
|
2059
2430
|
}).optional();
|
|
2060
2431
|
var stdio_mcp_schema = z.object({
|
|
2061
2432
|
enabled: z.boolean().default(false),
|
|
@@ -2134,6 +2505,11 @@ function freeze_config(config) {
|
|
|
2134
2505
|
if (config.gateway !== void 0) {
|
|
2135
2506
|
Object.freeze(config.gateway.platforms);
|
|
2136
2507
|
Object.freeze(config.gateway.token_envs);
|
|
2508
|
+
Object.freeze(config.gateway.allowed_users);
|
|
2509
|
+
Object.freeze(config.gateway.allowed_chats);
|
|
2510
|
+
if (Array.isArray(config.gateway.tools_enabled) === true) {
|
|
2511
|
+
Object.freeze(config.gateway.tools_enabled);
|
|
2512
|
+
}
|
|
2137
2513
|
Object.freeze(config.gateway);
|
|
2138
2514
|
}
|
|
2139
2515
|
if (config.mcp_servers !== void 0) {
|
|
@@ -2614,6 +2990,7 @@ function node_line_child(command, args, env) {
|
|
|
2614
2990
|
const queue = create_line_queue();
|
|
2615
2991
|
let failure;
|
|
2616
2992
|
const child = spawn3(command, [...args], { stdio: ["pipe", "pipe", "pipe"], env: child_env(env) });
|
|
2993
|
+
child.unref();
|
|
2617
2994
|
const reader = createInterface({ input: child.stdout });
|
|
2618
2995
|
reader.on("line", (line) => {
|
|
2619
2996
|
queue.push(line);
|
|
@@ -2658,25 +3035,27 @@ function default_line_spawner(command, args, env) {
|
|
|
2658
3035
|
async function open_and_register(registry, name, enabled, session) {
|
|
2659
3036
|
try {
|
|
2660
3037
|
register_listed(registry, name, await session.list_tools(), enabled, session);
|
|
3038
|
+
return session;
|
|
2661
3039
|
} catch (error) {
|
|
2662
3040
|
session.close();
|
|
2663
3041
|
const message = error instanceof Error ? error.message : "mcp skipped";
|
|
2664
3042
|
logger.warn(`mcp ${name} skipped: ${message}`);
|
|
3043
|
+
return void 0;
|
|
2665
3044
|
}
|
|
2666
3045
|
}
|
|
2667
3046
|
async function attach_stdio(registry, name, entry, config, runtime) {
|
|
2668
3047
|
const planned = plan_stdio(name, entry.command, entry.args, runtime?.env_path ?? process.env.PATH);
|
|
2669
3048
|
if (typeof planned === "string") {
|
|
2670
3049
|
logger.warn(`mcp ${name} skipped: ${planned}`);
|
|
2671
|
-
return;
|
|
3050
|
+
return void 0;
|
|
2672
3051
|
}
|
|
2673
3052
|
const spawn5 = runtime?.spawn ?? default_line_spawner;
|
|
2674
3053
|
const session = new McpSession(stdio_pipe(spawn5(planned.command, planned.args, entry.env)));
|
|
2675
|
-
|
|
3054
|
+
return open_and_register(registry, name, config.tools_enabled, session);
|
|
2676
3055
|
}
|
|
2677
3056
|
async function attach_http(registry, name, url, config, runtime) {
|
|
2678
3057
|
const session = new McpSession(http_pipe(url, runtime?.fetch_fn ?? fetch));
|
|
2679
|
-
|
|
3058
|
+
return open_and_register(registry, name, config.tools_enabled, session);
|
|
2680
3059
|
}
|
|
2681
3060
|
|
|
2682
3061
|
// src/mcp/mcp_tools.ts
|
|
@@ -2687,25 +3066,26 @@ function allowlist_wants_mcp(enabled) {
|
|
|
2687
3066
|
return enabled.some((name) => name.startsWith("mcp_") === true);
|
|
2688
3067
|
}
|
|
2689
3068
|
async function attach_enabled_mcp_tools(registry, config, runtime) {
|
|
3069
|
+
const sessions = [];
|
|
2690
3070
|
const servers = config.mcp_servers;
|
|
2691
3071
|
if (servers === void 0 || allowlist_wants_mcp(config.tools_enabled) === false) {
|
|
2692
|
-
return;
|
|
3072
|
+
return sessions;
|
|
2693
3073
|
}
|
|
2694
3074
|
for (const [name, entry] of Object.entries(servers)) {
|
|
2695
3075
|
if (entry.enabled !== true) {
|
|
2696
3076
|
continue;
|
|
2697
3077
|
}
|
|
2698
3078
|
try {
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
3079
|
+
const session = "url" in entry ? await attach_http(registry, name, entry.url, config, runtime) : await attach_stdio(registry, name, entry, config, runtime);
|
|
3080
|
+
if (session !== void 0) {
|
|
3081
|
+
sessions.push(session);
|
|
2702
3082
|
}
|
|
2703
|
-
await attach_stdio(registry, name, entry, config, runtime);
|
|
2704
3083
|
} catch (error) {
|
|
2705
3084
|
const message = error instanceof Error ? error.message : "mcp skipped";
|
|
2706
3085
|
logger.warn(`mcp ${name} skipped: ${message}`);
|
|
2707
3086
|
}
|
|
2708
3087
|
}
|
|
3088
|
+
return sessions;
|
|
2709
3089
|
}
|
|
2710
3090
|
|
|
2711
3091
|
// src/plugins/builtin/gatekeeper.plugin.ts
|
|
@@ -2912,8 +3292,12 @@ function gatekeeper_hooks(allow_self_commit) {
|
|
|
2912
3292
|
if (info.tool_name === "write_file" || info.tool_name === "edit_file") {
|
|
2913
3293
|
ctx.state?.set("dirty", true);
|
|
2914
3294
|
} else if (info.tool_name === "run_tests") {
|
|
2915
|
-
|
|
2916
|
-
|
|
3295
|
+
const filter = info.args["filter"];
|
|
3296
|
+
const filtered = typeof filter === "string" && filter.length > 0;
|
|
3297
|
+
if (filtered === false) {
|
|
3298
|
+
ctx.state?.set("tests_ok", true);
|
|
3299
|
+
ctx.state?.set("dirty", false);
|
|
3300
|
+
}
|
|
2917
3301
|
} else if (info.tool_name === "git_commit") {
|
|
2918
3302
|
ctx.state?.set("commits", state_count(ctx, "commits") + 1);
|
|
2919
3303
|
}
|
|
@@ -3055,6 +3439,7 @@ var MAX_ERROR_BODY_CHARS = 500;
|
|
|
3055
3439
|
var OVERFLOW_BODY_PATTERN = /context|token|maximum/i;
|
|
3056
3440
|
var OVERLOADED_STATUS = 529;
|
|
3057
3441
|
var UNPARSEABLE_ARGS_NOTE = "[unparseable tool arguments]";
|
|
3442
|
+
var EMPTY_TEXT_PLACEHOLDER = "(empty)";
|
|
3058
3443
|
var DEFAULT_MAX_TOKENS = 4096;
|
|
3059
3444
|
var AnthropicProvider = class {
|
|
3060
3445
|
name;
|
|
@@ -3168,7 +3553,7 @@ function to_anthropic_turns(messages) {
|
|
|
3168
3553
|
}
|
|
3169
3554
|
flush_tool_results(turns, pending_tool_results);
|
|
3170
3555
|
if (message.role === "user") {
|
|
3171
|
-
turns.push({ role: "user", content: [
|
|
3556
|
+
turns.push({ role: "user", content: [text_block(message.content)] });
|
|
3172
3557
|
} else {
|
|
3173
3558
|
turns.push({ role: "assistant", content: assistant_to_blocks(message) });
|
|
3174
3559
|
}
|
|
@@ -3182,11 +3567,14 @@ function flush_tool_results(turns, pending_tool_results) {
|
|
|
3182
3567
|
}
|
|
3183
3568
|
turns.push({ role: "user", content: pending_tool_results.splice(0, pending_tool_results.length) });
|
|
3184
3569
|
}
|
|
3570
|
+
function text_block(text) {
|
|
3571
|
+
return { type: "text", text: text.length > 0 ? text : EMPTY_TEXT_PLACEHOLDER };
|
|
3572
|
+
}
|
|
3185
3573
|
function tool_message_to_block(message) {
|
|
3186
3574
|
const block = {
|
|
3187
3575
|
type: "tool_result",
|
|
3188
3576
|
tool_use_id: message.tool_call_id,
|
|
3189
|
-
content: [
|
|
3577
|
+
content: [text_block(message.content)]
|
|
3190
3578
|
};
|
|
3191
3579
|
if (message.is_error === true) {
|
|
3192
3580
|
return { ...block, is_error: true };
|
|
@@ -3202,7 +3590,7 @@ function assistant_to_blocks(message) {
|
|
|
3202
3590
|
blocks.push({ type: "tool_use", id: tool_call.id, name: tool_call.name, input: tool_call.args });
|
|
3203
3591
|
}
|
|
3204
3592
|
if (blocks.length === 0) {
|
|
3205
|
-
blocks.push(
|
|
3593
|
+
blocks.push(text_block(""));
|
|
3206
3594
|
}
|
|
3207
3595
|
return blocks;
|
|
3208
3596
|
}
|
|
@@ -4046,6 +4434,9 @@ async function chat_with_failover(router, messages, tools, options) {
|
|
|
4046
4434
|
if (result.ok === true) {
|
|
4047
4435
|
return result.value;
|
|
4048
4436
|
}
|
|
4437
|
+
if (is_abort_failure(result.error, options?.signal) === true) {
|
|
4438
|
+
throw result.error;
|
|
4439
|
+
}
|
|
4049
4440
|
last_error = result.error;
|
|
4050
4441
|
log_fail_over(result.error);
|
|
4051
4442
|
}
|
|
@@ -4079,9 +4470,18 @@ async function attempt_provider(provider, messages, tools, options) {
|
|
|
4079
4470
|
})
|
|
4080
4471
|
};
|
|
4081
4472
|
} catch (error) {
|
|
4473
|
+
if (is_abort_failure(error, options?.signal) === true) {
|
|
4474
|
+
throw error;
|
|
4475
|
+
}
|
|
4082
4476
|
return { ok: false, error: to_provider_error(error, provider.name) };
|
|
4083
4477
|
}
|
|
4084
4478
|
}
|
|
4479
|
+
function is_abort_failure(error, signal) {
|
|
4480
|
+
if (signal?.aborted === true) {
|
|
4481
|
+
return true;
|
|
4482
|
+
}
|
|
4483
|
+
return error instanceof Error && error.name === "AbortError";
|
|
4484
|
+
}
|
|
4085
4485
|
function to_provider_error(error, fallback_name) {
|
|
4086
4486
|
if (error instanceof ProviderError) {
|
|
4087
4487
|
return error;
|
|
@@ -4163,12 +4563,18 @@ function log_compression_failure(error) {
|
|
|
4163
4563
|
}
|
|
4164
4564
|
logger.warn("context compression failed", error);
|
|
4165
4565
|
}
|
|
4566
|
+
function split_keep_recent(non_system, keep_recent) {
|
|
4567
|
+
let cut = Math.max(0, non_system.length - keep_recent);
|
|
4568
|
+
while (cut > 0 && non_system[cut]?.role === "tool") {
|
|
4569
|
+
cut -= 1;
|
|
4570
|
+
}
|
|
4571
|
+
return { recent: non_system.slice(cut), older: non_system.slice(0, cut) };
|
|
4572
|
+
}
|
|
4166
4573
|
async function compress_messages(deps, messages, params) {
|
|
4167
4574
|
const system_messages = messages.filter((message) => message.role === "system");
|
|
4168
4575
|
const non_system = messages.filter((message) => message.role !== "system");
|
|
4169
4576
|
const keep_recent = Math.max(0, params.keep_recent);
|
|
4170
|
-
const recent = non_system
|
|
4171
|
-
const older = non_system.slice(0, Math.max(0, non_system.length - recent.length));
|
|
4577
|
+
const { recent, older } = split_keep_recent(non_system, keep_recent);
|
|
4172
4578
|
if (older.length === 0) {
|
|
4173
4579
|
return { messages: [...messages], summary_chars: 0 };
|
|
4174
4580
|
}
|
|
@@ -4221,8 +4627,12 @@ function format_tool_result_content(result) {
|
|
|
4221
4627
|
}
|
|
4222
4628
|
return result.output;
|
|
4223
4629
|
}
|
|
4224
|
-
async function run_tool_calls(deps, history, turn, calls, emitter) {
|
|
4630
|
+
async function run_tool_calls(deps, history, turn, calls, emitter, signal) {
|
|
4225
4631
|
for (const call of calls) {
|
|
4632
|
+
if (signal_aborted(signal) === true) {
|
|
4633
|
+
history.push(cancelled_tool_message(call));
|
|
4634
|
+
continue;
|
|
4635
|
+
}
|
|
4226
4636
|
emitter?.emit({ type: "tool_call_start", turn, call });
|
|
4227
4637
|
const result = await deps.tools.execute(call.name, call.args, deps.tool_context);
|
|
4228
4638
|
const tool_message = {
|
|
@@ -4237,6 +4647,16 @@ async function run_tool_calls(deps, history, turn, calls, emitter) {
|
|
|
4237
4647
|
history.push(tool_message);
|
|
4238
4648
|
emitter?.emit({ type: "tool_call_end", turn, call, result });
|
|
4239
4649
|
}
|
|
4650
|
+
return signal_aborted(signal) === true ? "aborted" : "continued";
|
|
4651
|
+
}
|
|
4652
|
+
function cancelled_tool_message(call) {
|
|
4653
|
+
return {
|
|
4654
|
+
role: "tool",
|
|
4655
|
+
tool_call_id: call.id,
|
|
4656
|
+
name: call.name,
|
|
4657
|
+
content: format_tool_result_content({ ok: false, output: "", error: "cancelled" }),
|
|
4658
|
+
is_error: true
|
|
4659
|
+
};
|
|
4240
4660
|
}
|
|
4241
4661
|
async function call_chat(deps, history, params, emitter) {
|
|
4242
4662
|
try {
|
|
@@ -4246,6 +4666,9 @@ async function call_chat(deps, history, params, emitter) {
|
|
|
4246
4666
|
signal: params.signal
|
|
4247
4667
|
});
|
|
4248
4668
|
} catch (error) {
|
|
4669
|
+
if (signal_aborted(params.signal) === true) {
|
|
4670
|
+
throw error;
|
|
4671
|
+
}
|
|
4249
4672
|
if (error instanceof ProviderError) {
|
|
4250
4673
|
logger.error(`provider error kind=${error.kind} provider=${error.provider_name}`, error);
|
|
4251
4674
|
} else {
|
|
@@ -4283,24 +4706,38 @@ async function compress_if_needed(deps, history, params, emitter) {
|
|
|
4283
4706
|
function find_last_assistant(messages) {
|
|
4284
4707
|
return [...messages].reverse().find((message) => message.role === "assistant");
|
|
4285
4708
|
}
|
|
4709
|
+
function signal_aborted(signal) {
|
|
4710
|
+
return signal?.aborted === true;
|
|
4711
|
+
}
|
|
4712
|
+
function aborted_outcome(history, turns_used, emitter) {
|
|
4713
|
+
emitter?.emit({ type: "error", error: new DOMException("agent loop aborted", "AbortError") });
|
|
4714
|
+
return {
|
|
4715
|
+
messages: history,
|
|
4716
|
+
final: find_last_assistant(history),
|
|
4717
|
+
result: void 0,
|
|
4718
|
+
turns_used,
|
|
4719
|
+
stopped_reason: "aborted"
|
|
4720
|
+
};
|
|
4721
|
+
}
|
|
4286
4722
|
async function run_conversation(deps, messages, params) {
|
|
4287
4723
|
const history = seed_system_prompt(messages, params.system_prompt);
|
|
4288
4724
|
const emitter = deps.emitter;
|
|
4289
4725
|
for (const turn of turn_range(params.max_turns)) {
|
|
4290
|
-
if (params.signal
|
|
4291
|
-
|
|
4292
|
-
return {
|
|
4293
|
-
messages: history,
|
|
4294
|
-
final: find_last_assistant(history),
|
|
4295
|
-
result: void 0,
|
|
4296
|
-
turns_used: turn - 1,
|
|
4297
|
-
stopped_reason: "aborted"
|
|
4298
|
-
};
|
|
4726
|
+
if (signal_aborted(params.signal) === true) {
|
|
4727
|
+
return aborted_outcome(history, turn - 1, emitter);
|
|
4299
4728
|
}
|
|
4300
4729
|
emitter?.emit({ type: "turn_start", turn });
|
|
4301
4730
|
await compress_if_needed(deps, history, params, emitter);
|
|
4302
4731
|
emitter?.emit({ type: "llm_start", turn });
|
|
4303
|
-
|
|
4732
|
+
let result;
|
|
4733
|
+
try {
|
|
4734
|
+
result = await call_chat(deps, history, params, emitter);
|
|
4735
|
+
} catch (error) {
|
|
4736
|
+
if (signal_aborted(params.signal) === true) {
|
|
4737
|
+
return aborted_outcome(history, turn - 1, emitter);
|
|
4738
|
+
}
|
|
4739
|
+
throw error;
|
|
4740
|
+
}
|
|
4304
4741
|
emitter?.emit({ type: "llm_end", turn, result });
|
|
4305
4742
|
history.push(result.message);
|
|
4306
4743
|
const calls = result.message.tool_calls ?? [];
|
|
@@ -4309,7 +4746,10 @@ async function run_conversation(deps, messages, params) {
|
|
|
4309
4746
|
emitter?.emit({ type: "turn_end", turn });
|
|
4310
4747
|
return { messages: history, final: result.message, result, turns_used: turn, stopped_reason: "final" };
|
|
4311
4748
|
}
|
|
4312
|
-
await run_tool_calls(deps, history, turn, calls, emitter);
|
|
4749
|
+
const tool_status = await run_tool_calls(deps, history, turn, calls, emitter, params.signal);
|
|
4750
|
+
if (tool_status === "aborted") {
|
|
4751
|
+
return aborted_outcome(history, turn, emitter);
|
|
4752
|
+
}
|
|
4313
4753
|
}
|
|
4314
4754
|
emitter?.emit({ type: "budget_exhausted", turns_used: params.max_turns });
|
|
4315
4755
|
emitter?.emit({ type: "turn_end", turn: params.max_turns });
|
|
@@ -4389,7 +4829,8 @@ var Agent = class {
|
|
|
4389
4829
|
executor;
|
|
4390
4830
|
hook_runner;
|
|
4391
4831
|
mcp_runtime;
|
|
4392
|
-
|
|
4832
|
+
mcp_sessions = [];
|
|
4833
|
+
mcp_attach;
|
|
4393
4834
|
constructor(config, plugins = [], runtime) {
|
|
4394
4835
|
this.config = config;
|
|
4395
4836
|
this.mcp_runtime = runtime?.mcp;
|
|
@@ -4418,14 +4859,20 @@ var Agent = class {
|
|
|
4418
4859
|
async run(options) {
|
|
4419
4860
|
await this.attach_mcp_once();
|
|
4420
4861
|
const usage_total = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
4421
|
-
const
|
|
4862
|
+
const run_events = new AgentEmitter();
|
|
4863
|
+
const stop_forwarding = run_events.on((event) => this.events.emit(event));
|
|
4864
|
+
const stop_collecting = run_events.on(collect_usage(usage_total));
|
|
4422
4865
|
await this.call_plugin_run_start(options.input);
|
|
4423
4866
|
let outcome;
|
|
4424
4867
|
try {
|
|
4425
4868
|
const seed_messages = [...options.history ?? []];
|
|
4426
4869
|
seed_messages.push({ role: "user", content: options.input });
|
|
4427
|
-
const tool_context = {
|
|
4428
|
-
|
|
4870
|
+
const tool_context = {
|
|
4871
|
+
work_dir: this.config.work_dir,
|
|
4872
|
+
env: tool_env(this.config),
|
|
4873
|
+
signal: options.signal
|
|
4874
|
+
};
|
|
4875
|
+
outcome = await run_conversation(this.loop_deps(tool_context, run_events), seed_messages, {
|
|
4429
4876
|
system_prompt: this.config.system_prompt ?? DEFAULT_AGENT_SYSTEM_PROMPT,
|
|
4430
4877
|
max_turns: this.config.max_turns,
|
|
4431
4878
|
temperature: this.config.temperature,
|
|
@@ -4436,29 +4883,36 @@ var Agent = class {
|
|
|
4436
4883
|
});
|
|
4437
4884
|
} finally {
|
|
4438
4885
|
stop_collecting();
|
|
4886
|
+
stop_forwarding();
|
|
4439
4887
|
if (outcome !== void 0) {
|
|
4440
4888
|
await this.call_plugin_run_end(outcome);
|
|
4441
4889
|
}
|
|
4442
4890
|
}
|
|
4443
4891
|
const session_path = await this.persist_session(outcome, options, usage_total);
|
|
4444
|
-
|
|
4445
|
-
|
|
4892
|
+
return { outcome, messages: outcome.messages, usage_total, session_path };
|
|
4893
|
+
}
|
|
4894
|
+
/** Close MCP sessions so stdio children do not keep the event loop alive. */
|
|
4895
|
+
close() {
|
|
4896
|
+
for (const session of this.mcp_sessions) {
|
|
4897
|
+
session.close();
|
|
4898
|
+
}
|
|
4899
|
+
this.mcp_sessions = [];
|
|
4446
4900
|
}
|
|
4447
4901
|
/** tools/list once, before the model sees definitions. Empty allowlists never connect. */
|
|
4448
4902
|
async attach_mcp_once() {
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
await attach_enabled_mcp_tools(this.registry, this.config, this.mcp_runtime);
|
|
4903
|
+
this.mcp_attach ??= this.do_attach_mcp();
|
|
4904
|
+
await this.mcp_attach;
|
|
4905
|
+
}
|
|
4906
|
+
async do_attach_mcp() {
|
|
4907
|
+
this.mcp_sessions = await attach_enabled_mcp_tools(this.registry, this.config, this.mcp_runtime);
|
|
4454
4908
|
}
|
|
4455
4909
|
/** Per-run deps: the built-once ToolContext threads through every tool execution. */
|
|
4456
|
-
loop_deps(tool_context) {
|
|
4910
|
+
loop_deps(tool_context, emitter = this.events) {
|
|
4457
4911
|
return {
|
|
4458
4912
|
chat: (messages, tools, chat_options) => this.router.chat_with_failover(messages, tools, chat_options),
|
|
4459
4913
|
tools: this.executor,
|
|
4460
4914
|
definitions: () => this.registry.definitions(),
|
|
4461
|
-
emitter
|
|
4915
|
+
emitter,
|
|
4462
4916
|
tool_context
|
|
4463
4917
|
};
|
|
4464
4918
|
}
|
|
@@ -4513,12 +4967,20 @@ async function create_agent_with_plugins(raw_config) {
|
|
|
4513
4967
|
}
|
|
4514
4968
|
async function run_agent(raw_config, input, options) {
|
|
4515
4969
|
const agent = await create_agent_with_plugins(raw_config);
|
|
4516
|
-
|
|
4970
|
+
try {
|
|
4971
|
+
return await agent.run({ input, signal: options?.signal, label: options?.label });
|
|
4972
|
+
} finally {
|
|
4973
|
+
agent.close();
|
|
4974
|
+
}
|
|
4517
4975
|
}
|
|
4518
4976
|
|
|
4519
4977
|
export {
|
|
4520
4978
|
safe_json_parse,
|
|
4521
4979
|
truncate_text,
|
|
4980
|
+
DEFAULT_GATEWAY_TOKEN_ENVS,
|
|
4981
|
+
is_env_var_name,
|
|
4982
|
+
platform_token_env,
|
|
4983
|
+
read_platform_token,
|
|
4522
4984
|
logger,
|
|
4523
4985
|
register_builtin_tools,
|
|
4524
4986
|
catalog_by_name,
|
|
@@ -4530,10 +4992,8 @@ export {
|
|
|
4530
4992
|
plugin_errors_summary,
|
|
4531
4993
|
sleep,
|
|
4532
4994
|
ProviderError,
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
platform_token_env,
|
|
4536
|
-
read_platform_token,
|
|
4995
|
+
gateway_tools_enabled,
|
|
4996
|
+
check_gateway_sender,
|
|
4537
4997
|
parse_agent_config,
|
|
4538
4998
|
AgentEmitter,
|
|
4539
4999
|
Agent,
|
|
@@ -4541,4 +5001,4 @@ export {
|
|
|
4541
5001
|
create_agent_with_plugins,
|
|
4542
5002
|
run_agent
|
|
4543
5003
|
};
|
|
4544
|
-
//# sourceMappingURL=chunk-
|
|
5004
|
+
//# sourceMappingURL=chunk-WNFBIX4E.js.map
|