@livx.cc/agentx 0.99.77 → 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 +690 -248
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.js +300 -50
- 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
|
@@ -361,13 +361,13 @@ function clauses(sentence) {
|
|
|
361
361
|
return sentence.split(/[,;:]\s*|\s+(?:so|but|and|then|because|however|therefore|thus|although|though)\s+/i).map((c) => c.trim()).filter(Boolean);
|
|
362
362
|
}
|
|
363
363
|
function endsOnAnnouncedAction(text) {
|
|
364
|
-
const sentences = text.split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter(Boolean);
|
|
364
|
+
const sentences = text.split(/(?<=[.!?])\s+|\n+/).map((s) => s.replace(/^[\s#>*_`-]+|[*_`]+/g, "").trim()).filter(Boolean);
|
|
365
365
|
const last = sentences[sentences.length - 1];
|
|
366
|
-
if (!last || NEXT_STEP.test(
|
|
366
|
+
if (!last || sentences.slice(-2).some((s) => NEXT_STEP.test(s)) || SIGN_OFF.test(last) || AWAITS_USER.test(last)) return false;
|
|
367
367
|
if (PROGRESSIVE.test(last)) return true;
|
|
368
368
|
return clauses(last).some((c) => !NEXT_STEP.test(c) && !SIGN_OFF.test(c) && COMMITMENT.test(c));
|
|
369
369
|
}
|
|
370
|
-
var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
|
|
370
|
+
var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF, AWAITS_USER;
|
|
371
371
|
var init_llm = __esm({
|
|
372
372
|
"src/llm.ts"() {
|
|
373
373
|
"use strict";
|
|
@@ -385,6 +385,7 @@ var init_llm = __esm({
|
|
|
385
385
|
PROGRESSIVE = /^(?:also\s+|now\s+|then\s+)*(?:re-?)?(?:check|search|scan|read|verify|run|inspect|expand|look|examin|continu|proceed|try|fetch|load|open|test|build|grep|find|review|trac|dig|explor)\w*ing\b/i;
|
|
386
386
|
NEXT_STEP = /^next (?:step|run|up)\b/i;
|
|
387
387
|
SIGN_OFF = /^let me know\b/i;
|
|
388
|
+
AWAITS_USER = /^(?:do you )?(?:want|need) me to\b|\b(?:once|as soon as|after) you\b|\bwhen you(?:'re| are) ready\b/i;
|
|
388
389
|
}
|
|
389
390
|
});
|
|
390
391
|
|
|
@@ -404,6 +405,40 @@ var init_redact = __esm({
|
|
|
404
405
|
}
|
|
405
406
|
});
|
|
406
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
|
+
|
|
407
442
|
// src/tools.structured.ts
|
|
408
443
|
function ckAbort(signal) {
|
|
409
444
|
if (signal?.aborted) throw new Error("aborted");
|
|
@@ -446,7 +481,7 @@ async function walkFiles(fs, dir, signal, out = [], st, depth = 0) {
|
|
|
446
481
|
}
|
|
447
482
|
function scanRoots(fs, path, opts) {
|
|
448
483
|
const asked = path == null ? [] : (Array.isArray(path) ? path : [path]).map((p) => String(p)).filter(Boolean);
|
|
449
|
-
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);
|
|
450
485
|
return [...new Set(roots)].filter((r, _i, all) => !all.some((other) => other !== r && r.startsWith(`${other}/`)));
|
|
451
486
|
}
|
|
452
487
|
function scanNote(states) {
|
|
@@ -675,6 +710,7 @@ function parentDir(abs) {
|
|
|
675
710
|
return i <= 0 ? "/" : abs.slice(0, i);
|
|
676
711
|
}
|
|
677
712
|
async function mkdirp(fs, dir) {
|
|
713
|
+
dir = toVfsPath(dir);
|
|
678
714
|
if (dir === "/" || await fs.exists(dir)) return;
|
|
679
715
|
await mkdirp(fs, parentDir(dir));
|
|
680
716
|
if (!await fs.exists(dir)) await fs.createDir(dir);
|
|
@@ -735,6 +771,7 @@ var init_tools_structured = __esm({
|
|
|
735
771
|
"src/tools.structured.ts"() {
|
|
736
772
|
"use strict";
|
|
737
773
|
init_redact();
|
|
774
|
+
init_hostPath();
|
|
738
775
|
DEFAULT_SKIP_DIRS = ["node_modules", ".git", "dist", ".tmp"];
|
|
739
776
|
DEFAULT_MAX_DEPTH = 24;
|
|
740
777
|
SCAN_PARAMS = {
|
|
@@ -2018,20 +2055,58 @@ var NodeDiskFilesystem;
|
|
|
2018
2055
|
var init_NodeDiskFilesystem = __esm({
|
|
2019
2056
|
"src/NodeDiskFilesystem.ts"() {
|
|
2020
2057
|
"use strict";
|
|
2058
|
+
init_hostPath();
|
|
2021
2059
|
NodeDiskFilesystem = class _NodeDiskFilesystem {
|
|
2022
2060
|
constructor(baseDir, opts = {}) {
|
|
2023
2061
|
this.baseDir = baseDir;
|
|
2024
2062
|
this.opts = { denySymlinks: true, ...opts };
|
|
2063
|
+
this.drives = this.win && (baseDir === "/" || baseDir === "\\");
|
|
2025
2064
|
}
|
|
2026
2065
|
baseDir;
|
|
2027
2066
|
cwd = "/";
|
|
2028
2067
|
opts;
|
|
2068
|
+
win = isWinPaths();
|
|
2069
|
+
/** Windows + baseDir '/': the VFS root is the machine (drives as first segment), not a directory. */
|
|
2070
|
+
drives;
|
|
2029
2071
|
/** Ensure the root dir exists. */
|
|
2030
2072
|
async init() {
|
|
2073
|
+
if (this.drives) return;
|
|
2031
2074
|
await fsp.mkdir(this.baseDir, { recursive: true });
|
|
2032
2075
|
}
|
|
2033
2076
|
real(vpath) {
|
|
2034
|
-
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
|
+
}
|
|
2035
2110
|
}
|
|
2036
2111
|
// Verified non-symlink DIRECTORY components, with a short TTL: tree walks (Glob/Grep) hit the same
|
|
2037
2112
|
// parents thousands of times; re-lstat'ing each per op is the dominant syscall cost. The 1s window
|
|
@@ -2041,11 +2116,13 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2041
2116
|
static VERIFY_TTL_MS = 1e3;
|
|
2042
2117
|
/** Throw if any existing component of `real` is a symlink (escape vector). */
|
|
2043
2118
|
async assertNoSymlink(real) {
|
|
2119
|
+
this.assertLongNames(real);
|
|
2044
2120
|
if (!this.opts.denySymlinks) return;
|
|
2045
|
-
const
|
|
2046
|
-
|
|
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;
|
|
2047
2124
|
const parts = rel.split(np.sep);
|
|
2048
|
-
let cur =
|
|
2125
|
+
let cur = base;
|
|
2049
2126
|
const now3 = Date.now();
|
|
2050
2127
|
for (let i = 0; i < parts.length; i++) {
|
|
2051
2128
|
cur = np.join(cur, parts[i]);
|
|
@@ -2065,13 +2142,13 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2065
2142
|
}
|
|
2066
2143
|
}
|
|
2067
2144
|
resolvePath(path, cwd) {
|
|
2068
|
-
return PathResolver.resolve(path, cwd || this.cwd);
|
|
2145
|
+
return PathResolver.resolve(toVfsPath(path), toVfsPath(cwd || this.cwd));
|
|
2069
2146
|
}
|
|
2070
2147
|
getCwd() {
|
|
2071
2148
|
return this.cwd;
|
|
2072
2149
|
}
|
|
2073
2150
|
setCwd(path) {
|
|
2074
|
-
this.cwd = PathResolver.normalize(path);
|
|
2151
|
+
this.cwd = PathResolver.normalize(toVfsPath(path));
|
|
2075
2152
|
}
|
|
2076
2153
|
async readFile(path) {
|
|
2077
2154
|
const r = this.real(this.resolvePath(path));
|
|
@@ -2145,8 +2222,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2145
2222
|
}
|
|
2146
2223
|
}
|
|
2147
2224
|
async readDir(path) {
|
|
2148
|
-
const
|
|
2225
|
+
const v = this.resolvePath(path);
|
|
2226
|
+
if (this.isDriveRoot(v)) return this.listDrives();
|
|
2149
2227
|
try {
|
|
2228
|
+
const r = this.real(v);
|
|
2150
2229
|
await this.assertNoSymlink(r);
|
|
2151
2230
|
return await fsp.readdir(r);
|
|
2152
2231
|
} catch {
|
|
@@ -2166,8 +2245,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2166
2245
|
await fsp.mkdir(r);
|
|
2167
2246
|
}
|
|
2168
2247
|
async exists(path) {
|
|
2169
|
-
const
|
|
2248
|
+
const v = this.resolvePath(path);
|
|
2249
|
+
if (this.isDriveRoot(v)) return true;
|
|
2170
2250
|
try {
|
|
2251
|
+
const r = this.real(v);
|
|
2171
2252
|
await this.assertNoSymlink(r);
|
|
2172
2253
|
await fsp.stat(r);
|
|
2173
2254
|
return true;
|
|
@@ -2177,8 +2258,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2177
2258
|
}
|
|
2178
2259
|
async stat(path) {
|
|
2179
2260
|
let s;
|
|
2180
|
-
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 };
|
|
2181
2263
|
try {
|
|
2264
|
+
const r = this.real(v);
|
|
2182
2265
|
await this.assertNoSymlink(r);
|
|
2183
2266
|
s = await fsp.stat(r);
|
|
2184
2267
|
} catch {
|
|
@@ -2193,8 +2276,10 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2193
2276
|
};
|
|
2194
2277
|
}
|
|
2195
2278
|
async isDirectory(path) {
|
|
2196
|
-
const
|
|
2279
|
+
const v = this.resolvePath(path);
|
|
2280
|
+
if (this.isDriveRoot(v)) return true;
|
|
2197
2281
|
try {
|
|
2282
|
+
const r = this.real(v);
|
|
2198
2283
|
await this.assertNoSymlink(r);
|
|
2199
2284
|
return (await fsp.stat(r)).isDirectory();
|
|
2200
2285
|
} catch {
|
|
@@ -2202,14 +2287,20 @@ var init_NodeDiskFilesystem = __esm({
|
|
|
2202
2287
|
}
|
|
2203
2288
|
}
|
|
2204
2289
|
async isFile(path) {
|
|
2205
|
-
const r = this.real(this.resolvePath(path));
|
|
2206
2290
|
try {
|
|
2291
|
+
const r = this.real(this.resolvePath(path));
|
|
2207
2292
|
await this.assertNoSymlink(r);
|
|
2208
2293
|
return (await fsp.stat(r)).isFile();
|
|
2209
2294
|
} catch {
|
|
2210
2295
|
return false;
|
|
2211
2296
|
}
|
|
2212
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
|
+
}
|
|
2213
2304
|
};
|
|
2214
2305
|
}
|
|
2215
2306
|
});
|
|
@@ -2226,6 +2317,7 @@ var init_JailedFilesystem = __esm({
|
|
|
2226
2317
|
"src/JailedFilesystem.ts"() {
|
|
2227
2318
|
"use strict";
|
|
2228
2319
|
init_tools_structured();
|
|
2320
|
+
init_hostPath();
|
|
2229
2321
|
JailedFilesystem = class {
|
|
2230
2322
|
constructor(inner, options) {
|
|
2231
2323
|
this.inner = inner;
|
|
@@ -2239,8 +2331,10 @@ var init_JailedFilesystem = __esm({
|
|
|
2239
2331
|
denyRe;
|
|
2240
2332
|
roRe;
|
|
2241
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. */
|
|
2242
2336
|
abs(path) {
|
|
2243
|
-
return this.inner.resolvePath(path, this.inner.getCwd());
|
|
2337
|
+
return this.inner.resolvePath(toVfsPath(path), this.inner.getCwd());
|
|
2244
2338
|
}
|
|
2245
2339
|
/** Throw if reading `path` is not permitted (denied or out of confinement). */
|
|
2246
2340
|
guardRead(op, path) {
|
|
@@ -2325,7 +2419,7 @@ var init_JailedFilesystem = __esm({
|
|
|
2325
2419
|
return entries.filter((name) => this.visible(abs === "/" ? `/${name}` : `${abs}/${name}`));
|
|
2326
2420
|
}
|
|
2327
2421
|
resolvePath(path, cwd) {
|
|
2328
|
-
return this.inner.resolvePath(path, cwd ?? this.inner.getCwd());
|
|
2422
|
+
return this.inner.resolvePath(toVfsPath(path), cwd ?? this.inner.getCwd());
|
|
2329
2423
|
}
|
|
2330
2424
|
getCwd() {
|
|
2331
2425
|
return this.inner.getCwd();
|
|
@@ -2443,6 +2537,125 @@ var init_shell_sandbox = __esm({
|
|
|
2443
2537
|
}
|
|
2444
2538
|
});
|
|
2445
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
|
+
|
|
2446
2659
|
// src/tools.shell.ts
|
|
2447
2660
|
var tools_shell_exports = {};
|
|
2448
2661
|
__export(tools_shell_exports, {
|
|
@@ -2452,19 +2665,14 @@ __export(tools_shell_exports, {
|
|
|
2452
2665
|
makeShellJobTools: () => makeShellJobTools,
|
|
2453
2666
|
parsePsTime: () => parsePsTime,
|
|
2454
2667
|
psGroupCpuSeconds: () => psGroupCpuSeconds,
|
|
2455
|
-
sumTreeCpu: () => sumTreeCpu
|
|
2668
|
+
sumTreeCpu: () => sumTreeCpu,
|
|
2669
|
+
windowsShellNote: () => windowsShellNote
|
|
2456
2670
|
});
|
|
2457
2671
|
function killGroup(proc, signal) {
|
|
2458
|
-
|
|
2459
|
-
try {
|
|
2460
|
-
process.kill(-proc.pid, signal);
|
|
2461
|
-
return true;
|
|
2462
|
-
} catch {
|
|
2463
|
-
return false;
|
|
2464
|
-
}
|
|
2672
|
+
return killTree(proc?.pid, signal);
|
|
2465
2673
|
}
|
|
2466
2674
|
async function spawnArgvFor(command, cwd, osSandbox) {
|
|
2467
|
-
if (!osSandbox) return
|
|
2675
|
+
if (!osSandbox) return shellArgv(defaultShell(), command);
|
|
2468
2676
|
const opts = osSandbox === true ? {} : osSandbox;
|
|
2469
2677
|
const wrapper = await findSandboxWrapper();
|
|
2470
2678
|
const wrapped = wrapper ? sandboxArgv(command, cwd, opts, process.platform, process.env.TMPDIR) : null;
|
|
@@ -2492,9 +2700,17 @@ function parsePsTime(t) {
|
|
|
2492
2700
|
return Number(m[1] ?? 0) * 86400 + Number(m[2] ?? 0) * 3600 + Number(m[3]) * 60 + Number(m[4]);
|
|
2493
2701
|
}
|
|
2494
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
|
+
}
|
|
2495
2711
|
try {
|
|
2496
|
-
const { execFile } = await import("child_process");
|
|
2497
|
-
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))));
|
|
2498
2714
|
return sumTreeCpu(out, pgid);
|
|
2499
2715
|
} catch (e) {
|
|
2500
2716
|
log9.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
|
|
@@ -2545,7 +2761,7 @@ function makeRealShellTool(options) {
|
|
|
2545
2761
|
return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
|
|
2546
2762
|
},
|
|
2547
2763
|
get description() {
|
|
2548
|
-
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();
|
|
2549
2765
|
},
|
|
2550
2766
|
// Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
|
|
2551
2767
|
maxDurationMs: maxTimeoutMs,
|
|
@@ -2569,7 +2785,7 @@ function makeRealShellTool(options) {
|
|
|
2569
2785
|
return `Started background job ${id}. Poll output with ShellOutput({id:"${id}"}), check ShellStatus({id:"${id}"}), stop with ShellKill({id:"${id}"}).`;
|
|
2570
2786
|
}
|
|
2571
2787
|
const spawn = options.spawn ?? await nodeSpawn();
|
|
2572
|
-
let argv =
|
|
2788
|
+
let argv = shellArgv(shellFor(options.shell), cmd);
|
|
2573
2789
|
if (options.osSandbox) {
|
|
2574
2790
|
try {
|
|
2575
2791
|
argv = await spawnArgvFor(cmd, options.cwd, options.osSandbox);
|
|
@@ -2604,22 +2820,23 @@ function makeRealShellTool(options) {
|
|
|
2604
2820
|
}
|
|
2605
2821
|
};
|
|
2606
2822
|
try {
|
|
2607
|
-
return await new Promise((
|
|
2823
|
+
return await new Promise((resolve3) => {
|
|
2608
2824
|
let out = "";
|
|
2609
2825
|
let settled = false;
|
|
2610
2826
|
const finish = (s) => {
|
|
2611
2827
|
if (settled) return;
|
|
2612
2828
|
settled = true;
|
|
2613
|
-
|
|
2829
|
+
resolve3(s);
|
|
2614
2830
|
};
|
|
2615
2831
|
let proc;
|
|
2616
2832
|
try {
|
|
2617
|
-
|
|
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 });
|
|
2618
2835
|
} catch (e) {
|
|
2619
2836
|
return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
|
|
2620
2837
|
}
|
|
2621
2838
|
if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
|
|
2622
|
-
else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
2839
|
+
else if (!IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
|
|
2623
2840
|
const registry = options.registry;
|
|
2624
2841
|
if (registry?.adopt) {
|
|
2625
2842
|
onDeadline = () => {
|
|
@@ -2664,6 +2881,12 @@ function makeRealShellTool(options) {
|
|
|
2664
2881
|
}
|
|
2665
2882
|
};
|
|
2666
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
|
+
}
|
|
2667
2890
|
function handoffFor(timeoutMs, id, body, notifies) {
|
|
2668
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.");
|
|
2669
2892
|
return body ? `${head}
|
|
@@ -2718,7 +2941,7 @@ ${clean(out) || "(no output yet)"}`;
|
|
|
2718
2941
|
}
|
|
2719
2942
|
];
|
|
2720
2943
|
}
|
|
2721
|
-
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;
|
|
2722
2945
|
var init_tools_shell = __esm({
|
|
2723
2946
|
"src/tools.shell.ts"() {
|
|
2724
2947
|
"use strict";
|
|
@@ -2727,9 +2950,11 @@ var init_tools_shell = __esm({
|
|
|
2727
2950
|
init_logging();
|
|
2728
2951
|
init_shell_sandbox();
|
|
2729
2952
|
init_tools_jobs();
|
|
2953
|
+
init_platform();
|
|
2730
2954
|
log9 = forComponent("shell");
|
|
2731
2955
|
clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
|
|
2732
|
-
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 } : {} });
|
|
2733
2958
|
SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
|
|
2734
2959
|
ShellJobRegistry = class {
|
|
2735
2960
|
constructor(cfg) {
|
|
@@ -2744,8 +2969,8 @@ var init_tools_shell = __esm({
|
|
|
2744
2969
|
const h = this.track(command, { idleMs: 0 });
|
|
2745
2970
|
try {
|
|
2746
2971
|
const spawn = this.cfg.spawn ?? await nodeSpawn();
|
|
2747
|
-
const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) :
|
|
2748
|
-
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 }));
|
|
2749
2974
|
} catch (e) {
|
|
2750
2975
|
h.chunk(`failed to spawn: ${e?.message ?? e}`);
|
|
2751
2976
|
h.finish("error", void 0, e?.message ?? String(e));
|
|
@@ -3704,7 +3929,7 @@ init_OverlayFilesystem();
|
|
|
3704
3929
|
// src/worktree.ts
|
|
3705
3930
|
import { spawnSync } from "child_process";
|
|
3706
3931
|
import { symlinkSync, mkdirSync, statSync as statSync3, readFileSync as readFileSync3, rmSync } from "fs";
|
|
3707
|
-
import { join } from "path";
|
|
3932
|
+
import { join, resolve as resolve2 } from "path";
|
|
3708
3933
|
var VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
|
|
3709
3934
|
var MAX_SLUG_LENGTH = 64;
|
|
3710
3935
|
var GIT_NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
|
|
@@ -3738,7 +3963,7 @@ function git(args, cwd) {
|
|
|
3738
3963
|
}
|
|
3739
3964
|
function findGitRoot(from) {
|
|
3740
3965
|
const r = git(["rev-parse", "--show-toplevel"], from);
|
|
3741
|
-
return r.ok ? r.stdout : null;
|
|
3966
|
+
return r.ok ? process.platform === "win32" ? resolve2(r.stdout) : r.stdout : null;
|
|
3742
3967
|
}
|
|
3743
3968
|
function readWorktreeHead(worktreePath) {
|
|
3744
3969
|
try {
|
|
@@ -3792,7 +4017,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
|
|
|
3792
4017
|
continue;
|
|
3793
4018
|
}
|
|
3794
4019
|
try {
|
|
3795
|
-
symlinkSync(src, dst, "dir");
|
|
4020
|
+
symlinkSync(src, dst, process.platform === "win32" ? "junction" : "dir");
|
|
3796
4021
|
} catch (e) {
|
|
3797
4022
|
if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
|
|
3798
4023
|
}
|
|
@@ -4330,6 +4555,10 @@ var AgentOptions = class {
|
|
|
4330
4555
|
model = "anthropic/claude-sonnet-4-6";
|
|
4331
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.";
|
|
4332
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";
|
|
4333
4562
|
maxSteps = 25;
|
|
4334
4563
|
// --- automatic kill-switches (always on; protect the API budget against runaway loops/abuse) ---
|
|
4335
4564
|
/** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
|
|
@@ -4925,8 +5154,9 @@ var Agent = class _Agent {
|
|
|
4925
5154
|
};
|
|
4926
5155
|
const cursorPo = isCursorWithTools ? { toolTimeoutMs: effectiveToolTimeoutMs, toolExecutor: hostExec } : void 0;
|
|
4927
5156
|
const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
|
|
4928
|
-
|
|
4929
|
-
|
|
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));
|
|
4930
5160
|
claudeSteerPo.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
|
|
4931
5161
|
}
|
|
4932
5162
|
if (this.ccServer) this.ccExec = hostExec;
|
|
@@ -9133,6 +9363,10 @@ function describeCall(call) {
|
|
|
9133
9363
|
const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
|
|
9134
9364
|
return `${call.name}${hint}`;
|
|
9135
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
|
+
}
|
|
9136
9370
|
var DuplexAgentOptions = class {
|
|
9137
9371
|
/** Any ai.libx.js AIClient — shared by all tiers (routed by model). */
|
|
9138
9372
|
ai;
|
|
@@ -9422,12 +9656,26 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9422
9656
|
timeoutMs: 3e4,
|
|
9423
9657
|
failEmptyTurns: false,
|
|
9424
9658
|
// a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
|
|
9659
|
+
...this.claudeCodeReflex(),
|
|
9425
9660
|
...o.reflexOptions,
|
|
9426
9661
|
tools,
|
|
9427
9662
|
// Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
|
|
9428
9663
|
hooks: composeHooks(this.dispatchGuard(), o.reflexOptions?.hooks)
|
|
9429
9664
|
});
|
|
9430
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
|
+
}
|
|
9431
9679
|
/** Resolve memory tools + inject index into voice system prompt (once). */
|
|
9432
9680
|
async initMemory() {
|
|
9433
9681
|
if (!this.memoryReady) return;
|
|
@@ -9592,7 +9840,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9592
9840
|
await this.drainReflex();
|
|
9593
9841
|
this.scrubLeakFromTranscript();
|
|
9594
9842
|
this.scrubHeldText();
|
|
9595
|
-
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);
|
|
9596
9845
|
}
|
|
9597
9846
|
/** Remove complete stage-direction parentheticals from the UNFORWARDED reflex text (STAGE_DIRECTION_RE).
|
|
9598
9847
|
* Only the unforwarded region is touched — already-spoken audio can't be unsent, and splicing before
|
|
@@ -9698,6 +9947,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
|
|
|
9698
9947
|
* hosts/tests key on it. */
|
|
9699
9948
|
static FALLBACK_ACKS = ["Okay, on it.", "On it.", "Alright, working on it."];
|
|
9700
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.";
|
|
9701
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?"];
|
|
9702
9952
|
lastFallback = "";
|
|
9703
9953
|
/** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns.
|
|
@@ -10356,14 +10606,14 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
10356
10606
|
* '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
|
|
10357
10607
|
* task settle (callers map '' to deny / best-judgment). Workers never block forever. */
|
|
10358
10608
|
parkQuestion(askId, question) {
|
|
10359
|
-
return new Promise((
|
|
10609
|
+
return new Promise((resolve3) => {
|
|
10360
10610
|
let settled = false;
|
|
10361
10611
|
const finish = (answer) => {
|
|
10362
10612
|
if (settled) return;
|
|
10363
10613
|
settled = true;
|
|
10364
10614
|
clearTimeout(timer);
|
|
10365
10615
|
this.pendingAsks.delete(askId);
|
|
10366
|
-
|
|
10616
|
+
resolve3(answer);
|
|
10367
10617
|
};
|
|
10368
10618
|
const timer = setTimeout(() => {
|
|
10369
10619
|
this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
|
|
@@ -11110,11 +11360,11 @@ function buildMcpCatalog(servers) {
|
|
|
11110
11360
|
}
|
|
11111
11361
|
return { specs, routes };
|
|
11112
11362
|
}
|
|
11113
|
-
function searchOverCatalog(servers, specs, routes,
|
|
11363
|
+
function searchOverCatalog(servers, specs, routes, resolve3, options) {
|
|
11114
11364
|
const tools = specs.length ? makeMcpToolSearch(specs, (name, args, o) => {
|
|
11115
11365
|
const r = routes.get(name);
|
|
11116
11366
|
if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
|
|
11117
|
-
return
|
|
11367
|
+
return resolve3(r.server, r.rawName, args ?? {}, o);
|
|
11118
11368
|
}, options) : [];
|
|
11119
11369
|
return { tools, serverNames: servers, toolCount: specs.length };
|
|
11120
11370
|
}
|
|
@@ -11123,9 +11373,9 @@ function makeMcpToolSearchFromMounted(mounted, options) {
|
|
|
11123
11373
|
const byName = new Map(mounted.map((m) => [m.name, m]));
|
|
11124
11374
|
return searchOverCatalog(mounted.map((m) => m.name), specs, routes, (server, rawName, args, o) => byName.get(server).client.callTool(rawName, args, o), options);
|
|
11125
11375
|
}
|
|
11126
|
-
function makeLazyMcpToolSearch(servers,
|
|
11376
|
+
function makeLazyMcpToolSearch(servers, resolve3, options) {
|
|
11127
11377
|
const { specs, routes } = buildMcpCatalog(servers);
|
|
11128
|
-
return searchOverCatalog(servers.map((s) => s.name), specs, routes,
|
|
11378
|
+
return searchOverCatalog(servers.map((s) => s.name), specs, routes, resolve3, options);
|
|
11129
11379
|
}
|
|
11130
11380
|
|
|
11131
11381
|
// src/hooks.ts
|