@livx.cc/agentx 0.99.78 → 0.99.79
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/dist/{Agent-BBlKI8uD.d.ts → Agent-BK-heHrw.d.ts} +4 -0
- package/dist/cli.d.ts +24 -2
- package/dist/cli.js +686 -245
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +296 -47
- package/dist/index.js.map +1 -1
- package/dist/mcp.client.js +59 -2
- package/dist/mcp.client.js.map +1 -1
- package/dist/tools.shell.d.ts +23 -1
- package/dist/tools.shell.js +157 -19
- package/dist/tools.shell.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -405,6 +405,40 @@ var init_redact = __esm({
|
|
|
405
405
|
}
|
|
406
406
|
});
|
|
407
407
|
|
|
408
|
+
// src/hostPath.ts
|
|
409
|
+
function toVfsPath(p, platform = currentPlatform()) {
|
|
410
|
+
if (!isWinPaths(platform) || typeof p !== "string") return p;
|
|
411
|
+
let s = p.replace(/\\/g, "/");
|
|
412
|
+
s = s.replace(/^\/\/\?\/UNC\//i, "//").replace(/^\/\/\?\//, "");
|
|
413
|
+
const drive = /^([A-Za-z]):(?=\/|$)/.exec(s);
|
|
414
|
+
if (drive) return `/${drive[1].toLowerCase()}${s.slice(2) || "/"}`.replace(/\/+$/, "") || "/";
|
|
415
|
+
if (/^\/\/[^/]/.test(s)) return `/unc/${s.slice(2)}`;
|
|
416
|
+
return s;
|
|
417
|
+
}
|
|
418
|
+
function toHostPath(vpath, platform = currentPlatform()) {
|
|
419
|
+
if (!isWinPaths(platform)) return vpath;
|
|
420
|
+
const segs = vpath.split("/").filter(Boolean);
|
|
421
|
+
if (segs.length >= 1 && /^[A-Za-z]$/.test(segs[0])) return `${segs[0].toUpperCase()}:\\${segs.slice(1).join("\\")}`;
|
|
422
|
+
if (segs.length >= 3 && segs[0].toLowerCase() === "unc") return `\\\\${segs.slice(1).join("\\")}`;
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
function winSegmentProblem(seg) {
|
|
426
|
+
if (seg.includes(":")) return "alternate data stream / drive-relative name";
|
|
427
|
+
if (/[. ]$/.test(seg) && seg !== "." && seg !== "..") return "trailing dot or space";
|
|
428
|
+
if (WIN_DEVICE.test(seg)) return "reserved device name";
|
|
429
|
+
if (/[<>"|?*\x00-\x1f]/.test(seg)) return "invalid character";
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
var currentPlatform, isWinPaths, WIN_DEVICE;
|
|
433
|
+
var init_hostPath = __esm({
|
|
434
|
+
"src/hostPath.ts"() {
|
|
435
|
+
"use strict";
|
|
436
|
+
currentPlatform = () => typeof process !== "undefined" && process.platform || "browser";
|
|
437
|
+
isWinPaths = (platform = currentPlatform()) => platform === "win32";
|
|
438
|
+
WIN_DEVICE = /^(con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³]|conin\$|conout\$)(\..*)?$/i;
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
408
442
|
// src/tools.structured.ts
|
|
409
443
|
function ckAbort(signal) {
|
|
410
444
|
if (signal?.aborted) throw new Error("aborted");
|
|
@@ -447,7 +481,7 @@ async function walkFiles(fs, dir, signal, out = [], st, depth = 0) {
|
|
|
447
481
|
}
|
|
448
482
|
function scanRoots(fs, path, opts) {
|
|
449
483
|
const asked = path == null ? [] : (Array.isArray(path) ? path : [path]).map((p) => String(p)).filter(Boolean);
|
|
450
|
-
const roots = (asked.length ? asked : opts?.roots?.length ? opts.roots : [fsCwd(fs)]).map((r) => r.length > 1 && r.endsWith("/") ? r.slice(0, -1) : r);
|
|
484
|
+
const roots = (asked.length ? asked : opts?.roots?.length ? opts.roots : [fsCwd(fs)]).map((r) => toVfsPath(r)).map((r) => r.length > 1 && r.endsWith("/") ? r.slice(0, -1) : r);
|
|
451
485
|
return [...new Set(roots)].filter((r, _i, all) => !all.some((other) => other !== r && r.startsWith(`${other}/`)));
|
|
452
486
|
}
|
|
453
487
|
function scanNote(states) {
|
|
@@ -676,6 +710,7 @@ function parentDir(abs) {
|
|
|
676
710
|
return i <= 0 ? "/" : abs.slice(0, i);
|
|
677
711
|
}
|
|
678
712
|
async function mkdirp(fs, dir) {
|
|
713
|
+
dir = toVfsPath(dir);
|
|
679
714
|
if (dir === "/" || await fs.exists(dir)) return;
|
|
680
715
|
await mkdirp(fs, parentDir(dir));
|
|
681
716
|
if (!await fs.exists(dir)) await fs.createDir(dir);
|
|
@@ -736,6 +771,7 @@ var init_tools_structured = __esm({
|
|
|
736
771
|
"src/tools.structured.ts"() {
|
|
737
772
|
"use strict";
|
|
738
773
|
init_redact();
|
|
774
|
+
init_hostPath();
|
|
739
775
|
DEFAULT_SKIP_DIRS = ["node_modules", ".git", "dist", ".tmp"];
|
|
740
776
|
DEFAULT_MAX_DEPTH = 24;
|
|
741
777
|
SCAN_PARAMS = {
|
|
@@ -2019,20 +2055,58 @@ var NodeDiskFilesystem;
|
|
|
2019
2055
|
var init_NodeDiskFilesystem = __esm({
|
|
2020
2056
|
"src/NodeDiskFilesystem.ts"() {
|
|
2021
2057
|
"use strict";
|
|
2058
|
+
init_hostPath();
|
|
2022
2059
|
NodeDiskFilesystem = class _NodeDiskFilesystem {
|
|
2023
2060
|
constructor(baseDir, opts = {}) {
|
|
2024
2061
|
this.baseDir = baseDir;
|
|
2025
2062
|
this.opts = { denySymlinks: true, ...opts };
|
|
2063
|
+
this.drives = this.win && (baseDir === "/" || baseDir === "\\");
|
|
2026
2064
|
}
|
|
2027
2065
|
baseDir;
|
|
2028
2066
|
cwd = "/";
|
|
2029
2067
|
opts;
|
|
2068
|
+
win = isWinPaths();
|
|
2069
|
+
/** Windows + baseDir '/': the VFS root is the machine (drives as first segment), not a directory. */
|
|
2070
|
+
drives;
|
|
2030
2071
|
/** Ensure the root dir exists. */
|
|
2031
2072
|
async init() {
|
|
2073
|
+
if (this.drives) return;
|
|
2032
2074
|
await fsp.mkdir(this.baseDir, { recursive: true });
|
|
2033
2075
|
}
|
|
2034
2076
|
real(vpath) {
|
|
2035
|
-
return np.join(this.baseDir, "." + vpath);
|
|
2077
|
+
if (!this.win) return np.join(this.baseDir, "." + vpath);
|
|
2078
|
+
for (const seg of vpath.split("/")) {
|
|
2079
|
+
const why = seg && winSegmentProblem(seg);
|
|
2080
|
+
if (why) throw new Error(`File not found: ${vpath} (${why})`);
|
|
2081
|
+
}
|
|
2082
|
+
if (!this.drives) return np.join(this.baseDir, "." + vpath);
|
|
2083
|
+
const host = toHostPath(vpath);
|
|
2084
|
+
if (!host) throw new Error(`File not found: ${vpath}`);
|
|
2085
|
+
return host;
|
|
2086
|
+
}
|
|
2087
|
+
/** Windows machine root ('/' when baseDir is '/'): a synthetic directory listing the drives. */
|
|
2088
|
+
isDriveRoot(vpath) {
|
|
2089
|
+
return this.drives && vpath === "/";
|
|
2090
|
+
}
|
|
2091
|
+
/** Windows: refuse an 8.3 short name (`ENV~1` opens `.env`) — the only alias that needs the disk to
|
|
2092
|
+
* detect. Only segments that look like one (`~<digit>`) cost a lookup: the OS's long name for that
|
|
2093
|
+
* component must be the name that was asked for. */
|
|
2094
|
+
assertLongNames(real) {
|
|
2095
|
+
if (!this.win || !/~\d/.test(real)) return;
|
|
2096
|
+
const root = np.parse(real).root;
|
|
2097
|
+
const parts = real.slice(root.length).split(/[\\/]/).filter(Boolean);
|
|
2098
|
+
let cur = root;
|
|
2099
|
+
for (const part of parts) {
|
|
2100
|
+
cur = np.join(cur, part);
|
|
2101
|
+
if (!/~\d/.test(part)) continue;
|
|
2102
|
+
let long;
|
|
2103
|
+
try {
|
|
2104
|
+
long = np.basename(realpathSync2.native(cur));
|
|
2105
|
+
} catch {
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
if (long.toLowerCase() !== part.toLowerCase()) throw new Error("File not found: short (8.3) name not permitted");
|
|
2109
|
+
}
|
|
2036
2110
|
}
|
|
2037
2111
|
// Verified non-symlink DIRECTORY components, with a short TTL: tree walks (Glob/Grep) hit the same
|
|
2038
2112
|
// parents thousands of times; re-lstat'ing each per op is the dominant syscall cost. The 1s window
|
|
@@ -2042,11 +2116,13 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2042
2116
|
static VERIFY_TTL_MS = 1e3;
|
|
2043
2117
|
/** Throw if any existing component of `real` is a symlink (escape vector). */
|
|
2044
2118
|
async assertNoSymlink(real) {
|
|
2119
|
+
this.assertLongNames(real);
|
|
2045
2120
|
if (!this.opts.denySymlinks) return;
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2121
|
+
const base = this.drives ? np.parse(real).root : this.baseDir;
|
|
2122
|
+
const rel = np.relative(base, real);
|
|
2123
|
+
if (rel === "" || rel.startsWith("..") || np.isAbsolute(rel)) return;
|
|
2048
2124
|
const parts = rel.split(np.sep);
|
|
2049
|
-
let cur =
|
|
2125
|
+
let cur = base;
|
|
2050
2126
|
const now3 = Date.now();
|
|
2051
2127
|
for (let i = 0; i < parts.length; i++) {
|
|
2052
2128
|
cur = np.join(cur, parts[i]);
|
|
@@ -2066,13 +2142,13 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2066
2142
|
}
|
|
2067
2143
|
}
|
|
2068
2144
|
resolvePath(path, cwd) {
|
|
2069
|
-
return PathResolver.resolve(path, cwd || this.cwd);
|
|
2145
|
+
return PathResolver.resolve(toVfsPath(path), toVfsPath(cwd || this.cwd));
|
|
2070
2146
|
}
|
|
2071
2147
|
getCwd() {
|
|
2072
2148
|
return this.cwd;
|
|
2073
2149
|
}
|
|
2074
2150
|
setCwd(path) {
|
|
2075
|
-
this.cwd = PathResolver.normalize(path);
|
|
2151
|
+
this.cwd = PathResolver.normalize(toVfsPath(path));
|
|
2076
2152
|
}
|
|
2077
2153
|
async readFile(path) {
|
|
2078
2154
|
const r = this.real(this.resolvePath(path));
|
|
@@ -2146,8 +2222,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2146
2222
|
}
|
|
2147
2223
|
}
|
|
2148
2224
|
async readDir(path) {
|
|
2149
|
-
const
|
|
2225
|
+
const v = this.resolvePath(path);
|
|
2226
|
+
if (this.isDriveRoot(v)) return this.listDrives();
|
|
2150
2227
|
try {
|
|
2228
|
+
const r = this.real(v);
|
|
2151
2229
|
await this.assertNoSymlink(r);
|
|
2152
2230
|
return await fsp.readdir(r);
|
|
2153
2231
|
} catch {
|
|
@@ -2167,8 +2245,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2167
2245
|
await fsp.mkdir(r);
|
|
2168
2246
|
}
|
|
2169
2247
|
async exists(path) {
|
|
2170
|
-
const
|
|
2248
|
+
const v = this.resolvePath(path);
|
|
2249
|
+
if (this.isDriveRoot(v)) return true;
|
|
2171
2250
|
try {
|
|
2251
|
+
const r = this.real(v);
|
|
2172
2252
|
await this.assertNoSymlink(r);
|
|
2173
2253
|
await fsp.stat(r);
|
|
2174
2254
|
return true;
|
|
@@ -2178,8 +2258,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2178
2258
|
}
|
|
2179
2259
|
async stat(path) {
|
|
2180
2260
|
let s;
|
|
2181
|
-
const
|
|
2261
|
+
const v = this.resolvePath(path);
|
|
2262
|
+
if (this.isDriveRoot(v)) return { created: /* @__PURE__ */ new Date(0), modified: /* @__PURE__ */ new Date(0), size: 0, permissions: "drwxr-xr-x", isExecutable: false };
|
|
2182
2263
|
try {
|
|
2264
|
+
const r = this.real(v);
|
|
2183
2265
|
await this.assertNoSymlink(r);
|
|
2184
2266
|
s = await fsp.stat(r);
|
|
2185
2267
|
} catch {
|
|
@@ -2194,8 +2276,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2194
2276
|
};
|
|
2195
2277
|
}
|
|
2196
2278
|
async isDirectory(path) {
|
|
2197
|
-
const
|
|
2279
|
+
const v = this.resolvePath(path);
|
|
2280
|
+
if (this.isDriveRoot(v)) return true;
|
|
2198
2281
|
try {
|
|
2282
|
+
const r = this.real(v);
|
|
2199
2283
|
await this.assertNoSymlink(r);
|
|
2200
2284
|
return (await fsp.stat(r)).isDirectory();
|
|
2201
2285
|
} catch {
|
|
@@ -2203,14 +2287,20 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2203
2287
|
}
|
|
2204
2288
|
}
|
|
2205
2289
|
async isFile(path) {
|
|
2206
|
-
const r = this.real(this.resolvePath(path));
|
|
2207
2290
|
try {
|
|
2291
|
+
const r = this.real(this.resolvePath(path));
|
|
2208
2292
|
await this.assertNoSymlink(r);
|
|
2209
2293
|
return (await fsp.stat(r)).isFile();
|
|
2210
2294
|
} catch {
|
|
2211
2295
|
return false;
|
|
2212
2296
|
}
|
|
2213
2297
|
}
|
|
2298
|
+
/** Windows machine root: the drive letters that are mounted (`/c`, `/d`, …). */
|
|
2299
|
+
async listDrives() {
|
|
2300
|
+
const letters = "abcdefghijklmnopqrstuvwxyz".split("");
|
|
2301
|
+
const up = await Promise.all(letters.map((l) => fsp.stat(`${l.toUpperCase()}:\\`).then(() => true, () => false)));
|
|
2302
|
+
return letters.filter((_, i) => up[i]);
|
|
2303
|
+
}
|
|
2214
2304
|
};
|
|
2215
2305
|
}
|
|
2216
2306
|
});
|
|
@@ -2227,6 +2317,7 @@ var init_JailedFilesystem = __esm({
|
|
|
2227
2317
|
"src/JailedFilesystem.ts"() {
|
|
2228
2318
|
"use strict";
|
|
2229
2319
|
init_tools_structured();
|
|
2320
|
+
init_hostPath();
|
|
2230
2321
|
JailedFilesystem = class {
|
|
2231
2322
|
constructor(inner, options) {
|
|
2232
2323
|
this.inner = inner;
|
|
@@ -2240,8 +2331,10 @@ var init_JailedFilesystem = __esm({
|
|
|
2240
2331
|
denyRe;
|
|
2241
2332
|
roRe;
|
|
2242
2333
|
confineRe;
|
|
2334
|
+
/** Resolved VFS path — host-path syntax (Windows `C:\…`, backslashes) is normalized FIRST, so the
|
|
2335
|
+
* deny/readonly/confine globs always match the path that is actually opened. */
|
|
2243
2336
|
abs(path) {
|
|
2244
|
-
return this.inner.resolvePath(path, this.inner.getCwd());
|
|
2337
|
+
return this.inner.resolvePath(toVfsPath(path), this.inner.getCwd());
|
|
2245
2338
|
}
|
|
2246
2339
|
/** Throw if reading `path` is not permitted (denied or out of confinement). */
|
|
2247
2340
|
guardRead(op, path) {
|
|
@@ -2326,7 +2419,7 @@ var init_JailedFilesystem = __esm({
|
|
|
2326
2419
|
return entries.filter((name) => this.visible(abs === "/" ? `/${name}` : `${abs}/${name}`));
|
|
2327
2420
|
}
|
|
2328
2421
|
resolvePath(path, cwd) {
|
|
2329
|
-
return this.inner.resolvePath(path, cwd ?? this.inner.getCwd());
|
|
2422
|
+
return this.inner.resolvePath(toVfsPath(path), cwd ?? this.inner.getCwd());
|
|
2330
2423
|
}
|
|
2331
2424
|
getCwd() {
|
|
2332
2425
|
return this.inner.getCwd();
|
|
@@ -2444,6 +2537,125 @@ var init_shell_sandbox = __esm({
|
|
|
2444
2537
|
}
|
|
2445
2538
|
});
|
|
2446
2539
|
|
|
2540
|
+
// src/platform.ts
|
|
2541
|
+
import { spawnSync as spawnSync2, execFile } from "child_process";
|
|
2542
|
+
import { statSync as statSync4 } from "fs";
|
|
2543
|
+
import * as np2 from "path";
|
|
2544
|
+
function specFor(bin) {
|
|
2545
|
+
const name = np2.basename(bin).toLowerCase().replace(/\.exe$/, "");
|
|
2546
|
+
if (name === "powershell" || name === "pwsh") {
|
|
2547
|
+
const pre = "$ProgressPreference='SilentlyContinue';[Console]::OutputEncoding=[Text.Encoding]::UTF8;$OutputEncoding=[Text.Encoding]::UTF8;";
|
|
2548
|
+
return {
|
|
2549
|
+
bin,
|
|
2550
|
+
kind: "powershell",
|
|
2551
|
+
label: name === "pwsh" ? "PowerShell (pwsh)" : "Windows PowerShell",
|
|
2552
|
+
args: (c) => ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(pre + c, "utf16le").toString("base64")]
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
if (name === "cmd") return { bin, kind: "cmd", label: "cmd.exe", args: (c) => ["/d", "/s", "/c", `"${c}"`], spawnOptions: { windowsVerbatimArguments: true } };
|
|
2556
|
+
return { bin, kind: "sh", label: /bash/.test(name) && /git/i.test(bin) ? "Git Bash" : name, args: (c) => ["-c", c] };
|
|
2557
|
+
}
|
|
2558
|
+
function findGitBash(env = process.env) {
|
|
2559
|
+
const roots = [];
|
|
2560
|
+
const git2 = findOnPath("git", env);
|
|
2561
|
+
if (git2) {
|
|
2562
|
+
const dir = np2.dirname(git2);
|
|
2563
|
+
const up = (p, n) => {
|
|
2564
|
+
for (let i = 0; i < n; i++) p = np2.dirname(p);
|
|
2565
|
+
return p;
|
|
2566
|
+
};
|
|
2567
|
+
roots.push(/[\\/](mingw(32|64)|ucrt64|clang(32|64|arm64))[\\/]bin$/i.test(dir) ? up(dir, 2) : up(dir, 1));
|
|
2568
|
+
}
|
|
2569
|
+
for (const base of [env.ProgramFiles, env["ProgramFiles(x86)"], env.ProgramW6432, env.LOCALAPPDATA && np2.join(env.LOCALAPPDATA, "Programs")])
|
|
2570
|
+
if (base) roots.push(np2.join(base, "Git"));
|
|
2571
|
+
for (const r of roots) {
|
|
2572
|
+
const b = np2.join(r, "bin", "bash.exe");
|
|
2573
|
+
if (isFile(b)) return b;
|
|
2574
|
+
}
|
|
2575
|
+
return void 0;
|
|
2576
|
+
}
|
|
2577
|
+
function defaultShell() {
|
|
2578
|
+
if (!IS_WIN) return POSIX_SH;
|
|
2579
|
+
if (_shell) return _shell;
|
|
2580
|
+
const override = process.env.AGENTX_SHELL;
|
|
2581
|
+
if (override && (isFile(override) || findOnPath(override))) return _shell = specFor(isFile(override) ? override : findOnPath(override));
|
|
2582
|
+
const bash = findGitBash();
|
|
2583
|
+
if (bash) return _shell = specFor(bash);
|
|
2584
|
+
const sysRoot = process.env.SystemRoot || "C:\\Windows";
|
|
2585
|
+
const ps = np2.join(sysRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
2586
|
+
if (isFile(ps)) return _shell = specFor(ps);
|
|
2587
|
+
return _shell = specFor(process.env.ComSpec || "cmd.exe");
|
|
2588
|
+
}
|
|
2589
|
+
function shellFor(shell) {
|
|
2590
|
+
if (!IS_WIN) return shell ? { ...POSIX_SH, bin: shell } : POSIX_SH;
|
|
2591
|
+
return shell && isFile(shell) ? specFor(shell) : defaultShell();
|
|
2592
|
+
}
|
|
2593
|
+
function killTree(pid, signal) {
|
|
2594
|
+
if (!pid) return false;
|
|
2595
|
+
if (!IS_WIN) {
|
|
2596
|
+
try {
|
|
2597
|
+
process.kill(-pid, signal);
|
|
2598
|
+
return true;
|
|
2599
|
+
} catch {
|
|
2600
|
+
return false;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
try {
|
|
2604
|
+
const r = spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 1e4 });
|
|
2605
|
+
return r.status === 0;
|
|
2606
|
+
} catch {
|
|
2607
|
+
return false;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
async function winTreeCpuSeconds(root) {
|
|
2611
|
+
const script = 'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ProcessId) $($_.ParentProcessId) $([uint64]$_.UserModeTime + [uint64]$_.KernelModeTime)" }';
|
|
2612
|
+
const out = await new Promise((res, rej) => execFile(
|
|
2613
|
+
"powershell.exe",
|
|
2614
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
2615
|
+
{ timeout: 15e3, windowsHide: true },
|
|
2616
|
+
(e, so) => e ? rej(e) : res(String(so))
|
|
2617
|
+
));
|
|
2618
|
+
const rows = out.split(/\r?\n/).map((l) => l.trim().split(/\s+/)).filter((r) => r.length === 3).map(([pid, ppid, ticks]) => ({ pid: Number(pid), ppid: Number(ppid), sec: Number(ticks) / 1e7 }));
|
|
2619
|
+
const inTree = new Set(rows.filter((r) => r.pid === root).map((r) => r.pid));
|
|
2620
|
+
for (let grew = true; grew; ) {
|
|
2621
|
+
grew = false;
|
|
2622
|
+
for (const r of rows) if (!inTree.has(r.pid) && inTree.has(r.ppid) && r.pid !== r.ppid) {
|
|
2623
|
+
inTree.add(r.pid);
|
|
2624
|
+
grew = true;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
let total;
|
|
2628
|
+
for (const r of rows) if (inTree.has(r.pid) && Number.isFinite(r.sec)) total = (total ?? 0) + r.sec;
|
|
2629
|
+
return total;
|
|
2630
|
+
}
|
|
2631
|
+
function findOnPath(cmd, env = process.env) {
|
|
2632
|
+
const exts = IS_WIN && !/\.[^\\/.]+$/.test(cmd) ? (env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
2633
|
+
const cands = (base) => exts.map((e) => base + e);
|
|
2634
|
+
if (/[\\/]/.test(cmd)) return cands(cmd).find(isFile);
|
|
2635
|
+
const PATH = env.PATH ?? env.Path ?? "";
|
|
2636
|
+
for (const dir of PATH.split(np2.delimiter)) {
|
|
2637
|
+
if (!dir) continue;
|
|
2638
|
+
for (const p of cands(np2.join(dir, cmd))) if (isFile(p)) return p;
|
|
2639
|
+
}
|
|
2640
|
+
return void 0;
|
|
2641
|
+
}
|
|
2642
|
+
var IS_WIN, POSIX_SH, isFile, _shell, DETACH_OPTIONS;
|
|
2643
|
+
var init_platform = __esm({
|
|
2644
|
+
"src/platform.ts"() {
|
|
2645
|
+
"use strict";
|
|
2646
|
+
IS_WIN = process.platform === "win32";
|
|
2647
|
+
POSIX_SH = { bin: "/bin/sh", args: (c) => ["-c", c], label: "/bin/sh", kind: "sh" };
|
|
2648
|
+
isFile = (p) => {
|
|
2649
|
+
try {
|
|
2650
|
+
return statSync4(p).isFile();
|
|
2651
|
+
} catch {
|
|
2652
|
+
return false;
|
|
2653
|
+
}
|
|
2654
|
+
};
|
|
2655
|
+
DETACH_OPTIONS = IS_WIN ? { windowsHide: true } : { detached: true };
|
|
2656
|
+
}
|
|
2657
|
+
});
|
|
2658
|
+
|
|
2447
2659
|
// src/tools.shell.ts
|
|
2448
2660
|
var tools_shell_exports = {};
|
|
2449
2661
|
__export(tools_shell_exports, {
|
|
@@ -2453,19 +2665,14 @@ __export(tools_shell_exports, {
|
|
|
2453
2665
|
makeShellJobTools: () => makeShellJobTools,
|
|
2454
2666
|
parsePsTime: () => parsePsTime,
|
|
2455
2667
|
psGroupCpuSeconds: () => psGroupCpuSeconds,
|
|
2456
|
-
sumTreeCpu: () => sumTreeCpu
|
|
2668
|
+
sumTreeCpu: () => sumTreeCpu,
|
|
2669
|
+
windowsShellNote: () => windowsShellNote
|
|
2457
2670
|
});
|
|
2458
2671
|
function killGroup(proc, signal) {
|
|
2459
|
-
|
|
2460
|
-
try {
|
|
2461
|
-
process.kill(-proc.pid, signal);
|
|
2462
|
-
return true;
|
|
2463
|
-
} catch {
|
|
2464
|
-
return false;
|
|
2465
|
-
}
|
|
2672
|
+
return killTree(proc?.pid, signal);
|
|
2466
2673
|
}
|
|
2467
2674
|
async function spawnArgvFor(command, cwd, osSandbox) {
|
|
2468
|
-
if (!osSandbox) return
|
|
2675
|
+
if (!osSandbox) return shellArgv(defaultShell(), command);
|
|
2469
2676
|
const opts = osSandbox === true ? {} : osSandbox;
|
|
2470
2677
|
const wrapper = await findSandboxWrapper();
|
|
2471
2678
|
const wrapped = wrapper ? sandboxArgv(command, cwd, opts, process.platform, process.env.TMPDIR) : null;
|
|
@@ -2493,9 +2700,17 @@ function parsePsTime(t) {
|
|
|
2493
2700
|
return Number(m[1] ?? 0) * 86400 + Number(m[2] ?? 0) * 3600 + Number(m[3]) * 60 + Number(m[4]);
|
|
2494
2701
|
}
|
|
2495
2702
|
async function psGroupCpuSeconds(pgid) {
|
|
2703
|
+
if (IS_WIN) {
|
|
2704
|
+
try {
|
|
2705
|
+
return await winTreeCpuSeconds(pgid);
|
|
2706
|
+
} catch (e) {
|
|
2707
|
+
log9.debug(`cpu probe failed for ${pgid}: ${e}`);
|
|
2708
|
+
return void 0;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2496
2711
|
try {
|
|
2497
|
-
const { execFile } = await import("child_process");
|
|
2498
|
-
const out = await new Promise((res, rej) =>
|
|
2712
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
2713
|
+
const out = await new Promise((res, rej) => execFile2("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
|
|
2499
2714
|
return sumTreeCpu(out, pgid);
|
|
2500
2715
|
} catch (e) {
|
|
2501
2716
|
log9.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
@@ -2546,7 +2761,7 @@ function makeRealShellTool(options) {
|
|
|
2546
2761
|
return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
|
|
2547
2762
|
},
|
|
2548
2763
|
get description() {
|
|
2549
|
-
return
|
|
2764
|
+
return `Run a shell command via ${shellFor(options.shell).label} in the working directory. ` + windowsShellNote(shellFor(options.shell)) + "Executes any installed binary \u2014 ls, cat, grep, git, bun, node, curl, scripts, etc. Returns combined stdout+stderr; non-zero exits are prefixed `[exit N]`. Runs non-interactively with no terminal (stdin is /dev/null): commands that prompt for input fail fast rather than hang \u2014 for privileged actions use a non-interactive flag (e.g. `sudo -n`), or ask the user to run the command themselves. " + (options.registry?.adopt ? `A command still running after ${defaultTimeoutMs}ms is NOT killed \u2014 it is handed to a background job and you get its id, so you can keep checking on it (\`[still running]\`). Pass \`timeoutMs\` (max ${maxTimeoutMs}) to wait longer in the foreground, and always bound network commands yourself (e.g. \`curl -m 10\`). ` : `Each command is killed after ${defaultTimeoutMs}ms (result \`[exit 124]\` with whatever output it produced) \u2014 pass \`timeoutMs\` (max ${maxTimeoutMs}) for a legitimately slower command, and always bound network commands yourself (e.g. \`curl -m 10\`). `) + backgroundDoc();
|
|
2550
2765
|
},
|
|
2551
2766
|
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2552
2767
|
maxDurationMs: maxTimeoutMs,
|
|
@@ -2570,7 +2785,7 @@ function makeRealShellTool(options) {
|
|
|
2570
2785
|
return `Started background job ${id}. Poll output with ShellOutput({id:"${id}"}), check ShellStatus({id:"${id}"}), stop with ShellKill({id:"${id}"}).`;
|
|
2571
2786
|
}
|
|
2572
2787
|
const spawn = options.spawn ?? await nodeSpawn();
|
|
2573
|
-
let argv =
|
|
2788
|
+
let argv = shellArgv(shellFor(options.shell), cmd);
|
|
2574
2789
|
if (options.osSandbox) {
|
|
2575
2790
|
try {
|
|
2576
2791
|
argv = await spawnArgvFor(cmd, options.cwd, options.osSandbox);
|
|
@@ -2605,22 +2820,23 @@ function makeRealShellTool(options) {
|
|
|
2605
2820
|
}
|
|
2606
2821
|
};
|
|
2607
2822
|
try {
|
|
2608
|
-
return await new Promise((
|
|
2823
|
+
return await new Promise((resolve3) => {
|
|
2609
2824
|
let out = "";
|
|
2610
2825
|
let settled = false;
|
|
2611
2826
|
const finish = (s) => {
|
|
2612
2827
|
if (settled) return;
|
|
2613
2828
|
settled = true;
|
|
2614
|
-
|
|
2829
|
+
resolve3(s);
|
|
2615
2830
|
};
|
|
2616
2831
|
let proc;
|
|
2617
2832
|
try {
|
|
2618
|
-
|
|
2833
|
+
if (IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
2834
|
+
proc = spawn(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal, ...DETACHED, ...argv.spawnOptions });
|
|
2619
2835
|
} catch (e) {
|
|
2620
2836
|
return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
|
|
2621
2837
|
}
|
|
2622
2838
|
if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
|
|
2623
|
-
else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
2839
|
+
else if (!IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
2624
2840
|
const registry = options.registry;
|
|
2625
2841
|
if (registry?.adopt) {
|
|
2626
2842
|
onDeadline = () => {
|
|
@@ -2665,6 +2881,12 @@ function makeRealShellTool(options) {
|
|
|
2665
2881
|
}
|
|
2666
2882
|
};
|
|
2667
2883
|
}
|
|
2884
|
+
function windowsShellNote(sh) {
|
|
2885
|
+
if (!IS_WIN) return "";
|
|
2886
|
+
if (sh.kind === "sh") return "This is Windows: the shell is bash (POSIX syntax); drive paths are written /c/Users/\u2026 or C:/Users/\u2026, and Windows programs (.exe/.cmd) run directly. ";
|
|
2887
|
+
if (sh.kind === "powershell") return "This is Windows and the shell is PowerShell \u2014 use PowerShell syntax (`;` not `&&`, `$env:VAR`, Get-ChildItem/Select-String), not bash. ";
|
|
2888
|
+
return "This is Windows and the shell is cmd.exe \u2014 use cmd syntax (`&&`, `%VAR%`, dir/type/findstr), not bash. ";
|
|
2889
|
+
}
|
|
2668
2890
|
function handoffFor(timeoutMs, id, body, notifies) {
|
|
2669
2891
|
const head = `[still running] exceeded ${timeoutMs}ms, so it was handed to background job ${id} \u2014 NOT killed, it is still going (it is only stopped if it goes silent with no CPU activity for a long time, and then you are told why). Check on it with ShellOutput({id:"${id}"}) / ShellStatus({id:"${id}"}), stop it with ShellKill({id:"${id}"}). ` + (notifies ? "Its completion will be reported to you, so you may continue with other work meanwhile." : "Nothing will tell you when it finishes \u2014 poll it yourself before you rely on its result.");
|
|
2670
2892
|
return body ? `${head}
|
|
@@ -2719,7 +2941,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2719
2941
|
}
|
|
2720
2942
|
];
|
|
2721
2943
|
}
|
|
2722
|
-
var log9, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
2944
|
+
var log9, clean, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
|
|
2723
2945
|
var init_tools_shell = __esm({
|
|
2724
2946
|
"src/tools.shell.ts"() {
|
|
2725
2947
|
"use strict";
|
|
@@ -2728,9 +2950,11 @@ var init_tools_shell = __esm({
|
|
|
2728
2950
|
init_logging();
|
|
2729
2951
|
init_shell_sandbox();
|
|
2730
2952
|
init_tools_jobs();
|
|
2953
|
+
init_platform();
|
|
2731
2954
|
log9 = forComponent("shell");
|
|
2732
2955
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
2733
|
-
DETACHED = { stdio: ["ignore", "pipe", "pipe"],
|
|
2956
|
+
DETACHED = { stdio: ["ignore", "pipe", "pipe"], ...DETACH_OPTIONS };
|
|
2957
|
+
shellArgv = (sh, command) => ({ bin: sh.bin, args: sh.args(command), ...sh.spawnOptions ? { spawnOptions: sh.spawnOptions } : {} });
|
|
2734
2958
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
2735
2959
|
ShellJobRegistry = class {
|
|
2736
2960
|
constructor(cfg) {
|
|
@@ -2745,8 +2969,8 @@ var init_tools_shell = __esm({
|
|
|
2745
2969
|
const h = this.track(command, { idleMs: 0 });
|
|
2746
2970
|
try {
|
|
2747
2971
|
const spawn = this.cfg.spawn ?? await nodeSpawn();
|
|
2748
|
-
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) :
|
|
2749
|
-
this.attach(h, spawn(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg), ...DETACHED }));
|
|
2972
|
+
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) : shellArgv(defaultShell(), command);
|
|
2973
|
+
this.attach(h, spawn(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg), ...DETACHED, ...argv.spawnOptions }));
|
|
2750
2974
|
} catch (e) {
|
|
2751
2975
|
h.chunk(`failed to spawn: ${e?.message ?? e}`);
|
|
2752
2976
|
h.finish("error", void 0, e?.message ?? String(e));
|
|
@@ -3705,7 +3929,7 @@ init_OverlayFilesystem();
|
|
|
3705
3929
|
// src/worktree.ts
|
|
3706
3930
|
import { spawnSync } from "child_process";
|
|
3707
3931
|
import { symlinkSync, mkdirSync, statSync as statSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
3708
|
-
import { join } from "path";
|
|
3932
|
+
import { join, resolve as resolve2 } from "path";
|
|
3709
3933
|
var VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
|
|
3710
3934
|
var MAX_SLUG_LENGTH = 64;
|
|
3711
3935
|
var GIT_NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
|
|
@@ -3739,7 +3963,7 @@ function git(args, cwd) {
|
|
|
3739
3963
|
}
|
|
3740
3964
|
function findGitRoot(from) {
|
|
3741
3965
|
const r = git(["rev-parse", "--show-toplevel"], from);
|
|
3742
|
-
return r.ok ? r.stdout : null;
|
|
3966
|
+
return r.ok ? process.platform === "win32" ? resolve2(r.stdout) : r.stdout : null;
|
|
3743
3967
|
}
|
|
3744
3968
|
function readWorktreeHead(worktreePath) {
|
|
3745
3969
|
try {
|
|
@@ -3793,7 +4017,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
|
3793
4017
|
continue;
|
|
3794
4018
|
}
|
|
3795
4019
|
try {
|
|
3796
|
-
symlinkSync(src, dst, "dir");
|
|
4020
|
+
symlinkSync(src, dst, process.platform === "win32" ? "junction" : "dir");
|
|
3797
4021
|
} catch (e) {
|
|
3798
4022
|
if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
|
|
3799
4023
|
}
|
|
@@ -4331,6 +4555,10 @@ var AgentOptions = class {
|
|
|
4331
4555
|
model = "anthropic/claude-sonnet-4-6";
|
|
4332
4556
|
systemPrompt = "You are a coding agent operating on a virtual filesystem. Use the `bash` tool to explore (ls, grep, find, cat) and the `Read`/`Edit` tools to view and modify files. Always Read a file before Editing it \u2014 and before overwriting it with `Write` or a whole-file `ApplyEdits`: Write replaces the ENTIRE file, so blindly writing over an existing one destroys its unseen contents. If a file already exists, Read it first, then merge your changes (or Edit the specific part). When multiple tool calls are independent (e.g. several Reads or Greps with no dependency between them), issue them together in a single turn rather than one at a time. When the task is complete, reply with a short summary and make no further tool calls.\nRecovery: every turn's file changes are checkpointed. If the user asks to undo/revert/recover edits (including a file you overwrote), tell them to run `/rewind` (or `/undo` for the last turn) to restore the working tree \u2014 checkpoints cover files you edited even in .gitignored dirs. You cannot run `/rewind` yourself; surface it as the recovery path.\nWhen working with a knowledge base or document workspace: use `RepoMap` with scope \"docs\" to see all document headings and summaries (orient first); use `MemorySearch` to find memories by content when you don't know the exact slug; use `Recall` with multiple slugs to batch-load related facts in one call. Prefer targeted loads over reading everything \u2014 orient \u2192 search \u2192 load \u2192 act.";
|
|
4333
4557
|
tools = defaultTools();
|
|
4558
|
+
/** claude-code/* only: which agentx tools reach it as the in-process `agentx` MCP server (ccBridge.ts). 'memory' =
|
|
4559
|
+
* the ones Claude Code has no native twin for (CC_BRIDGED); 'all' = every tool this agent has — for an agent whose
|
|
4560
|
+
* tools ARE its job (the duplex reflex: Act/Think/Hold…), run with Claude Code's own tools off (providerOptions.tools: []). */
|
|
4561
|
+
ccBridge = "memory";
|
|
4334
4562
|
maxSteps = 25;
|
|
4335
4563
|
// --- automatic kill-switches (always on; protect the API budget against runaway loops/abuse) ---
|
|
4336
4564
|
/** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
|
|
@@ -4926,8 +5154,9 @@ var Agent = class _Agent {
|
|
|
4926
5154
|
};
|
|
4927
5155
|
const cursorPo = isCursorWithTools ? { toolTimeoutMs: effectiveToolTimeoutMs, toolExecutor: hostExec } : void 0;
|
|
4928
5156
|
const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
|
|
4929
|
-
|
|
4930
|
-
|
|
5157
|
+
const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name);
|
|
5158
|
+
if (claudeSteerPo && o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
|
|
5159
|
+
this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
|
|
4931
5160
|
claudeSteerPo.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
|
|
4932
5161
|
}
|
|
4933
5162
|
if (this.ccServer) this.ccExec = hostExec;
|
|
@@ -9134,6 +9363,10 @@ function describeCall(call) {
|
|
|
9134
9363
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
9135
9364
|
return `${call.name}${hint}`;
|
|
9136
9365
|
}
|
|
9366
|
+
function refusedCall(e) {
|
|
9367
|
+
const code = Number(e?.statusCode ?? e?.status);
|
|
9368
|
+
return code === 401 || code === 403 || /API key|unauthori[sz]ed|authentication|invalid x-api-key|permission denied/i.test(String(e?.message ?? e ?? ""));
|
|
9369
|
+
}
|
|
9137
9370
|
var DuplexAgentOptions = class {
|
|
9138
9371
|
/** Any ai.libx.js AIClient — shared by all tiers (routed by model). */
|
|
9139
9372
|
ai;
|
|
@@ -9423,12 +9656,26 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9423
9656
|
timeoutMs: 3e4,
|
|
9424
9657
|
failEmptyTurns: false,
|
|
9425
9658
|
// a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
|
|
9659
|
+
...this.claudeCodeReflex(),
|
|
9426
9660
|
...o.reflexOptions,
|
|
9427
9661
|
tools,
|
|
9428
9662
|
// Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
|
|
9429
9663
|
hooks: composeHooks(this.dispatchGuard(), o.reflexOptions?.hooks)
|
|
9430
9664
|
});
|
|
9431
9665
|
}
|
|
9666
|
+
/** A claude-code/* reflex (the keyless stand-in when the fast model's provider has no key): Claude Code runs its own
|
|
9667
|
+
* tool loop and takes no host tool schemas, so the reflex's tools (Act/Think/Hold…) reach it through the in-process
|
|
9668
|
+
* `agentx` MCP bridge (ccBridge 'all', which needs a warm session), with Claude Code's OWN tools off (`tools: []`) and
|
|
9669
|
+
* no other MCP server — the reflex only talks and routes; real work still goes through Act and its policy. */
|
|
9670
|
+
claudeCodeReflex() {
|
|
9671
|
+
const o = this.options;
|
|
9672
|
+
if (!o.reflexModel.startsWith("claude-code/")) return {};
|
|
9673
|
+
const po = o.providerOptionsFor?.(o.reflexModel) ?? {};
|
|
9674
|
+
return {
|
|
9675
|
+
ccBridge: "all",
|
|
9676
|
+
providerOptions: { ...po, claudeCodeSession: po.claudeCodeSession ?? crypto.randomUUID(), tools: [], mcpServers: {}, strictMcpConfig: true }
|
|
9677
|
+
};
|
|
9678
|
+
}
|
|
9432
9679
|
/** Resolve memory tools + inject index into voice system prompt (once). */
|
|
9433
9680
|
async initMemory() {
|
|
9434
9681
|
if (!this.memoryReady) return;
|
|
@@ -9593,7 +9840,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9593
9840
|
await this.drainReflex();
|
|
9594
9841
|
this.scrubLeakFromTranscript();
|
|
9595
9842
|
this.scrubHeldText();
|
|
9596
|
-
if (this.silentTurn)
|
|
9843
|
+
if (this.silentTurn && res?.finishReason === "error" && refusedCall(res.error)) this.emitHost({ kind: "text_delta", message: fallback ?? _DuplexAgent.FALLBACK_ERROR });
|
|
9844
|
+
else if (this.silentTurn) await this.ackIfSilent(fallback);
|
|
9597
9845
|
}
|
|
9598
9846
|
/** Remove complete stage-direction parentheticals from the UNFORWARDED reflex text (STAGE_DIRECTION_RE).
|
|
9599
9847
|
* Only the unforwarded region is touched — already-spoken audio can't be unsent, and splicing before
|
|
@@ -9699,6 +9947,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9699
9947
|
* hosts/tests key on it. */
|
|
9700
9948
|
static FALLBACK_ACKS = ["Okay, on it.", "On it.", "Alright, working on it."];
|
|
9701
9949
|
static FALLBACK_RETRY = ["Sorry, could you say that again?", "Hm, I missed that \u2014 could you say that again?"];
|
|
9950
|
+
static FALLBACK_ERROR = "Sorry, I couldn't answer that \u2014 something went wrong on my side.";
|
|
9702
9951
|
static FALLBACK_OWN = ["Sorry, I lost my train of thought there \u2014 give me that one more time?", "Hm, I got tangled up \u2014 ask me that once more?"];
|
|
9703
9952
|
lastFallback = "";
|
|
9704
9953
|
/** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns.
|
|
@@ -10357,14 +10606,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10357
10606
|
* '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
|
|
10358
10607
|
* task settle (callers map '' to deny / best-judgment). Workers never block forever. */
|
|
10359
10608
|
parkQuestion(askId, question) {
|
|
10360
|
-
return new Promise((
|
|
10609
|
+
return new Promise((resolve3) => {
|
|
10361
10610
|
let settled = false;
|
|
10362
10611
|
const finish = (answer) => {
|
|
10363
10612
|
if (settled) return;
|
|
10364
10613
|
settled = true;
|
|
10365
10614
|
clearTimeout(timer);
|
|
10366
10615
|
this.pendingAsks.delete(askId);
|
|
10367
|
-
|
|
10616
|
+
resolve3(answer);
|
|
10368
10617
|
};
|
|
10369
10618
|
const timer = setTimeout(() => {
|
|
10370
10619
|
this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
|
|
@@ -11111,11 +11360,11 @@ function buildMcpCatalog(servers) {
|
|
|
11111
11360
|
}
|
|
11112
11361
|
return { specs, routes };
|
|
11113
11362
|
}
|
|
11114
|
-
function searchOverCatalog(servers, specs, routes,
|
|
11363
|
+
function searchOverCatalog(servers, specs, routes, resolve3, options) {
|
|
11115
11364
|
const tools = specs.length ? makeMcpToolSearch(specs, (name, args, o) => {
|
|
11116
11365
|
const r = routes.get(name);
|
|
11117
11366
|
if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
|
|
11118
|
-
return
|
|
11367
|
+
return resolve3(r.server, r.rawName, args ?? {}, o);
|
|
11119
11368
|
}, options) : [];
|
|
11120
11369
|
return { tools, serverNames: servers, toolCount: specs.length };
|
|
11121
11370
|
}
|
|
@@ -11124,9 +11373,9 @@ function makeMcpToolSearchFromMounted(mounted, options) {
|
|
|
11124
11373
|
const byName = new Map(mounted.map((m) => [m.name, m]));
|
|
11125
11374
|
return searchOverCatalog(mounted.map((m) => m.name), specs, routes, (server, rawName, args, o) => byName.get(server).client.callTool(rawName, args, o), options);
|
|
11126
11375
|
}
|
|
11127
|
-
function makeLazyMcpToolSearch(servers,
|
|
11376
|
+
function makeLazyMcpToolSearch(servers, resolve3, options) {
|
|
11128
11377
|
const { specs, routes } = buildMcpCatalog(servers);
|
|
11129
|
-
return searchOverCatalog(servers.map((s) => s.name), specs, routes,
|
|
11378
|
+
return searchOverCatalog(servers.map((s) => s.name), specs, routes, resolve3, options);
|
|
11130
11379
|
}
|
|
11131
11380
|
|
|
11132
11381
|
// src/hooks.ts
|