@livx.cc/agentx 0.99.78 → 0.99.80

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/cli.js CHANGED
@@ -403,6 +403,68 @@ var init_redact = __esm({
403
403
  }
404
404
  });
405
405
 
406
+ // src/hostPath.ts
407
+ function toVfsPath(p, platform2 = currentPlatform()) {
408
+ if (!isWinPaths(platform2) || typeof p !== "string") return p;
409
+ let s = p.replace(/\\/g, "/");
410
+ s = s.replace(/^\/\/\?\/UNC\//i, "//").replace(/^\/\/\?\//, "");
411
+ const drive = /^([A-Za-z]):(?=\/|$)/.exec(s);
412
+ if (drive) return `/${drive[1].toLowerCase()}${s.slice(2) || "/"}`.replace(/\/+$/, "") || "/";
413
+ if (/^\/\/[^/]/.test(s)) return `/unc/${s.slice(2)}`;
414
+ return s;
415
+ }
416
+ function toHostPath(vpath, platform2 = currentPlatform()) {
417
+ if (!isWinPaths(platform2)) return vpath;
418
+ const segs = vpath.split("/").filter(Boolean);
419
+ if (segs.length >= 1 && /^[A-Za-z]$/.test(segs[0])) return `${segs[0].toUpperCase()}:\\${segs.slice(1).join("\\")}`;
420
+ if (segs.length >= 3 && segs[0].toLowerCase() === "unc") return `\\\\${segs.slice(1).join("\\")}`;
421
+ return null;
422
+ }
423
+ function withHostPaths(fs, platform2 = currentPlatform()) {
424
+ if (!isWinPaths(platform2)) return fs;
425
+ const PATH_ARGS = {
426
+ readFile: 1,
427
+ writeFile: 1,
428
+ deleteFile: 1,
429
+ readDir: 1,
430
+ createDir: 1,
431
+ exists: 1,
432
+ stat: 1,
433
+ isDirectory: 1,
434
+ isFile: 1,
435
+ setCwd: 1,
436
+ resolvePath: 2,
437
+ readFileBytes: 1,
438
+ realPath: 1,
439
+ canonicalPath: 1
440
+ };
441
+ return new Proxy(fs, {
442
+ get(target, prop, receiver) {
443
+ const v = Reflect.get(target, prop, receiver);
444
+ const n = typeof prop === "string" ? PATH_ARGS[prop] : void 0;
445
+ if (typeof v !== "function" || prop === "constructor") return v;
446
+ if (!n) return v.bind(target);
447
+ return (...args) => v.apply(target, args.map((a, i) => i < n && typeof a === "string" ? toVfsPath(a, platform2) : a));
448
+ }
449
+ });
450
+ }
451
+ function winSegmentProblem(seg) {
452
+ if (seg.includes(":")) return "alternate data stream / drive-relative name";
453
+ if (/[. ]$/.test(seg) && seg !== "." && seg !== "..") return "trailing dot or space";
454
+ if (WIN_DEVICE.test(seg)) return "reserved device name";
455
+ if (/[<>"|?*\x00-\x1f]/.test(seg)) return "invalid character";
456
+ return null;
457
+ }
458
+ var currentPlatform, isWinPaths, WIN_DEVICE;
459
+ var init_hostPath = __esm({
460
+ "src/hostPath.ts"() {
461
+ "use strict";
462
+ currentPlatform = () => typeof process !== "undefined" && process.platform || "browser";
463
+ isWinPaths = (platform2 = currentPlatform()) => platform2 === "win32";
464
+ WIN_DEVICE = /^(con|prn|aux|nul|com[0-9¹²³]|lpt[0-9¹²³]|conin\$|conout\$)(\..*)?$/i;
465
+ }
466
+ });
467
+
406
468
  // src/tools.structured.ts
407
469
  function ckAbort(signal) {
408
470
  if (signal?.aborted) throw new Error("aborted");
@@ -445,7 +507,7 @@ async function walkFiles(fs, dir, signal, out = [], st, depth = 0) {
445
507
  }
446
508
  function scanRoots(fs, path, opts) {
447
509
  const asked = path == null ? [] : (Array.isArray(path) ? path : [path]).map((p) => String(p)).filter(Boolean);
448
- const roots = (asked.length ? asked : opts?.roots?.length ? opts.roots : [fsCwd(fs)]).map((r) => r.length > 1 && r.endsWith("/") ? r.slice(0, -1) : r);
510
+ 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);
449
511
  return [...new Set(roots)].filter((r, _i, all) => !all.some((other) => other !== r && r.startsWith(`${other}/`)));
450
512
  }
451
513
  function scanNote(states) {
@@ -674,6 +736,7 @@ function parentDir(abs) {
674
736
  return i <= 0 ? "/" : abs.slice(0, i);
675
737
  }
676
738
  async function mkdirp(fs, dir) {
739
+ dir = toVfsPath(dir);
677
740
  if (dir === "/" || await fs.exists(dir)) return;
678
741
  await mkdirp(fs, parentDir(dir));
679
742
  if (!await fs.exists(dir)) await fs.createDir(dir);
@@ -734,6 +797,7 @@ var init_tools_structured = __esm({
734
797
  "src/tools.structured.ts"() {
735
798
  "use strict";
736
799
  init_redact();
800
+ init_hostPath();
737
801
  DEFAULT_SKIP_DIRS = ["node_modules", ".git", "dist", ".tmp"];
738
802
  DEFAULT_MAX_DEPTH = 24;
739
803
  SCAN_PARAMS = {
@@ -2014,7 +2078,7 @@ __export(worktree_exports, {
2014
2078
  });
2015
2079
  import { spawnSync } from "child_process";
2016
2080
  import { symlinkSync, mkdirSync, statSync as statSync4, readFileSync as readFileSync3, rmSync } from "fs";
2017
- import { join as join2 } from "path";
2081
+ import { join as join2, resolve as resolve2 } from "path";
2018
2082
  function validateWorktreeSlug(slug2) {
2019
2083
  if (!slug2) throw new Error("worktree slug must not be empty");
2020
2084
  if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
@@ -2045,7 +2109,7 @@ function git(args, cwd) {
2045
2109
  }
2046
2110
  function findGitRoot(from) {
2047
2111
  const r = git(["rev-parse", "--show-toplevel"], from);
2048
- return r.ok ? r.stdout : null;
2112
+ return r.ok ? process.platform === "win32" ? resolve2(r.stdout) : r.stdout : null;
2049
2113
  }
2050
2114
  function readWorktreeHead(worktreePath) {
2051
2115
  try {
@@ -2099,7 +2163,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
2099
2163
  continue;
2100
2164
  }
2101
2165
  try {
2102
- symlinkSync(src, dst, "dir");
2166
+ symlinkSync(src, dst, process.platform === "win32" ? "junction" : "dir");
2103
2167
  } catch (e) {
2104
2168
  if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
2105
2169
  }
@@ -2178,20 +2242,58 @@ var NodeDiskFilesystem;
2178
2242
  var init_NodeDiskFilesystem = __esm({
2179
2243
  "src/NodeDiskFilesystem.ts"() {
2180
2244
  "use strict";
2245
+ init_hostPath();
2181
2246
  NodeDiskFilesystem = class _NodeDiskFilesystem {
2182
2247
  constructor(baseDir, opts = {}) {
2183
2248
  this.baseDir = baseDir;
2184
2249
  this.opts = { denySymlinks: true, ...opts };
2250
+ this.drives = this.win && (baseDir === "/" || baseDir === "\\");
2185
2251
  }
2186
2252
  baseDir;
2187
2253
  cwd = "/";
2188
2254
  opts;
2255
+ win = isWinPaths();
2256
+ /** Windows + baseDir '/': the VFS root is the machine (drives as first segment), not a directory. */
2257
+ drives;
2189
2258
  /** Ensure the root dir exists. */
2190
2259
  async init() {
2260
+ if (this.drives) return;
2191
2261
  await fsp.mkdir(this.baseDir, { recursive: true });
2192
2262
  }
2193
2263
  real(vpath) {
2194
- return np.join(this.baseDir, "." + vpath);
2264
+ if (!this.win) return np.join(this.baseDir, "." + vpath);
2265
+ for (const seg of vpath.split("/")) {
2266
+ const why = seg && winSegmentProblem(seg);
2267
+ if (why) throw new Error(`File not found: ${vpath} (${why})`);
2268
+ }
2269
+ if (!this.drives) return np.join(this.baseDir, "." + vpath);
2270
+ const host = toHostPath(vpath);
2271
+ if (!host) throw new Error(`File not found: ${vpath}`);
2272
+ return host;
2273
+ }
2274
+ /** Windows machine root ('/' when baseDir is '/'): a synthetic directory listing the drives. */
2275
+ isDriveRoot(vpath) {
2276
+ return this.drives && vpath === "/";
2277
+ }
2278
+ /** Windows: refuse an 8.3 short name (`ENV~1` opens `.env`) — the only alias that needs the disk to
2279
+ * detect. Only segments that look like one (`~<digit>`) cost a lookup: the OS's long name for that
2280
+ * component must be the name that was asked for. */
2281
+ assertLongNames(real) {
2282
+ if (!this.win || !/~\d/.test(real)) return;
2283
+ const root = np.parse(real).root;
2284
+ const parts = real.slice(root.length).split(/[\\/]/).filter(Boolean);
2285
+ let cur = root;
2286
+ for (const part of parts) {
2287
+ cur = np.join(cur, part);
2288
+ if (!/~\d/.test(part)) continue;
2289
+ let long;
2290
+ try {
2291
+ long = np.basename(realpathSync2.native(cur));
2292
+ } catch {
2293
+ return;
2294
+ }
2295
+ if (long.toLowerCase() !== part.toLowerCase()) throw new Error("File not found: short (8.3) name not permitted");
2296
+ }
2195
2297
  }
2196
2298
  // Verified non-symlink DIRECTORY components, with a short TTL: tree walks (Glob/Grep) hit the same
2197
2299
  // parents thousands of times; re-lstat'ing each per op is the dominant syscall cost. The 1s window
@@ -2201,11 +2303,13 @@ var init_NodeDiskFilesystem = __esm({
2201
2303
  static VERIFY_TTL_MS = 1e3;
2202
2304
  /** Throw if any existing component of `real` is a symlink (escape vector). */
2203
2305
  async assertNoSymlink(real) {
2306
+ this.assertLongNames(real);
2204
2307
  if (!this.opts.denySymlinks) return;
2205
- const rel = np.relative(this.baseDir, real);
2206
- if (rel === "" || rel.startsWith("..")) return;
2308
+ const base = this.drives ? np.parse(real).root : this.baseDir;
2309
+ const rel = np.relative(base, real);
2310
+ if (rel === "" || rel.startsWith("..") || np.isAbsolute(rel)) return;
2207
2311
  const parts = rel.split(np.sep);
2208
- let cur = this.baseDir;
2312
+ let cur = base;
2209
2313
  const now4 = Date.now();
2210
2314
  for (let i = 0; i < parts.length; i++) {
2211
2315
  cur = np.join(cur, parts[i]);
@@ -2225,13 +2329,13 @@ var init_NodeDiskFilesystem = __esm({
2225
2329
  }
2226
2330
  }
2227
2331
  resolvePath(path, cwd) {
2228
- return PathResolver.resolve(path, cwd || this.cwd);
2332
+ return PathResolver.resolve(toVfsPath(path), toVfsPath(cwd || this.cwd));
2229
2333
  }
2230
2334
  getCwd() {
2231
2335
  return this.cwd;
2232
2336
  }
2233
2337
  setCwd(path) {
2234
- this.cwd = PathResolver.normalize(path);
2338
+ this.cwd = PathResolver.normalize(toVfsPath(path));
2235
2339
  }
2236
2340
  async readFile(path) {
2237
2341
  const r = this.real(this.resolvePath(path));
@@ -2305,8 +2409,10 @@ var init_NodeDiskFilesystem = __esm({
2305
2409
  }
2306
2410
  }
2307
2411
  async readDir(path) {
2308
- const r = this.real(this.resolvePath(path));
2412
+ const v = this.resolvePath(path);
2413
+ if (this.isDriveRoot(v)) return this.listDrives();
2309
2414
  try {
2415
+ const r = this.real(v);
2310
2416
  await this.assertNoSymlink(r);
2311
2417
  return await fsp.readdir(r);
2312
2418
  } catch {
@@ -2326,8 +2432,10 @@ var init_NodeDiskFilesystem = __esm({
2326
2432
  await fsp.mkdir(r);
2327
2433
  }
2328
2434
  async exists(path) {
2329
- const r = this.real(this.resolvePath(path));
2435
+ const v = this.resolvePath(path);
2436
+ if (this.isDriveRoot(v)) return true;
2330
2437
  try {
2438
+ const r = this.real(v);
2331
2439
  await this.assertNoSymlink(r);
2332
2440
  await fsp.stat(r);
2333
2441
  return true;
@@ -2337,8 +2445,10 @@ var init_NodeDiskFilesystem = __esm({
2337
2445
  }
2338
2446
  async stat(path) {
2339
2447
  let s;
2340
- const r = this.real(this.resolvePath(path));
2448
+ const v = this.resolvePath(path);
2449
+ if (this.isDriveRoot(v)) return { created: /* @__PURE__ */ new Date(0), modified: /* @__PURE__ */ new Date(0), size: 0, permissions: "drwxr-xr-x", isExecutable: false };
2341
2450
  try {
2451
+ const r = this.real(v);
2342
2452
  await this.assertNoSymlink(r);
2343
2453
  s = await fsp.stat(r);
2344
2454
  } catch {
@@ -2353,8 +2463,10 @@ var init_NodeDiskFilesystem = __esm({
2353
2463
  };
2354
2464
  }
2355
2465
  async isDirectory(path) {
2356
- const r = this.real(this.resolvePath(path));
2466
+ const v = this.resolvePath(path);
2467
+ if (this.isDriveRoot(v)) return true;
2357
2468
  try {
2469
+ const r = this.real(v);
2358
2470
  await this.assertNoSymlink(r);
2359
2471
  return (await fsp.stat(r)).isDirectory();
2360
2472
  } catch {
@@ -2362,14 +2474,20 @@ var init_NodeDiskFilesystem = __esm({
2362
2474
  }
2363
2475
  }
2364
2476
  async isFile(path) {
2365
- const r = this.real(this.resolvePath(path));
2366
2477
  try {
2478
+ const r = this.real(this.resolvePath(path));
2367
2479
  await this.assertNoSymlink(r);
2368
2480
  return (await fsp.stat(r)).isFile();
2369
2481
  } catch {
2370
2482
  return false;
2371
2483
  }
2372
2484
  }
2485
+ /** Windows machine root: the drive letters that are mounted (`/c`, `/d`, …). */
2486
+ async listDrives() {
2487
+ const letters = "abcdefghijklmnopqrstuvwxyz".split("");
2488
+ const up = await Promise.all(letters.map((l) => fsp.stat(`${l.toUpperCase()}:\\`).then(() => true, () => false)));
2489
+ return letters.filter((_, i) => up[i]);
2490
+ }
2373
2491
  };
2374
2492
  }
2375
2493
  });
@@ -2386,6 +2504,7 @@ var init_JailedFilesystem = __esm({
2386
2504
  "src/JailedFilesystem.ts"() {
2387
2505
  "use strict";
2388
2506
  init_tools_structured();
2507
+ init_hostPath();
2389
2508
  JailedFilesystem = class {
2390
2509
  constructor(inner, options) {
2391
2510
  this.inner = inner;
@@ -2399,8 +2518,10 @@ var init_JailedFilesystem = __esm({
2399
2518
  denyRe;
2400
2519
  roRe;
2401
2520
  confineRe;
2521
+ /** Resolved VFS path — host-path syntax (Windows `C:\…`, backslashes) is normalized FIRST, so the
2522
+ * deny/readonly/confine globs always match the path that is actually opened. */
2402
2523
  abs(path) {
2403
- return this.inner.resolvePath(path, this.inner.getCwd());
2524
+ return this.inner.resolvePath(toVfsPath(path), this.inner.getCwd());
2404
2525
  }
2405
2526
  /** Throw if reading `path` is not permitted (denied or out of confinement). */
2406
2527
  guardRead(op, path) {
@@ -2485,7 +2606,7 @@ var init_JailedFilesystem = __esm({
2485
2606
  return entries.filter((name) => this.visible(abs === "/" ? `/${name}` : `${abs}/${name}`));
2486
2607
  }
2487
2608
  resolvePath(path, cwd) {
2488
- return this.inner.resolvePath(path, cwd ?? this.inner.getCwd());
2609
+ return this.inner.resolvePath(toVfsPath(path), cwd ?? this.inner.getCwd());
2489
2610
  }
2490
2611
  getCwd() {
2491
2612
  return this.inner.getCwd();
@@ -2551,6 +2672,159 @@ var init_JailedFilesystem = __esm({
2551
2672
  }
2552
2673
  });
2553
2674
 
2675
+ // src/platform.ts
2676
+ import { spawnSync as spawnSync2, execFile } from "child_process";
2677
+ import { statSync as statSync5 } from "fs";
2678
+ import * as np2 from "path";
2679
+ function specFor(bin) {
2680
+ const name = np2.basename(bin).toLowerCase().replace(/\.exe$/, "");
2681
+ if (name === "powershell" || name === "pwsh") {
2682
+ const pre = "$ProgressPreference='SilentlyContinue';[Console]::OutputEncoding=[Text.Encoding]::UTF8;$OutputEncoding=[Text.Encoding]::UTF8;";
2683
+ return {
2684
+ bin,
2685
+ kind: "powershell",
2686
+ label: name === "pwsh" ? "PowerShell (pwsh)" : "Windows PowerShell",
2687
+ args: (c) => ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(pre + c, "utf16le").toString("base64")]
2688
+ };
2689
+ }
2690
+ if (name === "cmd") return { bin, kind: "cmd", label: "cmd.exe", args: (c) => ["/d", "/s", "/c", `"${c}"`], spawnOptions: { windowsVerbatimArguments: true } };
2691
+ return { bin, kind: "sh", label: /bash/.test(name) && /git/i.test(bin) ? "Git Bash" : name, args: (c) => ["-c", c] };
2692
+ }
2693
+ function findGitBash(env = process.env) {
2694
+ const roots = [];
2695
+ const git2 = findOnPath("git", env);
2696
+ if (git2) {
2697
+ const dir = np2.dirname(git2);
2698
+ const up = (p, n) => {
2699
+ for (let i = 0; i < n; i++) p = np2.dirname(p);
2700
+ return p;
2701
+ };
2702
+ roots.push(/[\\/](mingw(32|64)|ucrt64|clang(32|64|arm64))[\\/]bin$/i.test(dir) ? up(dir, 2) : up(dir, 1));
2703
+ }
2704
+ for (const base of [env.ProgramFiles, env["ProgramFiles(x86)"], env.ProgramW6432, env.LOCALAPPDATA && np2.join(env.LOCALAPPDATA, "Programs")])
2705
+ if (base) roots.push(np2.join(base, "Git"));
2706
+ for (const r of roots) {
2707
+ const b = np2.join(r, "bin", "bash.exe");
2708
+ if (isFile(b)) return b;
2709
+ }
2710
+ return void 0;
2711
+ }
2712
+ function defaultShell() {
2713
+ if (!IS_WIN) return POSIX_SH;
2714
+ if (_shell) return _shell;
2715
+ const override = process.env.AGENTX_SHELL;
2716
+ if (override && (isFile(override) || findOnPath(override))) return _shell = specFor(isFile(override) ? override : findOnPath(override));
2717
+ const bash = findGitBash();
2718
+ if (bash) return _shell = specFor(bash);
2719
+ const sysRoot = process.env.SystemRoot || "C:\\Windows";
2720
+ const ps = np2.join(sysRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
2721
+ if (isFile(ps)) return _shell = specFor(ps);
2722
+ return _shell = specFor(process.env.ComSpec || "cmd.exe");
2723
+ }
2724
+ function shellFor(shell) {
2725
+ if (!IS_WIN) return shell ? { ...POSIX_SH, bin: shell } : POSIX_SH;
2726
+ return shell && isFile(shell) ? specFor(shell) : defaultShell();
2727
+ }
2728
+ function shellOption() {
2729
+ if (!IS_WIN) return true;
2730
+ const s = defaultShell();
2731
+ return s.kind === "cmd" ? true : s.bin;
2732
+ }
2733
+ function killTree(pid, signal) {
2734
+ if (!pid) return false;
2735
+ if (!IS_WIN) {
2736
+ try {
2737
+ process.kill(-pid, signal);
2738
+ return true;
2739
+ } catch {
2740
+ return false;
2741
+ }
2742
+ }
2743
+ try {
2744
+ const r = spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 1e4 });
2745
+ return r.status === 0;
2746
+ } catch {
2747
+ return false;
2748
+ }
2749
+ }
2750
+ async function winTreeCpuSeconds(root) {
2751
+ const script = 'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ProcessId) $($_.ParentProcessId) $([uint64]$_.UserModeTime + [uint64]$_.KernelModeTime)" }';
2752
+ const out = await new Promise((res, rej) => execFile(
2753
+ "powershell.exe",
2754
+ ["-NoProfile", "-NonInteractive", "-Command", script],
2755
+ { timeout: 15e3, windowsHide: true },
2756
+ (e, so) => e ? rej(e) : res(String(so))
2757
+ ));
2758
+ 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 }));
2759
+ const inTree = new Set(rows.filter((r) => r.pid === root).map((r) => r.pid));
2760
+ for (let grew = true; grew; ) {
2761
+ grew = false;
2762
+ for (const r of rows) if (!inTree.has(r.pid) && inTree.has(r.ppid) && r.pid !== r.ppid) {
2763
+ inTree.add(r.pid);
2764
+ grew = true;
2765
+ }
2766
+ }
2767
+ let total;
2768
+ for (const r of rows) if (inTree.has(r.pid) && Number.isFinite(r.sec)) total = (total ?? 0) + r.sec;
2769
+ return total;
2770
+ }
2771
+ function findOnPath(cmd, env = process.env) {
2772
+ const exts = IS_WIN && !/\.[^\\/.]+$/.test(cmd) ? (env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
2773
+ const cands = (base) => exts.map((e) => base + e);
2774
+ if (/[\\/]/.test(cmd)) return cands(cmd).find(isFile);
2775
+ const PATH = env.PATH ?? env.Path ?? "";
2776
+ for (const dir of PATH.split(np2.delimiter)) {
2777
+ if (!dir) continue;
2778
+ for (const p of cands(np2.join(dir, cmd))) if (isFile(p)) return p;
2779
+ }
2780
+ return void 0;
2781
+ }
2782
+ function hasCommand(cmd) {
2783
+ if (!IS_WIN) return spawnSync2("which", [cmd]).status === 0;
2784
+ return !!findOnPath(cmd);
2785
+ }
2786
+ function cmdArg(arg, shim) {
2787
+ let a = arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1");
2788
+ a = `"${a}"`.replace(CMD_META, "^$1");
2789
+ return shim ? a.replace(CMD_META, "^$1") : a;
2790
+ }
2791
+ function resolveSpawn(command, args = [], env = process.env) {
2792
+ if (!IS_WIN) return { command, args };
2793
+ const found = findOnPath(command, env);
2794
+ if (!found || !/\.(cmd|bat)$/i.test(found)) return { command: found ?? command, args };
2795
+ const shim = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i.test(found);
2796
+ const line = [found.replace(CMD_META, "^$1"), ...args.map((a) => cmdArg(a, shim))].join(" ");
2797
+ return { command: env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], windowsVerbatimArguments: true };
2798
+ }
2799
+ function plainEnv(env, omit = []) {
2800
+ const out = {};
2801
+ const drop = new Set(IS_WIN ? omit.map((k) => k.toUpperCase()) : omit);
2802
+ for (const [k, v] of Object.entries(env)) {
2803
+ if (v === void 0 || drop.has(IS_WIN ? k.toUpperCase() : k)) continue;
2804
+ out[IS_WIN && k.toUpperCase() === "PATH" ? "PATH" : k] = v;
2805
+ }
2806
+ return out;
2807
+ }
2808
+ var IS_WIN, POSIX_SH, isFile, _shell, DETACH_OPTIONS, CMD_META, pathTail, DEFAULT_EDITOR;
2809
+ var init_platform = __esm({
2810
+ "src/platform.ts"() {
2811
+ "use strict";
2812
+ IS_WIN = process.platform === "win32";
2813
+ POSIX_SH = { bin: "/bin/sh", args: (c) => ["-c", c], label: "/bin/sh", kind: "sh" };
2814
+ isFile = (p) => {
2815
+ try {
2816
+ return statSync5(p).isFile();
2817
+ } catch {
2818
+ return false;
2819
+ }
2820
+ };
2821
+ DETACH_OPTIONS = IS_WIN ? { windowsHide: true } : { detached: true };
2822
+ CMD_META = /([()\][%!^"`<>&|;, *?])/g;
2823
+ pathTail = (p) => p.split(IS_WIN ? /[\\/]/ : "/").pop();
2824
+ DEFAULT_EDITOR = IS_WIN ? "notepad" : "vi";
2825
+ }
2826
+ });
2827
+
2554
2828
  // src/shell.sandbox.ts
2555
2829
  function writable(cwd, o, tmpDir) {
2556
2830
  const set = /* @__PURE__ */ new Set([cwd, "/tmp", "/private/tmp", "/private/var/folders", "/dev", ...tmpDir ? [tmpDir] : [], ...o.writePaths]);
@@ -2612,19 +2886,14 @@ __export(tools_shell_exports, {
2612
2886
  makeShellJobTools: () => makeShellJobTools,
2613
2887
  parsePsTime: () => parsePsTime,
2614
2888
  psGroupCpuSeconds: () => psGroupCpuSeconds,
2615
- sumTreeCpu: () => sumTreeCpu
2889
+ sumTreeCpu: () => sumTreeCpu,
2890
+ windowsShellNote: () => windowsShellNote
2616
2891
  });
2617
2892
  function killGroup(proc, signal) {
2618
- if (!proc?.pid) return false;
2619
- try {
2620
- process.kill(-proc.pid, signal);
2621
- return true;
2622
- } catch {
2623
- return false;
2624
- }
2893
+ return killTree(proc?.pid, signal);
2625
2894
  }
2626
2895
  async function spawnArgvFor(command, cwd, osSandbox) {
2627
- if (!osSandbox) return { bin: "/bin/sh", args: ["-c", command] };
2896
+ if (!osSandbox) return shellArgv(defaultShell(), command);
2628
2897
  const opts = osSandbox === true ? {} : osSandbox;
2629
2898
  const wrapper = await findSandboxWrapper();
2630
2899
  const wrapped = wrapper ? sandboxArgv(command, cwd, opts, process.platform, process.env.TMPDIR) : null;
@@ -2652,9 +2921,17 @@ function parsePsTime(t) {
2652
2921
  return Number(m[1] ?? 0) * 86400 + Number(m[2] ?? 0) * 3600 + Number(m[3]) * 60 + Number(m[4]);
2653
2922
  }
2654
2923
  async function psGroupCpuSeconds(pgid) {
2924
+ if (IS_WIN) {
2925
+ try {
2926
+ return await winTreeCpuSeconds(pgid);
2927
+ } catch (e) {
2928
+ log24.debug(`cpu probe failed for ${pgid}: ${e}`);
2929
+ return void 0;
2930
+ }
2931
+ }
2655
2932
  try {
2656
- const { execFile: execFile4 } = await import("child_process");
2657
- const out = await new Promise((res, rej) => execFile4("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
2933
+ const { execFile: execFile5 } = await import("child_process");
2934
+ const out = await new Promise((res, rej) => execFile5("ps", ["-A", "-o", "pid=,ppid=,pgid=,time="], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (e, so) => e ? rej(e) : res(String(so))));
2658
2935
  return sumTreeCpu(out, pgid);
2659
2936
  } catch (e) {
2660
2937
  log24.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
@@ -2705,7 +2982,7 @@ function makeRealShellTool(options) {
2705
2982
  return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
2706
2983
  },
2707
2984
  get description() {
2708
- 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();
2985
+ 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();
2709
2986
  },
2710
2987
  // Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
2711
2988
  maxDurationMs: maxTimeoutMs,
@@ -2729,7 +3006,7 @@ function makeRealShellTool(options) {
2729
3006
  return `Started background job ${id}. Poll output with ShellOutput({id:"${id}"}), check ShellStatus({id:"${id}"}), stop with ShellKill({id:"${id}"}).`;
2730
3007
  }
2731
3008
  const spawn4 = options.spawn ?? await nodeSpawn();
2732
- let argv = { bin: options.shell || "/bin/sh", args: ["-c", cmd] };
3009
+ let argv = shellArgv(shellFor(options.shell), cmd);
2733
3010
  if (options.osSandbox) {
2734
3011
  try {
2735
3012
  argv = await spawnArgvFor(cmd, options.cwd, options.osSandbox);
@@ -2764,22 +3041,23 @@ function makeRealShellTool(options) {
2764
3041
  }
2765
3042
  };
2766
3043
  try {
2767
- return await new Promise((resolve5) => {
3044
+ return await new Promise((resolve6) => {
2768
3045
  let out = "";
2769
3046
  let settled = false;
2770
3047
  const finish = (s) => {
2771
3048
  if (settled) return;
2772
3049
  settled = true;
2773
- resolve5(s);
3050
+ resolve6(s);
2774
3051
  };
2775
3052
  let proc;
2776
3053
  try {
2777
- proc = spawn4(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal, ...DETACHED });
3054
+ if (IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
3055
+ proc = spawn4(argv.bin, argv.args, { cwd: options.cwd, env: childEnv(options), signal: ctl.signal, ...DETACHED, ...argv.spawnOptions });
2778
3056
  } catch (e) {
2779
3057
  return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
2780
3058
  }
2781
3059
  if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
2782
- else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
3060
+ else if (!IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
2783
3061
  const registry = options.registry;
2784
3062
  if (registry?.adopt) {
2785
3063
  onDeadline = () => {
@@ -2824,6 +3102,12 @@ function makeRealShellTool(options) {
2824
3102
  }
2825
3103
  };
2826
3104
  }
3105
+ function windowsShellNote(sh) {
3106
+ if (!IS_WIN) return "";
3107
+ 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. ";
3108
+ 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. ";
3109
+ return "This is Windows and the shell is cmd.exe \u2014 use cmd syntax (`&&`, `%VAR%`, dir/type/findstr), not bash. ";
3110
+ }
2827
3111
  function handoffFor(timeoutMs, id, body, notifies) {
2828
3112
  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.");
2829
3113
  return body ? `${head}
@@ -2878,7 +3162,7 @@ ${clean2(out) || "(no output yet)"}`;
2878
3162
  }
2879
3163
  ];
2880
3164
  }
2881
- var log24, clean2, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
3165
+ var log24, clean2, DETACHED, shellArgv, SECRET_ENV_RE, _spawn, ShellJobRegistry, shellStatus, NO_JOB2;
2882
3166
  var init_tools_shell = __esm({
2883
3167
  "src/tools.shell.ts"() {
2884
3168
  "use strict";
@@ -2887,9 +3171,11 @@ var init_tools_shell = __esm({
2887
3171
  init_logging();
2888
3172
  init_shell_sandbox();
2889
3173
  init_tools_jobs();
3174
+ init_platform();
2890
3175
  log24 = forComponent("shell");
2891
3176
  clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
2892
- DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
3177
+ DETACHED = { stdio: ["ignore", "pipe", "pipe"], ...DETACH_OPTIONS };
3178
+ shellArgv = (sh, command) => ({ bin: sh.bin, args: sh.args(command), ...sh.spawnOptions ? { spawnOptions: sh.spawnOptions } : {} });
2893
3179
  SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
2894
3180
  ShellJobRegistry = class {
2895
3181
  constructor(cfg) {
@@ -2904,8 +3190,8 @@ var init_tools_shell = __esm({
2904
3190
  const h = this.track(command, { idleMs: 0 });
2905
3191
  try {
2906
3192
  const spawn4 = this.cfg.spawn ?? await nodeSpawn();
2907
- const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) : { bin: "/bin/sh", args: ["-c", command] };
2908
- this.attach(h, spawn4(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg), ...DETACHED }));
3193
+ const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) : shellArgv(defaultShell(), command);
3194
+ this.attach(h, spawn4(argv.bin, argv.args, { cwd: this.cfg.cwd, env: childEnv(this.cfg), ...DETACHED, ...argv.spawnOptions }));
2909
3195
  } catch (e) {
2910
3196
  h.chunk(`failed to spawn: ${e?.message ?? e}`);
2911
3197
  h.finish("error", void 0, e?.message ?? String(e));
@@ -3035,9 +3321,9 @@ var init_tools_shell = __esm({
3035
3321
 
3036
3322
  // cli/cli.ts
3037
3323
  import { createInterface } from "readline/promises";
3038
- import { existsSync as existsSync9, readFileSync as readFileSync10, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync7, unlinkSync as unlinkSync5 } from "fs";
3324
+ import { existsSync as existsSync9, readFileSync as readFileSync10, appendFileSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync8, unlinkSync as unlinkSync5 } from "fs";
3039
3325
  import { homedir as homedir10 } from "os";
3040
- import { spawnSync as spawnSync6 } from "child_process";
3326
+ import { spawnSync as spawnSync7 } from "child_process";
3041
3327
 
3042
3328
  // cli/clipboard.ts
3043
3329
  import { execFileSync } from "child_process";
@@ -3068,6 +3354,15 @@ function grabClipboardImage(dir, id, run = execFileSync, platform2 = process.pla
3068
3354
  }
3069
3355
  return null;
3070
3356
  }
3357
+ if (platform2 === "win32") {
3358
+ const ps = "Add-Type -AssemblyName System.Windows.Forms,System.Drawing; $i = [Windows.Forms.Clipboard]::GetImage(); if (-not $i) { exit 1 }; $i.Save($env:AGENTX_CLIP_OUT, [Drawing.Imaging.ImageFormat]::Png)";
3359
+ try {
3360
+ run("powershell.exe", ["-NoProfile", "-NonInteractive", "-STA", "-Command", ps], { ...SILENT, env: { ...process.env, AGENTX_CLIP_OUT: png }, windowsHide: true });
3361
+ if (nonEmpty(png)) return { path: png, mime: "image/png" };
3362
+ } catch {
3363
+ }
3364
+ return null;
3365
+ }
3071
3366
  if (platform2 !== "darwin") return null;
3072
3367
  const osa = (klass, out) => `set d to (the clipboard as \xABclass ${klass}\xBB)
3073
3368
  set f to open for access POSIX file ${q(out)} with write permission
@@ -3092,10 +3387,13 @@ close access f`;
3092
3387
  return null;
3093
3388
  }
3094
3389
  function copyTextToClipboard(text, platform2 = process.platform) {
3095
- const candidates = platform2 === "darwin" ? [["pbcopy", []]] : platform2 === "linux" ? [["wl-copy", []], ["xclip", ["-selection", "clipboard"]]] : [];
3390
+ const candidates = platform2 === "darwin" ? [["pbcopy", []]] : platform2 === "linux" ? [["wl-copy", []], ["xclip", ["-selection", "clipboard"]]] : (
3391
+ // Windows: Set-Clipboard reading stdin as UTF-8 (clip.exe would decode it with the OEM code page).
3392
+ platform2 === "win32" ? [["powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", "[Console]::InputEncoding = [Text.Encoding]::UTF8; Set-Clipboard -Value ([Console]::In.ReadToEnd())"]]] : []
3393
+ );
3096
3394
  for (const [cmd, args] of candidates) {
3097
3395
  try {
3098
- execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
3396
+ execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"], ...platform2 === "win32" ? { windowsHide: true } : {} });
3099
3397
  return true;
3100
3398
  } catch {
3101
3399
  }
@@ -3104,7 +3402,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
3104
3402
  }
3105
3403
 
3106
3404
  // cli/cli.ts
3107
- import { join as join14, resolve as resolve4, basename as basename2, extname, dirname as dirname5 } from "path";
3405
+ import { join as join15, resolve as resolve5, basename as basename4, extname, dirname as dirname6 } from "path";
3108
3406
  import { AIClient, DecideClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
3109
3407
 
3110
3408
  // src/ccBridge.ts
@@ -4340,6 +4638,10 @@ var AgentOptions = class {
4340
4638
  model = "anthropic/claude-sonnet-4-6";
4341
4639
  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.";
4342
4640
  tools = defaultTools();
4641
+ /** claude-code/* only: which agentx tools reach it as the in-process `agentx` MCP server (ccBridge.ts). 'memory' =
4642
+ * the ones Claude Code has no native twin for (CC_BRIDGED); 'all' = every tool this agent has — for an agent whose
4643
+ * tools ARE its job (the duplex reflex: Act/Think/Hold…), run with Claude Code's own tools off (providerOptions.tools: []). */
4644
+ ccBridge = "memory";
4343
4645
  maxSteps = 25;
4344
4646
  // --- automatic kill-switches (always on; protect the API budget against runaway loops/abuse) ---
4345
4647
  /** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
@@ -4935,8 +5237,9 @@ var Agent = class _Agent {
4935
5237
  };
4936
5238
  const cursorPo = isCursorWithTools ? { toolTimeoutMs: effectiveToolTimeoutMs, toolExecutor: hostExec } : void 0;
4937
5239
  const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
4938
- if (claudeSteerPo && o.providerOptions?.claudeCodeSession && this.activeTools.some((t) => CC_BRIDGED.has(t.name))) {
4939
- this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter((t) => CC_BRIDGED.has(t.name)), (n, a) => this.ccExec(n, a));
5240
+ const bridged = (t) => o.ccBridge === "all" || CC_BRIDGED.has(t.name);
5241
+ if (claudeSteerPo && o.providerOptions?.claudeCodeSession && this.activeTools.some(bridged)) {
5242
+ this.ccServer ??= ccToolServer("agentx", () => this.activeTools.filter(bridged), (n, a) => this.ccExec(n, a));
4940
5243
  claudeSteerPo.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
4941
5244
  }
4942
5245
  if (this.ccServer) this.ccExec = hostExec;
@@ -9331,6 +9634,10 @@ function describeCall(call) {
9331
9634
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
9332
9635
  return `${call.name}${hint}`;
9333
9636
  }
9637
+ function refusedCall(e) {
9638
+ const code = Number(e?.statusCode ?? e?.status);
9639
+ return code === 401 || code === 403 || /API key|unauthori[sz]ed|authentication|invalid x-api-key|permission denied/i.test(String(e?.message ?? e ?? ""));
9640
+ }
9334
9641
  var DuplexAgentOptions = class {
9335
9642
  /** Any ai.libx.js AIClient — shared by all tiers (routed by model). */
9336
9643
  ai;
@@ -9620,12 +9927,26 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9620
9927
  timeoutMs: 3e4,
9621
9928
  failEmptyTurns: false,
9622
9929
  // a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
9930
+ ...this.claudeCodeReflex(),
9623
9931
  ...o.reflexOptions,
9624
9932
  tools,
9625
9933
  // Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
9626
9934
  hooks: composeHooks(this.dispatchGuard(), o.reflexOptions?.hooks)
9627
9935
  });
9628
9936
  }
9937
+ /** A claude-code/* reflex (the keyless stand-in when the fast model's provider has no key): Claude Code runs its own
9938
+ * tool loop and takes no host tool schemas, so the reflex's tools (Act/Think/Hold…) reach it through the in-process
9939
+ * `agentx` MCP bridge (ccBridge 'all', which needs a warm session), with Claude Code's OWN tools off (`tools: []`) and
9940
+ * no other MCP server — the reflex only talks and routes; real work still goes through Act and its policy. */
9941
+ claudeCodeReflex() {
9942
+ const o = this.options;
9943
+ if (!o.reflexModel.startsWith("claude-code/")) return {};
9944
+ const po = o.providerOptionsFor?.(o.reflexModel) ?? {};
9945
+ return {
9946
+ ccBridge: "all",
9947
+ providerOptions: { ...po, claudeCodeSession: po.claudeCodeSession ?? crypto.randomUUID(), tools: [], mcpServers: {}, strictMcpConfig: true }
9948
+ };
9949
+ }
9629
9950
  /** Resolve memory tools + inject index into voice system prompt (once). */
9630
9951
  async initMemory() {
9631
9952
  if (!this.memoryReady) return;
@@ -9790,7 +10111,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9790
10111
  await this.drainReflex();
9791
10112
  this.scrubLeakFromTranscript();
9792
10113
  this.scrubHeldText();
9793
- if (this.silentTurn) await this.ackIfSilent(fallback);
10114
+ if (this.silentTurn && res?.finishReason === "error" && refusedCall(res.error)) this.emitHost({ kind: "text_delta", message: fallback ?? _DuplexAgent.FALLBACK_ERROR });
10115
+ else if (this.silentTurn) await this.ackIfSilent(fallback);
9794
10116
  }
9795
10117
  /** Remove complete stage-direction parentheticals from the UNFORWARDED reflex text (STAGE_DIRECTION_RE).
9796
10118
  * Only the unforwarded region is touched — already-spoken audio can't be unsent, and splicing before
@@ -9896,6 +10218,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9896
10218
  * hosts/tests key on it. */
9897
10219
  static FALLBACK_ACKS = ["Okay, on it.", "On it.", "Alright, working on it."];
9898
10220
  static FALLBACK_RETRY = ["Sorry, could you say that again?", "Hm, I missed that \u2014 could you say that again?"];
10221
+ static FALLBACK_ERROR = "Sorry, I couldn't answer that \u2014 something went wrong on my side.";
9899
10222
  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?"];
9900
10223
  lastFallback = "";
9901
10224
  /** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns.
@@ -10554,14 +10877,14 @@ Another agent just implemented the above. Independently check the CURRENT state
10554
10877
  * '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
10555
10878
  * task settle (callers map '' to deny / best-judgment). Workers never block forever. */
10556
10879
  parkQuestion(askId, question) {
10557
- return new Promise((resolve5) => {
10880
+ return new Promise((resolve6) => {
10558
10881
  let settled = false;
10559
10882
  const finish = (answer) => {
10560
10883
  if (settled) return;
10561
10884
  settled = true;
10562
10885
  clearTimeout(timer);
10563
10886
  this.pendingAsks.delete(askId);
10564
- resolve5(answer);
10887
+ resolve6(answer);
10565
10888
  };
10566
10889
  const timer = setTimeout(() => {
10567
10890
  this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
@@ -11308,11 +11631,11 @@ function buildMcpCatalog(servers) {
11308
11631
  }
11309
11632
  return { specs, routes };
11310
11633
  }
11311
- function searchOverCatalog(servers, specs, routes, resolve5, options) {
11634
+ function searchOverCatalog(servers, specs, routes, resolve6, options) {
11312
11635
  const tools = specs.length ? makeMcpToolSearch(specs, (name, args, o) => {
11313
11636
  const r = routes.get(name);
11314
11637
  if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
11315
- return resolve5(r.server, r.rawName, args ?? {}, o);
11638
+ return resolve6(r.server, r.rawName, args ?? {}, o);
11316
11639
  }, options) : [];
11317
11640
  return { tools, serverNames: servers, toolCount: specs.length };
11318
11641
  }
@@ -11330,6 +11653,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
11330
11653
 
11331
11654
  // src/mcp.client.ts
11332
11655
  init_logging();
11656
+ init_platform();
11333
11657
  import { spawn } from "child_process";
11334
11658
  import { createHash } from "crypto";
11335
11659
  var log23 = forComponent("mcp");
@@ -11357,7 +11681,9 @@ var StdioTransport = class {
11357
11681
  pending = /* @__PURE__ */ new Map();
11358
11682
  async start() {
11359
11683
  const { command, args = [], env, cwd } = this.spec;
11360
- const proc = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, ...env }, cwd });
11684
+ const childEnv2 = { ...process.env, ...env };
11685
+ const sp = resolveSpawn(command, args, childEnv2);
11686
+ const proc = spawn(sp.command, sp.args, { stdio: ["pipe", "pipe", "pipe"], env: childEnv2, cwd, ...IS_WIN ? { windowsHide: true, windowsVerbatimArguments: sp.windowsVerbatimArguments } : {} });
11361
11687
  this.proc = proc;
11362
11688
  proc.stdout.setEncoding("utf8");
11363
11689
  proc.stdout.on("data", (chunk) => this.onData(chunk));
@@ -11414,7 +11740,7 @@ var StdioTransport = class {
11414
11740
  const id = this.nextId++;
11415
11741
  const timeoutMs = deadlineFor(method, this.spec, opts);
11416
11742
  const { signal, onProgress } = opts;
11417
- return new Promise((resolve5, reject) => {
11743
+ return new Promise((resolve6, reject) => {
11418
11744
  if (signal?.aborted) return reject(abortError(signal));
11419
11745
  const timer = timeoutMs > 0 ? setTimeout(() => {
11420
11746
  this.pending.delete(id);
@@ -11429,7 +11755,7 @@ var StdioTransport = class {
11429
11755
  };
11430
11756
  const done = () => signal?.removeEventListener("abort", onAbort);
11431
11757
  signal?.addEventListener("abort", onAbort, { once: true });
11432
- this.pending.set(id, { resolve: resolve5, reject, timer, onProgress, done });
11758
+ this.pending.set(id, { resolve: resolve6, reject, timer, onProgress, done });
11433
11759
  try {
11434
11760
  this.write({ jsonrpc: "2.0", id, method, params: onProgress ? withProgressToken(params, id) : params });
11435
11761
  } catch (e) {
@@ -11450,7 +11776,7 @@ var StdioTransport = class {
11450
11776
  } catch (e) {
11451
11777
  log23.debug("stdin end failed", e);
11452
11778
  }
11453
- this.proc?.kill();
11779
+ if (!(IS_WIN && killTree(this.proc?.pid, "SIGTERM"))) this.proc?.kill();
11454
11780
  }
11455
11781
  };
11456
11782
  var McpAuthError = class extends Error {
@@ -11613,12 +11939,12 @@ function buildTransport(cfg) {
11613
11939
  }
11614
11940
  function withTimeout(p, ms, label) {
11615
11941
  if (!ms || ms <= 0) return p;
11616
- return new Promise((resolve5, reject) => {
11942
+ return new Promise((resolve6, reject) => {
11617
11943
  const timer = setTimeout(() => reject(new Error(`MCP "${label}" mount exceeded ${ms}ms`)), ms);
11618
11944
  timer.unref?.();
11619
11945
  p.then((v) => {
11620
11946
  clearTimeout(timer);
11621
- resolve5(v);
11947
+ resolve6(v);
11622
11948
  }, (e) => {
11623
11949
  clearTimeout(timer);
11624
11950
  reject(e);
@@ -11739,7 +12065,7 @@ var defaultPool = new McpPool();
11739
12065
  import { createServer } from "http";
11740
12066
  import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
11741
12067
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
11742
- import { dirname as dirname2 } from "path";
12068
+ import { dirname as dirname3 } from "path";
11743
12069
  var McpOAuthOptions = class {
11744
12070
  /** Where to persist tokens. Mode 0600. */
11745
12071
  storePath = ".agent/mcp-auth.json";
@@ -11772,7 +12098,7 @@ var McpOAuth = class {
11772
12098
  }
11773
12099
  }
11774
12100
  save(store) {
11775
- mkdirSync3(dirname2(this.options.storePath), { recursive: true });
12101
+ mkdirSync3(dirname3(this.options.storePath), { recursive: true });
11776
12102
  writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
11777
12103
  }
11778
12104
  // -- ATTENDED: one-time authorization ----------------------------------------
@@ -11864,7 +12190,7 @@ var McpOAuth = class {
11864
12190
  }
11865
12191
  /** Start a one-route loopback server, open the browser, and resolve with the returned auth code. */
11866
12192
  captureCode(authEndpoint, clientId, challenge, state) {
11867
- return new Promise((resolve5, reject) => {
12193
+ return new Promise((resolve6, reject) => {
11868
12194
  const server = createServer((req, res) => {
11869
12195
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
11870
12196
  if (url.pathname !== "/callback") {
@@ -11878,7 +12204,7 @@ var McpOAuth = class {
11878
12204
  clearTimeout(timer);
11879
12205
  if (gotState !== state) return reject(new Error("OAuth state mismatch (possible CSRF) \u2014 aborting"));
11880
12206
  if (!code) return reject(new Error(`authorization failed: ${url.searchParams.get("error") ?? "no code"}`));
11881
- resolve5({ code, redirectUri });
12207
+ resolve6({ code, redirectUri });
11882
12208
  });
11883
12209
  let redirectUri = "";
11884
12210
  const timer = setTimeout(() => {
@@ -11923,26 +12249,33 @@ function b64url(buf) {
11923
12249
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
11924
12250
  }
11925
12251
  function defaultOpenBrowser(url) {
11926
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
11927
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
11928
- import("child_process").then(({ spawn: spawn4 }) => spawn4(cmd, args, { stdio: "ignore", detached: true }).unref());
12252
+ const win = process.platform === "win32";
12253
+ const cmd = process.platform === "darwin" ? "open" : win ? "rundll32.exe" : "xdg-open";
12254
+ const args = win ? ["url.dll,FileProtocolHandler", url] : [url];
12255
+ import("child_process").then(({ spawn: spawn4 }) => spawn4(cmd, args, { stdio: "ignore", ...win ? { windowsHide: true } : { detached: true } }).unref());
11929
12256
  }
11930
12257
 
11931
12258
  // cli/core.ts
11932
12259
  import { randomUUID } from "crypto";
11933
- import { execFile as execFile2 } from "child_process";
11934
- import { resolve as resolve2, basename, join as join5 } from "path";
12260
+ import { execFile as execFile3 } from "child_process";
12261
+ import { resolve as resolve3, basename as basename3, join as join6 } from "path";
11935
12262
  import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
11936
12263
  import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
11937
12264
  init_tools_shell();
11938
12265
 
11939
12266
  // src/tools.notify.ts
11940
12267
  init_logging();
11941
- import { execFile } from "child_process";
12268
+ import { execFile as execFile2 } from "child_process";
11942
12269
  var log25 = forComponent("notify");
12270
+ var WIN_TOAST = [
12271
+ "[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null",
12272
+ "$t = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)",
12273
+ '$n = $t.GetElementsByTagName("text"); $n.Item(0).AppendChild($t.CreateTextNode($env:AGENTX_NOTIFY_TITLE)) | Out-Null; $n.Item(1).AppendChild($t.CreateTextNode($env:AGENTX_NOTIFY_BODY)) | Out-Null',
12274
+ '[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\\WindowsPowerShell\\v1.0\\powershell.exe").Show([Windows.UI.Notifications.ToastNotification]::new($t))'
12275
+ ].join("; ");
11943
12276
  function makeNotifyTool(opts = {}) {
11944
12277
  const platform2 = opts.platform ?? process.platform;
11945
- const run = opts.exec ?? execFile;
12278
+ const run = opts.exec ?? execFile2;
11946
12279
  return {
11947
12280
  name: "PushNotification",
11948
12281
  description: "Show an OS desktop notification to the user (out-of-band alert \u2014 e.g. a long task finished, input is needed). Use sparingly: only for events the user would want to be interrupted for.",
@@ -11958,14 +12291,15 @@ function makeNotifyTool(opts = {}) {
11958
12291
  const msg = String(message ?? "").slice(0, 256);
11959
12292
  const head = String(title ?? "agentx").slice(0, 64);
11960
12293
  if (!msg) return "Error: empty message";
11961
- const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : null;
12294
+ const argv = platform2 === "darwin" ? ["osascript", ["-e", `display notification ${JSON.stringify(msg)} with title ${JSON.stringify(head)}`]] : platform2 === "linux" ? ["notify-send", [head, msg]] : platform2 === "win32" ? ["powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WIN_TOAST]] : null;
11962
12295
  if (!argv) return `Notifications unavailable on ${platform2}.`;
11963
- return new Promise((resolve5) => {
11964
- run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
12296
+ const extra = platform2 === "win32" ? { windowsHide: true, env: { ...process.env, AGENTX_NOTIFY_TITLE: head, AGENTX_NOTIFY_BODY: msg } } : {};
12297
+ return new Promise((resolve6) => {
12298
+ run(argv[0], argv[1], { timeout: 5e3, ...extra }, (e) => {
11965
12299
  if (e) {
11966
12300
  log25.debug("notification failed", e);
11967
- resolve5(`Notification failed: ${e.message}`);
11968
- } else resolve5("Notification shown.");
12301
+ resolve6(`Notification failed: ${e.message}`);
12302
+ } else resolve6("Notification shown.");
11969
12303
  });
11970
12304
  });
11971
12305
  }
@@ -12013,13 +12347,15 @@ function readJsonFile(path, fallback) {
12013
12347
  }
12014
12348
 
12015
12349
  // cli/core.ts
12350
+ init_hostPath();
12351
+ init_platform();
12016
12352
  var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
12017
12353
  function autoWebTools() {
12018
12354
  return ["WebFetch", "WebSearch"];
12019
12355
  }
12020
12356
  function pdfTextViaPoppler(path) {
12021
12357
  return new Promise((res, rej) => {
12022
- execFile2("pdftotext", [path, "-"], { maxBuffer: 32 * 1024 * 1024, timeout: 3e4 }, (e, stdout) => {
12358
+ execFile3("pdftotext", [path, "-"], { maxBuffer: 32 * 1024 * 1024, timeout: 3e4 }, (e, stdout) => {
12023
12359
  if (e) rej(new Error(/ENOENT/.test(String(e.code ?? e.message)) ? "pdftotext not installed (brew/apt install poppler)" : e.message));
12024
12360
  else res(stdout);
12025
12361
  });
@@ -12083,7 +12419,7 @@ function toClaudeCodeMcp(servers) {
12083
12419
  function claudeCodeProviderOptions(model, cwd, mcpServers) {
12084
12420
  if (!(model ?? "").startsWith("claude-code/")) return void 0;
12085
12421
  const env = (() => {
12086
- const base = process.env.AGENTX_CC_USE_API_KEY ? process.env : (({ ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ...rest }) => rest)(process.env);
12422
+ const base = plainEnv(process.env, process.env.AGENTX_CC_USE_API_KEY ? [] : ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"]);
12087
12423
  return { ENABLE_CLAUDEAI_MCP_SERVERS: "false", ...base };
12088
12424
  })();
12089
12425
  const warm = process.env.AGENTX_CC_WARM === "0" ? {} : { claudeCodeSession: randomUUID() };
@@ -12097,7 +12433,7 @@ async function buildAgent(o) {
12097
12433
  throw new Error(
12098
12434
  "buildAgent: explicit `sandbox` required \u2014 false = disk (the REAL filesystem, full reach like Claude Code; secrets still hidden) or true = sandbox (an in-memory copy of cwd; the real disk is never modified). This forces a conscious sandboxing decision."
12099
12435
  );
12100
- const cwd = resolve2(o.cwd ?? process.cwd());
12436
+ const cwd = resolve3(o.cwd ?? process.cwd());
12101
12437
  const disk = new NodeDiskFilesystem("/", { denySymlinks: false });
12102
12438
  await disk.init();
12103
12439
  const jailedDisk = new JailedFilesystem(disk);
@@ -12111,21 +12447,22 @@ async function buildAgent(o) {
12111
12447
  `${isCursor ? "cursor" : "claude-code"}/* models cannot run in --sandbox/--boddb: the delegated agent runs its own real-disk tools and bypasses the VFS jail. Use disk mode (default).`
12112
12448
  );
12113
12449
  let fs = jailedDisk;
12450
+ const vcwd = toVfsPath(cwd);
12114
12451
  if (o.sandbox) {
12115
- const mem = new MemFilesystem3();
12116
- await mkdirp(mem, cwd);
12117
- await hydrate(jailedDisk, mem, cwd);
12118
- mem.setCwd(cwd);
12452
+ const mem = withHostPaths(new MemFilesystem3());
12453
+ await mkdirp(mem, vcwd);
12454
+ await hydrate(jailedDisk, mem, vcwd);
12455
+ mem.setCwd(vcwd);
12119
12456
  fs = mem;
12120
12457
  } else if (o.boddb) {
12121
- const root = resolve2(o.boddb);
12458
+ const root = resolve3(o.boddb);
12122
12459
  mkdirSync4(root, { recursive: true });
12123
- const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
12124
- const bod = new BodDbFilesystem(db);
12460
+ const db = new BodDB2({ path: join6(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join6(root, "files") } });
12461
+ const bod = withHostPaths(new BodDbFilesystem(db));
12125
12462
  const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
12126
- await mkdirp(bod, cwd);
12127
- if (o.seed && firstRun) await hydrate(jailedDisk, bod, cwd);
12128
- bod.setCwd(cwd);
12463
+ await mkdirp(bod, vcwd);
12464
+ if (o.seed && firstRun) await hydrate(jailedDisk, bod, vcwd);
12465
+ bod.setCwd(vcwd);
12129
12466
  fs = bod;
12130
12467
  }
12131
12468
  let mountNote = "";
@@ -12134,8 +12471,8 @@ async function buildAgent(o) {
12134
12471
  const mounts = [];
12135
12472
  const notes = [];
12136
12473
  for (const d of o.addDirs) {
12137
- const abs = resolve2(d);
12138
- let name = basename(abs) || "dir", prefix = `/${name}`, n = 2;
12474
+ const abs = resolve3(d);
12475
+ let name = basename3(abs) || "dir", prefix = `/${name}`, n = 2;
12139
12476
  while (used.has(prefix)) prefix = `/${name}-${n++}`;
12140
12477
  used.add(prefix);
12141
12478
  const md = new NodeDiskFilesystem(abs);
@@ -12187,7 +12524,7 @@ Reference files in them by their mount path (the left side).`;
12187
12524
  });
12188
12525
  realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox, jobsSurviveRun: o.singleRun !== true }), ...makeShellJobTools(jobs)];
12189
12526
  }
12190
- const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${cwd}/.agent/scratch` : `${tmpdir()}/agentx-scratch-${process.pid}`) : void 0;
12527
+ const scratchDir = o.scratch ? o.scratchDir ?? (virtual ? `${vcwd}/.agent/scratch` : toVfsPath(`${tmpdir()}/agentx-scratch-${process.pid}`)) : void 0;
12191
12528
  const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
12192
12529
  return agentRef.agent = new Agent({
12193
12530
  ai: o.ai,
@@ -12212,12 +12549,13 @@ Reference files in them by their mount path (the left side).`;
12212
12549
  const envNote = `Current date: ${now4.toLocaleDateString("en-CA")} ${now4.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })} (${Intl.DateTimeFormat().resolvedOptions().timeZone})
12213
12550
  Platform: ${platformNames[platform()] ?? platform()} ${arch()} (${release()})
12214
12551
  User: ${userInfo().username}
12215
- Shell: ${process.env.SHELL ?? "unknown"}`;
12552
+ Shell: ${process.env.SHELL ?? (isWinPaths() ? defaultShell().label : "unknown")}`;
12216
12553
  const cwdNote = o.boddb ? `Working directory: ${cwd}
12217
12554
  DATABASE WORKSPACE: your files live in a database (bod-db), not on the real disk \u2014 the real disk is never modified and nothing outside this tree exists. Use paths relative to the working directory, or absolute paths under it.` : o.sandbox ? `Working directory: ${cwd}
12218
12555
  SANDBOX: you operate on an in-memory COPY of this directory \u2014 the real disk is never modified and nothing outside it exists. Use paths relative to the working directory, or absolute paths under it.` : `Working directory: ${cwd}
12219
- The filesystem root '/' is the real machine root \u2014 you have full filesystem access, like a normal shell. Use paths relative to the working directory, or absolute paths. Secret files (.env, .ssh, keys, .git) are hidden for safety.`;
12220
- const shellNote = useRealShell && !virtual ? "The `Shell` tool runs /bin/sh \u2014 you can run any installed binary (git, bun, node, curl, etc). Use `Shell` for all shell commands." : void 0;
12556
+ The filesystem root '/' is the real machine root \u2014 you have full filesystem access, like a normal shell. Use paths relative to the working directory, or absolute paths. Secret files (.env, .ssh, keys, .git) are hidden for safety.` + (isWinPaths() ? `
12557
+ Windows paths: file tools accept ${cwd} style paths and the same path written ${vcwd} (drive letter as the first segment); tool results list paths in the ${vcwd.split("/").slice(0, 2).join("/")}/\u2026 form. '/' itself lists the drives.` : "");
12558
+ const shellNote = useRealShell && !virtual ? `The \`Shell\` tool runs ${defaultShell().label} \u2014 you can run any installed binary (git, bun, node, curl, etc). Use \`Shell\` for all shell commands.` : void 0;
12221
12559
  const toneNote = "Be concise. Do not use emojis unless the user asks. Do not explain what you are about to do \u2014 just do it.";
12222
12560
  const mcpNote = (() => {
12223
12561
  const names = new Set(Object.keys(o.mcpServers ?? {}));
@@ -12300,10 +12638,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
12300
12638
 
12301
12639
  // cli/voice.ts
12302
12640
  init_logging();
12303
- import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
12304
- import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync5 } from "fs";
12641
+ init_platform();
12642
+ import { spawn as spawn2, spawnSync as spawnSync3 } from "child_process";
12643
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, statSync as statSync6 } from "fs";
12305
12644
  import { homedir as homedir3 } from "os";
12306
- import { dirname as dirname3, join as join6 } from "path";
12645
+ import { dirname as dirname4, join as join7 } from "path";
12307
12646
  import { fileURLToPath } from "url";
12308
12647
  var log27 = forComponent("VoiceIO");
12309
12648
  var now3 = () => performance.now();
@@ -12346,10 +12685,10 @@ var Player = class {
12346
12685
  this.proc = null;
12347
12686
  }
12348
12687
  };
12349
- var nativeDir = () => join6(dirname3(fileURLToPath(import.meta.url)), "native");
12688
+ var nativeDir = () => join7(dirname4(fileURLToPath(import.meta.url)), "native");
12350
12689
  function detectFfmpegMic() {
12351
12690
  if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
12352
- const r = spawnSync2("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" });
12691
+ const r = spawnSync3("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" });
12353
12692
  if (r.error?.code === "ENOENT") throw new Error("ffmpeg not installed \u2014 run `brew install ffmpeg` to enable voice");
12354
12693
  if (r.error) throw new Error(`ffmpeg failed to run (${r.error.message}) \u2014 likely a wrong-arch binary; run \`brew reinstall ffmpeg\``);
12355
12694
  const out = r.stderr ?? "";
@@ -12360,13 +12699,24 @@ function detectFfmpegMic() {
12360
12699
  log27.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
12361
12700
  return `:${mic.idx}`;
12362
12701
  }
12702
+ function detectDshowMic() {
12703
+ const env = process.env.MIC_DEVICE;
12704
+ if (env) return env.startsWith("audio=") ? env : `audio=${env}`;
12705
+ const r = spawnSync3("ffmpeg", ["-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "dummy"], { encoding: "utf8", windowsHide: true });
12706
+ if (r.error?.code === "ENOENT") throw new Error("ffmpeg not installed \u2014 install it (e.g. `winget install ffmpeg`) and put it on PATH to enable voice");
12707
+ const devices = [...(r.stderr ?? "").matchAll(/"([^"]+)"\s*\(audio\)/g)].map((m) => m[1]);
12708
+ const mic = devices.find((d) => !/stereo mix|loopback|virtual|cable|teams/i.test(d)) ?? devices[0];
12709
+ if (!mic) throw new Error("no audio input device found (set MIC_DEVICE to the DirectShow device name)");
12710
+ log27.debug(`ffmpeg dshow mic: ${mic}`);
12711
+ return `audio=${mic}`;
12712
+ }
12363
12713
  function detectedInputDevice() {
12364
12714
  if (process.platform !== "darwin") return null;
12365
12715
  let name = "";
12366
- if (spawnSync2("which", ["SwitchAudioSource"]).status === 0) {
12367
- name = (spawnSync2("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
12716
+ if (spawnSync3("which", ["SwitchAudioSource"]).status === 0) {
12717
+ name = (spawnSync3("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
12368
12718
  } else {
12369
- const out = spawnSync2("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
12719
+ const out = spawnSync3("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
12370
12720
  let last = "";
12371
12721
  for (const ln of out.split("\n")) {
12372
12722
  const hdr = ln.match(/^\s{6,}(\S.*?):\s*$/);
@@ -12385,21 +12735,21 @@ function detectedInputDevice() {
12385
12735
  }
12386
12736
  function resolveAecBinary() {
12387
12737
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
12388
- const src = join6(nativeDir(), "mic-aec.swift");
12389
- const plist = join6(nativeDir(), "Info.plist");
12738
+ const src = join7(nativeDir(), "mic-aec.swift");
12739
+ const plist = join7(nativeDir(), "Info.plist");
12390
12740
  if (!existsSync4(src)) return null;
12391
- const cacheDir = join6(homedir3(), ".agent", "cache");
12392
- const bin = join6(cacheDir, "mic-aec");
12393
- if (existsSync4(bin) && statSync5(bin).mtimeMs >= statSync5(src).mtimeMs) return bin;
12394
- if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
12741
+ const cacheDir = join7(homedir3(), ".agent", "cache");
12742
+ const bin = join7(cacheDir, "mic-aec");
12743
+ if (existsSync4(bin) && statSync6(bin).mtimeMs >= statSync6(src).mtimeMs) return bin;
12744
+ if (spawnSync3("which", ["swiftc"]).status !== 0) return null;
12395
12745
  mkdirSync5(cacheDir, { recursive: true });
12396
12746
  log27.info("compiling AEC mic helper (first run)\u2026");
12397
- const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
12747
+ const build = spawnSync3("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
12398
12748
  if (build.status !== 0) {
12399
12749
  log27.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
12400
12750
  return null;
12401
12751
  }
12402
- const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
12752
+ const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
12403
12753
  if (sign.status !== 0) {
12404
12754
  log27.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
12405
12755
  return null;
@@ -12409,14 +12759,14 @@ function resolveAecBinary() {
12409
12759
  function openMicSettings() {
12410
12760
  if (process.platform !== "darwin") return;
12411
12761
  try {
12412
- spawnSync2("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
12762
+ spawnSync3("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
12413
12763
  } catch {
12414
12764
  }
12415
12765
  }
12416
12766
  function micPermissionStatus() {
12417
12767
  const bin = resolveAecBinary();
12418
12768
  if (!bin) return null;
12419
- const r = spawnSync2(bin, ["--check-mic"], { encoding: "utf8" });
12769
+ const r = spawnSync3(bin, ["--check-mic"], { encoding: "utf8" });
12420
12770
  const out = (r.stdout ?? "").trim();
12421
12771
  if (out === "authorized") return "authorized";
12422
12772
  if (out === "denied" || out === "restricted") return "denied";
@@ -12426,7 +12776,7 @@ function micPermissionStatus() {
12426
12776
  function aecUnavailableHint() {
12427
12777
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
12428
12778
  if (resolveAecBinary()) return null;
12429
- if (spawnSync2("which", ["swiftc"]).status !== 0) return "install Xcode CLI tools (`xcode-select --install`) for echo cancellation";
12779
+ if (spawnSync3("which", ["swiftc"]).status !== 0) return "install Xcode CLI tools (`xcode-select --install`) for echo cancellation";
12430
12780
  return null;
12431
12781
  }
12432
12782
  var NodeMicSource = class {
@@ -12442,12 +12792,13 @@ var NodeMicSource = class {
12442
12792
  if (this.bin) {
12443
12793
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
12444
12794
  } else {
12445
- if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
12795
+ if (!hasCommand("ffmpeg")) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
12446
12796
  log27.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
12797
+ const input = process.platform === "win32" ? ["-f", "dshow", "-i", detectDshowMic()] : ["-f", "avfoundation", "-i", detectFfmpegMic()];
12447
12798
  this.proc = spawn2(
12448
12799
  "ffmpeg",
12449
- ["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
12450
- { stdio: ["ignore", "pipe", "pipe"] }
12800
+ ["-loglevel", "error", ...input, "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
12801
+ { stdio: ["ignore", "pipe", "pipe"], ...process.platform === "win32" ? { windowsHide: true } : {} }
12451
12802
  );
12452
12803
  this.proc.stderr.on("data", (d) => log27.warn(`ffmpeg: ${String(d).trim()}`));
12453
12804
  }
@@ -12834,7 +13185,7 @@ function fakeVoiceParts(uttFile) {
12834
13185
  // src/voice/diagSink.ts
12835
13186
  init_logging();
12836
13187
  import { appendFile, mkdirSync as mkdirSync6 } from "fs";
12837
- import { dirname as dirname4 } from "path";
13188
+ import { dirname as dirname5 } from "path";
12838
13189
  var log28 = forComponent("VoiceDiag");
12839
13190
  var JsonlDiagSink = class {
12840
13191
  constructor(path) {
@@ -12859,7 +13210,7 @@ var JsonlDiagSink = class {
12859
13210
  if (this.writing || this.disabled || !this.queue.length) return;
12860
13211
  if (!this.dirReady) {
12861
13212
  try {
12862
- mkdirSync6(dirname4(this.path), { recursive: true });
13213
+ mkdirSync6(dirname5(this.path), { recursive: true });
12863
13214
  this.dirReady = true;
12864
13215
  } catch (e) {
12865
13216
  return this.disable(e);
@@ -12891,13 +13242,13 @@ var JsonlDiagSink = class {
12891
13242
  // cli/config.ts
12892
13243
  import { homedir as homedir5 } from "os";
12893
13244
  import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
12894
- import { join as join8 } from "path";
13245
+ import { join as join9 } from "path";
12895
13246
  import { pathToFileURL } from "url";
12896
13247
 
12897
13248
  // cli/permissions.ts
12898
13249
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
12899
13250
  import { homedir as homedir4 } from "os";
12900
- import { join as join7 } from "path";
13251
+ import { join as join8 } from "path";
12901
13252
  var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
12902
13253
  function parseOne(raw, decision) {
12903
13254
  const m = RULE_RE.exec(raw.trim());
@@ -12919,13 +13270,13 @@ function parsePermRules(perms, explicit = false) {
12919
13270
  function describeRule(r) {
12920
13271
  return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
12921
13272
  }
12922
- var PERM_FILE = (cwd) => join7(cwd, ".agent", "permissions.json");
13273
+ var PERM_FILE = (cwd) => join8(cwd, ".agent", "permissions.json");
12923
13274
  function loadPersistedRules(cwd) {
12924
13275
  const j = readJsonFile(PERM_FILE(cwd), null);
12925
13276
  return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
12926
13277
  }
12927
13278
  function loadClaudeSettings(cwd, home = homedir4()) {
12928
- const files = [join7(home, ".claude", "settings.json"), join7(cwd, ".claude", "settings.json"), join7(cwd, ".claude", "settings.local.json")];
13279
+ const files = [join8(home, ".claude", "settings.json"), join8(cwd, ".claude", "settings.json"), join8(cwd, ".claude", "settings.local.json")];
12929
13280
  let out = {};
12930
13281
  for (const p of files) {
12931
13282
  const perms = readJsonFile(p, null)?.permissions;
@@ -12938,7 +13289,7 @@ function persistRule(cwd, decision, ruleStr) {
12938
13289
  const list = cur[decision] ??= [];
12939
13290
  if (!list.includes(ruleStr)) list.push(ruleStr);
12940
13291
  try {
12941
- mkdirSync7(join7(cwd, ".agent"), { recursive: true });
13292
+ mkdirSync7(join8(cwd, ".agent"), { recursive: true });
12942
13293
  writeFileSync4(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
12943
13294
  } catch {
12944
13295
  }
@@ -12952,7 +13303,7 @@ function mergePerms(a, b) {
12952
13303
  }
12953
13304
  return Object.keys(out).length ? out : void 0;
12954
13305
  }
12955
- var TRUST_FILE = join7(homedir4(), ".agent", "trusted.json");
13306
+ var TRUST_FILE = join8(homedir4(), ".agent", "trusted.json");
12956
13307
  function isTrusted(cwd, file = TRUST_FILE) {
12957
13308
  const list = readJsonFile(file, []);
12958
13309
  return Array.isArray(list) && list.includes(cwd);
@@ -12962,7 +13313,7 @@ function trustDir(cwd, file = TRUST_FILE) {
12962
13313
  if (!Array.isArray(list)) list = [];
12963
13314
  if (!list.includes(cwd)) list.push(cwd);
12964
13315
  try {
12965
- mkdirSync7(join7(file, ".."), { recursive: true });
13316
+ mkdirSync7(join8(file, ".."), { recursive: true });
12966
13317
  writeFileSync4(file, JSON.stringify(list, null, 2) + "\n");
12967
13318
  } catch {
12968
13319
  }
@@ -12972,7 +13323,7 @@ function trustDir(cwd, file = TRUST_FILE) {
12972
13323
  var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
12973
13324
  async function loadFrom(dir) {
12974
13325
  for (const f of FILES) {
12975
- const p = join8(dir, ".agent", f);
13326
+ const p = join9(dir, ".agent", f);
12976
13327
  if (!existsSync5(p)) continue;
12977
13328
  try {
12978
13329
  const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
@@ -12985,7 +13336,7 @@ async function loadFrom(dir) {
12985
13336
  return {};
12986
13337
  }
12987
13338
  function loadSettings(dir) {
12988
- const p = join8(dir, ".agent", "settings.json");
13339
+ const p = join9(dir, ".agent", "settings.json");
12989
13340
  if (!existsSync5(p)) return {};
12990
13341
  try {
12991
13342
  const raw = JSON.parse(readFileSync7(p, "utf8"));
@@ -13032,7 +13383,8 @@ function applyOverlay(cfg, file) {
13032
13383
  }
13033
13384
 
13034
13385
  // cli/hooks-config.ts
13035
- import { spawnSync as spawnSync3 } from "child_process";
13386
+ import { spawnSync as spawnSync4 } from "child_process";
13387
+ init_platform();
13036
13388
  var log29 = forComponent("hooks");
13037
13389
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
13038
13390
  function ruleMatches(rule, toolName) {
@@ -13042,8 +13394,10 @@ function ruleMatches(rule, toolName) {
13042
13394
  }
13043
13395
  function runCmd(rule, env) {
13044
13396
  try {
13045
- const r = spawnSync3(rule.command, {
13046
- shell: true,
13397
+ const r = spawnSync4(rule.command, {
13398
+ shell: shellOption(),
13399
+ // /bin/sh; Windows: Git Bash when installed (hooks are written in sh)
13400
+ windowsHide: true,
13047
13401
  encoding: "utf8",
13048
13402
  timeout: rule.timeoutMs ?? 1e4,
13049
13403
  env: { ...process.env, ...env }
@@ -13154,26 +13508,45 @@ function formatDiff(ops, opts = {}) {
13154
13508
  }
13155
13509
 
13156
13510
  // cli/session.ts
13157
- import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync, statSync as statSync6 } from "fs";
13511
+ import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync5, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync, statSync as statSync7 } from "fs";
13158
13512
  import { homedir as homedir6 } from "os";
13159
- import { join as join9 } from "path";
13513
+ import { join as join10 } from "path";
13514
+ init_platform();
13160
13515
  var log30 = forComponent("session");
13161
- var globalDir = () => join9(homedir6(), ".agent", "sessions");
13516
+ var globalDir = () => join10(homedir6(), ".agent", "sessions");
13517
+ function indexLink(target, link2) {
13518
+ try {
13519
+ symlinkSync2(target, link2);
13520
+ } catch (e) {
13521
+ if (!IS_WIN) throw e;
13522
+ writeFileSync5(link2, JSON.stringify({ agentxSessionLink: target }));
13523
+ }
13524
+ }
13525
+ function readIndexLink(link2) {
13526
+ try {
13527
+ return readlinkSync(link2);
13528
+ } catch (e) {
13529
+ if (!IS_WIN) throw e;
13530
+ const target = JSON.parse(readFileSync8(link2, "utf8")).agentxSessionLink;
13531
+ if (typeof target !== "string") throw e;
13532
+ return target;
13533
+ }
13534
+ }
13162
13535
  var SessionStore = class {
13163
13536
  dir;
13164
13537
  constructor(cwd) {
13165
- this.dir = join9(cwd, ".agent", "sessions");
13538
+ this.dir = join10(cwd, ".agent", "sessions");
13166
13539
  }
13167
13540
  /** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
13168
13541
  newId(now4 = Date.now(), cwd) {
13169
13542
  const d = new Date(now4);
13170
13543
  const p = (n, w = 2) => String(n).padStart(w, "0");
13171
- const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
13544
+ const slug2 = pathTail(cwd ?? process.cwd())?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
13172
13545
  let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
13173
- if (existsSync6(this.dir) && existsSync6(join9(this.dir, `${id}.json`))) {
13546
+ if (existsSync6(this.dir) && existsSync6(join10(this.dir, `${id}.json`))) {
13174
13547
  for (let i = 2; i <= 99; i++) {
13175
13548
  const c = `${id}-${i}`;
13176
- if (!existsSync6(join9(this.dir, `${c}.json`))) {
13549
+ if (!existsSync6(join10(this.dir, `${c}.json`))) {
13177
13550
  id = c;
13178
13551
  break;
13179
13552
  }
@@ -13188,15 +13561,15 @@ var SessionStore = class {
13188
13561
  save(data) {
13189
13562
  if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
13190
13563
  if (!existsSync6(this.dir)) mkdirSync8(this.dir, { recursive: true });
13191
- const path = join9(this.dir, `${data.meta.id}.json`);
13564
+ const path = join10(this.dir, `${data.meta.id}.json`);
13192
13565
  const tmp = `${path}.${process.pid}.tmp`;
13193
13566
  writeFileSync5(tmp, JSON.stringify(data));
13194
13567
  renameSync(tmp, path);
13195
13568
  try {
13196
13569
  const gd = globalDir();
13197
13570
  if (!existsSync6(gd)) mkdirSync8(gd, { recursive: true });
13198
- const link2 = join9(gd, `${data.meta.id}.json`);
13199
- if (!existsSync6(link2)) symlinkSync2(path, link2);
13571
+ const link2 = join10(gd, `${data.meta.id}.json`);
13572
+ if (!existsSync6(link2)) indexLink(path, link2);
13200
13573
  } catch {
13201
13574
  }
13202
13575
  }
@@ -13205,7 +13578,7 @@ var SessionStore = class {
13205
13578
  log30.debug(`rejecting unsafe session id: ${id}`);
13206
13579
  return void 0;
13207
13580
  }
13208
- const path = join9(this.dir, `${id}.json`);
13581
+ const path = join10(this.dir, `${id}.json`);
13209
13582
  if (!existsSync6(path)) return void 0;
13210
13583
  try {
13211
13584
  return JSON.parse(readFileSync8(path, "utf8"));
@@ -13221,7 +13594,7 @@ var SessionStore = class {
13221
13594
  for (const f of readdirSync(this.dir)) {
13222
13595
  if (!f.endsWith(".json")) continue;
13223
13596
  try {
13224
- metas.push(JSON.parse(readFileSync8(join9(this.dir, f), "utf8")).meta);
13597
+ metas.push(JSON.parse(readFileSync8(join10(this.dir, f), "utf8")).meta);
13225
13598
  } catch (e) {
13226
13599
  log30.debug(`skipping unreadable session file ${f}`, e);
13227
13600
  }
@@ -13236,7 +13609,7 @@ var SessionStore = class {
13236
13609
  for (const f of readdirSync(this.dir)) {
13237
13610
  if (!f.endsWith(".json")) continue;
13238
13611
  try {
13239
- const st = statSync6(join9(this.dir, f));
13612
+ const st = statSync7(join10(this.dir, f));
13240
13613
  out.push({ id: f.slice(0, -5), mtime: st.mtimeMs, size: st.size });
13241
13614
  } catch {
13242
13615
  }
@@ -13255,10 +13628,10 @@ var SessionStore = class {
13255
13628
  function globalSessionLoad(idOrPrefix) {
13256
13629
  const gd = globalDir();
13257
13630
  if (!existsSync6(gd)) return void 0;
13258
- const exact = join9(gd, `${idOrPrefix}.json`);
13631
+ const exact = join10(gd, `${idOrPrefix}.json`);
13259
13632
  if (existsSync6(exact)) {
13260
13633
  try {
13261
- const target = readlinkSync(exact);
13634
+ const target = readIndexLink(exact);
13262
13635
  return JSON.parse(readFileSync8(target, "utf8"));
13263
13636
  } catch {
13264
13637
  return void 0;
@@ -13269,7 +13642,7 @@ function globalSessionLoad(idOrPrefix) {
13269
13642
  if (!f.endsWith(".json")) continue;
13270
13643
  const base = f.slice(0, -5);
13271
13644
  if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
13272
- const target = readlinkSync(join9(gd, f));
13645
+ const target = readIndexLink(join10(gd, f));
13273
13646
  return JSON.parse(readFileSync8(target, "utf8"));
13274
13647
  }
13275
13648
  }
@@ -13284,10 +13657,10 @@ function globalSessionList() {
13284
13657
  for (const f of readdirSync(gd)) {
13285
13658
  if (!f.endsWith(".json")) continue;
13286
13659
  try {
13287
- const target = readlinkSync(join9(gd, f));
13660
+ const target = readIndexLink(join10(gd, f));
13288
13661
  if (!existsSync6(target)) {
13289
13662
  try {
13290
- unlinkSync(join9(gd, f));
13663
+ unlinkSync(join10(gd, f));
13291
13664
  } catch {
13292
13665
  }
13293
13666
  continue;
@@ -13457,17 +13830,17 @@ ${formatDiff(ops)}`);
13457
13830
  };
13458
13831
 
13459
13832
  // cli/gitCheckpoints.ts
13460
- import { execFile as execFile3 } from "child_process";
13833
+ import { execFile as execFile4 } from "child_process";
13461
13834
  import { promisify } from "util";
13462
13835
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
13463
13836
  import { homedir as homedir7 } from "os";
13464
- import { join as join10, resolve as resolve3, sep as sep3, parse as parsePath } from "path";
13837
+ import { join as join11, resolve as resolve4, sep as sep3, parse as parsePath } from "path";
13465
13838
  var log31 = forComponent("checkpoints");
13466
- var exec = promisify(execFile3);
13839
+ var exec = promisify(execFile4);
13467
13840
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
13468
13841
  function isBroadRoot(p) {
13469
- const abs = resolve3(p);
13470
- return abs === resolve3(homedir7()) || abs === parsePath(abs).root;
13842
+ const abs = resolve4(p);
13843
+ return abs === resolve4(homedir7()) || abs === parsePath(abs).root;
13471
13844
  }
13472
13845
  var ShadowRepo = class {
13473
13846
  // undefined = unprobed; false = git/this root unusable
@@ -13504,8 +13877,8 @@ var ShadowRepo = class {
13504
13877
  mkdirSync9(this.gitDir, { recursive: true });
13505
13878
  const valid = await this.run("rev-parse", "--git-dir").then(() => true, () => false);
13506
13879
  if (!valid) await this.run("init", "-q");
13507
- mkdirSync9(join10(this.gitDir, "info"), { recursive: true });
13508
- writeFileSync6(join10(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
13880
+ mkdirSync9(join11(this.gitDir, "info"), { recursive: true });
13881
+ writeFileSync6(join11(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
13509
13882
  this.ready = true;
13510
13883
  } catch (e) {
13511
13884
  log31.debug(`git checkpoints unavailable for ${this.workTree}`, e);
@@ -13614,13 +13987,13 @@ var GitCheckpoints = class {
13614
13987
  }
13615
13988
  /** cwd + any add-dir mounts that live OUTSIDE cwd (in-cwd mounts are already covered by the cwd root). */
13616
13989
  roots() {
13617
- const cwd = resolve3(this.options.workTree);
13990
+ const cwd = resolve4(this.options.workTree);
13618
13991
  const out = [{ workTree: cwd, gitDir: this.options.gitDir }];
13619
13992
  for (const d of this.options.addDirs ?? []) {
13620
- const abs = resolve3(d);
13993
+ const abs = resolve4(d);
13621
13994
  if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
13622
13995
  if (cwd.startsWith(abs + sep3)) continue;
13623
- out.push({ workTree: abs, gitDir: join10(abs, ".agent", "checkpoints.git") });
13996
+ out.push({ workTree: abs, gitDir: join11(abs, ".agent", "checkpoints.git") });
13624
13997
  }
13625
13998
  return out;
13626
13999
  }
@@ -13707,7 +14080,7 @@ var GitCheckpoints = class {
13707
14080
  preToolUse: async (call) => {
13708
14081
  if (!this.snapshotted || !this.repos.length) return;
13709
14082
  for (const rel of mutatedPaths(call)) {
13710
- const abs = resolve3(this.options.workTree, rel);
14083
+ const abs = resolve4(this.options.workTree, rel);
13711
14084
  if (this.forced.has(abs)) continue;
13712
14085
  this.forced.add(abs);
13713
14086
  const i = this.repoIndexFor(abs);
@@ -13763,7 +14136,7 @@ var GitCheckpointsOptions = class {
13763
14136
  /** Real working tree to snapshot (the launch cwd). */
13764
14137
  workTree = process.cwd();
13765
14138
  /** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
13766
- gitDir = join10(process.cwd(), ".agent", "checkpoints.git");
14139
+ gitDir = join11(process.cwd(), ".agent", "checkpoints.git");
13767
14140
  /** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
13768
14141
  addDirs = [];
13769
14142
  /** Conversation id → per-session restore-point ref. */
@@ -13836,10 +14209,10 @@ function completePath(listDir, ref) {
13836
14209
 
13837
14210
  // cli/lineEditor.ts
13838
14211
  import { emitKeypressEvents } from "readline";
13839
- import { spawnSync as spawnSync4 } from "child_process";
14212
+ import { spawnSync as spawnSync5 } from "child_process";
13840
14213
  import { writeFileSync as writeFileSync7, readFileSync as readFileSync9, unlinkSync as unlinkSync2 } from "fs";
13841
14214
  import { tmpdir as tmpdir2 } from "os";
13842
- import { join as join11 } from "path";
14215
+ import { join as join12 } from "path";
13843
14216
 
13844
14217
  // cli/bidi.ts
13845
14218
  var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
@@ -14336,6 +14709,7 @@ function releaseKeyInput() {
14336
14709
  }
14337
14710
 
14338
14711
  // cli/lineEditor.ts
14712
+ init_platform();
14339
14713
  var cpWidth = (cp) => {
14340
14714
  if (cp === 8205 || cp === 65039 || cp >= 768 && cp <= 879) return 0;
14341
14715
  return cp >= 4352 && (cp <= 4447 || cp === 9001 || cp === 9002 || cp >= 11904 && cp <= 42191 && cp !== 12351 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 126976 && cp <= 129791 || cp >= 131072 && cp <= 262141) ? 2 : 1;
@@ -15024,14 +15398,15 @@ function createLineEditor(out, input = keyInput) {
15024
15398
  curRow = cursorRow;
15025
15399
  }
15026
15400
  function externalEdit(s) {
15027
- const spec = process.env.VISUAL || process.env.EDITOR || "vi";
15401
+ const spec = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
15028
15402
  const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
15029
- const file = join11(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
15403
+ const file = join12(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
15030
15404
  try {
15031
15405
  writeFileSync7(file, s.buf);
15032
15406
  process.stdin.setRawMode(false);
15033
15407
  out.write("\x1B[?2004l");
15034
- const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
15408
+ const sp = resolveSpawn(cmd, [...cargs, file]);
15409
+ const r = spawnSync5(sp.command, sp.args, { stdio: "inherit", ...sp.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {} });
15035
15410
  if (r.status === 0) {
15036
15411
  const text = readFileSync9(file, "utf8").replace(/\n$/, "");
15037
15412
  s.reset();
@@ -15069,10 +15444,10 @@ function createLineEditor(out, input = keyInput) {
15069
15444
  render(s, promptOf(), maxVisible, opts.status);
15070
15445
  };
15071
15446
  process.on("SIGWINCH", onResize);
15072
- return new Promise((resolve5) => {
15447
+ return new Promise((resolve6) => {
15073
15448
  activeAbort = () => {
15074
15449
  finish();
15075
- resolve5(null);
15450
+ resolve6(null);
15076
15451
  };
15077
15452
  const redraw = () => render(s, promptOf(), maxVisible, opts.status);
15078
15453
  let lastStatus = opts.status?.() ?? "";
@@ -15152,20 +15527,20 @@ function createLineEditor(out, input = keyInput) {
15152
15527
  if (action === "submit") {
15153
15528
  finish();
15154
15529
  opts.onSubmit?.();
15155
- return resolve5(s.expand());
15530
+ return resolve6(s.expand());
15156
15531
  }
15157
15532
  if (action === "eof") {
15158
15533
  finish();
15159
- return resolve5(null);
15534
+ return resolve6(null);
15160
15535
  }
15161
15536
  if (action === "rewind") {
15162
15537
  finish();
15163
- return resolve5(REWIND);
15538
+ return resolve6(REWIND);
15164
15539
  }
15165
15540
  if (action === "cancel") {
15166
15541
  if (s.buf.length === 0) {
15167
15542
  finish();
15168
- return resolve5(null);
15543
+ return resolve6(null);
15169
15544
  }
15170
15545
  s.reset();
15171
15546
  s.refresh();
@@ -15307,7 +15682,7 @@ function selectMenu(stream, opts) {
15307
15682
  out.write(dim2(` ${filterHint}\u2191/\u2193 select \xB7 ${back}\u23CE choose \xB7 esc cancel${more}`));
15308
15683
  prevLines++;
15309
15684
  };
15310
- return new Promise((resolve5) => {
15685
+ return new Promise((resolve6) => {
15311
15686
  const wasRaw = !!keyInput.isRaw;
15312
15687
  menusOpen++;
15313
15688
  emitKeypressEvents(keyInput);
@@ -15326,7 +15701,7 @@ function selectMenu(stream, opts) {
15326
15701
  } catch {
15327
15702
  }
15328
15703
  }
15329
- resolve5(val);
15704
+ resolve6(val);
15330
15705
  };
15331
15706
  const drillIn = () => {
15332
15707
  const it = items[sel];
@@ -15447,19 +15822,19 @@ function windowAround(count, sel, max) {
15447
15822
  var plainBuf = "";
15448
15823
  var plainEnded = false;
15449
15824
  function readPlainLine() {
15450
- return new Promise((resolve5) => {
15825
+ return new Promise((resolve6) => {
15451
15826
  const emit = () => {
15452
15827
  const nl = plainBuf.indexOf("\n");
15453
15828
  if (nl >= 0) {
15454
15829
  const line = plainBuf.slice(0, nl);
15455
15830
  plainBuf = plainBuf.slice(nl + 1);
15456
- resolve5(line);
15831
+ resolve6(line);
15457
15832
  return true;
15458
15833
  }
15459
15834
  if (plainEnded) {
15460
15835
  const rest = plainBuf;
15461
15836
  plainBuf = "";
15462
- resolve5(rest.length ? rest : null);
15837
+ resolve6(rest.length ? rest : null);
15463
15838
  return true;
15464
15839
  }
15465
15840
  return false;
@@ -15485,11 +15860,15 @@ function readPlainLine() {
15485
15860
  });
15486
15861
  }
15487
15862
 
15863
+ // cli/cli.ts
15864
+ init_platform();
15865
+ init_hostPath();
15866
+
15488
15867
  // cli/osScheduler.ts
15489
- import { spawnSync as spawnSync5 } from "child_process";
15868
+ import { spawnSync as spawnSync6 } from "child_process";
15490
15869
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
15491
15870
  import { homedir as homedir8 } from "os";
15492
- import { join as join12 } from "path";
15871
+ import { join as join13 } from "path";
15493
15872
  var log32 = forComponent("os-sched");
15494
15873
  var OsScheduler = class {
15495
15874
  options;
@@ -15497,13 +15876,13 @@ var OsScheduler = class {
15497
15876
  this.options = { ...new OsSchedulerOptions(), ...options };
15498
15877
  }
15499
15878
  get dir() {
15500
- return join12(this.options.home, ".agent", "sched");
15879
+ return join13(this.options.home, ".agent", "sched");
15501
15880
  }
15502
15881
  label(id) {
15503
15882
  return `cc.livx.agentx.sched-${id}`;
15504
15883
  }
15505
15884
  plistPath(id) {
15506
- return join12(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
15885
+ return join13(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
15507
15886
  }
15508
15887
  run(cmd, args, input) {
15509
15888
  return this.options.exec(cmd, args, input);
@@ -15519,11 +15898,11 @@ var OsScheduler = class {
15519
15898
  const script = this.writeScript(spec, oneOff);
15520
15899
  const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
15521
15900
  const meta = { ...spec, created: Date.now(), mechanism };
15522
- writeFileSync8(join12(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
15901
+ writeFileSync8(join13(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
15523
15902
  return mechanism;
15524
15903
  }
15525
15904
  cancel(id) {
15526
- const meta = readJsonFile(join12(this.dir, `${id}.json`), null);
15905
+ const meta = readJsonFile(join13(this.dir, `${id}.json`), null);
15527
15906
  if (!meta) return false;
15528
15907
  try {
15529
15908
  if (this.options.platform === "darwin") {
@@ -15556,7 +15935,7 @@ var OsScheduler = class {
15556
15935
  }
15557
15936
  for (const f of [`${id}.json`, `${id}.sh`]) {
15558
15937
  try {
15559
- unlinkSync3(join12(this.dir, f));
15938
+ unlinkSync3(join13(this.dir, f));
15560
15939
  } catch {
15561
15940
  }
15562
15941
  }
@@ -15564,19 +15943,19 @@ var OsScheduler = class {
15564
15943
  }
15565
15944
  list() {
15566
15945
  if (!existsSync7(this.dir)) return [];
15567
- return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join12(this.dir, f), null)).filter(Boolean);
15946
+ return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join13(this.dir, f), null)).filter(Boolean);
15568
15947
  }
15569
15948
  /** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
15570
15949
  writeScript(spec, oneOff) {
15571
- const p = join12(this.dir, `${spec.id}.sh`);
15950
+ const p = join13(this.dir, `${spec.id}.sh`);
15572
15951
  const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
15573
- const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
15574
- ` : `rm -f ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
15952
+ const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join13(this.dir, `${spec.id}.json`))} ${q2(p)}
15953
+ ` : `rm -f ${q2(join13(this.dir, `${spec.id}.json`))} ${q2(p)}
15575
15954
  ` : "";
15576
15955
  writeFileSync8(p, `#!/bin/sh
15577
15956
  # agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
15578
15957
  cd ${q2(spec.cwd)} || exit 1
15579
- ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join12(this.dir, `${spec.id}.log`))} 2>&1
15958
+ ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join13(this.dir, `${spec.id}.log`))} 2>&1
15580
15959
  ${cleanup}`);
15581
15960
  chmodSync(p, 493);
15582
15961
  return p;
@@ -15613,7 +15992,7 @@ ${trigger}
15613
15992
  <key>RunAtLoad</key><false/>
15614
15993
  </dict></plist>
15615
15994
  `;
15616
- mkdirSync10(join12(this.options.home, "Library", "LaunchAgents"), { recursive: true });
15995
+ mkdirSync10(join13(this.options.home, "Library", "LaunchAgents"), { recursive: true });
15617
15996
  writeFileSync8(this.plistPath(spec.id), plist);
15618
15997
  this.run("launchctl", ["load", this.plistPath(spec.id)]);
15619
15998
  return `launchd:${this.label(spec.id)}`;
@@ -15651,7 +16030,7 @@ var OsSchedulerOptions = class {
15651
16030
  /** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
15652
16031
  * and the job id is what atrm needs for cancel. Throws on non-zero exit. */
15653
16032
  exec = (cmd, args, input) => {
15654
- const r = spawnSync5(cmd, args, { input, encoding: "utf8" });
16033
+ const r = spawnSync6(cmd, args, { input, encoding: "utf8" });
15655
16034
  if (r.error) throw r.error;
15656
16035
  if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
15657
16036
  return `${r.stdout ?? ""}${r.stderr ?? ""}`;
@@ -15668,10 +16047,11 @@ import { createServer as createServer2, createConnection } from "net";
15668
16047
  import { spawn as spawn3 } from "child_process";
15669
16048
  import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
15670
16049
  import { homedir as homedir9 } from "os";
15671
- import { join as join13 } from "path";
16050
+ import { join as join14 } from "path";
16051
+ init_platform();
15672
16052
  var log33 = forComponent("remote-trigger");
15673
- var TRIGGER_DIR = () => join13(homedir9(), ".agent", "triggers");
15674
- var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
16053
+ var TRIGGER_DIR = () => join14(homedir9(), ".agent", "triggers");
16054
+ var sockPath = (sessionId, dir = TRIGGER_DIR()) => join14(dir, `${sessionId}.sock`);
15675
16055
  var TriggerServer = class {
15676
16056
  constructor(onTrigger) {
15677
16057
  this.onTrigger = onTrigger;
@@ -15764,6 +16144,15 @@ function triggerLive(sessionId, req, dir = TRIGGER_DIR(), timeoutMs = 3e3) {
15764
16144
  });
15765
16145
  });
15766
16146
  }
16147
+ function headlessArgv(opts, text, id) {
16148
+ const script = `${opts.agentx} -p "$1" --resume "$2" --yes`;
16149
+ if (!IS_WIN) return ["/bin/sh", ["-c", script, "sh", text, id]];
16150
+ if (opts.agentxArgv?.length) {
16151
+ const r = resolveSpawn(opts.agentxArgv[0], [...opts.agentxArgv.slice(1), "-p", text, "--resume", id, "--yes"]);
16152
+ return [r.command, r.args];
16153
+ }
16154
+ return [defaultShell().bin, ["-c", script, "sh", text, id]];
16155
+ }
15767
16156
  function makeRemoteTriggerTool(opts) {
15768
16157
  const dir = opts.triggerDir ?? TRIGGER_DIR();
15769
16158
  const doSpawn = opts.spawnFn ?? spawn3;
@@ -15790,9 +16179,10 @@ function makeRemoteTriggerTool(opts) {
15790
16179
  if (live?.ok) return `Delivered to live session ${full} \u2014 it runs there; the user sees it in that terminal.`;
15791
16180
  if (!target.meta.cwd || !existsSync8(target.meta.cwd)) return `Error: session ${full} has no reachable cwd (${target.meta.cwd ?? "unknown"}).`;
15792
16181
  return new Promise((res) => {
15793
- const child = doSpawn("/bin/sh", ["-c", `${opts.agentx} -p "$1" --resume "$2" --yes`, "sh", text, full], {
16182
+ const child = doSpawn(...headlessArgv(opts, text, full), {
15794
16183
  cwd: target.meta.cwd,
15795
- stdio: ["ignore", "pipe", "pipe"]
16184
+ stdio: ["ignore", "pipe", "pipe"],
16185
+ ...IS_WIN ? { windowsHide: true } : {}
15796
16186
  });
15797
16187
  let out = "", errOut = "";
15798
16188
  child.stdout?.on("data", (d) => {
@@ -15857,7 +16247,7 @@ async function checkForUpdate(current) {
15857
16247
  return `Update available: ${current} \u2192 ${latest}. Run \`bun add -g @livx.cc/agentx\` to update.`;
15858
16248
  }
15859
16249
  function installUpdate() {
15860
- const r = spawnSync6("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
16250
+ const r = spawnSync7("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
15861
16251
  return (r.status ?? 1) === 0;
15862
16252
  }
15863
16253
  var typeaheadOnLine = false;
@@ -16157,11 +16547,11 @@ function loadEnvFile(file) {
16157
16547
  }
16158
16548
  }
16159
16549
  function loadInstallEnv() {
16160
- let dir = dirname5(import.meta.path);
16161
- for (let i = 0; i < 5 && !existsSync9(join14(dir, "package.json")); i++) dir = dirname5(dir);
16550
+ let dir = dirname6(import.meta.path);
16551
+ for (let i = 0; i < 5 && !existsSync9(join15(dir, "package.json")); i++) dir = dirname6(dir);
16162
16552
  for (const name of [".env", ".env.local"]) {
16163
- loadEnvFile(join14(dir, name));
16164
- loadEnvFile(join14(homedir10(), ".agent", name));
16553
+ loadEnvFile(join15(dir, name));
16554
+ loadEnvFile(join15(homedir10(), ".agent", name));
16165
16555
  }
16166
16556
  }
16167
16557
  async function loadBodifySecrets() {
@@ -16195,6 +16585,31 @@ function apiKeysFromEnv() {
16195
16585
  }
16196
16586
  return keys;
16197
16587
  }
16588
+ var KEYLESS_PROVIDERS = /* @__PURE__ */ new Set(["claude-code"]);
16589
+ function modelUsable(model, apiKeys) {
16590
+ const p = model.split("/")[0];
16591
+ return KEYLESS_PROVIDERS.has(p) || !!apiKeys[p];
16592
+ }
16593
+ function duplexModels(o) {
16594
+ const dflt = new DuplexAgentOptions(), notes = [];
16595
+ const cc = o.actModel.startsWith("claude-code/"), anthropic = !!o.apiKeys.anthropic;
16596
+ const why = (m) => `no ${m.split("/")[0].toUpperCase()}_API_KEY`;
16597
+ const asked = o.voiceModel ?? o.reflexModel;
16598
+ let reflexModel = asked ? resolveModelOrNewest(asked) : void 0;
16599
+ const reflex = reflexModel ?? dflt.reflexModel;
16600
+ if (!modelUsable(reflex, o.apiKeys)) {
16601
+ const to = anthropic ? "anthropic/claude-haiku-4-5" : cc ? "claude-code/haiku" : o.actModel.startsWith("cursor/") ? void 0 : o.actModel;
16602
+ if (to) reflexModel = to;
16603
+ notes.push(`voice model ${reflex} can't run here (${why(reflex)})${to ? ` \u2014 using ${to}` : ""}`);
16604
+ }
16605
+ let thinkModel = o.thinkModel === void 0 ? void 0 : o.thinkModel === false ? false : resolveModelOrNewest(String(o.thinkModel));
16606
+ const think = thinkModel === void 0 ? dflt.thinkModel : thinkModel;
16607
+ if (think !== false && !modelUsable(think, o.apiKeys)) {
16608
+ thinkModel = cc ? "claude-code/opus" : o.actModel;
16609
+ notes.push(`think model ${think} can't run here (${why(think)}) \u2014 using ${thinkModel}`);
16610
+ }
16611
+ return { ...reflexModel ? { reflexModel } : {}, ...thinkModel !== void 0 ? { thinkModel } : {}, notes };
16612
+ }
16198
16613
  function makeHost(format = "text", opts) {
16199
16614
  const streamJson = format === "stream-json";
16200
16615
  const cleanStdout = format === "json";
@@ -16803,7 +17218,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
16803
17218
  stream: args.stream,
16804
17219
  host: makeHost(args.outputFormat, { stream: true }),
16805
17220
  planMode: args.plan || cfg.planMode || false,
16806
- permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(resolve4(args.cwd ?? process.cwd())) }) : void 0,
17221
+ permissions: rules.length ? new PermissionPolicy({ rules, default: "allow", host: makeHost(), ask: makeAskResolver(resolve5(args.cwd ?? process.cwd())) }) : void 0,
16807
17222
  subagents: args.subagents || cfg.subagents || false,
16808
17223
  maxSteps: args.maxSteps ?? cfg.maxSteps,
16809
17224
  reasoning: args.reasoning ?? cfg.reasoning,
@@ -16885,16 +17300,23 @@ var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg"
16885
17300
  function mentionRefs(line) {
16886
17301
  return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
16887
17302
  }
16888
- var untilde = (p) => p.startsWith("~/") ? join14(homedir10(), p.slice(2)) : p;
17303
+ var untilde = (p) => p.startsWith("~/") || IS_WIN && p.startsWith("~\\") ? join15(homedir10(), p.slice(2)) : p;
17304
+ var PATH_LIKE = IS_WIN ? /^(\/|~[\\/]|\.\.?[\\/]|[A-Za-z]:[\\/]|\\\\)/ : /^(\/|~\/|\.\/|\.\.\/)/;
17305
+ var userPath = (cwd, p) => {
17306
+ const t = untilde(p);
17307
+ if (t !== p) return t;
17308
+ const host = IS_WIN && p.startsWith("/") ? toHostPath(p) : null;
17309
+ return host ? resolve5(host) : resolve5(cwd, p);
17310
+ };
16889
17311
  function readImageParts(cwd, line) {
16890
17312
  const refs = mentionRefs(line);
16891
17313
  const parts = [];
16892
17314
  for (const ref of refs) {
16893
17315
  const mime = IMG_EXT[extname(ref).toLowerCase()];
16894
17316
  if (!mime) continue;
16895
- const abs = ref.startsWith("~/") ? untilde(ref) : resolve4(cwd, ref);
17317
+ const abs = userPath(cwd, ref);
16896
17318
  try {
16897
- statSync7(abs);
17319
+ statSync8(abs);
16898
17320
  } catch {
16899
17321
  continue;
16900
17322
  }
@@ -16906,16 +17328,16 @@ function pastePathClassifier(cwd) {
16906
17328
  return (text) => {
16907
17329
  let t = text.trim();
16908
17330
  if (!t || t.includes("\n")) return null;
16909
- t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
16910
- if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
16911
- const abs = t.startsWith("~/") ? untilde(t) : resolve4(cwd, t);
17331
+ t = (IS_WIN ? t : t.replace(/\\ /g, " ")).replace(/^['"]|['"]$/g, "");
17332
+ if (!PATH_LIKE.test(t)) return null;
17333
+ const abs = userPath(cwd, t);
16912
17334
  try {
16913
- if (!statSync7(abs).isFile()) return null;
17335
+ if (!statSync8(abs).isFile()) return null;
16914
17336
  } catch {
16915
17337
  return null;
16916
17338
  }
16917
17339
  const isImg = !!IMG_EXT[extname(abs).toLowerCase()];
16918
- return { display: isImg ? "Image" : `File ${basename2(abs)}`, ref: /\s/.test(abs) ? `@"${abs}"` : "@" + abs };
17340
+ return { display: isImg ? "Image" : `File ${basename4(abs)}`, ref: /\s/.test(abs) ? `@"${abs}"` : "@" + abs };
16919
17341
  };
16920
17342
  }
16921
17343
  var mcpMentionResolver;
@@ -17039,7 +17461,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
17039
17461
  store.save(session);
17040
17462
  } catch {
17041
17463
  }
17042
- return { ok: false };
17464
+ return { ok: false, error: e };
17043
17465
  } finally {
17044
17466
  spinner.stop();
17045
17467
  spinner.turnStart = 0;
@@ -17090,7 +17512,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
17090
17512
  }
17091
17513
  return { ok, res };
17092
17514
  }
17093
- var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join14(store.dir, `${data.meta.id}.images`), cwd]);
17515
+ var remintSessionImages = (data, store, cwd) => remintImageRefs(data.messages, [join15(store.dir, `${data.meta.id}.images`), cwd]);
17094
17516
  function startSession(args, store, agent, cwd) {
17095
17517
  if (args.resume || args.cont) {
17096
17518
  const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
@@ -17137,24 +17559,24 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
17137
17559
  `;
17138
17560
  function initInstructions(cwd) {
17139
17561
  for (const f of ["AGENTS.md", "CLAUDE.md"]) {
17140
- if (existsSync9(join14(cwd, f))) {
17562
+ if (existsSync9(join15(cwd, f))) {
17141
17563
  err(yellow(` ${f} already exists \u2014 leaving it as-is
17142
17564
  `));
17143
17565
  return;
17144
17566
  }
17145
17567
  }
17146
- const path = join14(cwd, "AGENTS.md");
17147
- writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
17568
+ const path = join15(cwd, "AGENTS.md");
17569
+ writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename4(cwd)));
17148
17570
  err(green(` created ${path}
17149
17571
  `) + dim(" edit it, then it auto-loads into every run.\n"));
17150
17572
  }
17151
17573
  function persistSetting(cwd, key, value) {
17152
- const path = join14(cwd, ".agent", "settings.json");
17574
+ const path = join15(cwd, ".agent", "settings.json");
17153
17575
  try {
17154
17576
  const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
17155
17577
  if (obj[key] === value) return;
17156
17578
  obj[key] = value;
17157
- mkdirSync12(dirname5(path), { recursive: true });
17579
+ mkdirSync12(dirname6(path), { recursive: true });
17158
17580
  writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
17159
17581
  } catch (e) {
17160
17582
  err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
@@ -17187,7 +17609,7 @@ function installCancelGuards(mounted) {
17187
17609
  });
17188
17610
  }
17189
17611
  async function repl(args, ai, cfg, cwd) {
17190
- const oauth = new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") });
17612
+ const oauth = new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") });
17191
17613
  const mounted = await mountMcp(cfg, oauth);
17192
17614
  let mcpToolNames = [];
17193
17615
  const initialMcpTools = mcpAgentTools(mounted);
@@ -17210,7 +17632,7 @@ async function repl(args, ai, cfg, cwd) {
17210
17632
  },
17211
17633
  tickMs: 15e3
17212
17634
  });
17213
- const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve4(process.argv[1])}` : "agentx";
17635
+ const agentxInvocation = () => process.argv[1] ? `${process.execPath} ${resolve5(process.argv[1])}` : "agentx";
17214
17636
  const osSched = new OsScheduler({ agentx: agentxInvocation() });
17215
17637
  const osBackend = osSched.available() ? {
17216
17638
  get sessionId() {
@@ -17233,7 +17655,7 @@ async function repl(args, ai, cfg, cwd) {
17233
17655
  scheduleQueue.push(req.prompt);
17234
17656
  }
17235
17657
  });
17236
- agent.options.tools = [...agent.options.tools ?? [], makeRemoteTriggerTool({ agentx: agentxInvocation(), selfId: () => session.meta.id })];
17658
+ agent.options.tools = [...agent.options.tools ?? [], makeRemoteTriggerTool({ agentx: agentxInvocation(), agentxArgv: process.argv[1] ? [process.execPath, resolve5(process.argv[1])] : ["agentx"], selfId: () => session.meta.id })];
17237
17659
  const duplex = args.duplex;
17238
17660
  let dx;
17239
17661
  let voiceIO;
@@ -17438,6 +17860,9 @@ async function repl(args, ai, cfg, cwd) {
17438
17860
  return `Rewound ${n} task checkpoint(s): restored ${r.restored} file(s), deleted ${r.deleted}.`;
17439
17861
  }
17440
17862
  };
17863
+ const { notes: tierNotes, ...tierModels } = duplexModels({ voiceModel: args.voiceModel, reflexModel: cfg.reflexModel, thinkModel: args.thinkModel ?? cfg.thinkModel, actModel: agent.options.model, apiKeys: { ...cfg.apiKeys, ...apiKeysFromEnv() } });
17864
+ for (const n of tierNotes) err(yellow(` \u26A0 ${n}
17865
+ `));
17441
17866
  dx = new DuplexAgent({
17442
17867
  ai,
17443
17868
  ...instinct ? { bargeIntent: (t) => instinct.bargeIntent(t) } : {},
@@ -17445,11 +17870,10 @@ async function repl(args, ai, cfg, cwd) {
17445
17870
  fs: agent.options.fs,
17446
17871
  memoryDir: agent.options.memoryDir,
17447
17872
  memoryUserDir: agent.options.memoryUserDir,
17448
- ...args.voiceModel ?? cfg.reflexModel ? { reflexModel: resolveModelOrNewest(args.voiceModel ?? cfg.reflexModel) } : {},
17873
+ ...tierModels,
17449
17874
  actModel: agent.options.model,
17450
17875
  actOptions: workerOptions,
17451
17876
  providerOptionsFor: (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)),
17452
- ...(args.thinkModel ?? cfg.thinkModel) !== void 0 ? { thinkModel: (args.thinkModel ?? cfg.thinkModel) === false ? false : resolveModelOrNewest(String(args.thinkModel ?? cfg.thinkModel)) } : {},
17453
17877
  host,
17454
17878
  ...args.voice ? { voiceStyle: "conversational", progressUpdates: true, askRelay: true, emotionTags: emotionsOn, spokenBudgetChars: 350 } : {},
17455
17879
  // voice: progress asides + worker questions relayed through the conversation; emotion tags taught only with a TTS that speaks them
@@ -17463,7 +17887,7 @@ async function repl(args, ai, cfg, cwd) {
17463
17887
  quickLook: {
17464
17888
  branch: () => {
17465
17889
  try {
17466
- const head = readFileSync10(join14(cwd, ".git", "HEAD"), "utf8").trim();
17890
+ const head = readFileSync10(join15(cwd, ".git", "HEAD"), "utf8").trim();
17467
17891
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
17468
17892
  } catch {
17469
17893
  return "not a git repository";
@@ -17597,7 +18021,7 @@ async function repl(args, ai, cfg, cwd) {
17597
18021
  const pendingImages = [];
17598
18022
  const bangContext = [];
17599
18023
  const grabClipboardAttachment = () => {
17600
- const dir = join14(store.dir, `${session.meta.id}.images`);
18024
+ const dir = join15(store.dir, `${session.meta.id}.images`);
17601
18025
  try {
17602
18026
  mkdirSync12(dir, { recursive: true });
17603
18027
  } catch (e) {
@@ -17655,7 +18079,7 @@ async function repl(args, ai, cfg, cwd) {
17655
18079
  err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
17656
18080
  `));
17657
18081
  }
17658
- const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join14(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
18082
+ const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join15(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
17659
18083
  const cpHooks = checkpoints.hooks?.();
17660
18084
  if (cpHooks) {
17661
18085
  agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
@@ -17707,11 +18131,11 @@ ${lines.join("\n")}
17707
18131
  Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
17708
18132
  </system-reminder>`;
17709
18133
  };
17710
- const histPath = join14(cwd, ".agent", "history");
18134
+ const histPath = join15(cwd, ".agent", "history");
17711
18135
  const history = existsSync9(histPath) ? readFileSync10(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
17712
18136
  const remember2 = (line) => {
17713
18137
  try {
17714
- mkdirSync12(join14(cwd, ".agent"), { recursive: true });
18138
+ mkdirSync12(join15(cwd, ".agent"), { recursive: true });
17715
18139
  appendFileSync(histPath, line + "\n");
17716
18140
  } catch (e) {
17717
18141
  log34.debug("history write failed", e);
@@ -18014,8 +18438,8 @@ ${task}`;
18014
18438
  const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
18015
18439
  if (wasRaw) process.stdin.setRawMode(false);
18016
18440
  try {
18017
- const { spawnSync: spawnSync7 } = await import("child_process");
18018
- const r = spawnSync7("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
18441
+ const { spawnSync: spawnSync8 } = await import("child_process");
18442
+ const r = spawnSync8("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
18019
18443
  if (r.error) err(text);
18020
18444
  } finally {
18021
18445
  if (wasRaw) process.stdin.setRawMode(true);
@@ -18035,8 +18459,7 @@ ${task}`;
18035
18459
  keys.length ? ok(`provider keys: ${keys.join(", ")}`) : bad("no provider keys set (ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY)");
18036
18460
  process.env.BODIFY_API_KEY && process.env.BODIFY_APP_ID ? ok(`bodify secrets: ${process.env.BODIFY_APP_ID}`) : warn("bodify secrets: not configured (set BODIFY_API_KEY + BODIFY_APP_ID)");
18037
18461
  {
18038
- const { spawnSync: spawnSync7 } = await import("child_process");
18039
- const has = (cmd) => spawnSync7("which", [cmd]).status === 0;
18462
+ const has = hasCommand;
18040
18463
  if (!VoiceIO.available()) warn("voice: keys missing (SONIOX_API_KEY / CARTESIA_API_KEY / CARTESIA_VOICE_ID) \u2014 voice disabled");
18041
18464
  else {
18042
18465
  ok("voice: keys present");
@@ -18463,12 +18886,13 @@ ${task}`;
18463
18886
  return;
18464
18887
  }
18465
18888
  }
18466
- const ed = process.env.VISUAL || process.env.EDITOR || "vi";
18889
+ const ed = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
18467
18890
  const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
18468
18891
  if (wasRaw) process.stdin.setRawMode(false);
18469
18892
  try {
18470
- const { spawnSync: spawnSync7 } = await import("child_process");
18471
- spawnSync7(ed, [idx], { stdio: "inherit" });
18893
+ const { spawnSync: spawnSync8 } = await import("child_process");
18894
+ const r = IS_WIN ? resolveSpawn(ed, [toHostPath(toVfsPath(idx)) ?? idx]) : { command: ed, args: [idx] };
18895
+ spawnSync8(r.command, r.args, { stdio: "inherit", ...r.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {} });
18472
18896
  } finally {
18473
18897
  if (wasRaw) process.stdin.setRawMode(true);
18474
18898
  }
@@ -18818,10 +19242,10 @@ ${task}`;
18818
19242
  return;
18819
19243
  }
18820
19244
  const md = exportMarkdown(session.meta, shown);
18821
- const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
18822
- const path = resolve4(cwd, name);
19245
+ const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join15(".agent", "exports", `${session.meta.id}.md`);
19246
+ const path = resolve5(cwd, name);
18823
19247
  try {
18824
- mkdirSync12(dirname5(path), { recursive: true });
19248
+ mkdirSync12(dirname6(path), { recursive: true });
18825
19249
  writeFileSync9(path, md);
18826
19250
  err(green(` \u2713 exported \u2192 ${path}
18827
19251
  `) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
@@ -18904,7 +19328,7 @@ ${task}`;
18904
19328
  });
18905
19329
  const listDir = (absDir) => {
18906
19330
  try {
18907
- return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
19331
+ return readdirSync4(join15(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
18908
19332
  } catch (e) {
18909
19333
  log34.debug("completion readdir failed", absDir, e);
18910
19334
  return null;
@@ -19089,7 +19513,7 @@ ${out}
19089
19513
  return false;
19090
19514
  }
19091
19515
  const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
19092
- voiceDiag = new JsonlDiagSink(join14(store.dir, `${session.meta.id}.voice.jsonl`));
19516
+ voiceDiag = new JsonlDiagSink(join15(store.dir, `${session.meta.id}.voice.jsonl`));
19093
19517
  voiceIO = new VoiceIO({
19094
19518
  ...fakeVoice ?? {},
19095
19519
  onDiag: (ev) => voiceDiag?.push(ev),
@@ -19490,9 +19914,17 @@ ${out}
19490
19914
  disposeClaudeCodeSessions();
19491
19915
  await closeMcp(mounted);
19492
19916
  }
19917
+ function duplexError(e, reflexModel) {
19918
+ if (!e) return "";
19919
+ const msg = String(e?.message ?? e).trim();
19920
+ const key = msg.match(/API key is required for ([\w-]+)/i);
19921
+ if (key) return `No API key for ${key[1]} (voice model ${reflexModel}) \u2014 set ${key[1].toUpperCase().replace(/-/g, "_")}_API_KEY in ~/.agent/.env, or choose another voice model (--voice-model).`;
19922
+ const code = e?.statusCode;
19923
+ return code && !msg.includes(String(code)) ? `${msg} (${code})` : msg;
19924
+ }
19493
19925
  async function duplexServe(args, ai, cfg, cwd) {
19494
19926
  const out = (o) => process.stdout.write(JSON.stringify(o) + "\n");
19495
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") }));
19927
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
19496
19928
  installCancelGuards(mounted);
19497
19929
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
19498
19930
  const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
@@ -19512,16 +19944,18 @@ async function duplexServe(args, ai, cfg, cwd) {
19512
19944
  };
19513
19945
  let persist = () => {
19514
19946
  };
19947
+ const { notes, ...tierModels } = duplexModels({ voiceModel: args.voiceModel, reflexModel: cfg.reflexModel, thinkModel: args.thinkModel ?? cfg.thinkModel, actModel: agent.options.model, apiKeys: { ...cfg.apiKeys, ...apiKeysFromEnv() } });
19948
+ for (const n of notes) err(yellow(` \u26A0 ${n}
19949
+ `));
19515
19950
  const dx = new DuplexAgent({
19516
19951
  ai,
19517
19952
  fs: agent.options.fs,
19518
19953
  memoryDir: agent.options.memoryDir,
19519
19954
  memoryUserDir: agent.options.memoryUserDir,
19520
- ...args.voiceModel ?? cfg.reflexModel ? { reflexModel: resolveModelOrNewest(args.voiceModel ?? cfg.reflexModel) } : {},
19955
+ ...tierModels,
19521
19956
  actModel: agent.options.model,
19522
19957
  actOptions: { ...wo, planMode: false },
19523
19958
  providerOptionsFor: (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)),
19524
- ...(args.thinkModel ?? cfg.thinkModel) !== void 0 ? { thinkModel: (args.thinkModel ?? cfg.thinkModel) === false ? false : resolveModelOrNewest(String(args.thinkModel ?? cfg.thinkModel)) } : {},
19525
19959
  host,
19526
19960
  // --voice: the CLI voice register (progress asides, spoken budget). No emotion tags: the host's TTS may not speak them.
19527
19961
  ...args.voice ? { voiceStyle: "conversational", progressUpdates: true, spokenBudgetChars: 350 } : {},
@@ -19539,7 +19973,7 @@ async function duplexServe(args, ai, cfg, cwd) {
19539
19973
  log34.warn("duplex session save failed", e);
19540
19974
  }
19541
19975
  };
19542
- out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel });
19976
+ out({ type: "ready", sessionId: session.meta.id, reflexModel: dx.options.reflexModel, actModel: dx.options.actModel, ...notes.length ? { notes } : {} });
19543
19977
  const done = new Promise((resolveDone) => {
19544
19978
  const rl = createInterface({ input: process.stdin });
19545
19979
  rl.on("line", (line) => {
@@ -19559,7 +19993,10 @@ async function duplexServe(args, ai, cfg, cwd) {
19559
19993
  if (delivery || reply) dx.parkCutDelivery(delivery, !!reply, heard);
19560
19994
  const note = reply && reply.full.length - reply.heard.length > 40 ? interruptionNote(reply) : "";
19561
19995
  const id = m.id ? String(m.id) : void 0;
19562
- void runTurn(dx.voice, store, session, m.text + note, void 0, cwd, (c) => dx.send(c, { id, replaces: m.replaces ? String(m.replaces) : void 0, followUp: typeof m.followUp === "string" || m.followUp && typeof m.followUp === "object" ? m.followUp : void 0 })).then(({ res }) => out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error" })).catch((e) => {
19996
+ void runTurn(dx.voice, store, session, m.text + note, void 0, cwd, (c) => dx.send(c, { id, replaces: m.replaces ? String(m.replaces) : void 0, followUp: typeof m.followUp === "string" || m.followUp && typeof m.followUp === "object" ? m.followUp : void 0 })).then(({ res, error }) => {
19997
+ const why = (res?.finishReason ?? "error") === "error" ? duplexError(res?.error ?? error, dx.options.reflexModel) : "";
19998
+ out({ type: "turn_done", id, finishReason: res?.finishReason ?? "error", ...why ? { error: why } : {} });
19999
+ }).catch((e) => {
19563
20000
  log34.error("duplex turn failed", e);
19564
20001
  out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
19565
20002
  });
@@ -19614,7 +20051,7 @@ async function main() {
19614
20051
  let worktreeCleanup;
19615
20052
  if (args.worktree) {
19616
20053
  const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
19617
- const session = enterWorktree2(args.worktree, resolve4(args.cwd ?? process.cwd()));
20054
+ const session = enterWorktree2(args.worktree, resolve5(args.cwd ?? process.cwd()));
19618
20055
  err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
19619
20056
  `));
19620
20057
  worktreeCleanup = () => {
@@ -19624,8 +20061,8 @@ async function main() {
19624
20061
  `));
19625
20062
  };
19626
20063
  }
19627
- const cwd = resolve4(args.cwd ?? process.cwd());
19628
- const cfg = await loadConfig(cwd, args.mcpConfig && resolve4(args.mcpConfig));
20064
+ const cwd = resolve5(args.cwd ?? process.cwd());
20065
+ const cfg = await loadConfig(cwd, args.mcpConfig && resolve5(args.mcpConfig));
19629
20066
  if (!args.autoRoute && !args.model && (cfg.model === "auto" || !cfg.model && await jevAvailable())) {
19630
20067
  args.autoRoute = true;
19631
20068
  args.model = autoModels().standard;
@@ -19657,7 +20094,8 @@ async function main() {
19657
20094
  loadInstallEnv();
19658
20095
  await loadBodifySecrets();
19659
20096
  const apiKeys = { ...cfg.apiKeys, ...apiKeysFromEnv() };
19660
- if (!Object.keys(apiKeys).length) {
20097
+ const keyless = String(args.model ?? cfg.model ?? "").startsWith("claude-code/");
20098
+ if (!Object.keys(apiKeys).length && !keyless) {
19661
20099
  console.error(red("No provider key found. Set ANTHROPIC_API_KEY (or OPENAI_API_KEY / GOOGLE_API_KEY / GROQ_API_KEY) in ~/.agent/.env, or set BODIFY_API_KEY + BODIFY_APP_ID to pull from Bodify."));
19662
20100
  process.exit(1);
19663
20101
  }
@@ -19674,7 +20112,7 @@ async function main() {
19674
20112
  }
19675
20113
  });
19676
20114
  if (args.task) {
19677
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") }));
20115
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") }));
19678
20116
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
19679
20117
  const store = new SessionStore(cwd);
19680
20118
  const session = startSession(args, store, agent, cwd);
@@ -19712,6 +20150,8 @@ export {
19712
20150
  cacheMultipliers,
19713
20151
  condenseReplay,
19714
20152
  costOf,
20153
+ duplexError,
20154
+ duplexModels,
19715
20155
  estimateTranscriptTokens,
19716
20156
  expandMentions,
19717
20157
  exportMarkdown,
@@ -19723,6 +20163,7 @@ export {
19723
20163
  makeHost,
19724
20164
  mcpMentionResolver,
19725
20165
  mentionRefs,
20166
+ modelUsable,
19726
20167
  parseArgs,
19727
20168
  pastePathClassifier,
19728
20169
  readImageParts,