@livx.cc/agentx 0.99.77 → 0.99.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -359,13 +359,13 @@ function clauses(sentence) {
359
359
  return sentence.split(/[,;:]\s*|\s+(?:so|but|and|then|because|however|therefore|thus|although|though)\s+/i).map((c) => c.trim()).filter(Boolean);
360
360
  }
361
361
  function endsOnAnnouncedAction(text) {
362
- const sentences = text.split(/(?<=[.!?])\s+|\n+/).map((s) => s.trim()).filter(Boolean);
362
+ const sentences = text.split(/(?<=[.!?])\s+|\n+/).map((s) => s.replace(/^[\s#>*_`-]+|[*_`]+/g, "").trim()).filter(Boolean);
363
363
  const last = sentences[sentences.length - 1];
364
- if (!last || NEXT_STEP.test(last) || SIGN_OFF.test(last)) return false;
364
+ if (!last || sentences.slice(-2).some((s) => NEXT_STEP.test(s)) || SIGN_OFF.test(last) || AWAITS_USER.test(last)) return false;
365
365
  if (PROGRESSIVE.test(last)) return true;
366
366
  return clauses(last).some((c) => !NEXT_STEP.test(c) && !SIGN_OFF.test(c) && COMMITMENT.test(c));
367
367
  }
368
- var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF;
368
+ var log3, p_sep, IMAGE_ELIDE_STUB, IMAGE_REF_SCHEME, REF_REGISTRY_MAX, refRegistry, isImageRefUrl, bearsImage, COMMITMENT, PROGRESSIVE, NEXT_STEP, SIGN_OFF, AWAITS_USER;
369
369
  var init_llm = __esm({
370
370
  "src/llm.ts"() {
371
371
  "use strict";
@@ -383,6 +383,7 @@ var init_llm = __esm({
383
383
  PROGRESSIVE = /^(?:also\s+|now\s+|then\s+)*(?:re-?)?(?:check|search|scan|read|verify|run|inspect|expand|look|examin|continu|proceed|try|fetch|load|open|test|build|grep|find|review|trac|dig|explor)\w*ing\b/i;
384
384
  NEXT_STEP = /^next (?:step|run|up)\b/i;
385
385
  SIGN_OFF = /^let me know\b/i;
386
+ AWAITS_USER = /^(?:do you )?(?:want|need) me to\b|\b(?:once|as soon as|after) you\b|\bwhen you(?:'re| are) ready\b/i;
386
387
  }
387
388
  });
388
389
 
@@ -402,6 +403,68 @@ var init_redact = __esm({
402
403
  }
403
404
  });
404
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
+
405
468
  // src/tools.structured.ts
406
469
  function ckAbort(signal) {
407
470
  if (signal?.aborted) throw new Error("aborted");
@@ -444,7 +507,7 @@ async function walkFiles(fs, dir, signal, out = [], st, depth = 0) {
444
507
  }
445
508
  function scanRoots(fs, path, opts) {
446
509
  const asked = path == null ? [] : (Array.isArray(path) ? path : [path]).map((p) => String(p)).filter(Boolean);
447
- 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);
448
511
  return [...new Set(roots)].filter((r, _i, all) => !all.some((other) => other !== r && r.startsWith(`${other}/`)));
449
512
  }
450
513
  function scanNote(states) {
@@ -673,6 +736,7 @@ function parentDir(abs) {
673
736
  return i <= 0 ? "/" : abs.slice(0, i);
674
737
  }
675
738
  async function mkdirp(fs, dir) {
739
+ dir = toVfsPath(dir);
676
740
  if (dir === "/" || await fs.exists(dir)) return;
677
741
  await mkdirp(fs, parentDir(dir));
678
742
  if (!await fs.exists(dir)) await fs.createDir(dir);
@@ -733,6 +797,7 @@ var init_tools_structured = __esm({
733
797
  "src/tools.structured.ts"() {
734
798
  "use strict";
735
799
  init_redact();
800
+ init_hostPath();
736
801
  DEFAULT_SKIP_DIRS = ["node_modules", ".git", "dist", ".tmp"];
737
802
  DEFAULT_MAX_DEPTH = 24;
738
803
  SCAN_PARAMS = {
@@ -2013,7 +2078,7 @@ __export(worktree_exports, {
2013
2078
  });
2014
2079
  import { spawnSync } from "child_process";
2015
2080
  import { symlinkSync, mkdirSync, statSync as statSync4, readFileSync as readFileSync3, rmSync } from "fs";
2016
- import { join as join2 } from "path";
2081
+ import { join as join2, resolve as resolve2 } from "path";
2017
2082
  function validateWorktreeSlug(slug2) {
2018
2083
  if (!slug2) throw new Error("worktree slug must not be empty");
2019
2084
  if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
@@ -2044,7 +2109,7 @@ function git(args, cwd) {
2044
2109
  }
2045
2110
  function findGitRoot(from) {
2046
2111
  const r = git(["rev-parse", "--show-toplevel"], from);
2047
- return r.ok ? r.stdout : null;
2112
+ return r.ok ? process.platform === "win32" ? resolve2(r.stdout) : r.stdout : null;
2048
2113
  }
2049
2114
  function readWorktreeHead(worktreePath) {
2050
2115
  try {
@@ -2098,7 +2163,7 @@ function symlinkDirs(repoRoot, worktreePath, dirs) {
2098
2163
  continue;
2099
2164
  }
2100
2165
  try {
2101
- symlinkSync(src, dst, "dir");
2166
+ symlinkSync(src, dst, process.platform === "win32" ? "junction" : "dir");
2102
2167
  } catch (e) {
2103
2168
  if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
2104
2169
  }
@@ -2177,20 +2242,58 @@ var NodeDiskFilesystem;
2177
2242
  var init_NodeDiskFilesystem = __esm({
2178
2243
  "src/NodeDiskFilesystem.ts"() {
2179
2244
  "use strict";
2245
+ init_hostPath();
2180
2246
  NodeDiskFilesystem = class _NodeDiskFilesystem {
2181
2247
  constructor(baseDir, opts = {}) {
2182
2248
  this.baseDir = baseDir;
2183
2249
  this.opts = { denySymlinks: true, ...opts };
2250
+ this.drives = this.win && (baseDir === "/" || baseDir === "\\");
2184
2251
  }
2185
2252
  baseDir;
2186
2253
  cwd = "/";
2187
2254
  opts;
2255
+ win = isWinPaths();
2256
+ /** Windows + baseDir '/': the VFS root is the machine (drives as first segment), not a directory. */
2257
+ drives;
2188
2258
  /** Ensure the root dir exists. */
2189
2259
  async init() {
2260
+ if (this.drives) return;
2190
2261
  await fsp.mkdir(this.baseDir, { recursive: true });
2191
2262
  }
2192
2263
  real(vpath) {
2193
- 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
+ }
2194
2297
  }
2195
2298
  // Verified non-symlink DIRECTORY components, with a short TTL: tree walks (Glob/Grep) hit the same
2196
2299
  // parents thousands of times; re-lstat'ing each per op is the dominant syscall cost. The 1s window
@@ -2200,11 +2303,13 @@ var init_NodeDiskFilesystem = __esm({
2200
2303
  static VERIFY_TTL_MS = 1e3;
2201
2304
  /** Throw if any existing component of `real` is a symlink (escape vector). */
2202
2305
  async assertNoSymlink(real) {
2306
+ this.assertLongNames(real);
2203
2307
  if (!this.opts.denySymlinks) return;
2204
- const rel = np.relative(this.baseDir, real);
2205
- 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;
2206
2311
  const parts = rel.split(np.sep);
2207
- let cur = this.baseDir;
2312
+ let cur = base;
2208
2313
  const now4 = Date.now();
2209
2314
  for (let i = 0; i < parts.length; i++) {
2210
2315
  cur = np.join(cur, parts[i]);
@@ -2224,13 +2329,13 @@ var init_NodeDiskFilesystem = __esm({
2224
2329
  }
2225
2330
  }
2226
2331
  resolvePath(path, cwd) {
2227
- return PathResolver.resolve(path, cwd || this.cwd);
2332
+ return PathResolver.resolve(toVfsPath(path), toVfsPath(cwd || this.cwd));
2228
2333
  }
2229
2334
  getCwd() {
2230
2335
  return this.cwd;
2231
2336
  }
2232
2337
  setCwd(path) {
2233
- this.cwd = PathResolver.normalize(path);
2338
+ this.cwd = PathResolver.normalize(toVfsPath(path));
2234
2339
  }
2235
2340
  async readFile(path) {
2236
2341
  const r = this.real(this.resolvePath(path));
@@ -2304,8 +2409,10 @@ var init_NodeDiskFilesystem = __esm({
2304
2409
  }
2305
2410
  }
2306
2411
  async readDir(path) {
2307
- const r = this.real(this.resolvePath(path));
2412
+ const v = this.resolvePath(path);
2413
+ if (this.isDriveRoot(v)) return this.listDrives();
2308
2414
  try {
2415
+ const r = this.real(v);
2309
2416
  await this.assertNoSymlink(r);
2310
2417
  return await fsp.readdir(r);
2311
2418
  } catch {
@@ -2325,8 +2432,10 @@ var init_NodeDiskFilesystem = __esm({
2325
2432
  await fsp.mkdir(r);
2326
2433
  }
2327
2434
  async exists(path) {
2328
- const r = this.real(this.resolvePath(path));
2435
+ const v = this.resolvePath(path);
2436
+ if (this.isDriveRoot(v)) return true;
2329
2437
  try {
2438
+ const r = this.real(v);
2330
2439
  await this.assertNoSymlink(r);
2331
2440
  await fsp.stat(r);
2332
2441
  return true;
@@ -2336,8 +2445,10 @@ var init_NodeDiskFilesystem = __esm({
2336
2445
  }
2337
2446
  async stat(path) {
2338
2447
  let s;
2339
- 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 };
2340
2450
  try {
2451
+ const r = this.real(v);
2341
2452
  await this.assertNoSymlink(r);
2342
2453
  s = await fsp.stat(r);
2343
2454
  } catch {
@@ -2352,8 +2463,10 @@ var init_NodeDiskFilesystem = __esm({
2352
2463
  };
2353
2464
  }
2354
2465
  async isDirectory(path) {
2355
- const r = this.real(this.resolvePath(path));
2466
+ const v = this.resolvePath(path);
2467
+ if (this.isDriveRoot(v)) return true;
2356
2468
  try {
2469
+ const r = this.real(v);
2357
2470
  await this.assertNoSymlink(r);
2358
2471
  return (await fsp.stat(r)).isDirectory();
2359
2472
  } catch {
@@ -2361,14 +2474,20 @@ var init_NodeDiskFilesystem = __esm({
2361
2474
  }
2362
2475
  }
2363
2476
  async isFile(path) {
2364
- const r = this.real(this.resolvePath(path));
2365
2477
  try {
2478
+ const r = this.real(this.resolvePath(path));
2366
2479
  await this.assertNoSymlink(r);
2367
2480
  return (await fsp.stat(r)).isFile();
2368
2481
  } catch {
2369
2482
  return false;
2370
2483
  }
2371
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
+ }
2372
2491
  };
2373
2492
  }
2374
2493
  });
@@ -2385,6 +2504,7 @@ var init_JailedFilesystem = __esm({
2385
2504
  "src/JailedFilesystem.ts"() {
2386
2505
  "use strict";
2387
2506
  init_tools_structured();
2507
+ init_hostPath();
2388
2508
  JailedFilesystem = class {
2389
2509
  constructor(inner, options) {
2390
2510
  this.inner = inner;
@@ -2398,8 +2518,10 @@ var init_JailedFilesystem = __esm({
2398
2518
  denyRe;
2399
2519
  roRe;
2400
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. */
2401
2523
  abs(path) {
2402
- return this.inner.resolvePath(path, this.inner.getCwd());
2524
+ return this.inner.resolvePath(toVfsPath(path), this.inner.getCwd());
2403
2525
  }
2404
2526
  /** Throw if reading `path` is not permitted (denied or out of confinement). */
2405
2527
  guardRead(op, path) {
@@ -2484,7 +2606,7 @@ var init_JailedFilesystem = __esm({
2484
2606
  return entries.filter((name) => this.visible(abs === "/" ? `/${name}` : `${abs}/${name}`));
2485
2607
  }
2486
2608
  resolvePath(path, cwd) {
2487
- return this.inner.resolvePath(path, cwd ?? this.inner.getCwd());
2609
+ return this.inner.resolvePath(toVfsPath(path), cwd ?? this.inner.getCwd());
2488
2610
  }
2489
2611
  getCwd() {
2490
2612
  return this.inner.getCwd();
@@ -2550,6 +2672,159 @@ var init_JailedFilesystem = __esm({
2550
2672
  }
2551
2673
  });
2552
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
+
2553
2828
  // src/shell.sandbox.ts
2554
2829
  function writable(cwd, o, tmpDir) {
2555
2830
  const set = /* @__PURE__ */ new Set([cwd, "/tmp", "/private/tmp", "/private/var/folders", "/dev", ...tmpDir ? [tmpDir] : [], ...o.writePaths]);
@@ -2611,19 +2886,14 @@ __export(tools_shell_exports, {
2611
2886
  makeShellJobTools: () => makeShellJobTools,
2612
2887
  parsePsTime: () => parsePsTime,
2613
2888
  psGroupCpuSeconds: () => psGroupCpuSeconds,
2614
- sumTreeCpu: () => sumTreeCpu
2889
+ sumTreeCpu: () => sumTreeCpu,
2890
+ windowsShellNote: () => windowsShellNote
2615
2891
  });
2616
2892
  function killGroup(proc, signal) {
2617
- if (!proc?.pid) return false;
2618
- try {
2619
- process.kill(-proc.pid, signal);
2620
- return true;
2621
- } catch {
2622
- return false;
2623
- }
2893
+ return killTree(proc?.pid, signal);
2624
2894
  }
2625
2895
  async function spawnArgvFor(command, cwd, osSandbox) {
2626
- if (!osSandbox) return { bin: "/bin/sh", args: ["-c", command] };
2896
+ if (!osSandbox) return shellArgv(defaultShell(), command);
2627
2897
  const opts = osSandbox === true ? {} : osSandbox;
2628
2898
  const wrapper = await findSandboxWrapper();
2629
2899
  const wrapped = wrapper ? sandboxArgv(command, cwd, opts, process.platform, process.env.TMPDIR) : null;
@@ -2651,9 +2921,17 @@ function parsePsTime(t) {
2651
2921
  return Number(m[1] ?? 0) * 86400 + Number(m[2] ?? 0) * 3600 + Number(m[3]) * 60 + Number(m[4]);
2652
2922
  }
2653
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
+ }
2654
2932
  try {
2655
- const { execFile: execFile4 } = await import("child_process");
2656
- 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))));
2657
2935
  return sumTreeCpu(out, pgid);
2658
2936
  } catch (e) {
2659
2937
  log24.debug(`ps cpu probe failed for group ${pgid}: ${e}`);
@@ -2704,7 +2982,7 @@ function makeRealShellTool(options) {
2704
2982
  return { ...this, description: rebound.description, run: rebound.run, withCwd: rebound.withCwd };
2705
2983
  },
2706
2984
  get description() {
2707
- 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();
2708
2986
  },
2709
2987
  // Declared so transports above the agent loop can size their own deadline with headroom (see AgentTool.maxDurationMs).
2710
2988
  maxDurationMs: maxTimeoutMs,
@@ -2728,7 +3006,7 @@ function makeRealShellTool(options) {
2728
3006
  return `Started background job ${id}. Poll output with ShellOutput({id:"${id}"}), check ShellStatus({id:"${id}"}), stop with ShellKill({id:"${id}"}).`;
2729
3007
  }
2730
3008
  const spawn4 = options.spawn ?? await nodeSpawn();
2731
- let argv = { bin: options.shell || "/bin/sh", args: ["-c", cmd] };
3009
+ let argv = shellArgv(shellFor(options.shell), cmd);
2732
3010
  if (options.osSandbox) {
2733
3011
  try {
2734
3012
  argv = await spawnArgvFor(cmd, options.cwd, options.osSandbox);
@@ -2763,22 +3041,23 @@ function makeRealShellTool(options) {
2763
3041
  }
2764
3042
  };
2765
3043
  try {
2766
- return await new Promise((resolve5) => {
3044
+ return await new Promise((resolve6) => {
2767
3045
  let out = "";
2768
3046
  let settled = false;
2769
3047
  const finish = (s) => {
2770
3048
  if (settled) return;
2771
3049
  settled = true;
2772
- resolve5(s);
3050
+ resolve6(s);
2773
3051
  };
2774
3052
  let proc;
2775
3053
  try {
2776
- 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 });
2777
3056
  } catch (e) {
2778
3057
  return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);
2779
3058
  }
2780
3059
  if (ctl.signal.aborted) killGroup(proc, "SIGKILL");
2781
- else ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
3060
+ else if (!IS_WIN) ctl.signal.addEventListener("abort", () => killGroup(proc, "SIGKILL"), { once: true });
2782
3061
  const registry = options.registry;
2783
3062
  if (registry?.adopt) {
2784
3063
  onDeadline = () => {
@@ -2823,6 +3102,12 @@ function makeRealShellTool(options) {
2823
3102
  }
2824
3103
  };
2825
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
+ }
2826
3111
  function handoffFor(timeoutMs, id, body, notifies) {
2827
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.");
2828
3113
  return body ? `${head}
@@ -2877,7 +3162,7 @@ ${clean2(out) || "(no output yet)"}`;
2877
3162
  }
2878
3163
  ];
2879
3164
  }
2880
- 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;
2881
3166
  var init_tools_shell = __esm({
2882
3167
  "src/tools.shell.ts"() {
2883
3168
  "use strict";
@@ -2886,9 +3171,11 @@ var init_tools_shell = __esm({
2886
3171
  init_logging();
2887
3172
  init_shell_sandbox();
2888
3173
  init_tools_jobs();
3174
+ init_platform();
2889
3175
  log24 = forComponent("shell");
2890
3176
  clean2 = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
2891
- 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 } : {} });
2892
3179
  SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
2893
3180
  ShellJobRegistry = class {
2894
3181
  constructor(cfg) {
@@ -2903,8 +3190,8 @@ var init_tools_shell = __esm({
2903
3190
  const h = this.track(command, { idleMs: 0 });
2904
3191
  try {
2905
3192
  const spawn4 = this.cfg.spawn ?? await nodeSpawn();
2906
- const argv = this.cfg.osSandbox ? await spawnArgvFor(command, this.cfg.cwd, this.cfg.osSandbox) : { bin: "/bin/sh", args: ["-c", command] };
2907
- 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 }));
2908
3195
  } catch (e) {
2909
3196
  h.chunk(`failed to spawn: ${e?.message ?? e}`);
2910
3197
  h.finish("error", void 0, e?.message ?? String(e));
@@ -3034,9 +3321,9 @@ var init_tools_shell = __esm({
3034
3321
 
3035
3322
  // cli/cli.ts
3036
3323
  import { createInterface } from "readline/promises";
3037
- 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";
3038
3325
  import { homedir as homedir10 } from "os";
3039
- import { spawnSync as spawnSync6 } from "child_process";
3326
+ import { spawnSync as spawnSync7 } from "child_process";
3040
3327
 
3041
3328
  // cli/clipboard.ts
3042
3329
  import { execFileSync } from "child_process";
@@ -3067,6 +3354,15 @@ function grabClipboardImage(dir, id, run = execFileSync, platform2 = process.pla
3067
3354
  }
3068
3355
  return null;
3069
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
+ }
3070
3366
  if (platform2 !== "darwin") return null;
3071
3367
  const osa = (klass, out) => `set d to (the clipboard as \xABclass ${klass}\xBB)
3072
3368
  set f to open for access POSIX file ${q(out)} with write permission
@@ -3091,10 +3387,13 @@ close access f`;
3091
3387
  return null;
3092
3388
  }
3093
3389
  function copyTextToClipboard(text, platform2 = process.platform) {
3094
- 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
+ );
3095
3394
  for (const [cmd, args] of candidates) {
3096
3395
  try {
3097
- execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
3396
+ execFileSync(cmd, args, { input: text, stdio: ["pipe", "ignore", "ignore"], ...platform2 === "win32" ? { windowsHide: true } : {} });
3098
3397
  return true;
3099
3398
  } catch {
3100
3399
  }
@@ -3103,7 +3402,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
3103
3402
  }
3104
3403
 
3105
3404
  // cli/cli.ts
3106
- 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";
3107
3406
  import { AIClient, DecideClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions, disposeClaudeCodeSessions } from "ai.libx.js";
3108
3407
 
3109
3408
  // src/ccBridge.ts
@@ -4339,6 +4638,10 @@ var AgentOptions = class {
4339
4638
  model = "anthropic/claude-sonnet-4-6";
4340
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.";
4341
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";
4342
4645
  maxSteps = 25;
4343
4646
  // --- automatic kill-switches (always on; protect the API budget against runaway loops/abuse) ---
4344
4647
  /** Hard ceiling on accumulated tokens (prompt+completion) across the run; cache READS count at 0.1×
@@ -4934,8 +5237,9 @@ var Agent = class _Agent {
4934
5237
  };
4935
5238
  const cursorPo = isCursorWithTools ? { toolTimeoutMs: effectiveToolTimeoutMs, toolExecutor: hostExec } : void 0;
4936
5239
  const claudeSteerPo = o.model.startsWith("claude-code/") ? { steer: () => this.drainPendingSteers() } : void 0;
4937
- if (claudeSteerPo && o.providerOptions?.claudeCodeSession && this.activeTools.some((t) => CC_BRIDGED.has(t.name))) {
4938
- 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));
4939
5243
  claudeSteerPo.mcpServers = { ...o.providerOptions?.mcpServers, agentx: this.ccServer };
4940
5244
  }
4941
5245
  if (this.ccServer) this.ccExec = hostExec;
@@ -9330,6 +9634,10 @@ function describeCall(call) {
9330
9634
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
9331
9635
  return `${call.name}${hint}`;
9332
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
+ }
9333
9641
  var DuplexAgentOptions = class {
9334
9642
  /** Any ai.libx.js AIClient — shared by all tiers (routed by model). */
9335
9643
  ai;
@@ -9619,12 +9927,26 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9619
9927
  timeoutMs: 3e4,
9620
9928
  failEmptyTurns: false,
9621
9929
  // a silent reflex turn after a dispatch is legitimate — the voice layer repairs it
9930
+ ...this.claudeCodeReflex(),
9622
9931
  ...o.reflexOptions,
9623
9932
  tools,
9624
9933
  // Composed AFTER the spread so the dispatch guard can't be dropped by reflexOptions.
9625
9934
  hooks: composeHooks(this.dispatchGuard(), o.reflexOptions?.hooks)
9626
9935
  });
9627
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
+ }
9628
9950
  /** Resolve memory tools + inject index into voice system prompt (once). */
9629
9951
  async initMemory() {
9630
9952
  if (!this.memoryReady) return;
@@ -9789,7 +10111,8 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9789
10111
  await this.drainReflex();
9790
10112
  this.scrubLeakFromTranscript();
9791
10113
  this.scrubHeldText();
9792
- 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);
9793
10116
  }
9794
10117
  /** Remove complete stage-direction parentheticals from the UNFORWARDED reflex text (STAGE_DIRECTION_RE).
9795
10118
  * Only the unforwarded region is touched — already-spoken audio can't be unsent, and splicing before
@@ -9895,6 +10218,7 @@ ${nowLine()}. Anchor every relative or time-sensitive reference \u2014 "today",
9895
10218
  * hosts/tests key on it. */
9896
10219
  static FALLBACK_ACKS = ["Okay, on it.", "On it.", "Alright, working on it."];
9897
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.";
9898
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?"];
9899
10223
  lastFallback = "";
9900
10224
  /** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns.
@@ -10553,14 +10877,14 @@ Another agent just implemented the above. Independently check the CURRENT state
10553
10877
  * '[task <id> asks] …' and resolves with the user's answer via AnswerTask — or '' on timeout/
10554
10878
  * task settle (callers map '' to deny / best-judgment). Workers never block forever. */
10555
10879
  parkQuestion(askId, question) {
10556
- return new Promise((resolve5) => {
10880
+ return new Promise((resolve6) => {
10557
10881
  let settled = false;
10558
10882
  const finish = (answer) => {
10559
10883
  if (settled) return;
10560
10884
  settled = true;
10561
10885
  clearTimeout(timer);
10562
10886
  this.pendingAsks.delete(askId);
10563
- resolve5(answer);
10887
+ resolve6(answer);
10564
10888
  };
10565
10889
  const timer = setTimeout(() => {
10566
10890
  this.notify("task_ask_timeout", `task ${askId}: question timed out \u2014 proceeding without an answer`);
@@ -11307,11 +11631,11 @@ function buildMcpCatalog(servers) {
11307
11631
  }
11308
11632
  return { specs, routes };
11309
11633
  }
11310
- function searchOverCatalog(servers, specs, routes, resolve5, options) {
11634
+ function searchOverCatalog(servers, specs, routes, resolve6, options) {
11311
11635
  const tools = specs.length ? makeMcpToolSearch(specs, (name, args, o) => {
11312
11636
  const r = routes.get(name);
11313
11637
  if (!r) throw new Error(`unknown MCP tool '${name}' \u2014 use ToolSearch to find valid names`);
11314
- return resolve5(r.server, r.rawName, args ?? {}, o);
11638
+ return resolve6(r.server, r.rawName, args ?? {}, o);
11315
11639
  }, options) : [];
11316
11640
  return { tools, serverNames: servers, toolCount: specs.length };
11317
11641
  }
@@ -11329,6 +11653,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
11329
11653
 
11330
11654
  // src/mcp.client.ts
11331
11655
  init_logging();
11656
+ init_platform();
11332
11657
  import { spawn } from "child_process";
11333
11658
  import { createHash } from "crypto";
11334
11659
  var log23 = forComponent("mcp");
@@ -11356,7 +11681,9 @@ var StdioTransport = class {
11356
11681
  pending = /* @__PURE__ */ new Map();
11357
11682
  async start() {
11358
11683
  const { command, args = [], env, cwd } = this.spec;
11359
- 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 } : {} });
11360
11687
  this.proc = proc;
11361
11688
  proc.stdout.setEncoding("utf8");
11362
11689
  proc.stdout.on("data", (chunk) => this.onData(chunk));
@@ -11413,7 +11740,7 @@ var StdioTransport = class {
11413
11740
  const id = this.nextId++;
11414
11741
  const timeoutMs = deadlineFor(method, this.spec, opts);
11415
11742
  const { signal, onProgress } = opts;
11416
- return new Promise((resolve5, reject) => {
11743
+ return new Promise((resolve6, reject) => {
11417
11744
  if (signal?.aborted) return reject(abortError(signal));
11418
11745
  const timer = timeoutMs > 0 ? setTimeout(() => {
11419
11746
  this.pending.delete(id);
@@ -11428,7 +11755,7 @@ var StdioTransport = class {
11428
11755
  };
11429
11756
  const done = () => signal?.removeEventListener("abort", onAbort);
11430
11757
  signal?.addEventListener("abort", onAbort, { once: true });
11431
- this.pending.set(id, { resolve: resolve5, reject, timer, onProgress, done });
11758
+ this.pending.set(id, { resolve: resolve6, reject, timer, onProgress, done });
11432
11759
  try {
11433
11760
  this.write({ jsonrpc: "2.0", id, method, params: onProgress ? withProgressToken(params, id) : params });
11434
11761
  } catch (e) {
@@ -11449,7 +11776,7 @@ var StdioTransport = class {
11449
11776
  } catch (e) {
11450
11777
  log23.debug("stdin end failed", e);
11451
11778
  }
11452
- this.proc?.kill();
11779
+ if (!(IS_WIN && killTree(this.proc?.pid, "SIGTERM"))) this.proc?.kill();
11453
11780
  }
11454
11781
  };
11455
11782
  var McpAuthError = class extends Error {
@@ -11612,12 +11939,12 @@ function buildTransport(cfg) {
11612
11939
  }
11613
11940
  function withTimeout(p, ms, label) {
11614
11941
  if (!ms || ms <= 0) return p;
11615
- return new Promise((resolve5, reject) => {
11942
+ return new Promise((resolve6, reject) => {
11616
11943
  const timer = setTimeout(() => reject(new Error(`MCP "${label}" mount exceeded ${ms}ms`)), ms);
11617
11944
  timer.unref?.();
11618
11945
  p.then((v) => {
11619
11946
  clearTimeout(timer);
11620
- resolve5(v);
11947
+ resolve6(v);
11621
11948
  }, (e) => {
11622
11949
  clearTimeout(timer);
11623
11950
  reject(e);
@@ -11738,7 +12065,7 @@ var defaultPool = new McpPool();
11738
12065
  import { createServer } from "http";
11739
12066
  import { randomBytes as randomBytes2, createHash as createHash2 } from "crypto";
11740
12067
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
11741
- import { dirname as dirname2 } from "path";
12068
+ import { dirname as dirname3 } from "path";
11742
12069
  var McpOAuthOptions = class {
11743
12070
  /** Where to persist tokens. Mode 0600. */
11744
12071
  storePath = ".agent/mcp-auth.json";
@@ -11771,7 +12098,7 @@ var McpOAuth = class {
11771
12098
  }
11772
12099
  }
11773
12100
  save(store) {
11774
- mkdirSync3(dirname2(this.options.storePath), { recursive: true });
12101
+ mkdirSync3(dirname3(this.options.storePath), { recursive: true });
11775
12102
  writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
11776
12103
  }
11777
12104
  // -- ATTENDED: one-time authorization ----------------------------------------
@@ -11863,7 +12190,7 @@ var McpOAuth = class {
11863
12190
  }
11864
12191
  /** Start a one-route loopback server, open the browser, and resolve with the returned auth code. */
11865
12192
  captureCode(authEndpoint, clientId, challenge, state) {
11866
- return new Promise((resolve5, reject) => {
12193
+ return new Promise((resolve6, reject) => {
11867
12194
  const server = createServer((req, res) => {
11868
12195
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
11869
12196
  if (url.pathname !== "/callback") {
@@ -11877,7 +12204,7 @@ var McpOAuth = class {
11877
12204
  clearTimeout(timer);
11878
12205
  if (gotState !== state) return reject(new Error("OAuth state mismatch (possible CSRF) \u2014 aborting"));
11879
12206
  if (!code) return reject(new Error(`authorization failed: ${url.searchParams.get("error") ?? "no code"}`));
11880
- resolve5({ code, redirectUri });
12207
+ resolve6({ code, redirectUri });
11881
12208
  });
11882
12209
  let redirectUri = "";
11883
12210
  const timer = setTimeout(() => {
@@ -11922,26 +12249,33 @@ function b64url(buf) {
11922
12249
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
11923
12250
  }
11924
12251
  function defaultOpenBrowser(url) {
11925
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
11926
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
11927
- 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());
11928
12256
  }
11929
12257
 
11930
12258
  // cli/core.ts
11931
12259
  import { randomUUID } from "crypto";
11932
- import { execFile as execFile2 } from "child_process";
11933
- 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";
11934
12262
  import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
11935
12263
  import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
11936
12264
  init_tools_shell();
11937
12265
 
11938
12266
  // src/tools.notify.ts
11939
12267
  init_logging();
11940
- import { execFile } from "child_process";
12268
+ import { execFile as execFile2 } from "child_process";
11941
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("; ");
11942
12276
  function makeNotifyTool(opts = {}) {
11943
12277
  const platform2 = opts.platform ?? process.platform;
11944
- const run = opts.exec ?? execFile;
12278
+ const run = opts.exec ?? execFile2;
11945
12279
  return {
11946
12280
  name: "PushNotification",
11947
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.",
@@ -11957,14 +12291,15 @@ function makeNotifyTool(opts = {}) {
11957
12291
  const msg = String(message ?? "").slice(0, 256);
11958
12292
  const head = String(title ?? "agentx").slice(0, 64);
11959
12293
  if (!msg) return "Error: empty message";
11960
- 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;
11961
12295
  if (!argv) return `Notifications unavailable on ${platform2}.`;
11962
- return new Promise((resolve5) => {
11963
- 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) => {
11964
12299
  if (e) {
11965
12300
  log25.debug("notification failed", e);
11966
- resolve5(`Notification failed: ${e.message}`);
11967
- } else resolve5("Notification shown.");
12301
+ resolve6(`Notification failed: ${e.message}`);
12302
+ } else resolve6("Notification shown.");
11968
12303
  });
11969
12304
  });
11970
12305
  }
@@ -12012,13 +12347,15 @@ function readJsonFile(path, fallback) {
12012
12347
  }
12013
12348
 
12014
12349
  // cli/core.ts
12350
+ init_hostPath();
12351
+ init_platform();
12015
12352
  var DEFAULT_TOOLS = ["bash", "Read", "Edit", "Write", "Grep", "Glob", "MultiEdit", "ApplyEdits", "RepoMap", "TodoWrite"];
12016
12353
  function autoWebTools() {
12017
12354
  return ["WebFetch", "WebSearch"];
12018
12355
  }
12019
12356
  function pdfTextViaPoppler(path) {
12020
12357
  return new Promise((res, rej) => {
12021
- execFile2("pdftotext", [path, "-"], { maxBuffer: 32 * 1024 * 1024, timeout: 3e4 }, (e, stdout) => {
12358
+ execFile3("pdftotext", [path, "-"], { maxBuffer: 32 * 1024 * 1024, timeout: 3e4 }, (e, stdout) => {
12022
12359
  if (e) rej(new Error(/ENOENT/.test(String(e.code ?? e.message)) ? "pdftotext not installed (brew/apt install poppler)" : e.message));
12023
12360
  else res(stdout);
12024
12361
  });
@@ -12082,7 +12419,7 @@ function toClaudeCodeMcp(servers) {
12082
12419
  function claudeCodeProviderOptions(model, cwd, mcpServers) {
12083
12420
  if (!(model ?? "").startsWith("claude-code/")) return void 0;
12084
12421
  const env = (() => {
12085
- 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"]);
12086
12423
  return { ENABLE_CLAUDEAI_MCP_SERVERS: "false", ...base };
12087
12424
  })();
12088
12425
  const warm = process.env.AGENTX_CC_WARM === "0" ? {} : { claudeCodeSession: randomUUID() };
@@ -12096,7 +12433,7 @@ async function buildAgent(o) {
12096
12433
  throw new Error(
12097
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."
12098
12435
  );
12099
- const cwd = resolve2(o.cwd ?? process.cwd());
12436
+ const cwd = resolve3(o.cwd ?? process.cwd());
12100
12437
  const disk = new NodeDiskFilesystem("/", { denySymlinks: false });
12101
12438
  await disk.init();
12102
12439
  const jailedDisk = new JailedFilesystem(disk);
@@ -12110,21 +12447,22 @@ async function buildAgent(o) {
12110
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).`
12111
12448
  );
12112
12449
  let fs = jailedDisk;
12450
+ const vcwd = toVfsPath(cwd);
12113
12451
  if (o.sandbox) {
12114
- const mem = new MemFilesystem3();
12115
- await mkdirp(mem, cwd);
12116
- await hydrate(jailedDisk, mem, cwd);
12117
- mem.setCwd(cwd);
12452
+ const mem = withHostPaths(new MemFilesystem3());
12453
+ await mkdirp(mem, vcwd);
12454
+ await hydrate(jailedDisk, mem, vcwd);
12455
+ mem.setCwd(vcwd);
12118
12456
  fs = mem;
12119
12457
  } else if (o.boddb) {
12120
- const root = resolve2(o.boddb);
12458
+ const root = resolve3(o.boddb);
12121
12459
  mkdirSync4(root, { recursive: true });
12122
- const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
12123
- 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));
12124
12462
  const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
12125
- await mkdirp(bod, cwd);
12126
- if (o.seed && firstRun) await hydrate(jailedDisk, bod, cwd);
12127
- bod.setCwd(cwd);
12463
+ await mkdirp(bod, vcwd);
12464
+ if (o.seed && firstRun) await hydrate(jailedDisk, bod, vcwd);
12465
+ bod.setCwd(vcwd);
12128
12466
  fs = bod;
12129
12467
  }
12130
12468
  let mountNote = "";
@@ -12133,8 +12471,8 @@ async function buildAgent(o) {
12133
12471
  const mounts = [];
12134
12472
  const notes = [];
12135
12473
  for (const d of o.addDirs) {
12136
- const abs = resolve2(d);
12137
- let name = basename(abs) || "dir", prefix = `/${name}`, n = 2;
12474
+ const abs = resolve3(d);
12475
+ let name = basename3(abs) || "dir", prefix = `/${name}`, n = 2;
12138
12476
  while (used.has(prefix)) prefix = `/${name}-${n++}`;
12139
12477
  used.add(prefix);
12140
12478
  const md = new NodeDiskFilesystem(abs);
@@ -12186,7 +12524,7 @@ Reference files in them by their mount path (the left side).`;
12186
12524
  });
12187
12525
  realShell = [makeRealShellTool({ cwd, registry: jobs, osSandbox: o.osSandbox, jobsSurviveRun: o.singleRun !== true }), ...makeShellJobTools(jobs)];
12188
12526
  }
12189
- 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;
12190
12528
  const scratch = scratchDir ? new Scratch(fs, { dir: scratchDir }) : void 0;
12191
12529
  return agentRef.agent = new Agent({
12192
12530
  ai: o.ai,
@@ -12211,12 +12549,13 @@ Reference files in them by their mount path (the left side).`;
12211
12549
  const envNote = `Current date: ${now4.toLocaleDateString("en-CA")} ${now4.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" })} (${Intl.DateTimeFormat().resolvedOptions().timeZone})
12212
12550
  Platform: ${platformNames[platform()] ?? platform()} ${arch()} (${release()})
12213
12551
  User: ${userInfo().username}
12214
- Shell: ${process.env.SHELL ?? "unknown"}`;
12552
+ Shell: ${process.env.SHELL ?? (isWinPaths() ? defaultShell().label : "unknown")}`;
12215
12553
  const cwdNote = o.boddb ? `Working directory: ${cwd}
12216
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}
12217
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}
12218
- 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.`;
12219
- 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;
12220
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.";
12221
12560
  const mcpNote = (() => {
12222
12561
  const names = new Set(Object.keys(o.mcpServers ?? {}));
@@ -12299,10 +12638,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
12299
12638
 
12300
12639
  // cli/voice.ts
12301
12640
  init_logging();
12302
- import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
12303
- 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";
12304
12644
  import { homedir as homedir3 } from "os";
12305
- import { dirname as dirname3, join as join6 } from "path";
12645
+ import { dirname as dirname4, join as join7 } from "path";
12306
12646
  import { fileURLToPath } from "url";
12307
12647
  var log27 = forComponent("VoiceIO");
12308
12648
  var now3 = () => performance.now();
@@ -12345,10 +12685,10 @@ var Player = class {
12345
12685
  this.proc = null;
12346
12686
  }
12347
12687
  };
12348
- var nativeDir = () => join6(dirname3(fileURLToPath(import.meta.url)), "native");
12688
+ var nativeDir = () => join7(dirname4(fileURLToPath(import.meta.url)), "native");
12349
12689
  function detectFfmpegMic() {
12350
12690
  if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
12351
- 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" });
12352
12692
  if (r.error?.code === "ENOENT") throw new Error("ffmpeg not installed \u2014 run `brew install ffmpeg` to enable voice");
12353
12693
  if (r.error) throw new Error(`ffmpeg failed to run (${r.error.message}) \u2014 likely a wrong-arch binary; run \`brew reinstall ffmpeg\``);
12354
12694
  const out = r.stderr ?? "";
@@ -12359,13 +12699,24 @@ function detectFfmpegMic() {
12359
12699
  log27.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
12360
12700
  return `:${mic.idx}`;
12361
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
+ }
12362
12713
  function detectedInputDevice() {
12363
12714
  if (process.platform !== "darwin") return null;
12364
12715
  let name = "";
12365
- if (spawnSync2("which", ["SwitchAudioSource"]).status === 0) {
12366
- 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();
12367
12718
  } else {
12368
- const out = spawnSync2("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
12719
+ const out = spawnSync3("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
12369
12720
  let last = "";
12370
12721
  for (const ln of out.split("\n")) {
12371
12722
  const hdr = ln.match(/^\s{6,}(\S.*?):\s*$/);
@@ -12384,21 +12735,21 @@ function detectedInputDevice() {
12384
12735
  }
12385
12736
  function resolveAecBinary() {
12386
12737
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
12387
- const src = join6(nativeDir(), "mic-aec.swift");
12388
- const plist = join6(nativeDir(), "Info.plist");
12738
+ const src = join7(nativeDir(), "mic-aec.swift");
12739
+ const plist = join7(nativeDir(), "Info.plist");
12389
12740
  if (!existsSync4(src)) return null;
12390
- const cacheDir = join6(homedir3(), ".agent", "cache");
12391
- const bin = join6(cacheDir, "mic-aec");
12392
- if (existsSync4(bin) && statSync5(bin).mtimeMs >= statSync5(src).mtimeMs) return bin;
12393
- 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;
12394
12745
  mkdirSync5(cacheDir, { recursive: true });
12395
12746
  log27.info("compiling AEC mic helper (first run)\u2026");
12396
- 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" });
12397
12748
  if (build.status !== 0) {
12398
12749
  log27.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
12399
12750
  return null;
12400
12751
  }
12401
- const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
12752
+ const sign = spawnSync3("codesign", ["-fs", "-", bin], { encoding: "utf8" });
12402
12753
  if (sign.status !== 0) {
12403
12754
  log27.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
12404
12755
  return null;
@@ -12408,14 +12759,14 @@ function resolveAecBinary() {
12408
12759
  function openMicSettings() {
12409
12760
  if (process.platform !== "darwin") return;
12410
12761
  try {
12411
- spawnSync2("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
12762
+ spawnSync3("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"]);
12412
12763
  } catch {
12413
12764
  }
12414
12765
  }
12415
12766
  function micPermissionStatus() {
12416
12767
  const bin = resolveAecBinary();
12417
12768
  if (!bin) return null;
12418
- const r = spawnSync2(bin, ["--check-mic"], { encoding: "utf8" });
12769
+ const r = spawnSync3(bin, ["--check-mic"], { encoding: "utf8" });
12419
12770
  const out = (r.stdout ?? "").trim();
12420
12771
  if (out === "authorized") return "authorized";
12421
12772
  if (out === "denied" || out === "restricted") return "denied";
@@ -12425,7 +12776,7 @@ function micPermissionStatus() {
12425
12776
  function aecUnavailableHint() {
12426
12777
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
12427
12778
  if (resolveAecBinary()) return null;
12428
- 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";
12429
12780
  return null;
12430
12781
  }
12431
12782
  var NodeMicSource = class {
@@ -12441,12 +12792,13 @@ var NodeMicSource = class {
12441
12792
  if (this.bin) {
12442
12793
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
12443
12794
  } else {
12444
- 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");
12445
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()];
12446
12798
  this.proc = spawn2(
12447
12799
  "ffmpeg",
12448
- ["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
12449
- { 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 } : {} }
12450
12802
  );
12451
12803
  this.proc.stderr.on("data", (d) => log27.warn(`ffmpeg: ${String(d).trim()}`));
12452
12804
  }
@@ -12833,7 +13185,7 @@ function fakeVoiceParts(uttFile) {
12833
13185
  // src/voice/diagSink.ts
12834
13186
  init_logging();
12835
13187
  import { appendFile, mkdirSync as mkdirSync6 } from "fs";
12836
- import { dirname as dirname4 } from "path";
13188
+ import { dirname as dirname5 } from "path";
12837
13189
  var log28 = forComponent("VoiceDiag");
12838
13190
  var JsonlDiagSink = class {
12839
13191
  constructor(path) {
@@ -12858,7 +13210,7 @@ var JsonlDiagSink = class {
12858
13210
  if (this.writing || this.disabled || !this.queue.length) return;
12859
13211
  if (!this.dirReady) {
12860
13212
  try {
12861
- mkdirSync6(dirname4(this.path), { recursive: true });
13213
+ mkdirSync6(dirname5(this.path), { recursive: true });
12862
13214
  this.dirReady = true;
12863
13215
  } catch (e) {
12864
13216
  return this.disable(e);
@@ -12890,13 +13242,13 @@ var JsonlDiagSink = class {
12890
13242
  // cli/config.ts
12891
13243
  import { homedir as homedir5 } from "os";
12892
13244
  import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
12893
- import { join as join8 } from "path";
13245
+ import { join as join9 } from "path";
12894
13246
  import { pathToFileURL } from "url";
12895
13247
 
12896
13248
  // cli/permissions.ts
12897
13249
  import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
12898
13250
  import { homedir as homedir4 } from "os";
12899
- import { join as join7 } from "path";
13251
+ import { join as join8 } from "path";
12900
13252
  var RULE_RE = /^(\w[\w*-]*)(?:\((.+)\))?$/;
12901
13253
  function parseOne(raw, decision) {
12902
13254
  const m = RULE_RE.exec(raw.trim());
@@ -12918,13 +13270,13 @@ function parsePermRules(perms, explicit = false) {
12918
13270
  function describeRule(r) {
12919
13271
  return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
12920
13272
  }
12921
- var PERM_FILE = (cwd) => join7(cwd, ".agent", "permissions.json");
13273
+ var PERM_FILE = (cwd) => join8(cwd, ".agent", "permissions.json");
12922
13274
  function loadPersistedRules(cwd) {
12923
13275
  const j = readJsonFile(PERM_FILE(cwd), null);
12924
13276
  return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
12925
13277
  }
12926
13278
  function loadClaudeSettings(cwd, home = homedir4()) {
12927
- 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")];
12928
13280
  let out = {};
12929
13281
  for (const p of files) {
12930
13282
  const perms = readJsonFile(p, null)?.permissions;
@@ -12937,7 +13289,7 @@ function persistRule(cwd, decision, ruleStr) {
12937
13289
  const list = cur[decision] ??= [];
12938
13290
  if (!list.includes(ruleStr)) list.push(ruleStr);
12939
13291
  try {
12940
- mkdirSync7(join7(cwd, ".agent"), { recursive: true });
13292
+ mkdirSync7(join8(cwd, ".agent"), { recursive: true });
12941
13293
  writeFileSync4(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
12942
13294
  } catch {
12943
13295
  }
@@ -12951,7 +13303,7 @@ function mergePerms(a, b) {
12951
13303
  }
12952
13304
  return Object.keys(out).length ? out : void 0;
12953
13305
  }
12954
- var TRUST_FILE = join7(homedir4(), ".agent", "trusted.json");
13306
+ var TRUST_FILE = join8(homedir4(), ".agent", "trusted.json");
12955
13307
  function isTrusted(cwd, file = TRUST_FILE) {
12956
13308
  const list = readJsonFile(file, []);
12957
13309
  return Array.isArray(list) && list.includes(cwd);
@@ -12961,7 +13313,7 @@ function trustDir(cwd, file = TRUST_FILE) {
12961
13313
  if (!Array.isArray(list)) list = [];
12962
13314
  if (!list.includes(cwd)) list.push(cwd);
12963
13315
  try {
12964
- mkdirSync7(join7(file, ".."), { recursive: true });
13316
+ mkdirSync7(join8(file, ".."), { recursive: true });
12965
13317
  writeFileSync4(file, JSON.stringify(list, null, 2) + "\n");
12966
13318
  } catch {
12967
13319
  }
@@ -12971,7 +13323,7 @@ function trustDir(cwd, file = TRUST_FILE) {
12971
13323
  var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
12972
13324
  async function loadFrom(dir) {
12973
13325
  for (const f of FILES) {
12974
- const p = join8(dir, ".agent", f);
13326
+ const p = join9(dir, ".agent", f);
12975
13327
  if (!existsSync5(p)) continue;
12976
13328
  try {
12977
13329
  const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
@@ -12984,7 +13336,7 @@ async function loadFrom(dir) {
12984
13336
  return {};
12985
13337
  }
12986
13338
  function loadSettings(dir) {
12987
- const p = join8(dir, ".agent", "settings.json");
13339
+ const p = join9(dir, ".agent", "settings.json");
12988
13340
  if (!existsSync5(p)) return {};
12989
13341
  try {
12990
13342
  const raw = JSON.parse(readFileSync7(p, "utf8"));
@@ -13031,7 +13383,8 @@ function applyOverlay(cfg, file) {
13031
13383
  }
13032
13384
 
13033
13385
  // cli/hooks-config.ts
13034
- import { spawnSync as spawnSync3 } from "child_process";
13386
+ import { spawnSync as spawnSync4 } from "child_process";
13387
+ init_platform();
13035
13388
  var log29 = forComponent("hooks");
13036
13389
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
13037
13390
  function ruleMatches(rule, toolName) {
@@ -13041,8 +13394,10 @@ function ruleMatches(rule, toolName) {
13041
13394
  }
13042
13395
  function runCmd(rule, env) {
13043
13396
  try {
13044
- const r = spawnSync3(rule.command, {
13045
- 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,
13046
13401
  encoding: "utf8",
13047
13402
  timeout: rule.timeoutMs ?? 1e4,
13048
13403
  env: { ...process.env, ...env }
@@ -13153,26 +13508,45 @@ function formatDiff(ops, opts = {}) {
13153
13508
  }
13154
13509
 
13155
13510
  // cli/session.ts
13156
- 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";
13157
13512
  import { homedir as homedir6 } from "os";
13158
- import { join as join9 } from "path";
13513
+ import { join as join10 } from "path";
13514
+ init_platform();
13159
13515
  var log30 = forComponent("session");
13160
- 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
+ }
13161
13535
  var SessionStore = class {
13162
13536
  dir;
13163
13537
  constructor(cwd) {
13164
- this.dir = join9(cwd, ".agent", "sessions");
13538
+ this.dir = join10(cwd, ".agent", "sessions");
13165
13539
  }
13166
13540
  /** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
13167
13541
  newId(now4 = Date.now(), cwd) {
13168
13542
  const d = new Date(now4);
13169
13543
  const p = (n, w = 2) => String(n).padStart(w, "0");
13170
- 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";
13171
13545
  let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
13172
- if (existsSync6(this.dir) && existsSync6(join9(this.dir, `${id}.json`))) {
13546
+ if (existsSync6(this.dir) && existsSync6(join10(this.dir, `${id}.json`))) {
13173
13547
  for (let i = 2; i <= 99; i++) {
13174
13548
  const c = `${id}-${i}`;
13175
- if (!existsSync6(join9(this.dir, `${c}.json`))) {
13549
+ if (!existsSync6(join10(this.dir, `${c}.json`))) {
13176
13550
  id = c;
13177
13551
  break;
13178
13552
  }
@@ -13187,15 +13561,15 @@ var SessionStore = class {
13187
13561
  save(data) {
13188
13562
  if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
13189
13563
  if (!existsSync6(this.dir)) mkdirSync8(this.dir, { recursive: true });
13190
- const path = join9(this.dir, `${data.meta.id}.json`);
13564
+ const path = join10(this.dir, `${data.meta.id}.json`);
13191
13565
  const tmp = `${path}.${process.pid}.tmp`;
13192
13566
  writeFileSync5(tmp, JSON.stringify(data));
13193
13567
  renameSync(tmp, path);
13194
13568
  try {
13195
13569
  const gd = globalDir();
13196
13570
  if (!existsSync6(gd)) mkdirSync8(gd, { recursive: true });
13197
- const link2 = join9(gd, `${data.meta.id}.json`);
13198
- if (!existsSync6(link2)) symlinkSync2(path, link2);
13571
+ const link2 = join10(gd, `${data.meta.id}.json`);
13572
+ if (!existsSync6(link2)) indexLink(path, link2);
13199
13573
  } catch {
13200
13574
  }
13201
13575
  }
@@ -13204,7 +13578,7 @@ var SessionStore = class {
13204
13578
  log30.debug(`rejecting unsafe session id: ${id}`);
13205
13579
  return void 0;
13206
13580
  }
13207
- const path = join9(this.dir, `${id}.json`);
13581
+ const path = join10(this.dir, `${id}.json`);
13208
13582
  if (!existsSync6(path)) return void 0;
13209
13583
  try {
13210
13584
  return JSON.parse(readFileSync8(path, "utf8"));
@@ -13220,7 +13594,7 @@ var SessionStore = class {
13220
13594
  for (const f of readdirSync(this.dir)) {
13221
13595
  if (!f.endsWith(".json")) continue;
13222
13596
  try {
13223
- metas.push(JSON.parse(readFileSync8(join9(this.dir, f), "utf8")).meta);
13597
+ metas.push(JSON.parse(readFileSync8(join10(this.dir, f), "utf8")).meta);
13224
13598
  } catch (e) {
13225
13599
  log30.debug(`skipping unreadable session file ${f}`, e);
13226
13600
  }
@@ -13235,7 +13609,7 @@ var SessionStore = class {
13235
13609
  for (const f of readdirSync(this.dir)) {
13236
13610
  if (!f.endsWith(".json")) continue;
13237
13611
  try {
13238
- const st = statSync6(join9(this.dir, f));
13612
+ const st = statSync7(join10(this.dir, f));
13239
13613
  out.push({ id: f.slice(0, -5), mtime: st.mtimeMs, size: st.size });
13240
13614
  } catch {
13241
13615
  }
@@ -13254,10 +13628,10 @@ var SessionStore = class {
13254
13628
  function globalSessionLoad(idOrPrefix) {
13255
13629
  const gd = globalDir();
13256
13630
  if (!existsSync6(gd)) return void 0;
13257
- const exact = join9(gd, `${idOrPrefix}.json`);
13631
+ const exact = join10(gd, `${idOrPrefix}.json`);
13258
13632
  if (existsSync6(exact)) {
13259
13633
  try {
13260
- const target = readlinkSync(exact);
13634
+ const target = readIndexLink(exact);
13261
13635
  return JSON.parse(readFileSync8(target, "utf8"));
13262
13636
  } catch {
13263
13637
  return void 0;
@@ -13268,7 +13642,7 @@ function globalSessionLoad(idOrPrefix) {
13268
13642
  if (!f.endsWith(".json")) continue;
13269
13643
  const base = f.slice(0, -5);
13270
13644
  if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
13271
- const target = readlinkSync(join9(gd, f));
13645
+ const target = readIndexLink(join10(gd, f));
13272
13646
  return JSON.parse(readFileSync8(target, "utf8"));
13273
13647
  }
13274
13648
  }
@@ -13283,10 +13657,10 @@ function globalSessionList() {
13283
13657
  for (const f of readdirSync(gd)) {
13284
13658
  if (!f.endsWith(".json")) continue;
13285
13659
  try {
13286
- const target = readlinkSync(join9(gd, f));
13660
+ const target = readIndexLink(join10(gd, f));
13287
13661
  if (!existsSync6(target)) {
13288
13662
  try {
13289
- unlinkSync(join9(gd, f));
13663
+ unlinkSync(join10(gd, f));
13290
13664
  } catch {
13291
13665
  }
13292
13666
  continue;
@@ -13456,17 +13830,17 @@ ${formatDiff(ops)}`);
13456
13830
  };
13457
13831
 
13458
13832
  // cli/gitCheckpoints.ts
13459
- import { execFile as execFile3 } from "child_process";
13833
+ import { execFile as execFile4 } from "child_process";
13460
13834
  import { promisify } from "util";
13461
13835
  import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync9 } from "fs";
13462
13836
  import { homedir as homedir7 } from "os";
13463
- 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";
13464
13838
  var log31 = forComponent("checkpoints");
13465
- var exec = promisify(execFile3);
13839
+ var exec = promisify(execFile4);
13466
13840
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
13467
13841
  function isBroadRoot(p) {
13468
- const abs = resolve3(p);
13469
- return abs === resolve3(homedir7()) || abs === parsePath(abs).root;
13842
+ const abs = resolve4(p);
13843
+ return abs === resolve4(homedir7()) || abs === parsePath(abs).root;
13470
13844
  }
13471
13845
  var ShadowRepo = class {
13472
13846
  // undefined = unprobed; false = git/this root unusable
@@ -13503,8 +13877,8 @@ var ShadowRepo = class {
13503
13877
  mkdirSync9(this.gitDir, { recursive: true });
13504
13878
  const valid = await this.run("rev-parse", "--git-dir").then(() => true, () => false);
13505
13879
  if (!valid) await this.run("init", "-q");
13506
- mkdirSync9(join10(this.gitDir, "info"), { recursive: true });
13507
- 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");
13508
13882
  this.ready = true;
13509
13883
  } catch (e) {
13510
13884
  log31.debug(`git checkpoints unavailable for ${this.workTree}`, e);
@@ -13613,13 +13987,13 @@ var GitCheckpoints = class {
13613
13987
  }
13614
13988
  /** cwd + any add-dir mounts that live OUTSIDE cwd (in-cwd mounts are already covered by the cwd root). */
13615
13989
  roots() {
13616
- const cwd = resolve3(this.options.workTree);
13990
+ const cwd = resolve4(this.options.workTree);
13617
13991
  const out = [{ workTree: cwd, gitDir: this.options.gitDir }];
13618
13992
  for (const d of this.options.addDirs ?? []) {
13619
- const abs = resolve3(d);
13993
+ const abs = resolve4(d);
13620
13994
  if (abs === cwd || abs.startsWith(cwd + sep3)) continue;
13621
13995
  if (cwd.startsWith(abs + sep3)) continue;
13622
- out.push({ workTree: abs, gitDir: join10(abs, ".agent", "checkpoints.git") });
13996
+ out.push({ workTree: abs, gitDir: join11(abs, ".agent", "checkpoints.git") });
13623
13997
  }
13624
13998
  return out;
13625
13999
  }
@@ -13706,7 +14080,7 @@ var GitCheckpoints = class {
13706
14080
  preToolUse: async (call) => {
13707
14081
  if (!this.snapshotted || !this.repos.length) return;
13708
14082
  for (const rel of mutatedPaths(call)) {
13709
- const abs = resolve3(this.options.workTree, rel);
14083
+ const abs = resolve4(this.options.workTree, rel);
13710
14084
  if (this.forced.has(abs)) continue;
13711
14085
  this.forced.add(abs);
13712
14086
  const i = this.repoIndexFor(abs);
@@ -13762,7 +14136,7 @@ var GitCheckpointsOptions = class {
13762
14136
  /** Real working tree to snapshot (the launch cwd). */
13763
14137
  workTree = process.cwd();
13764
14138
  /** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
13765
- gitDir = join10(process.cwd(), ".agent", "checkpoints.git");
14139
+ gitDir = join11(process.cwd(), ".agent", "checkpoints.git");
13766
14140
  /** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
13767
14141
  addDirs = [];
13768
14142
  /** Conversation id → per-session restore-point ref. */
@@ -13835,10 +14209,10 @@ function completePath(listDir, ref) {
13835
14209
 
13836
14210
  // cli/lineEditor.ts
13837
14211
  import { emitKeypressEvents } from "readline";
13838
- import { spawnSync as spawnSync4 } from "child_process";
14212
+ import { spawnSync as spawnSync5 } from "child_process";
13839
14213
  import { writeFileSync as writeFileSync7, readFileSync as readFileSync9, unlinkSync as unlinkSync2 } from "fs";
13840
14214
  import { tmpdir as tmpdir2 } from "os";
13841
- import { join as join11 } from "path";
14215
+ import { join as join12 } from "path";
13842
14216
 
13843
14217
  // cli/bidi.ts
13844
14218
  var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
@@ -14335,6 +14709,7 @@ function releaseKeyInput() {
14335
14709
  }
14336
14710
 
14337
14711
  // cli/lineEditor.ts
14712
+ init_platform();
14338
14713
  var cpWidth = (cp) => {
14339
14714
  if (cp === 8205 || cp === 65039 || cp >= 768 && cp <= 879) return 0;
14340
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;
@@ -15023,14 +15398,15 @@ function createLineEditor(out, input = keyInput) {
15023
15398
  curRow = cursorRow;
15024
15399
  }
15025
15400
  function externalEdit(s) {
15026
- const spec = process.env.VISUAL || process.env.EDITOR || "vi";
15401
+ const spec = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
15027
15402
  const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
15028
- const file = join11(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
15403
+ const file = join12(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
15029
15404
  try {
15030
15405
  writeFileSync7(file, s.buf);
15031
15406
  process.stdin.setRawMode(false);
15032
15407
  out.write("\x1B[?2004l");
15033
- 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 } : {} });
15034
15410
  if (r.status === 0) {
15035
15411
  const text = readFileSync9(file, "utf8").replace(/\n$/, "");
15036
15412
  s.reset();
@@ -15068,10 +15444,10 @@ function createLineEditor(out, input = keyInput) {
15068
15444
  render(s, promptOf(), maxVisible, opts.status);
15069
15445
  };
15070
15446
  process.on("SIGWINCH", onResize);
15071
- return new Promise((resolve5) => {
15447
+ return new Promise((resolve6) => {
15072
15448
  activeAbort = () => {
15073
15449
  finish();
15074
- resolve5(null);
15450
+ resolve6(null);
15075
15451
  };
15076
15452
  const redraw = () => render(s, promptOf(), maxVisible, opts.status);
15077
15453
  let lastStatus = opts.status?.() ?? "";
@@ -15151,20 +15527,20 @@ function createLineEditor(out, input = keyInput) {
15151
15527
  if (action === "submit") {
15152
15528
  finish();
15153
15529
  opts.onSubmit?.();
15154
- return resolve5(s.expand());
15530
+ return resolve6(s.expand());
15155
15531
  }
15156
15532
  if (action === "eof") {
15157
15533
  finish();
15158
- return resolve5(null);
15534
+ return resolve6(null);
15159
15535
  }
15160
15536
  if (action === "rewind") {
15161
15537
  finish();
15162
- return resolve5(REWIND);
15538
+ return resolve6(REWIND);
15163
15539
  }
15164
15540
  if (action === "cancel") {
15165
15541
  if (s.buf.length === 0) {
15166
15542
  finish();
15167
- return resolve5(null);
15543
+ return resolve6(null);
15168
15544
  }
15169
15545
  s.reset();
15170
15546
  s.refresh();
@@ -15306,7 +15682,7 @@ function selectMenu(stream, opts) {
15306
15682
  out.write(dim2(` ${filterHint}\u2191/\u2193 select \xB7 ${back}\u23CE choose \xB7 esc cancel${more}`));
15307
15683
  prevLines++;
15308
15684
  };
15309
- return new Promise((resolve5) => {
15685
+ return new Promise((resolve6) => {
15310
15686
  const wasRaw = !!keyInput.isRaw;
15311
15687
  menusOpen++;
15312
15688
  emitKeypressEvents(keyInput);
@@ -15325,7 +15701,7 @@ function selectMenu(stream, opts) {
15325
15701
  } catch {
15326
15702
  }
15327
15703
  }
15328
- resolve5(val);
15704
+ resolve6(val);
15329
15705
  };
15330
15706
  const drillIn = () => {
15331
15707
  const it = items[sel];
@@ -15446,19 +15822,19 @@ function windowAround(count, sel, max) {
15446
15822
  var plainBuf = "";
15447
15823
  var plainEnded = false;
15448
15824
  function readPlainLine() {
15449
- return new Promise((resolve5) => {
15825
+ return new Promise((resolve6) => {
15450
15826
  const emit = () => {
15451
15827
  const nl = plainBuf.indexOf("\n");
15452
15828
  if (nl >= 0) {
15453
15829
  const line = plainBuf.slice(0, nl);
15454
15830
  plainBuf = plainBuf.slice(nl + 1);
15455
- resolve5(line);
15831
+ resolve6(line);
15456
15832
  return true;
15457
15833
  }
15458
15834
  if (plainEnded) {
15459
15835
  const rest = plainBuf;
15460
15836
  plainBuf = "";
15461
- resolve5(rest.length ? rest : null);
15837
+ resolve6(rest.length ? rest : null);
15462
15838
  return true;
15463
15839
  }
15464
15840
  return false;
@@ -15484,11 +15860,15 @@ function readPlainLine() {
15484
15860
  });
15485
15861
  }
15486
15862
 
15863
+ // cli/cli.ts
15864
+ init_platform();
15865
+ init_hostPath();
15866
+
15487
15867
  // cli/osScheduler.ts
15488
- import { spawnSync as spawnSync5 } from "child_process";
15868
+ import { spawnSync as spawnSync6 } from "child_process";
15489
15869
  import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync7 } from "fs";
15490
15870
  import { homedir as homedir8 } from "os";
15491
- import { join as join12 } from "path";
15871
+ import { join as join13 } from "path";
15492
15872
  var log32 = forComponent("os-sched");
15493
15873
  var OsScheduler = class {
15494
15874
  options;
@@ -15496,13 +15876,13 @@ var OsScheduler = class {
15496
15876
  this.options = { ...new OsSchedulerOptions(), ...options };
15497
15877
  }
15498
15878
  get dir() {
15499
- return join12(this.options.home, ".agent", "sched");
15879
+ return join13(this.options.home, ".agent", "sched");
15500
15880
  }
15501
15881
  label(id) {
15502
15882
  return `cc.livx.agentx.sched-${id}`;
15503
15883
  }
15504
15884
  plistPath(id) {
15505
- return join12(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
15885
+ return join13(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
15506
15886
  }
15507
15887
  run(cmd, args, input) {
15508
15888
  return this.options.exec(cmd, args, input);
@@ -15518,11 +15898,11 @@ var OsScheduler = class {
15518
15898
  const script = this.writeScript(spec, oneOff);
15519
15899
  const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
15520
15900
  const meta = { ...spec, created: Date.now(), mechanism };
15521
- 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));
15522
15902
  return mechanism;
15523
15903
  }
15524
15904
  cancel(id) {
15525
- const meta = readJsonFile(join12(this.dir, `${id}.json`), null);
15905
+ const meta = readJsonFile(join13(this.dir, `${id}.json`), null);
15526
15906
  if (!meta) return false;
15527
15907
  try {
15528
15908
  if (this.options.platform === "darwin") {
@@ -15555,7 +15935,7 @@ var OsScheduler = class {
15555
15935
  }
15556
15936
  for (const f of [`${id}.json`, `${id}.sh`]) {
15557
15937
  try {
15558
- unlinkSync3(join12(this.dir, f));
15938
+ unlinkSync3(join13(this.dir, f));
15559
15939
  } catch {
15560
15940
  }
15561
15941
  }
@@ -15563,19 +15943,19 @@ var OsScheduler = class {
15563
15943
  }
15564
15944
  list() {
15565
15945
  if (!existsSync7(this.dir)) return [];
15566
- 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);
15567
15947
  }
15568
15948
  /** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
15569
15949
  writeScript(spec, oneOff) {
15570
- const p = join12(this.dir, `${spec.id}.sh`);
15950
+ const p = join13(this.dir, `${spec.id}.sh`);
15571
15951
  const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
15572
- 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)}
15573
- ` : `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)}
15574
15954
  ` : "";
15575
15955
  writeFileSync8(p, `#!/bin/sh
15576
15956
  # agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
15577
15957
  cd ${q2(spec.cwd)} || exit 1
15578
- ${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
15579
15959
  ${cleanup}`);
15580
15960
  chmodSync(p, 493);
15581
15961
  return p;
@@ -15612,7 +15992,7 @@ ${trigger}
15612
15992
  <key>RunAtLoad</key><false/>
15613
15993
  </dict></plist>
15614
15994
  `;
15615
- mkdirSync10(join12(this.options.home, "Library", "LaunchAgents"), { recursive: true });
15995
+ mkdirSync10(join13(this.options.home, "Library", "LaunchAgents"), { recursive: true });
15616
15996
  writeFileSync8(this.plistPath(spec.id), plist);
15617
15997
  this.run("launchctl", ["load", this.plistPath(spec.id)]);
15618
15998
  return `launchd:${this.label(spec.id)}`;
@@ -15650,7 +16030,7 @@ var OsSchedulerOptions = class {
15650
16030
  /** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
15651
16031
  * and the job id is what atrm needs for cancel. Throws on non-zero exit. */
15652
16032
  exec = (cmd, args, input) => {
15653
- const r = spawnSync5(cmd, args, { input, encoding: "utf8" });
16033
+ const r = spawnSync6(cmd, args, { input, encoding: "utf8" });
15654
16034
  if (r.error) throw r.error;
15655
16035
  if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
15656
16036
  return `${r.stdout ?? ""}${r.stderr ?? ""}`;
@@ -15667,10 +16047,11 @@ import { createServer as createServer2, createConnection } from "net";
15667
16047
  import { spawn as spawn3 } from "child_process";
15668
16048
  import { existsSync as existsSync8, mkdirSync as mkdirSync11, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
15669
16049
  import { homedir as homedir9 } from "os";
15670
- import { join as join13 } from "path";
16050
+ import { join as join14 } from "path";
16051
+ init_platform();
15671
16052
  var log33 = forComponent("remote-trigger");
15672
- var TRIGGER_DIR = () => join13(homedir9(), ".agent", "triggers");
15673
- 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`);
15674
16055
  var TriggerServer = class {
15675
16056
  constructor(onTrigger) {
15676
16057
  this.onTrigger = onTrigger;
@@ -15763,6 +16144,15 @@ function triggerLive(sessionId, req, dir = TRIGGER_DIR(), timeoutMs = 3e3) {
15763
16144
  });
15764
16145
  });
15765
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
+ }
15766
16156
  function makeRemoteTriggerTool(opts) {
15767
16157
  const dir = opts.triggerDir ?? TRIGGER_DIR();
15768
16158
  const doSpawn = opts.spawnFn ?? spawn3;
@@ -15789,9 +16179,10 @@ function makeRemoteTriggerTool(opts) {
15789
16179
  if (live?.ok) return `Delivered to live session ${full} \u2014 it runs there; the user sees it in that terminal.`;
15790
16180
  if (!target.meta.cwd || !existsSync8(target.meta.cwd)) return `Error: session ${full} has no reachable cwd (${target.meta.cwd ?? "unknown"}).`;
15791
16181
  return new Promise((res) => {
15792
- const child = doSpawn("/bin/sh", ["-c", `${opts.agentx} -p "$1" --resume "$2" --yes`, "sh", text, full], {
16182
+ const child = doSpawn(...headlessArgv(opts, text, full), {
15793
16183
  cwd: target.meta.cwd,
15794
- stdio: ["ignore", "pipe", "pipe"]
16184
+ stdio: ["ignore", "pipe", "pipe"],
16185
+ ...IS_WIN ? { windowsHide: true } : {}
15795
16186
  });
15796
16187
  let out = "", errOut = "";
15797
16188
  child.stdout?.on("data", (d) => {
@@ -15856,7 +16247,7 @@ async function checkForUpdate(current) {
15856
16247
  return `Update available: ${current} \u2192 ${latest}. Run \`bun add -g @livx.cc/agentx\` to update.`;
15857
16248
  }
15858
16249
  function installUpdate() {
15859
- const r = spawnSync6("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
16250
+ const r = spawnSync7("bun", ["add", "-g", "@livx.cc/agentx"], { stdio: "inherit" });
15860
16251
  return (r.status ?? 1) === 0;
15861
16252
  }
15862
16253
  var typeaheadOnLine = false;
@@ -16156,11 +16547,11 @@ function loadEnvFile(file) {
16156
16547
  }
16157
16548
  }
16158
16549
  function loadInstallEnv() {
16159
- let dir = dirname5(import.meta.path);
16160
- 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);
16161
16552
  for (const name of [".env", ".env.local"]) {
16162
- loadEnvFile(join14(dir, name));
16163
- loadEnvFile(join14(homedir10(), ".agent", name));
16553
+ loadEnvFile(join15(dir, name));
16554
+ loadEnvFile(join15(homedir10(), ".agent", name));
16164
16555
  }
16165
16556
  }
16166
16557
  async function loadBodifySecrets() {
@@ -16194,6 +16585,31 @@ function apiKeysFromEnv() {
16194
16585
  }
16195
16586
  return keys;
16196
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
+ }
16197
16613
  function makeHost(format = "text", opts) {
16198
16614
  const streamJson = format === "stream-json";
16199
16615
  const cleanStdout = format === "json";
@@ -16802,7 +17218,7 @@ function optsFor(args, ai, cfg = {}, extraTools = []) {
16802
17218
  stream: args.stream,
16803
17219
  host: makeHost(args.outputFormat, { stream: true }),
16804
17220
  planMode: args.plan || cfg.planMode || false,
16805
- 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,
16806
17222
  subagents: args.subagents || cfg.subagents || false,
16807
17223
  maxSteps: args.maxSteps ?? cfg.maxSteps,
16808
17224
  reasoning: args.reasoning ?? cfg.reasoning,
@@ -16884,16 +17300,23 @@ var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg"
16884
17300
  function mentionRefs(line) {
16885
17301
  return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
16886
17302
  }
16887
- 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
+ };
16888
17311
  function readImageParts(cwd, line) {
16889
17312
  const refs = mentionRefs(line);
16890
17313
  const parts = [];
16891
17314
  for (const ref of refs) {
16892
17315
  const mime = IMG_EXT[extname(ref).toLowerCase()];
16893
17316
  if (!mime) continue;
16894
- const abs = ref.startsWith("~/") ? untilde(ref) : resolve4(cwd, ref);
17317
+ const abs = userPath(cwd, ref);
16895
17318
  try {
16896
- statSync7(abs);
17319
+ statSync8(abs);
16897
17320
  } catch {
16898
17321
  continue;
16899
17322
  }
@@ -16905,16 +17328,16 @@ function pastePathClassifier(cwd) {
16905
17328
  return (text) => {
16906
17329
  let t = text.trim();
16907
17330
  if (!t || t.includes("\n")) return null;
16908
- t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
16909
- if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
16910
- 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);
16911
17334
  try {
16912
- if (!statSync7(abs).isFile()) return null;
17335
+ if (!statSync8(abs).isFile()) return null;
16913
17336
  } catch {
16914
17337
  return null;
16915
17338
  }
16916
17339
  const isImg = !!IMG_EXT[extname(abs).toLowerCase()];
16917
- 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 };
16918
17341
  };
16919
17342
  }
16920
17343
  var mcpMentionResolver;
@@ -17038,7 +17461,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
17038
17461
  store.save(session);
17039
17462
  } catch {
17040
17463
  }
17041
- return { ok: false };
17464
+ return { ok: false, error: e };
17042
17465
  } finally {
17043
17466
  spinner.stop();
17044
17467
  spinner.turnStart = 0;
@@ -17089,7 +17512,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
17089
17512
  }
17090
17513
  return { ok, res };
17091
17514
  }
17092
- 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]);
17093
17516
  function startSession(args, store, agent, cwd) {
17094
17517
  if (args.resume || args.cont) {
17095
17518
  const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
@@ -17136,24 +17559,24 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
17136
17559
  `;
17137
17560
  function initInstructions(cwd) {
17138
17561
  for (const f of ["AGENTS.md", "CLAUDE.md"]) {
17139
- if (existsSync9(join14(cwd, f))) {
17562
+ if (existsSync9(join15(cwd, f))) {
17140
17563
  err(yellow(` ${f} already exists \u2014 leaving it as-is
17141
17564
  `));
17142
17565
  return;
17143
17566
  }
17144
17567
  }
17145
- const path = join14(cwd, "AGENTS.md");
17146
- 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)));
17147
17570
  err(green(` created ${path}
17148
17571
  `) + dim(" edit it, then it auto-loads into every run.\n"));
17149
17572
  }
17150
17573
  function persistSetting(cwd, key, value) {
17151
- const path = join14(cwd, ".agent", "settings.json");
17574
+ const path = join15(cwd, ".agent", "settings.json");
17152
17575
  try {
17153
17576
  const obj = existsSync9(path) ? JSON.parse(readFileSync10(path, "utf8")) : {};
17154
17577
  if (obj[key] === value) return;
17155
17578
  obj[key] = value;
17156
- mkdirSync12(dirname5(path), { recursive: true });
17579
+ mkdirSync12(dirname6(path), { recursive: true });
17157
17580
  writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
17158
17581
  } catch (e) {
17159
17582
  err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
@@ -17186,7 +17609,7 @@ function installCancelGuards(mounted) {
17186
17609
  });
17187
17610
  }
17188
17611
  async function repl(args, ai, cfg, cwd) {
17189
- const oauth = new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") });
17612
+ const oauth = new McpOAuth({ storePath: join15(cwd, ".agent", "mcp-auth.json") });
17190
17613
  const mounted = await mountMcp(cfg, oauth);
17191
17614
  let mcpToolNames = [];
17192
17615
  const initialMcpTools = mcpAgentTools(mounted);
@@ -17209,7 +17632,7 @@ async function repl(args, ai, cfg, cwd) {
17209
17632
  },
17210
17633
  tickMs: 15e3
17211
17634
  });
17212
- 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";
17213
17636
  const osSched = new OsScheduler({ agentx: agentxInvocation() });
17214
17637
  const osBackend = osSched.available() ? {
17215
17638
  get sessionId() {
@@ -17232,7 +17655,7 @@ async function repl(args, ai, cfg, cwd) {
17232
17655
  scheduleQueue.push(req.prompt);
17233
17656
  }
17234
17657
  });
17235
- 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 })];
17236
17659
  const duplex = args.duplex;
17237
17660
  let dx;
17238
17661
  let voiceIO;
@@ -17437,6 +17860,9 @@ async function repl(args, ai, cfg, cwd) {
17437
17860
  return `Rewound ${n} task checkpoint(s): restored ${r.restored} file(s), deleted ${r.deleted}.`;
17438
17861
  }
17439
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
+ `));
17440
17866
  dx = new DuplexAgent({
17441
17867
  ai,
17442
17868
  ...instinct ? { bargeIntent: (t) => instinct.bargeIntent(t) } : {},
@@ -17444,11 +17870,10 @@ async function repl(args, ai, cfg, cwd) {
17444
17870
  fs: agent.options.fs,
17445
17871
  memoryDir: agent.options.memoryDir,
17446
17872
  memoryUserDir: agent.options.memoryUserDir,
17447
- ...args.voiceModel ?? cfg.reflexModel ? { reflexModel: resolveModelOrNewest(args.voiceModel ?? cfg.reflexModel) } : {},
17873
+ ...tierModels,
17448
17874
  actModel: agent.options.model,
17449
17875
  actOptions: workerOptions,
17450
17876
  providerOptionsFor: (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)),
17451
- ...(args.thinkModel ?? cfg.thinkModel) !== void 0 ? { thinkModel: (args.thinkModel ?? cfg.thinkModel) === false ? false : resolveModelOrNewest(String(args.thinkModel ?? cfg.thinkModel)) } : {},
17452
17877
  host,
17453
17878
  ...args.voice ? { voiceStyle: "conversational", progressUpdates: true, askRelay: true, emotionTags: emotionsOn, spokenBudgetChars: 350 } : {},
17454
17879
  // voice: progress asides + worker questions relayed through the conversation; emotion tags taught only with a TTS that speaks them
@@ -17462,7 +17887,7 @@ async function repl(args, ai, cfg, cwd) {
17462
17887
  quickLook: {
17463
17888
  branch: () => {
17464
17889
  try {
17465
- const head = readFileSync10(join14(cwd, ".git", "HEAD"), "utf8").trim();
17890
+ const head = readFileSync10(join15(cwd, ".git", "HEAD"), "utf8").trim();
17466
17891
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
17467
17892
  } catch {
17468
17893
  return "not a git repository";
@@ -17596,7 +18021,7 @@ async function repl(args, ai, cfg, cwd) {
17596
18021
  const pendingImages = [];
17597
18022
  const bangContext = [];
17598
18023
  const grabClipboardAttachment = () => {
17599
- const dir = join14(store.dir, `${session.meta.id}.images`);
18024
+ const dir = join15(store.dir, `${session.meta.id}.images`);
17600
18025
  try {
17601
18026
  mkdirSync12(dir, { recursive: true });
17602
18027
  } catch (e) {
@@ -17654,7 +18079,7 @@ async function repl(args, ai, cfg, cwd) {
17654
18079
  err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
17655
18080
  `));
17656
18081
  }
17657
- 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 });
17658
18083
  const cpHooks = checkpoints.hooks?.();
17659
18084
  if (cpHooks) {
17660
18085
  agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
@@ -17706,11 +18131,11 @@ ${lines.join("\n")}
17706
18131
  Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
17707
18132
  </system-reminder>`;
17708
18133
  };
17709
- const histPath = join14(cwd, ".agent", "history");
18134
+ const histPath = join15(cwd, ".agent", "history");
17710
18135
  const history = existsSync9(histPath) ? readFileSync10(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
17711
18136
  const remember2 = (line) => {
17712
18137
  try {
17713
- mkdirSync12(join14(cwd, ".agent"), { recursive: true });
18138
+ mkdirSync12(join15(cwd, ".agent"), { recursive: true });
17714
18139
  appendFileSync(histPath, line + "\n");
17715
18140
  } catch (e) {
17716
18141
  log34.debug("history write failed", e);
@@ -18013,8 +18438,8 @@ ${task}`;
18013
18438
  const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
18014
18439
  if (wasRaw) process.stdin.setRawMode(false);
18015
18440
  try {
18016
- const { spawnSync: spawnSync7 } = await import("child_process");
18017
- 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"] });
18018
18443
  if (r.error) err(text);
18019
18444
  } finally {
18020
18445
  if (wasRaw) process.stdin.setRawMode(true);
@@ -18034,8 +18459,7 @@ ${task}`;
18034
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)");
18035
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)");
18036
18461
  {
18037
- const { spawnSync: spawnSync7 } = await import("child_process");
18038
- const has = (cmd) => spawnSync7("which", [cmd]).status === 0;
18462
+ const has = hasCommand;
18039
18463
  if (!VoiceIO.available()) warn("voice: keys missing (SONIOX_API_KEY / CARTESIA_API_KEY / CARTESIA_VOICE_ID) \u2014 voice disabled");
18040
18464
  else {
18041
18465
  ok("voice: keys present");
@@ -18462,12 +18886,13 @@ ${task}`;
18462
18886
  return;
18463
18887
  }
18464
18888
  }
18465
- const ed = process.env.VISUAL || process.env.EDITOR || "vi";
18889
+ const ed = process.env.VISUAL || process.env.EDITOR || DEFAULT_EDITOR;
18466
18890
  const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
18467
18891
  if (wasRaw) process.stdin.setRawMode(false);
18468
18892
  try {
18469
- const { spawnSync: spawnSync7 } = await import("child_process");
18470
- 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 } : {} });
18471
18896
  } finally {
18472
18897
  if (wasRaw) process.stdin.setRawMode(true);
18473
18898
  }
@@ -18817,10 +19242,10 @@ ${task}`;
18817
19242
  return;
18818
19243
  }
18819
19244
  const md = exportMarkdown(session.meta, shown);
18820
- const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
18821
- 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);
18822
19247
  try {
18823
- mkdirSync12(dirname5(path), { recursive: true });
19248
+ mkdirSync12(dirname6(path), { recursive: true });
18824
19249
  writeFileSync9(path, md);
18825
19250
  err(green(` \u2713 exported \u2192 ${path}
18826
19251
  `) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
@@ -18903,7 +19328,7 @@ ${task}`;
18903
19328
  });
18904
19329
  const listDir = (absDir) => {
18905
19330
  try {
18906
- 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() }));
18907
19332
  } catch (e) {
18908
19333
  log34.debug("completion readdir failed", absDir, e);
18909
19334
  return null;
@@ -19088,7 +19513,7 @@ ${out}
19088
19513
  return false;
19089
19514
  }
19090
19515
  const fakeVoice = process.env.AGENTX_VOICE_FAKE ? fakeVoiceParts(process.env.AGENTX_VOICE_FAKE) : null;
19091
- voiceDiag = new JsonlDiagSink(join14(store.dir, `${session.meta.id}.voice.jsonl`));
19516
+ voiceDiag = new JsonlDiagSink(join15(store.dir, `${session.meta.id}.voice.jsonl`));
19092
19517
  voiceIO = new VoiceIO({
19093
19518
  ...fakeVoice ?? {},
19094
19519
  onDiag: (ev) => voiceDiag?.push(ev),
@@ -19489,9 +19914,17 @@ ${out}
19489
19914
  disposeClaudeCodeSessions();
19490
19915
  await closeMcp(mounted);
19491
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
+ }
19492
19925
  async function duplexServe(args, ai, cfg, cwd) {
19493
19926
  const out = (o) => process.stdout.write(JSON.stringify(o) + "\n");
19494
- 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") }));
19495
19928
  installCancelGuards(mounted);
19496
19929
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted, { quiet: true }), mounted.map((m) => m.name));
19497
19930
  const { host: _h, stream: _s, signal: _g, providerOptions: _po, ...wo } = agent.options;
@@ -19511,16 +19944,18 @@ async function duplexServe(args, ai, cfg, cwd) {
19511
19944
  };
19512
19945
  let persist = () => {
19513
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
+ `));
19514
19950
  const dx = new DuplexAgent({
19515
19951
  ai,
19516
19952
  fs: agent.options.fs,
19517
19953
  memoryDir: agent.options.memoryDir,
19518
19954
  memoryUserDir: agent.options.memoryUserDir,
19519
- ...args.voiceModel ?? cfg.reflexModel ? { reflexModel: resolveModelOrNewest(args.voiceModel ?? cfg.reflexModel) } : {},
19955
+ ...tierModels,
19520
19956
  actModel: agent.options.model,
19521
19957
  actOptions: { ...wo, planMode: false },
19522
19958
  providerOptionsFor: (m) => providerOptionsFor(m, cwd, cfg.mcpServers, mounted.map((x) => x.name)),
19523
- ...(args.thinkModel ?? cfg.thinkModel) !== void 0 ? { thinkModel: (args.thinkModel ?? cfg.thinkModel) === false ? false : resolveModelOrNewest(String(args.thinkModel ?? cfg.thinkModel)) } : {},
19524
19959
  host,
19525
19960
  // --voice: the CLI voice register (progress asides, spoken budget). No emotion tags: the host's TTS may not speak them.
19526
19961
  ...args.voice ? { voiceStyle: "conversational", progressUpdates: true, spokenBudgetChars: 350 } : {},
@@ -19538,7 +19973,7 @@ async function duplexServe(args, ai, cfg, cwd) {
19538
19973
  log34.warn("duplex session save failed", e);
19539
19974
  }
19540
19975
  };
19541
- 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 } : {} });
19542
19977
  const done = new Promise((resolveDone) => {
19543
19978
  const rl = createInterface({ input: process.stdin });
19544
19979
  rl.on("line", (line) => {
@@ -19558,7 +19993,10 @@ async function duplexServe(args, ai, cfg, cwd) {
19558
19993
  if (delivery || reply) dx.parkCutDelivery(delivery, !!reply, heard);
19559
19994
  const note = reply && reply.full.length - reply.heard.length > 40 ? interruptionNote(reply) : "";
19560
19995
  const id = m.id ? String(m.id) : void 0;
19561
- 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) => {
19562
20000
  log34.error("duplex turn failed", e);
19563
20001
  out({ type: "turn_done", id, finishReason: "error", error: String(e?.message ?? e) });
19564
20002
  });
@@ -19613,7 +20051,7 @@ async function main() {
19613
20051
  let worktreeCleanup;
19614
20052
  if (args.worktree) {
19615
20053
  const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
19616
- const session = enterWorktree2(args.worktree, resolve4(args.cwd ?? process.cwd()));
20054
+ const session = enterWorktree2(args.worktree, resolve5(args.cwd ?? process.cwd()));
19617
20055
  err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
19618
20056
  `));
19619
20057
  worktreeCleanup = () => {
@@ -19623,8 +20061,8 @@ async function main() {
19623
20061
  `));
19624
20062
  };
19625
20063
  }
19626
- const cwd = resolve4(args.cwd ?? process.cwd());
19627
- 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));
19628
20066
  if (!args.autoRoute && !args.model && (cfg.model === "auto" || !cfg.model && await jevAvailable())) {
19629
20067
  args.autoRoute = true;
19630
20068
  args.model = autoModels().standard;
@@ -19656,7 +20094,8 @@ async function main() {
19656
20094
  loadInstallEnv();
19657
20095
  await loadBodifySecrets();
19658
20096
  const apiKeys = { ...cfg.apiKeys, ...apiKeysFromEnv() };
19659
- if (!Object.keys(apiKeys).length) {
20097
+ const keyless = String(args.model ?? cfg.model ?? "").startsWith("claude-code/");
20098
+ if (!Object.keys(apiKeys).length && !keyless) {
19660
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."));
19661
20100
  process.exit(1);
19662
20101
  }
@@ -19673,7 +20112,7 @@ async function main() {
19673
20112
  }
19674
20113
  });
19675
20114
  if (args.task) {
19676
- 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") }));
19677
20116
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted), mounted.map((m) => m.name));
19678
20117
  const store = new SessionStore(cwd);
19679
20118
  const session = startSession(args, store, agent, cwd);
@@ -19711,6 +20150,8 @@ export {
19711
20150
  cacheMultipliers,
19712
20151
  condenseReplay,
19713
20152
  costOf,
20153
+ duplexError,
20154
+ duplexModels,
19714
20155
  estimateTranscriptTokens,
19715
20156
  expandMentions,
19716
20157
  exportMarkdown,
@@ -19722,6 +20163,7 @@ export {
19722
20163
  makeHost,
19723
20164
  mcpMentionResolver,
19724
20165
  mentionRefs,
20166
+ modelUsable,
19725
20167
  parseArgs,
19726
20168
  pastePathClassifier,
19727
20169
  readImageParts,