@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/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(last) || SIGN_OFF.test(last)) return false;
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 rel = np.relative(this.baseDir, real);
2046
- if (rel === "" || rel.startsWith("..")) return;
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 = this.baseDir;
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 r = this.real(this.resolvePath(path));
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 r = this.real(this.resolvePath(path));
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 r = this.real(this.resolvePath(path));
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 r = this.real(this.resolvePath(path));
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
- if (!proc?.pid) return false;
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 { bin: "/bin/sh", args: ["-c", command] };
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) => execFile("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
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 "Run a shell command via /bin/sh in the working directory. 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();
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 = { bin: options.shell || "/bin/sh", args: ["-c", cmd] };
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((resolve2) => {
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
- resolve2(s);
2829
+ resolve3(s);
2614
2830
  };
2615
2831
  let proc;
2616
2832
  try {
2617
- proc = spawn(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal, ...DETACHED });
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"], detached: true };
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) : { bin: "/bin/sh", args: ["-c", command] };
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
- if (claudeSteerPo && o.providerOptions?.claudeCodeSession && this.activeTools.some((t) => CC_BRIDGED.has(t.name))) {
4929
- this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter((t) => CC_BRIDGED.has(t.name)), (n, a) => this.ccExec(n, a));
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) await this.ackIfSilent(fallback);
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((resolve2) => {
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
- resolve2(answer);
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, resolve2, options) {
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 resolve2(r.server, r.rawName, args ?? {}, o);
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, resolve2, options) {
11376
+ function makeLazyMcpToolSearch(servers, resolve3, options) {
11127
11377
  const { specs, routes } = buildMcpCatalog(servers);
11128
- return searchOverCatalog(servers.map((s) => s.name), specs, routes, resolve2, options);
11378
+ return searchOverCatalog(servers.map((s) => s.name), specs, routes, resolve3, options);
11129
11379
  }
11130
11380
 
11131
11381
  // src/hooks.ts