@livx.cc/agentx 0.94.38 → 0.95.2

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
@@ -1059,6 +1059,172 @@ var init_tools = __esm({
1059
1059
  }
1060
1060
  });
1061
1061
 
1062
+ // src/worktree.ts
1063
+ var worktree_exports = {};
1064
+ __export(worktree_exports, {
1065
+ cleanupWorktree: () => cleanupWorktree,
1066
+ enterWorktree: () => enterWorktree,
1067
+ exitWorktree: () => exitWorktree,
1068
+ findGitRoot: () => findGitRoot,
1069
+ getOrCreateWorktree: () => getOrCreateWorktree,
1070
+ getWorktreeSession: () => getWorktreeSession,
1071
+ validateWorktreeSlug: () => validateWorktreeSlug,
1072
+ worktreeBranchName: () => worktreeBranchName
1073
+ });
1074
+ import { spawnSync } from "child_process";
1075
+ import { symlinkSync, mkdirSync, statSync as statSync2, readFileSync, rmSync } from "fs";
1076
+ import { join as join2 } from "path";
1077
+ function validateWorktreeSlug(slug2) {
1078
+ if (!slug2) throw new Error("worktree slug must not be empty");
1079
+ if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
1080
+ for (const seg of slug2.split("/")) {
1081
+ if (seg === "." || seg === "..") throw new Error(`worktree slug must not contain "." or ".." segments`);
1082
+ if (!VALID_SEGMENT.test(seg)) throw new Error(`worktree slug segment "${seg}" contains invalid characters (allowed: a-z A-Z 0-9 . _ -)`);
1083
+ }
1084
+ }
1085
+ function flattenSlug(slug2) {
1086
+ return slug2.replaceAll("/", "+");
1087
+ }
1088
+ function worktreeBranchName(slug2) {
1089
+ return `worktree-${flattenSlug(slug2)}`;
1090
+ }
1091
+ function worktreesDir(repoRoot) {
1092
+ return join2(repoRoot, ".claude", "worktrees");
1093
+ }
1094
+ function worktreePathFor(repoRoot, slug2) {
1095
+ return join2(worktreesDir(repoRoot), flattenSlug(slug2));
1096
+ }
1097
+ function git(args, cwd) {
1098
+ const r = spawnSync("git", args, {
1099
+ cwd,
1100
+ stdio: ["ignore", "pipe", "pipe"],
1101
+ env: { ...process.env, ...GIT_NO_PROMPT_ENV }
1102
+ });
1103
+ return { ok: r.status === 0, stdout: (r.stdout ?? "").toString().trim(), stderr: (r.stderr ?? "").toString().trim() };
1104
+ }
1105
+ function findGitRoot(from) {
1106
+ const r = git(["rev-parse", "--show-toplevel"], from);
1107
+ return r.ok ? r.stdout : null;
1108
+ }
1109
+ function readWorktreeHead(worktreePath) {
1110
+ try {
1111
+ const gitFile = readFileSync(join2(worktreePath, ".git"), "utf-8").trim();
1112
+ const m = gitFile.match(/^gitdir:\s*(.+)$/);
1113
+ if (!m) return null;
1114
+ const headPath = join2(m[1], "HEAD");
1115
+ const head = readFileSync(headPath, "utf-8").trim();
1116
+ const refMatch = head.match(/^ref:\s*(.+)$/);
1117
+ if (!refMatch) return head.length === 40 ? head : null;
1118
+ const refPath = join2(m[1], "..", refMatch[1]);
1119
+ try {
1120
+ return readFileSync(refPath, "utf-8").trim();
1121
+ } catch {
1122
+ }
1123
+ const r = git(["rev-parse", "HEAD"], worktreePath);
1124
+ return r.ok ? r.stdout : null;
1125
+ } catch {
1126
+ return null;
1127
+ }
1128
+ }
1129
+ function getOrCreateWorktree(repoRoot, slug2) {
1130
+ validateWorktreeSlug(slug2);
1131
+ const worktreePath = worktreePathFor(repoRoot, slug2);
1132
+ const branch = worktreeBranchName(slug2);
1133
+ const existingHead = readWorktreeHead(worktreePath);
1134
+ if (existingHead) return { worktreePath, branch, headCommit: existingHead, existed: true };
1135
+ mkdirSync(worktreesDir(repoRoot), { recursive: true });
1136
+ const defaultBranch = git(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoRoot);
1137
+ let baseBranch = defaultBranch.ok ? defaultBranch.stdout.replace(/^origin\//, "") : "HEAD";
1138
+ const originRef = `origin/${baseBranch}`;
1139
+ const hasOrigin = git(["rev-parse", "--verify", originRef], repoRoot);
1140
+ if (!hasOrigin.ok) {
1141
+ const fetched = git(["fetch", "origin", baseBranch], repoRoot);
1142
+ if (!fetched.ok) baseBranch = "HEAD";
1143
+ }
1144
+ const base = baseBranch === "HEAD" ? "HEAD" : originRef;
1145
+ const r = git(["worktree", "add", "-B", branch, worktreePath, base], repoRoot);
1146
+ if (!r.ok) throw new Error(`failed to create worktree: ${r.stderr}`);
1147
+ const headSha = git(["rev-parse", "HEAD"], worktreePath);
1148
+ symlinkDirs(repoRoot, worktreePath, ["node_modules", ".bun"]);
1149
+ return { worktreePath, branch, headCommit: headSha.stdout, existed: false, baseBranch };
1150
+ }
1151
+ function symlinkDirs(repoRoot, worktreePath, dirs) {
1152
+ for (const dir of dirs) {
1153
+ const src = join2(repoRoot, dir);
1154
+ const dst = join2(worktreePath, dir);
1155
+ try {
1156
+ statSync2(src);
1157
+ } catch {
1158
+ continue;
1159
+ }
1160
+ try {
1161
+ symlinkSync(src, dst, "dir");
1162
+ } catch (e) {
1163
+ if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
1164
+ }
1165
+ }
1166
+ }
1167
+ function cleanupWorktree(repoRoot, slug2, opts) {
1168
+ validateWorktreeSlug(slug2);
1169
+ const worktreePath = worktreePathFor(repoRoot, slug2);
1170
+ const branch = worktreeBranchName(slug2);
1171
+ try {
1172
+ statSync2(worktreePath);
1173
+ } catch {
1174
+ return { removed: false, reason: "not found" };
1175
+ }
1176
+ if (!opts?.force) {
1177
+ const status = git(["status", "--porcelain", "-uno"], worktreePath);
1178
+ if (status.ok && status.stdout) return { removed: false, reason: "uncommitted changes" };
1179
+ }
1180
+ for (const dir of ["node_modules", ".bun"]) {
1181
+ try {
1182
+ rmSync(join2(worktreePath, dir));
1183
+ } catch {
1184
+ }
1185
+ }
1186
+ const rm = git(["worktree", "remove", "--force", worktreePath], repoRoot);
1187
+ if (!rm.ok) return { removed: false, reason: rm.stderr };
1188
+ git(["branch", "-D", branch], repoRoot);
1189
+ return { removed: true };
1190
+ }
1191
+ function getWorktreeSession() {
1192
+ return _session;
1193
+ }
1194
+ function enterWorktree(slug2, cwd) {
1195
+ const repoRoot = findGitRoot(cwd);
1196
+ if (!repoRoot) throw new Error("--worktree requires a git repository");
1197
+ const info = getOrCreateWorktree(repoRoot, slug2);
1198
+ process.chdir(info.worktreePath);
1199
+ _session = {
1200
+ originalCwd: cwd,
1201
+ worktreePath: info.worktreePath,
1202
+ slug: slug2,
1203
+ branch: info.branch,
1204
+ headCommit: info.headCommit
1205
+ };
1206
+ return _session;
1207
+ }
1208
+ function exitWorktree() {
1209
+ if (!_session) return null;
1210
+ const repoRoot = findGitRoot(_session.originalCwd);
1211
+ if (!repoRoot) return null;
1212
+ process.chdir(_session.originalCwd);
1213
+ const result = cleanupWorktree(repoRoot, _session.slug);
1214
+ _session = null;
1215
+ return result;
1216
+ }
1217
+ var VALID_SEGMENT, MAX_SLUG_LENGTH, GIT_NO_PROMPT_ENV, _session;
1218
+ var init_worktree = __esm({
1219
+ "src/worktree.ts"() {
1220
+ "use strict";
1221
+ VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
1222
+ MAX_SLUG_LENGTH = 64;
1223
+ GIT_NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
1224
+ _session = null;
1225
+ }
1226
+ });
1227
+
1062
1228
  // src/NodeDiskFilesystem.ts
1063
1229
  var NodeDiskFilesystem_exports = {};
1064
1230
  __export(NodeDiskFilesystem_exports, {
@@ -1589,7 +1755,7 @@ function makeRealShellTool(options) {
1589
1755
  proc.stderr?.on("data", collect);
1590
1756
  proc.on("error", (err2) => {
1591
1757
  if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
1592
- log12.debug("shell spawn error", err2);
1758
+ log13.debug("shell spawn error", err2);
1593
1759
  finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean(out) : ""}`);
1594
1760
  });
1595
1761
  proc.on("close", (code) => {
@@ -1651,7 +1817,7 @@ ${clean(out) || "(no output yet)"}`;
1651
1817
  }
1652
1818
  ];
1653
1819
  }
1654
- var log12, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
1820
+ var log13, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
1655
1821
  var init_tools_shell = __esm({
1656
1822
  "src/tools.shell.ts"() {
1657
1823
  "use strict";
@@ -1659,7 +1825,7 @@ var init_tools_shell = __esm({
1659
1825
  init_redact();
1660
1826
  init_logging();
1661
1827
  init_shell_sandbox();
1662
- log12 = forComponent("shell");
1828
+ log13 = forComponent("shell");
1663
1829
  clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
1664
1830
  DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
1665
1831
  SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
@@ -1741,7 +1907,7 @@ var init_tools_shell = __esm({
1741
1907
 
1742
1908
  // cli/cli.ts
1743
1909
  import { createInterface } from "readline/promises";
1744
- import { existsSync as existsSync10, readFileSync as readFileSync6, appendFileSync, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8, readdirSync as readdirSync4, statSync as statSync3, unlinkSync as unlinkSync5 } from "fs";
1910
+ import { existsSync as existsSync10, readFileSync as readFileSync7, appendFileSync, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, readdirSync as readdirSync4, statSync as statSync4, unlinkSync as unlinkSync5 } from "fs";
1745
1911
  import { homedir as homedir9, tmpdir as tmpdir3 } from "os";
1746
1912
 
1747
1913
  // cli/clipboard.ts
@@ -1809,7 +1975,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
1809
1975
  }
1810
1976
 
1811
1977
  // cli/cli.ts
1812
- import { join as join12, resolve as resolve3, basename as basename2, extname, dirname as dirname4 } from "path";
1978
+ import { join as join14, resolve as resolve3, basename as basename2, extname, dirname as dirname4 } from "path";
1813
1979
  import { AIClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions } from "ai.libx.js";
1814
1980
 
1815
1981
  // src/llm.ts
@@ -2456,7 +2622,13 @@ ${sections.join("\n\n---\n\n")}`;
2456
2622
 
2457
2623
  // src/subagent.ts
2458
2624
  init_tools();
2625
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2626
+ import { join as join4 } from "path";
2459
2627
  init_OverlayFilesystem();
2628
+ init_worktree();
2629
+ init_NodeDiskFilesystem();
2630
+ init_logging();
2631
+ var log3 = forComponent("subagent");
2460
2632
  async function boundedPool(items, limit, fn) {
2461
2633
  const out = new Array(items.length);
2462
2634
  let next = 0;
@@ -2469,13 +2641,86 @@ async function boundedPool(items, limit, fn) {
2469
2641
  await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
2470
2642
  return out;
2471
2643
  }
2644
+ function sanitizeSlug(raw) {
2645
+ return raw.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").slice(0, 40);
2646
+ }
2647
+ function createWorktreeFs(cwd, label, index) {
2648
+ const repoRoot = findGitRoot(cwd);
2649
+ if (!repoRoot) return 'isolation: "worktree" requires a git repository';
2650
+ const slug2 = `sub-${index}-${sanitizeSlug(label)}`;
2651
+ try {
2652
+ const info = getOrCreateWorktree(repoRoot, slug2);
2653
+ return { repoRoot, slug: slug2, worktreePath: info.worktreePath, branch: info.branch };
2654
+ } catch (e) {
2655
+ return `worktree creation failed: ${e instanceof Error ? e.message : String(e)}`;
2656
+ }
2657
+ }
2658
+ function releaseWorktree(handle) {
2659
+ return cleanupWorktree(handle.repoRoot, handle.slug, { force: true });
2660
+ }
2661
+ function worktreePromptPrefix(branch) {
2662
+ return `You are running in an isolated git worktree on branch "${branch}". When you finish making changes, commit them with a clear message before replying with your summary. Your worktree will be removed after you finish \u2014 only committed work survives (on the branch).
2663
+
2664
+ `;
2665
+ }
2666
+ var traceSeq = 0;
2667
+ function traceFileFor(dir, label, depth) {
2668
+ try {
2669
+ mkdirSync2(dir, { recursive: true });
2670
+ const id = `${Date.now().toString(36)}-${(traceSeq++).toString(36)}`;
2671
+ return join4(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
2672
+ } catch (e) {
2673
+ log3.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
2674
+ return void 0;
2675
+ }
2676
+ }
2677
+ function flushTrace(file, meta, messages, status) {
2678
+ try {
2679
+ writeFileSync2(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
2680
+ } catch (e) {
2681
+ log3.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
2682
+ }
2683
+ }
2684
+ function installTrace(child, file, meta) {
2685
+ const prev = child.options.host;
2686
+ child.options.host = {
2687
+ ...prev,
2688
+ notify: (e) => {
2689
+ if (e.kind === "turn_start") flushTrace(file, meta, child.transcript, "running");
2690
+ prev?.notify?.(e);
2691
+ }
2692
+ };
2693
+ }
2694
+ function abortHint(label, res, file) {
2695
+ const last = (res.text || "").trim().slice(0, 500);
2696
+ const lines = [
2697
+ `[sub-task '${label}' did NOT finish \u2014 ${res.finishReason} after ${res.steps} step(s). Treat its work as INCOMPLETE.]`,
2698
+ last ? `Progress so far: ${last}` : "It produced no summary before stopping."
2699
+ ];
2700
+ if (file) lines.push(`Full transcript (every step it took): ${file} \u2014 Read it to see what was actually done.`);
2701
+ lines.push("Decide: ignore it, re-delegate a fresh Task that continues from the trace, or start over. Do NOT assume the sub-task completed.");
2702
+ return lines.join("\n");
2703
+ }
2704
+ async function runChildTraced(args) {
2705
+ const { opts, label, agentType, prompt } = args;
2706
+ if (args.signal) args.childOpts.signal = args.signal;
2707
+ const file = opts.traceDir ? traceFileFor(opts.traceDir, label, args.childDepth) : void 0;
2708
+ const meta = { label, agentType, prompt };
2709
+ const child = new Agent(args.childOpts);
2710
+ if (file) installTrace(child, file, meta);
2711
+ const res = await child.run(prompt);
2712
+ if (file) flushTrace(file, meta, child.transcript, res.finishReason);
2713
+ const result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
2714
+ await opts.hooks?.onSubagentStop?.(result, { label, agentType });
2715
+ return { res, result };
2716
+ }
2472
2717
  function childOptionsFor(opts, fs, depth, maxDepth, agentType) {
2473
2718
  let def;
2474
2719
  if (agentType) {
2475
2720
  def = (opts.agents ?? []).find((a) => a.name === agentType);
2476
2721
  if (!def) return `no subagent type '${agentType}'. Available: ${(opts.agents ?? []).map((a) => a.name).join(", ") || "(none defined)"}`;
2477
2722
  }
2478
- const childOpts = { ai: opts.ai, fs, model: def?.model ?? opts.model, subagents: true, depth: depth + 1, maxDepth };
2723
+ const childOpts = { ai: opts.ai, fs, model: def?.model ?? opts.model, subagents: true, depth: depth + 1, maxDepth, traceDir: opts.traceDir };
2479
2724
  if (opts.maxSteps != null) childOpts.maxSteps = opts.maxSteps;
2480
2725
  if (def?.systemPrompt) childOpts.systemPrompt = def.systemPrompt;
2481
2726
  if (def?.tools?.length) {
@@ -2492,7 +2737,7 @@ function makeTaskTool(opts) {
2492
2737
  const maxDepth = opts.maxDepth ?? 2;
2493
2738
  return {
2494
2739
  name: "Task",
2495
- description: "Delegate a self-contained sub-task to a child agent over the same filesystem. It runs autonomously with its own step budget and returns a concise summary \u2014 use to isolate context-heavy work (broad search, a scoped refactor). Provide a short `description` and a full `prompt`. Set `background: true` to run it detached (returns a job id to poll with JobOutput while you keep working); its file edits are overlay-isolated and commit when it finishes.",
2740
+ description: 'Delegate a self-contained sub-task to a child agent over the same filesystem. It runs autonomously with its own step budget and returns a concise summary \u2014 use to isolate context-heavy work (broad search, a scoped refactor). Provide a short `description` and a full `prompt`. If it is interrupted or fails, the result is a STATUS HINT with a pointer to its full transcript (Read it to recover what it did, then decide whether to continue or restart) \u2014 do not assume an incomplete sub-task succeeded. Set `background: true` to run it detached (returns a job id to poll with JobOutput while you keep working); its file edits are overlay-isolated and commit when it finishes. Set `isolation: "worktree"` to run in a real git worktree (needed when the child uses real shell commands that must see/modify actual files) \u2014 slower (~200ms setup), auto-cleaned if no changes.',
2496
2741
  parameters: {
2497
2742
  type: "object",
2498
2743
  required: ["description", "prompt"],
@@ -2500,36 +2745,83 @@ function makeTaskTool(opts) {
2500
2745
  description: { type: "string", description: "a short (3-5 word) label for the sub-task" },
2501
2746
  prompt: { type: "string", description: "the full instructions the child agent should carry out" },
2502
2747
  agentType: { type: "string", description: "optional named subagent type (its persona, model, and scoped tools) \u2014 see the catalog" },
2503
- background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" }
2748
+ background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" },
2749
+ isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode: "overlay" (default, VFS layer) or "worktree" (real git worktree for shell-heavy work)' }
2504
2750
  }
2505
2751
  },
2506
- async run({ description, prompt, agentType, background }, ctx) {
2752
+ async run({ description, prompt, agentType, background, isolation }, ctx) {
2507
2753
  if (depth >= maxDepth) {
2508
2754
  return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
2509
2755
  }
2510
2756
  const label = String(description ?? agentType ?? "sub-task");
2757
+ const useWorktree = isolation === "worktree";
2511
2758
  if (background && ctx.jobs) {
2512
2759
  const id = ctx.jobs.start(async ({ signal }) => {
2513
- const overlay = new OverlayFilesystem(opts.fs);
2514
- const childOpts2 = childOptionsFor(opts, overlay, depth, maxDepth, agentType);
2760
+ let fs2;
2761
+ let commit;
2762
+ let wtHandle2;
2763
+ if (useWorktree) {
2764
+ const h = createWorktreeFs(process.cwd(), label, 0);
2765
+ if (typeof h === "string") throw new Error(h);
2766
+ wtHandle2 = h;
2767
+ fs2 = new NodeDiskFilesystem(h.worktreePath);
2768
+ commit = async () => {
2769
+ };
2770
+ } else {
2771
+ const overlay = new OverlayFilesystem(opts.fs);
2772
+ fs2 = overlay;
2773
+ commit = () => overlay.commit();
2774
+ }
2775
+ const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType);
2515
2776
  if (typeof childOpts2 === "string") throw new Error(childOpts2);
2516
- childOpts2.signal = signal;
2517
- const res2 = await new Agent(childOpts2).run(String(prompt ?? ""));
2518
- if (signal.aborted) return "[killed before commit]";
2519
- await overlay.commit();
2520
- const summary2 = res2.text || `(child '${label}' finished with no summary; finishReason=${res2.finishReason})`;
2521
- await opts.hooks?.onSubagentStop?.(summary2, { label, agentType });
2522
- return summary2;
2777
+ const childPrompt = wtHandle2 ? worktreePromptPrefix(wtHandle2.branch) + String(prompt ?? "") : String(prompt ?? "");
2778
+ const { res, result } = await runChildTraced({ opts, childOpts: childOpts2, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal });
2779
+ if (res.finishReason === "aborted" || signal.aborted) {
2780
+ wtHandle2 && releaseWorktree(wtHandle2);
2781
+ return result;
2782
+ }
2783
+ await commit();
2784
+ let summary = result;
2785
+ if (wtHandle2) {
2786
+ const r = releaseWorktree(wtHandle2);
2787
+ if (!r.removed) summary += `
2788
+
2789
+ [worktree removal failed: ${r.reason}]`;
2790
+ }
2791
+ return summary;
2523
2792
  }, { kind: "agent", label });
2524
2793
  return `Started background sub-task ${id} ('${label}') \u2014 poll with JobOutput({id:"${id}"}).`;
2525
2794
  }
2526
- const childOpts = childOptionsFor(opts, opts.fs, depth, maxDepth, agentType);
2527
- if (typeof childOpts === "string") return `Error: ${childOpts}`;
2528
- const child = new Agent(childOpts);
2529
- const res = await child.run(String(prompt ?? ""));
2530
- const summary = res.text || `(child agent finished '${label}' with no summary; finishReason=${res.finishReason})`;
2531
- await opts.hooks?.onSubagentStop?.(summary, { label, agentType });
2532
- return summary;
2795
+ let fs;
2796
+ let wtHandle;
2797
+ if (useWorktree) {
2798
+ const h = createWorktreeFs(process.cwd(), label, 0);
2799
+ if (typeof h === "string") return `Error: ${h}`;
2800
+ wtHandle = h;
2801
+ fs = new NodeDiskFilesystem(h.worktreePath);
2802
+ } else {
2803
+ fs = opts.fs;
2804
+ }
2805
+ const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType);
2806
+ if (typeof childOpts === "string") {
2807
+ wtHandle && releaseWorktree(wtHandle);
2808
+ return `Error: ${childOpts}`;
2809
+ }
2810
+ try {
2811
+ const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(prompt ?? "") : String(prompt ?? "");
2812
+ const { result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal: ctx.signal });
2813
+ let summary = result;
2814
+ if (wtHandle) {
2815
+ const r = releaseWorktree(wtHandle);
2816
+ if (!r.removed) summary += `
2817
+
2818
+ [worktree removal failed: ${r.reason}]`;
2819
+ }
2820
+ return summary;
2821
+ } catch (e) {
2822
+ if (wtHandle) releaseWorktree(wtHandle);
2823
+ throw e;
2824
+ }
2533
2825
  }
2534
2826
  };
2535
2827
  }
@@ -2539,7 +2831,7 @@ function makeTaskBatchTool(opts) {
2539
2831
  const maxParallel = opts.maxParallel ?? 4;
2540
2832
  return {
2541
2833
  name: "TaskBatch",
2542
- description: "Delegate SEVERAL independent sub-tasks to child agents that run concurrently; returns all their summaries. Each child is write-isolated (its file edits are merged back in array order). Use for parallel fan-out (review/search/scoped refactors across files). Provide `tasks: [{ description, prompt, agentType? }]`.",
2834
+ description: 'Delegate SEVERAL independent sub-tasks to child agents that run concurrently; returns all their summaries. Each child is write-isolated (its file edits are merged back in array order). Use for parallel fan-out (review/search/scoped refactors across files). Provide `tasks: [{ description, prompt, agentType? }]`. Set `isolation: "worktree"` to run each child in its own git worktree (needed for real shell commands) \u2014 slower, auto-cleaned if no changes.',
2543
2835
  parameters: {
2544
2836
  type: "object",
2545
2837
  required: ["tasks"],
@@ -2556,30 +2848,53 @@ function makeTaskBatchTool(opts) {
2556
2848
  agentType: { type: "string", description: "optional named subagent type" }
2557
2849
  }
2558
2850
  }
2559
- }
2851
+ },
2852
+ isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
2560
2853
  }
2561
2854
  },
2562
- async run({ tasks }, _ctx) {
2855
+ async run({ tasks, isolation }, ctx) {
2563
2856
  if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
2564
2857
  const list = Array.isArray(tasks) ? tasks : [];
2565
2858
  if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
2859
+ const useWorktree = isolation === "worktree";
2566
2860
  const results = await boundedPool(list, maxParallel, async (t, i) => {
2567
2861
  const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
2568
- const overlay = new OverlayFilesystem(opts.fs);
2569
- const childOpts = childOptionsFor(opts, overlay, depth, maxDepth, t?.agentType);
2862
+ let fs;
2863
+ let overlay;
2864
+ let wtHandle;
2865
+ if (useWorktree) {
2866
+ const h = createWorktreeFs(process.cwd(), label, i);
2867
+ if (typeof h === "string") return { label, error: h, ok: false };
2868
+ wtHandle = h;
2869
+ fs = new NodeDiskFilesystem(h.worktreePath);
2870
+ } else {
2871
+ overlay = new OverlayFilesystem(opts.fs);
2872
+ fs = overlay;
2873
+ }
2874
+ const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType);
2570
2875
  if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
2571
2876
  try {
2572
- const res = await new Agent(childOpts).run(String(t?.prompt ?? ""));
2573
- await opts.hooks?.onSubagentStop?.(res.text, { label, agentType: t?.agentType });
2574
- return { label, text: res.text, overlay, ok: res.finishReason !== "error" };
2877
+ const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(t?.prompt ?? "") : String(t?.prompt ?? "");
2878
+ const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal: ctx.signal });
2879
+ return { label, text: result, overlay, wtHandle, ok: res.finishReason !== "error" && res.finishReason !== "aborted" };
2575
2880
  } catch (e) {
2576
- return { label, error: e instanceof Error ? e.message : String(e), ok: false };
2881
+ return { label, error: e instanceof Error ? e.message : String(e), wtHandle, ok: false };
2577
2882
  }
2578
2883
  });
2579
- for (const r of results) if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
2580
- });
2581
- return results.map((r, i) => `### ${i + 1}. ${r.label}
2582
- ${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}`).join("\n\n");
2884
+ const lines = [];
2885
+ for (const r of results) {
2886
+ if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
2887
+ });
2888
+ let extra = "";
2889
+ if (r.wtHandle) {
2890
+ const wr = releaseWorktree(r.wtHandle);
2891
+ if (!wr.removed) extra = `
2892
+ [worktree removal failed: ${wr.reason}]`;
2893
+ }
2894
+ lines.push(`### ${lines.length + 1}. ${r.label}
2895
+ ${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}${extra}`);
2896
+ }
2897
+ return lines.join("\n\n");
2583
2898
  }
2584
2899
  };
2585
2900
  }
@@ -2824,7 +3139,7 @@ function reasoningToChatFragment(model, effort) {
2824
3139
  }
2825
3140
 
2826
3141
  // src/Agent.ts
2827
- var log3 = forComponent("Agent");
3142
+ var log4 = forComponent("Agent");
2828
3143
  function isAbortError(err2) {
2829
3144
  const e = err2;
2830
3145
  const blob = `${e?.message ?? ""} ${e?.name ?? ""} ${e?.code ?? ""} ${e?.cause?.name ?? ""}`;
@@ -2900,6 +3215,11 @@ var AgentOptions = class {
2900
3215
  depth = 0;
2901
3216
  /** Hard ceiling on subagent nesting (beyond it the `Task` tool refuses to spawn). */
2902
3217
  maxDepth = 2;
3218
+ /** Real-disk dir where child agents stream their transcript as they run (origin-anchored — survives
3219
+ * worktree teardown / a child's own cwd). When set, an interrupted/failed child commits a STATUS HINT
3220
+ * + a pointer to its trace instead of a dangling tool_call, so the parent can read it and decide
3221
+ * whether to ignore, pick it up, or restart. Unset (library/sandbox default) = no tracing. */
3222
+ traceDir;
2903
3223
  /** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
2904
3224
  stream = false;
2905
3225
  /** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
@@ -3020,7 +3340,7 @@ var Agent = class _Agent {
3020
3340
  const disk = new NodeDiskFilesystem2(process.cwd());
3021
3341
  await disk.init();
3022
3342
  this.options.fs = new JailedFilesystem2(disk);
3023
- log3.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
3343
+ log4.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
3024
3344
  }
3025
3345
  this.buildCtx();
3026
3346
  }
@@ -3074,7 +3394,7 @@ var Agent = class _Agent {
3074
3394
  agents = loaded.agents;
3075
3395
  if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
3076
3396
  }
3077
- const taskOpts = { ai: o.ai, model: o.model, fs, depth: o.depth, maxDepth: o.maxDepth, agents, hooks: o.hooks };
3397
+ const taskOpts = { ai: o.ai, model: o.model, fs, depth: o.depth, maxDepth: o.maxDepth, agents, hooks: o.hooks, traceDir: o.traceDir };
3078
3398
  tools = [...tools, makeTaskTool(taskOpts), makeTaskBatchTool(taskOpts)];
3079
3399
  }
3080
3400
  if (o.checkpoints) tools = [...tools, ...checkpointTools()];
@@ -3163,7 +3483,7 @@ var Agent = class _Agent {
3163
3483
  let lastFp = "";
3164
3484
  let repeats = 0;
3165
3485
  const kill = (finishReason) => {
3166
- log3.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
3486
+ log4.warn(`kill-switch: ${finishReason} (steps=${steps}, tokens=${usage.totalTokens}, budgetTokens=${Math.round(usage.totalTokens - 0.9 * usage.cacheReadTokens)}, ms=${Date.now() - start - this.parkedMs}${this.parkedMs ? ` +${this.parkedMs} parked` : ""})`);
3167
3487
  this.ctx.jobs?.killAll();
3168
3488
  return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
3169
3489
  };
@@ -3209,7 +3529,7 @@ var Agent = class _Agent {
3209
3529
  const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < 2 && (network || serverSide);
3210
3530
  if (!transient) throw err2;
3211
3531
  const waitMs = 1e3 * (attempt + 1);
3212
- log3.warn(`network drop mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
3532
+ log4.warn(`network drop mid-step (${err2?.message ?? err2}) \u2014 retrying in ${waitMs}ms`);
3213
3533
  o.host?.notify?.({ kind: "retry", message: `connection dropped \u2014 retrying step (#${attempt + 1})` });
3214
3534
  await new Promise((r) => setTimeout(r, waitMs));
3215
3535
  }
@@ -3225,7 +3545,7 @@ var Agent = class _Agent {
3225
3545
  bodyStr = void 0;
3226
3546
  }
3227
3547
  if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
3228
- log3.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err2);
3548
+ log4.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err2);
3229
3549
  return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
3230
3550
  }
3231
3551
  if (o.signal?.aborted) return kill("aborted");
@@ -3252,7 +3572,7 @@ var Agent = class _Agent {
3252
3572
  });
3253
3573
  }
3254
3574
  if (toolCalls.length === 0) {
3255
- log3.verbose(`completed in ${steps} step(s)`);
3575
+ log4.verbose(`completed in ${steps} step(s)`);
3256
3576
  await this.ctx.jobs?.drain();
3257
3577
  await this.activeHooks?.onStop?.(res.content ?? "");
3258
3578
  return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
@@ -3318,13 +3638,13 @@ var Agent = class _Agent {
3318
3638
  const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
3319
3639
  if (decision?.block) {
3320
3640
  const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
3321
- log3.debug(`${tc.function.name} -> ${blocked}`);
3641
+ log4.debug(`${tc.function.name} -> ${blocked}`);
3322
3642
  await hooks?.postToolUse?.(call, blocked, meta);
3323
3643
  return blocked;
3324
3644
  }
3325
3645
  this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
3326
3646
  if (earlyError) {
3327
- log3.debug(`${tc.function.name} -> ${earlyError}`);
3647
+ log4.debug(`${tc.function.name} -> ${earlyError}`);
3328
3648
  await hooks?.postToolUse?.(call, earlyError, meta);
3329
3649
  this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
3330
3650
  return earlyError;
@@ -3333,12 +3653,12 @@ var Agent = class _Agent {
3333
3653
  let images;
3334
3654
  let threw = false;
3335
3655
  try {
3336
- log3.debug(`${tc.function.name}(${tc.function.arguments})`);
3656
+ log4.debug(`${tc.function.name}(${tc.function.arguments})`);
3337
3657
  this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
3338
3658
  try {
3339
3659
  hooks.onToolOutput(call, chunk, meta);
3340
3660
  } catch (e) {
3341
- log3.debug(`onToolOutput hook error: ${e}`);
3661
+ log4.debug(`onToolOutput hook error: ${e}`);
3342
3662
  }
3343
3663
  } : void 0;
3344
3664
  const raw = await tool.run(args, this.ctx);
@@ -3350,7 +3670,7 @@ var Agent = class _Agent {
3350
3670
  }
3351
3671
  } catch (e) {
3352
3672
  const msg = e instanceof Error ? e.message : String(e);
3353
- log3.debug(`${tc.function.name} -> error: ${msg}`);
3673
+ log4.debug(`${tc.function.name} -> error: ${msg}`);
3354
3674
  result = `Error: ${msg}`;
3355
3675
  threw = true;
3356
3676
  } finally {
@@ -3476,7 +3796,7 @@ function fitTokenBudget(messages, maxTokens) {
3476
3796
  const ids = callIdSet(messages.slice(from));
3477
3797
  while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
3478
3798
  if (total > maxTokens)
3479
- log3.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
3799
+ log4.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
3480
3800
  return [...head, ...messages.slice(from)];
3481
3801
  }
3482
3802
  function compact(m, max, focus) {
@@ -4033,7 +4353,7 @@ init_tools_web();
4033
4353
  init_tools();
4034
4354
  init_tools_structured();
4035
4355
  init_logging();
4036
- var log4 = forComponent("scratch");
4356
+ var log5 = forComponent("scratch");
4037
4357
  var SCRATCH_DIR = "/scratch";
4038
4358
  function shortArgs(args) {
4039
4359
  try {
@@ -4073,7 +4393,7 @@ var Scratch = class {
4073
4393
  await (this.dirReady ??= mkdirp(this.fs, dir));
4074
4394
  await this.fs.writeFile(path, header + raw);
4075
4395
  } catch (e) {
4076
- log4.debug("scratch write failed; returning raw", e);
4396
+ log5.debug("scratch write failed; returning raw", e);
4077
4397
  return raw;
4078
4398
  }
4079
4399
  const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
@@ -4103,7 +4423,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
4103
4423
  await (this.dirReady ??= mkdirp(this.fs, dir));
4104
4424
  await this.fs.writeFile(path, header + full);
4105
4425
  } catch (e) {
4106
- log4.debug("scratch spill failed; cropping lossy", e);
4426
+ log5.debug("scratch spill failed; cropping lossy", e);
4107
4427
  return full.slice(0, pageBytes) + `
4108
4428
 
4109
4429
  [output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
@@ -4149,7 +4469,7 @@ Question: ${q2}`);
4149
4469
  const answer = (res.text ?? "").trim();
4150
4470
  return answer || "(no answer found in scratch)";
4151
4471
  } catch (e) {
4152
- log4.debug("Ask peek failed", e);
4472
+ log5.debug("Ask peek failed", e);
4153
4473
  return `Error querying scratch: ${e?.message ?? e}`;
4154
4474
  }
4155
4475
  }
@@ -4158,7 +4478,7 @@ Question: ${q2}`);
4158
4478
 
4159
4479
  // src/lessons.ts
4160
4480
  init_logging();
4161
- var log5 = forComponent("Lessons");
4481
+ var log6 = forComponent("Lessons");
4162
4482
  var LessonOptionsDefaults = class {
4163
4483
  minRepeats = 2;
4164
4484
  };
@@ -4183,15 +4503,15 @@ function lessonCapture(options) {
4183
4503
  counts.set(lesson.slug, n);
4184
4504
  if (n < o.minRepeats) return;
4185
4505
  written.add(lesson.slug);
4186
- await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log5.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
4187
- log5.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
4506
+ await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log6.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
4507
+ log6.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
4188
4508
  }
4189
4509
  };
4190
4510
  }
4191
4511
 
4192
4512
  // src/reflect.ts
4193
4513
  init_logging();
4194
- var log6 = forComponent("Reflect");
4514
+ var log7 = forComponent("Reflect");
4195
4515
  async function reflectOnRun(o) {
4196
4516
  const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
4197
4517
  if (!digest.trim()) return null;
@@ -4207,7 +4527,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
4207
4527
  const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
4208
4528
  text = r?.content ?? "";
4209
4529
  } catch (e) {
4210
- log6.warn(`reflection call failed: ${e?.message ?? e}`);
4530
+ log7.warn(`reflection call failed: ${e?.message ?? e}`);
4211
4531
  return null;
4212
4532
  }
4213
4533
  const m = text.match(/LESSON:\s*(.+)/i);
@@ -4217,10 +4537,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
4217
4537
  try {
4218
4538
  await writeFact(o.fs, o.dir, slug2, lesson);
4219
4539
  } catch (e) {
4220
- log6.warn(`could not persist lesson: ${e?.message ?? e}`);
4540
+ log7.warn(`could not persist lesson: ${e?.message ?? e}`);
4221
4541
  return null;
4222
4542
  }
4223
- log6.debug(`reflection persisted ${slug2}`);
4543
+ log7.debug(`reflection persisted ${slug2}`);
4224
4544
  return slug2;
4225
4545
  }
4226
4546
  function digestRun(messages, maxChars) {
@@ -4237,7 +4557,7 @@ function digestRun(messages, maxChars) {
4237
4557
  // src/duplex.ts
4238
4558
  import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
4239
4559
  init_logging();
4240
- var log7 = forComponent("DuplexAgent");
4560
+ var log8 = forComponent("DuplexAgent");
4241
4561
  function describeCall(call) {
4242
4562
  const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
4243
4563
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
@@ -4377,7 +4697,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4377
4697
  const m = this.reflexBuf.match(RESERVED_EVENT_MARKER);
4378
4698
  if (m) {
4379
4699
  this.fabricationCut = true;
4380
- log7.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
4700
+ log8.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
4381
4701
  const safe = this.reflexBuf.slice(this.reflexForwarded, m.index);
4382
4702
  if (!safe) return;
4383
4703
  if (safe.trim()) this.spokeThisTurn = true;
@@ -4456,7 +4776,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4456
4776
  try {
4457
4777
  await this.voice.send("[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools.");
4458
4778
  } catch (e) {
4459
- log7.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
4779
+ log8.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
4460
4780
  } finally {
4461
4781
  this.nudging = false;
4462
4782
  }
@@ -4604,7 +4924,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4604
4924
  this.notify("task_verify", `task ${id}: verifying`, { id });
4605
4925
  const cres = await new Agent(agentOpts).run(checkBrief);
4606
4926
  if (cres.finishReason !== "stop") {
4607
- log7.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
4927
+ log8.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
4608
4928
  this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
4609
4929
  }
4610
4930
  const sum = (a = 0, b = 0) => a + b;
@@ -4707,7 +5027,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4707
5027
  }
4708
5028
  rec.status = "done";
4709
5029
  rec.result = res.text;
4710
- log7.verbose(`task ${id} done (${res.steps} steps)`);
5030
+ log8.verbose(`task ${id} done (${res.steps} steps)`);
4711
5031
  this.notify("task_done", `task ${id} (${rec.label}) completed`, {
4712
5032
  id,
4713
5033
  text: res.text,
@@ -4725,7 +5045,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4725
5045
  this.dropAsk(rec.id);
4726
5046
  rec.status = "error";
4727
5047
  rec.result = msg;
4728
- log7.warn(`task ${rec.id} failed: ${msg}`);
5048
+ log8.warn(`task ${rec.id} failed: ${msg}`);
4729
5049
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
4730
5050
  this.queueRevoice(`[task ${rec.id} failed] ${msg}`);
4731
5051
  }
@@ -5027,7 +5347,7 @@ init_logging();
5027
5347
 
5028
5348
  // src/voice/engine.ts
5029
5349
  init_logging();
5030
- var log8 = forComponent("VoiceEngine");
5350
+ var log9 = forComponent("VoiceEngine");
5031
5351
  var now = () => performance.now();
5032
5352
  var forSpeech = (t) => t.replace(/[*_`#]+/g, "").replace(/^[ \t]*[-•]\s+/gm, "").replace(/\s*[\u2013\u2014]\s*/g, ", ").replace(/[\u2010\u2011]/g, "-").replace(/\s*\|\s*/g, ", ").replace(/(\d)\s+%/g, "$1%").replace(/\.{3,}/g, ".");
5033
5353
  var VoiceEngineOptions = class {
@@ -5135,7 +5455,7 @@ var VoiceEngine = class _VoiceEngine {
5135
5455
  this.stt.onLevel = (rms) => this.handleLevel(rms);
5136
5456
  await Promise.all([this.tts.connect(), this.stt.start()]);
5137
5457
  this.setState("listening");
5138
- log8.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
5458
+ log9.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
5139
5459
  }
5140
5460
  get usingAec() {
5141
5461
  return this.stt.usingAec;
@@ -5187,7 +5507,7 @@ var VoiceEngine = class _VoiceEngine {
5187
5507
  this.reply += text;
5188
5508
  for (const w of this.words(this.reply)) this.echoWords.add(w);
5189
5509
  this.tts.speak(forSpeech(text), true);
5190
- if (!this.spokeDeltas && this.turnStartAt) log8.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
5510
+ if (!this.spokeDeltas && this.turnStartAt) log9.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
5191
5511
  this.spokeDeltas = true;
5192
5512
  this.setState("speaking");
5193
5513
  }
@@ -5208,7 +5528,7 @@ var VoiceEngine = class _VoiceEngine {
5208
5528
  }
5209
5529
  this.drainTimer = null;
5210
5530
  this.speaking = false;
5211
- if (this.turnStartAt) log8.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
5531
+ if (this.turnStartAt) log9.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
5212
5532
  this.echoUntil = now() + 2500;
5213
5533
  if (!this.usingAec) this.stt.reset();
5214
5534
  this.setState("listening");
@@ -5349,7 +5669,7 @@ var VoiceEngine = class _VoiceEngine {
5349
5669
  this.pendingUtt = this.pendingUtt ? `${this.pendingUtt} ${text}` : text;
5350
5670
  if (this.pendingTimer) clearTimeout(this.pendingTimer);
5351
5671
  if (this.options.incompleteMergeMs && this.looksIncomplete(this.pendingUtt)) {
5352
- log8.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
5672
+ log9.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
5353
5673
  this.options.onHold();
5354
5674
  if (this.options.holdFiller && !this.speaking) {
5355
5675
  this.beginSpeech();
@@ -5447,7 +5767,7 @@ async function resolveAuth(auth) {
5447
5767
  }
5448
5768
 
5449
5769
  // src/voice/soniox.ts
5450
- var log9 = forComponent("SonioxSTT");
5770
+ var log10 = forComponent("SonioxSTT");
5451
5771
  var now2 = () => performance.now();
5452
5772
  var SonioxSTTOptions = class {
5453
5773
  auth = "";
@@ -5504,9 +5824,9 @@ var SonioxSTT = class {
5504
5824
  this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
5505
5825
  this.ws.onclose = (ev) => {
5506
5826
  if (this.stopped) return;
5507
- log9.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
5827
+ log10.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
5508
5828
  this.reset();
5509
- this.connectWs().catch((e) => log9.error(`soniox reconnect failed: ${e.message}`));
5829
+ this.connectWs().catch((e) => log10.error(`soniox reconnect failed: ${e.message}`));
5510
5830
  };
5511
5831
  }
5512
5832
  async start() {
@@ -5516,7 +5836,7 @@ var SonioxSTT = class {
5516
5836
  this.endpointTimer = setInterval(() => {
5517
5837
  const combined = (this.finalText + this.partialText).trim();
5518
5838
  if (!combined || now2() - this.lastChangeAt < this.options.silenceEndpointMs) return;
5519
- if (this.firstTokenAt) log9.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
5839
+ if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
5520
5840
  this.reset();
5521
5841
  this.onUtterance(combined, now2());
5522
5842
  }, 120);
@@ -5533,7 +5853,7 @@ var SonioxSTT = class {
5533
5853
  });
5534
5854
  }
5535
5855
  handle(m) {
5536
- if (m.error_message) return log9.error(`soniox: ${m.error_message}`);
5856
+ if (m.error_message) return log10.error(`soniox: ${m.error_message}`);
5537
5857
  let endpoint = false;
5538
5858
  for (const t of m.tokens ?? []) {
5539
5859
  if (t.text === "<end>") endpoint = true;
@@ -5549,7 +5869,7 @@ var SonioxSTT = class {
5549
5869
  this.onPartial(combined);
5550
5870
  if (endpoint && this.finalText.trim()) {
5551
5871
  const utterance = this.finalText.trim();
5552
- if (this.firstTokenAt) log9.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
5872
+ if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
5553
5873
  this.reset();
5554
5874
  this.onUtterance(utterance, now2());
5555
5875
  }
@@ -5571,7 +5891,7 @@ var SonioxSTT = class {
5571
5891
 
5572
5892
  // src/voice/cartesia.ts
5573
5893
  init_logging();
5574
- var log10 = forComponent("CartesiaTTS");
5894
+ var log11 = forComponent("CartesiaTTS");
5575
5895
  var now3 = () => performance.now();
5576
5896
  var CartesiaTTSOptions = class {
5577
5897
  auth = "";
@@ -5621,9 +5941,9 @@ var CartesiaTTS = class _CartesiaTTS {
5621
5941
  this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
5622
5942
  });
5623
5943
  this.ws.onclose = (ev) => {
5624
- log10.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
5944
+ log11.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
5625
5945
  if (!this.closed) {
5626
- this.connecting = this.doConnect().catch((e) => log10.error(`cartesia reconnect failed: ${e.message}`));
5946
+ this.connecting = this.doConnect().catch((e) => log11.error(`cartesia reconnect failed: ${e.message}`));
5627
5947
  }
5628
5948
  };
5629
5949
  this.ws.onmessage = (ev) => {
@@ -5645,11 +5965,11 @@ var CartesiaTTS = class _CartesiaTTS {
5645
5965
  this.down = true;
5646
5966
  this.downAt = now3();
5647
5967
  this.consecutiveOk = 0;
5648
- log10.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
5968
+ log11.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
5649
5969
  this.onDone();
5650
5970
  this.startProbe();
5651
5971
  } else if (!this.down) {
5652
- log10.warn(`cartesia: ${JSON.stringify(m)}`);
5972
+ log11.warn(`cartesia: ${JSON.stringify(m)}`);
5653
5973
  }
5654
5974
  }
5655
5975
  };
@@ -5663,7 +5983,7 @@ var CartesiaTTS = class _CartesiaTTS {
5663
5983
  this.consecutiveOk = 0;
5664
5984
  this.stopProbe();
5665
5985
  const downMs = this.downAt ? now3() - this.downAt : 0;
5666
- (downMs < 2e3 ? log10.debug : log10.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
5986
+ (downMs < 2e3 ? log11.debug : log11.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
5667
5987
  }
5668
5988
  /** Ensure the WS is open before sending — reconnects if idle-closed. */
5669
5989
  async ensureConnected() {
@@ -5736,13 +6056,14 @@ function base64ToBytes(b64) {
5736
6056
  }
5737
6057
 
5738
6058
  // src/index.ts
6059
+ init_worktree();
5739
6060
  import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor as CommandExecutor2, registerHeadlessCommands as registerHeadlessCommands2 } from "@livx.cc/wcli/core";
5740
6061
 
5741
6062
  // src/mcp.client.ts
5742
6063
  init_logging();
5743
6064
  import { spawn } from "child_process";
5744
6065
  import { createHash } from "crypto";
5745
- var log11 = forComponent("mcp");
6066
+ var log12 = forComponent("mcp");
5746
6067
  var PROTOCOL_VERSION = "2025-06-18";
5747
6068
  var DEFAULT_TIMEOUT_MS = 3e4;
5748
6069
  var StdioTransport = class {
@@ -5761,7 +6082,7 @@ var StdioTransport = class {
5761
6082
  proc.stdout.setEncoding("utf8");
5762
6083
  proc.stdout.on("data", (chunk) => this.onData(chunk));
5763
6084
  proc.stderr.setEncoding("utf8");
5764
- proc.stderr.on("data", (chunk) => log11.debug(`[${command}] stderr:`, chunk.trimEnd()));
6085
+ proc.stderr.on("data", (chunk) => log12.debug(`[${command}] stderr:`, chunk.trimEnd()));
5765
6086
  proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
5766
6087
  proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
5767
6088
  }
@@ -5775,7 +6096,7 @@ var StdioTransport = class {
5775
6096
  try {
5776
6097
  this.dispatch(JSON.parse(line));
5777
6098
  } catch (e) {
5778
- log11.debug("dropping non-JSON line from MCP server:", line, e);
6099
+ log12.debug("dropping non-JSON line from MCP server:", line, e);
5779
6100
  }
5780
6101
  }
5781
6102
  }
@@ -5824,7 +6145,7 @@ var StdioTransport = class {
5824
6145
  try {
5825
6146
  this.proc?.stdin?.end();
5826
6147
  } catch (e) {
5827
- log11.debug("stdin end failed", e);
6148
+ log12.debug("stdin end failed", e);
5828
6149
  }
5829
6150
  this.proc?.kill();
5830
6151
  }
@@ -5893,7 +6214,7 @@ function parseSseResponse(body) {
5893
6214
  const obj = JSON.parse(trimmed.slice(5).trim());
5894
6215
  if (obj && (obj.result !== void 0 || obj.error !== void 0)) return obj;
5895
6216
  } catch (e) {
5896
- log11.debug("skipping unparseable SSE data line", e);
6217
+ log12.debug("skipping unparseable SSE data line", e);
5897
6218
  }
5898
6219
  }
5899
6220
  return {};
@@ -5961,7 +6282,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
5961
6282
  return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
5962
6283
  })(), mountTimeoutMs, name);
5963
6284
  } catch (e) {
5964
- await client.close().catch((err2) => log11.debug(`close after failed mount of "${name}": ${err2}`));
6285
+ await client.close().catch((err2) => log12.debug(`close after failed mount of "${name}": ${err2}`));
5965
6286
  throw e;
5966
6287
  }
5967
6288
  }
@@ -5972,15 +6293,15 @@ function validEntries(servers) {
5972
6293
  return Object.entries(servers).filter(([name, cfg]) => {
5973
6294
  if (!cfg || cfg.disabled) return false;
5974
6295
  if (!cfg.command && !cfg.url) {
5975
- log11.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
6296
+ log12.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
5976
6297
  return false;
5977
6298
  }
5978
6299
  return true;
5979
6300
  });
5980
6301
  }
5981
6302
  function logMountFailure(name, e) {
5982
- if (e instanceof McpAuthError) log11.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
5983
- else log11.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
6303
+ if (e instanceof McpAuthError) log12.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
6304
+ else log12.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
5984
6305
  }
5985
6306
  async function mountMcpServers(servers = {}, opts = {}) {
5986
6307
  const entries = validEntries(servers);
@@ -5990,7 +6311,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
5990
6311
  const name = entries[i][0];
5991
6312
  if (r.status === "fulfilled") {
5992
6313
  out.push(r.value);
5993
- log11.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
6314
+ log12.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
5994
6315
  } else logMountFailure(name, r.reason);
5995
6316
  });
5996
6317
  return out;
@@ -6030,7 +6351,7 @@ var McpPool = class {
6030
6351
  const prev = this.warm.get(key);
6031
6352
  if (prev) {
6032
6353
  clearTimeout(prev.timer);
6033
- if (prev.client !== client) void prev.client.close().catch((err2) => log11.debug(`warm-pool replace close failed: ${err2}`));
6354
+ if (prev.client !== client) void prev.client.close().catch((err2) => log12.debug(`warm-pool replace close failed: ${err2}`));
6034
6355
  }
6035
6356
  const e = { client, timer: void 0 };
6036
6357
  this.warm.set(key, e);
@@ -6047,7 +6368,7 @@ var McpPool = class {
6047
6368
  const e = this.warm.get(key);
6048
6369
  if (!e) return;
6049
6370
  this.warm.delete(key);
6050
- await e.client.close().catch((err2) => log11.debug(`warm-pool evict close failed: ${err2}`));
6371
+ await e.client.close().catch((err2) => log12.debug(`warm-pool evict close failed: ${err2}`));
6051
6372
  }
6052
6373
  async closeAll() {
6053
6374
  for (const e of this.warm.values()) {
@@ -6064,7 +6385,7 @@ var defaultPool = new McpPool();
6064
6385
  // cli/mcpOAuth.ts
6065
6386
  import { createServer } from "http";
6066
6387
  import { randomBytes, createHash as createHash2 } from "crypto";
6067
- import { readFileSync, writeFileSync as writeFileSync2, mkdirSync, existsSync } from "fs";
6388
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
6068
6389
  import { dirname as dirname2 } from "path";
6069
6390
  var McpOAuthOptions = class {
6070
6391
  /** Where to persist tokens. Mode 0600. */
@@ -6092,14 +6413,14 @@ var McpOAuth = class {
6092
6413
  // -- store I/O ----------------------------------------------------------------
6093
6414
  load() {
6094
6415
  try {
6095
- return existsSync(this.options.storePath) ? JSON.parse(readFileSync(this.options.storePath, "utf8")) : {};
6416
+ return existsSync(this.options.storePath) ? JSON.parse(readFileSync2(this.options.storePath, "utf8")) : {};
6096
6417
  } catch {
6097
6418
  return {};
6098
6419
  }
6099
6420
  }
6100
6421
  save(store) {
6101
- mkdirSync(dirname2(this.options.storePath), { recursive: true });
6102
- writeFileSync2(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
6422
+ mkdirSync3(dirname2(this.options.storePath), { recursive: true });
6423
+ writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
6103
6424
  }
6104
6425
  // -- ATTENDED: one-time authorization ----------------------------------------
6105
6426
  /** Run the authorization-code + PKCE flow and persist the resulting tokens for `serverUrl`. */
@@ -6257,15 +6578,15 @@ function defaultOpenBrowser(url) {
6257
6578
  // cli/core.ts
6258
6579
  import { randomUUID } from "crypto";
6259
6580
  import { execFile as execFile2 } from "child_process";
6260
- import { resolve, basename, join as join3 } from "path";
6261
- import { existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
6581
+ import { resolve, basename, join as join5 } from "path";
6582
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
6262
6583
  import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
6263
6584
  init_tools_shell();
6264
6585
 
6265
6586
  // src/tools.notify.ts
6266
6587
  init_logging();
6267
6588
  import { execFile } from "child_process";
6268
- var log13 = forComponent("notify");
6589
+ var log14 = forComponent("notify");
6269
6590
  function makeNotifyTool(opts = {}) {
6270
6591
  const platform2 = opts.platform ?? process.platform;
6271
6592
  const run = opts.exec ?? execFile;
@@ -6289,7 +6610,7 @@ function makeNotifyTool(opts = {}) {
6289
6610
  return new Promise((resolve4) => {
6290
6611
  run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
6291
6612
  if (e) {
6292
- log13.debug("notification failed", e);
6613
+ log14.debug("notification failed", e);
6293
6614
  resolve4(`Notification failed: ${e.message}`);
6294
6615
  } else resolve4("Notification shown.");
6295
6616
  });
@@ -6303,9 +6624,9 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
6303
6624
 
6304
6625
  // cli/util.ts
6305
6626
  init_logging();
6306
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
6627
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
6307
6628
  import { homedir } from "os";
6308
- var log14 = forComponent("cli-util");
6629
+ var log15 = forComponent("cli-util");
6309
6630
  function dotDirs(base, sub, opts = {}) {
6310
6631
  const home = opts.home ?? homedir();
6311
6632
  const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
@@ -6322,7 +6643,7 @@ function parseJson(text, fallback, what = "json") {
6322
6643
  try {
6323
6644
  return JSON.parse(text);
6324
6645
  } catch (e) {
6325
- log14.debug(`parseJson(${what}) failed: ${e.message}`);
6646
+ log15.debug(`parseJson(${what}) failed: ${e.message}`);
6326
6647
  return fallback;
6327
6648
  }
6328
6649
  }
@@ -6330,9 +6651,9 @@ function readJsonFile(path, fallback) {
6330
6651
  if (!existsSync2(path)) return fallback;
6331
6652
  let text;
6332
6653
  try {
6333
- text = readFileSync2(path, "utf8");
6654
+ text = readFileSync3(path, "utf8");
6334
6655
  } catch (e) {
6335
- log14.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
6656
+ log15.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
6336
6657
  return fallback;
6337
6658
  }
6338
6659
  return parseJson(text, fallback, path);
@@ -6416,8 +6737,8 @@ async function buildAgent(o) {
6416
6737
  fs = mem;
6417
6738
  } else if (o.boddb) {
6418
6739
  const root = resolve(o.boddb);
6419
- mkdirSync2(root, { recursive: true });
6420
- const db = new BodDB2({ path: join3(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join3(root, "files") } });
6740
+ mkdirSync4(root, { recursive: true });
6741
+ const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
6421
6742
  const bod = new BodDbFilesystem(db);
6422
6743
  const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
6423
6744
  await mkdirp(bod, cwd);
@@ -6544,6 +6865,9 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
6544
6865
  ...scratch ? { capToolResult: (full, info) => scratch.spill(full, info) } : {},
6545
6866
  backgroundJobs: o.backgroundJobs ?? virtual,
6546
6867
  // default ON in virtual modes (no real shell there); disk uses ShellJobRegistry
6868
+ // Subagent transcript traces, origin-anchored under .agent/traces (disk mode only — sandbox/db modes
6869
+ // must never write the real disk). Lets an interrupted/failed child leave a readable trail + pointer.
6870
+ ...!virtual ? { traceDir: `${cwd}/.agent/traces` } : {},
6547
6871
  skillsDir: dots("skills"),
6548
6872
  commandsDir: dots("commands"),
6549
6873
  memoryDir,
@@ -6576,12 +6900,12 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
6576
6900
 
6577
6901
  // cli/voice.ts
6578
6902
  init_logging();
6579
- import { spawn as spawn2, spawnSync } from "child_process";
6580
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync2 } from "fs";
6903
+ import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
6904
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
6581
6905
  import { homedir as homedir3 } from "os";
6582
- import { dirname as dirname3, join as join4 } from "path";
6906
+ import { dirname as dirname3, join as join6 } from "path";
6583
6907
  import { fileURLToPath } from "url";
6584
- var log15 = forComponent("VoiceIO");
6908
+ var log16 = forComponent("VoiceIO");
6585
6909
  var now4 = () => performance.now();
6586
6910
  var Player = class {
6587
6911
  proc = null;
@@ -6595,7 +6919,7 @@ var Player = class {
6595
6919
  ["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
6596
6920
  { stdio: ["pipe", "ignore", "ignore"] }
6597
6921
  );
6598
- this.proc.on("error", (e) => log15.warn(`ffplay error: ${e.message}`));
6922
+ this.proc.on("error", (e) => log16.warn(`ffplay error: ${e.message}`));
6599
6923
  this.proc.stdin.on("error", () => {
6600
6924
  });
6601
6925
  this.bytesWritten = 0;
@@ -6622,24 +6946,24 @@ var Player = class {
6622
6946
  this.proc = null;
6623
6947
  }
6624
6948
  };
6625
- var nativeDir = () => join4(dirname3(fileURLToPath(import.meta.url)), "native");
6949
+ var nativeDir = () => join6(dirname3(fileURLToPath(import.meta.url)), "native");
6626
6950
  function detectFfmpegMic() {
6627
6951
  if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
6628
- const out = spawnSync("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" }).stderr;
6952
+ const out = spawnSync2("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" }).stderr;
6629
6953
  const audio = out.slice(out.indexOf("audio devices"));
6630
6954
  const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
6631
6955
  const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
6632
6956
  if (!mic) throw new Error("no audio input device found");
6633
- log15.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
6957
+ log16.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
6634
6958
  return `:${mic.idx}`;
6635
6959
  }
6636
6960
  function detectedInputDevice() {
6637
6961
  if (process.platform !== "darwin") return null;
6638
6962
  let name = "";
6639
- if (spawnSync("which", ["SwitchAudioSource"]).status === 0) {
6640
- name = (spawnSync("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
6963
+ if (spawnSync2("which", ["SwitchAudioSource"]).status === 0) {
6964
+ name = (spawnSync2("SwitchAudioSource", ["-c", "-t", "input"], { encoding: "utf8" }).stdout || "").trim();
6641
6965
  } else {
6642
- const out = spawnSync("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
6966
+ const out = spawnSync2("system_profiler", ["SPAudioDataType"], { encoding: "utf8" }).stdout || "";
6643
6967
  let last = "";
6644
6968
  for (const ln of out.split("\n")) {
6645
6969
  const hdr = ln.match(/^\s{6,}(\S.*?):\s*$/);
@@ -6658,23 +6982,23 @@ function detectedInputDevice() {
6658
6982
  }
6659
6983
  function resolveAecBinary() {
6660
6984
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
6661
- const src = join4(nativeDir(), "mic-aec.swift");
6662
- const plist = join4(nativeDir(), "Info.plist");
6985
+ const src = join6(nativeDir(), "mic-aec.swift");
6986
+ const plist = join6(nativeDir(), "Info.plist");
6663
6987
  if (!existsSync4(src)) return null;
6664
- const cacheDir = join4(homedir3(), ".agent", "cache");
6665
- const bin = join4(cacheDir, "mic-aec");
6666
- if (existsSync4(bin) && statSync2(bin).mtimeMs >= statSync2(src).mtimeMs) return bin;
6667
- if (spawnSync("which", ["swiftc"]).status !== 0) return null;
6668
- mkdirSync3(cacheDir, { recursive: true });
6669
- log15.info("compiling AEC mic helper (first run)\u2026");
6670
- const build = spawnSync("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
6988
+ const cacheDir = join6(homedir3(), ".agent", "cache");
6989
+ const bin = join6(cacheDir, "mic-aec");
6990
+ if (existsSync4(bin) && statSync3(bin).mtimeMs >= statSync3(src).mtimeMs) return bin;
6991
+ if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
6992
+ mkdirSync5(cacheDir, { recursive: true });
6993
+ log16.info("compiling AEC mic helper (first run)\u2026");
6994
+ const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
6671
6995
  if (build.status !== 0) {
6672
- log15.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
6996
+ log16.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
6673
6997
  return null;
6674
6998
  }
6675
- const sign = spawnSync("codesign", ["-fs", "-", bin], { encoding: "utf8" });
6999
+ const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
6676
7000
  if (sign.status !== 0) {
6677
- log15.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
7001
+ log16.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
6678
7002
  return null;
6679
7003
  }
6680
7004
  return bin;
@@ -6692,17 +7016,17 @@ var NodeMicSource = class {
6692
7016
  if (this.bin) {
6693
7017
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
6694
7018
  } else {
6695
- if (spawnSync("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
6696
- log15.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
7019
+ if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
7020
+ log16.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
6697
7021
  this.proc = spawn2(
6698
7022
  "ffmpeg",
6699
7023
  ["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
6700
7024
  { stdio: ["ignore", "pipe", "pipe"] }
6701
7025
  );
6702
- this.proc.stderr.on("data", (d) => log15.warn(`ffmpeg: ${String(d).trim()}`));
7026
+ this.proc.stderr.on("data", (d) => log16.warn(`ffmpeg: ${String(d).trim()}`));
6703
7027
  }
6704
7028
  this.proc.on("exit", (c) => {
6705
- if (c && !this.stopped) log15.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
7029
+ if (c && !this.stopped) log16.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
6706
7030
  });
6707
7031
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
6708
7032
  }
@@ -6736,11 +7060,11 @@ var AecDuplexAudio = class {
6736
7060
  this.proc.stdin.on("error", () => {
6737
7061
  });
6738
7062
  this.proc.on("exit", (c) => {
6739
- if (c && !this.stopped) log15.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
7063
+ if (c && !this.stopped) log16.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
6740
7064
  });
6741
7065
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
6742
7066
  this.proc.stderr.on("data", (d) => {
6743
- for (const ln of String(d).split("\n")) if (ln.trim()) log15.debug(`mic-aec: ${ln.trim()}`);
7067
+ for (const ln of String(d).split("\n")) if (ln.trim()) log16.debug(`mic-aec: ${ln.trim()}`);
6744
7068
  });
6745
7069
  }
6746
7070
  stop() {
@@ -6861,13 +7185,13 @@ var VoiceIO = class extends VoiceEngine {
6861
7185
 
6862
7186
  // cli/config.ts
6863
7187
  import { homedir as homedir4 } from "os";
6864
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
6865
- import { join as join5 } from "path";
7188
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
7189
+ import { join as join7 } from "path";
6866
7190
  import { pathToFileURL } from "url";
6867
7191
  var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
6868
7192
  async function loadFrom(dir) {
6869
7193
  for (const f of FILES) {
6870
- const p = join5(dir, ".agent", f);
7194
+ const p = join7(dir, ".agent", f);
6871
7195
  if (!existsSync5(p)) continue;
6872
7196
  try {
6873
7197
  const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
@@ -6880,10 +7204,10 @@ async function loadFrom(dir) {
6880
7204
  return {};
6881
7205
  }
6882
7206
  function loadSettings(dir) {
6883
- const p = join5(dir, ".agent", "settings.json");
7207
+ const p = join7(dir, ".agent", "settings.json");
6884
7208
  if (!existsSync5(p)) return {};
6885
7209
  try {
6886
- const raw = JSON.parse(readFileSync3(p, "utf8"));
7210
+ const raw = JSON.parse(readFileSync4(p, "utf8"));
6887
7211
  const cfg = {};
6888
7212
  if (raw.mcpServers && typeof raw.mcpServers === "object") cfg.mcpServers = raw.mcpServers;
6889
7213
  if (raw.permissions && typeof raw.permissions === "object") cfg.permissions = raw.permissions;
@@ -6916,8 +7240,8 @@ async function loadConfig(cwd) {
6916
7240
  }
6917
7241
 
6918
7242
  // cli/hooks-config.ts
6919
- import { spawnSync as spawnSync2 } from "child_process";
6920
- var log16 = forComponent("hooks");
7243
+ import { spawnSync as spawnSync3 } from "child_process";
7244
+ var log17 = forComponent("hooks");
6921
7245
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
6922
7246
  function ruleMatches(rule, toolName) {
6923
7247
  if (!rule.tool || rule.tool === "*") return true;
@@ -6926,7 +7250,7 @@ function ruleMatches(rule, toolName) {
6926
7250
  }
6927
7251
  function runCmd(rule, env) {
6928
7252
  try {
6929
- const r = spawnSync2(rule.command, {
7253
+ const r = spawnSync3(rule.command, {
6930
7254
  shell: true,
6931
7255
  encoding: "utf8",
6932
7256
  timeout: rule.timeoutMs ?? 1e4,
@@ -6934,7 +7258,7 @@ function runCmd(rule, env) {
6934
7258
  });
6935
7259
  return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
6936
7260
  } catch (e) {
6937
- log16.debug(`hook command failed: ${rule.command}`, e);
7261
+ log17.debug(`hook command failed: ${rule.command}`, e);
6938
7262
  return { code: 1, out: String(e?.message ?? e) };
6939
7263
  }
6940
7264
  }
@@ -7038,15 +7362,15 @@ function formatDiff(ops, opts = {}) {
7038
7362
  }
7039
7363
 
7040
7364
  // cli/session.ts
7041
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, readdirSync, renameSync, symlinkSync, unlinkSync, readlinkSync } from "fs";
7365
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
7042
7366
  import { homedir as homedir5 } from "os";
7043
- import { join as join6 } from "path";
7044
- var log17 = forComponent("session");
7045
- var globalDir = () => join6(homedir5(), ".agent", "sessions");
7367
+ import { join as join8 } from "path";
7368
+ var log18 = forComponent("session");
7369
+ var globalDir = () => join8(homedir5(), ".agent", "sessions");
7046
7370
  var SessionStore = class {
7047
7371
  dir;
7048
7372
  constructor(cwd) {
7049
- this.dir = join6(cwd, ".agent", "sessions");
7373
+ this.dir = join8(cwd, ".agent", "sessions");
7050
7374
  }
7051
7375
  /** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
7052
7376
  newId(now5 = Date.now(), cwd) {
@@ -7054,10 +7378,10 @@ var SessionStore = class {
7054
7378
  const p = (n, w = 2) => String(n).padStart(w, "0");
7055
7379
  const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
7056
7380
  let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
7057
- if (existsSync6(this.dir) && existsSync6(join6(this.dir, `${id}.json`))) {
7381
+ if (existsSync6(this.dir) && existsSync6(join8(this.dir, `${id}.json`))) {
7058
7382
  for (let i = 2; i <= 99; i++) {
7059
7383
  const c = `${id}-${i}`;
7060
- if (!existsSync6(join6(this.dir, `${c}.json`))) {
7384
+ if (!existsSync6(join8(this.dir, `${c}.json`))) {
7061
7385
  id = c;
7062
7386
  break;
7063
7387
  }
@@ -7071,30 +7395,30 @@ var SessionStore = class {
7071
7395
  }
7072
7396
  save(data) {
7073
7397
  if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
7074
- if (!existsSync6(this.dir)) mkdirSync4(this.dir, { recursive: true });
7075
- const path = join6(this.dir, `${data.meta.id}.json`);
7398
+ if (!existsSync6(this.dir)) mkdirSync6(this.dir, { recursive: true });
7399
+ const path = join8(this.dir, `${data.meta.id}.json`);
7076
7400
  const tmp = `${path}.${process.pid}.tmp`;
7077
- writeFileSync3(tmp, JSON.stringify(data));
7401
+ writeFileSync4(tmp, JSON.stringify(data));
7078
7402
  renameSync(tmp, path);
7079
7403
  try {
7080
7404
  const gd = globalDir();
7081
- if (!existsSync6(gd)) mkdirSync4(gd, { recursive: true });
7082
- const link2 = join6(gd, `${data.meta.id}.json`);
7083
- if (!existsSync6(link2)) symlinkSync(path, link2);
7405
+ if (!existsSync6(gd)) mkdirSync6(gd, { recursive: true });
7406
+ const link2 = join8(gd, `${data.meta.id}.json`);
7407
+ if (!existsSync6(link2)) symlinkSync2(path, link2);
7084
7408
  } catch {
7085
7409
  }
7086
7410
  }
7087
7411
  load(id) {
7088
7412
  if (!this.safeId(id)) {
7089
- log17.debug(`rejecting unsafe session id: ${id}`);
7413
+ log18.debug(`rejecting unsafe session id: ${id}`);
7090
7414
  return void 0;
7091
7415
  }
7092
- const path = join6(this.dir, `${id}.json`);
7416
+ const path = join8(this.dir, `${id}.json`);
7093
7417
  if (!existsSync6(path)) return void 0;
7094
7418
  try {
7095
- return JSON.parse(readFileSync4(path, "utf8"));
7419
+ return JSON.parse(readFileSync5(path, "utf8"));
7096
7420
  } catch (e) {
7097
- log17.debug(`unreadable session ${id} \u2014 ignoring`, e);
7421
+ log18.debug(`unreadable session ${id} \u2014 ignoring`, e);
7098
7422
  return void 0;
7099
7423
  }
7100
7424
  }
@@ -7105,9 +7429,9 @@ var SessionStore = class {
7105
7429
  for (const f of readdirSync(this.dir)) {
7106
7430
  if (!f.endsWith(".json")) continue;
7107
7431
  try {
7108
- metas.push(JSON.parse(readFileSync4(join6(this.dir, f), "utf8")).meta);
7432
+ metas.push(JSON.parse(readFileSync5(join8(this.dir, f), "utf8")).meta);
7109
7433
  } catch (e) {
7110
- log17.debug(`skipping unreadable session file ${f}`, e);
7434
+ log18.debug(`skipping unreadable session file ${f}`, e);
7111
7435
  }
7112
7436
  }
7113
7437
  return metas.sort((a, b) => b.updated - a.updated);
@@ -7124,11 +7448,11 @@ var SessionStore = class {
7124
7448
  function globalSessionLoad(idOrPrefix) {
7125
7449
  const gd = globalDir();
7126
7450
  if (!existsSync6(gd)) return void 0;
7127
- const exact = join6(gd, `${idOrPrefix}.json`);
7451
+ const exact = join8(gd, `${idOrPrefix}.json`);
7128
7452
  if (existsSync6(exact)) {
7129
7453
  try {
7130
7454
  const target = readlinkSync(exact);
7131
- return JSON.parse(readFileSync4(target, "utf8"));
7455
+ return JSON.parse(readFileSync5(target, "utf8"));
7132
7456
  } catch {
7133
7457
  return void 0;
7134
7458
  }
@@ -7138,8 +7462,8 @@ function globalSessionLoad(idOrPrefix) {
7138
7462
  if (!f.endsWith(".json")) continue;
7139
7463
  const base = f.slice(0, -5);
7140
7464
  if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
7141
- const target = readlinkSync(join6(gd, f));
7142
- return JSON.parse(readFileSync4(target, "utf8"));
7465
+ const target = readlinkSync(join8(gd, f));
7466
+ return JSON.parse(readFileSync5(target, "utf8"));
7143
7467
  }
7144
7468
  }
7145
7469
  } catch {
@@ -7153,15 +7477,15 @@ function globalSessionList() {
7153
7477
  for (const f of readdirSync(gd)) {
7154
7478
  if (!f.endsWith(".json")) continue;
7155
7479
  try {
7156
- const target = readlinkSync(join6(gd, f));
7480
+ const target = readlinkSync(join8(gd, f));
7157
7481
  if (!existsSync6(target)) {
7158
7482
  try {
7159
- unlinkSync(join6(gd, f));
7483
+ unlinkSync(join8(gd, f));
7160
7484
  } catch {
7161
7485
  }
7162
7486
  continue;
7163
7487
  }
7164
- metas.push(JSON.parse(readFileSync4(target, "utf8")).meta);
7488
+ metas.push(JSON.parse(readFileSync5(target, "utf8")).meta);
7165
7489
  } catch {
7166
7490
  }
7167
7491
  }
@@ -7273,18 +7597,18 @@ ${formatDiff(ops)}`);
7273
7597
  // cli/gitCheckpoints.ts
7274
7598
  import { execFile as execFile3 } from "child_process";
7275
7599
  import { promisify } from "util";
7276
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, existsSync as existsSync7 } from "fs";
7277
- import { join as join7, resolve as resolve2, sep as sep2 } from "path";
7278
- var log18 = forComponent("checkpoints");
7600
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, existsSync as existsSync7 } from "fs";
7601
+ import { join as join9, resolve as resolve2, sep as sep2 } from "path";
7602
+ var log19 = forComponent("checkpoints");
7279
7603
  var exec = promisify(execFile3);
7280
7604
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
7281
7605
  var ShadowRepo = class {
7282
7606
  // undefined = unprobed; false = git/this root unusable
7283
- constructor(workTree, gitDir, exclude, git) {
7607
+ constructor(workTree, gitDir, exclude, git2) {
7284
7608
  this.workTree = workTree;
7285
7609
  this.gitDir = gitDir;
7286
7610
  this.exclude = exclude;
7287
- this.git = git;
7611
+ this.git = git2;
7288
7612
  }
7289
7613
  workTree;
7290
7614
  gitDir;
@@ -7307,13 +7631,13 @@ var ShadowRepo = class {
7307
7631
  try {
7308
7632
  await exec(this.git, ["--version"]);
7309
7633
  if (!existsSync7(this.gitDir)) {
7310
- mkdirSync5(this.gitDir, { recursive: true });
7634
+ mkdirSync7(this.gitDir, { recursive: true });
7311
7635
  await this.run("init", "-q");
7312
7636
  }
7313
- writeFileSync4(join7(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
7637
+ writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
7314
7638
  this.ready = true;
7315
7639
  } catch (e) {
7316
- log18.debug(`git checkpoints unavailable for ${this.workTree}`, e);
7640
+ log19.debug(`git checkpoints unavailable for ${this.workTree}`, e);
7317
7641
  this.ready = false;
7318
7642
  }
7319
7643
  return this.ready;
@@ -7324,7 +7648,7 @@ var ShadowRepo = class {
7324
7648
  }
7325
7649
  async commit(label, forced = []) {
7326
7650
  await this.run("add", "-A");
7327
- for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log18.debug(`force-add failed: ${p}`, e));
7651
+ for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log19.debug(`force-add failed: ${p}`, e));
7328
7652
  await this.run("commit", "--allow-empty", "-q", "-m", label);
7329
7653
  }
7330
7654
  /** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
@@ -7332,8 +7656,8 @@ var ShadowRepo = class {
7332
7656
  * turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
7333
7657
  * point per turn, so the REPL's turn↔frame mapping stays intact. */
7334
7658
  async amendForced(paths) {
7335
- for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log18.debug(`force-capture failed: ${p}`, e));
7336
- await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log18.debug("amend failed", e));
7659
+ for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log19.debug(`force-capture failed: ${p}`, e));
7660
+ await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log19.debug("amend failed", e));
7337
7661
  }
7338
7662
  /** Commits on `ref`, oldest-first (canonical index space). */
7339
7663
  async log(ref) {
@@ -7393,7 +7717,7 @@ var ShadowRepo = class {
7393
7717
  await this.run("gc", "--auto", "-q").catch(() => {
7394
7718
  });
7395
7719
  } catch (e) {
7396
- log18.debug("checkpoint prune failed", e);
7720
+ log19.debug("checkpoint prune failed", e);
7397
7721
  }
7398
7722
  }
7399
7723
  };
@@ -7425,7 +7749,7 @@ var GitCheckpoints = class {
7425
7749
  const abs = resolve2(d);
7426
7750
  if (abs === cwd || abs.startsWith(cwd + sep2)) continue;
7427
7751
  if (cwd.startsWith(abs + sep2)) continue;
7428
- out.push({ workTree: abs, gitDir: join7(abs, ".agent", "checkpoints.git") });
7752
+ out.push({ workTree: abs, gitDir: join9(abs, ".agent", "checkpoints.git") });
7429
7753
  }
7430
7754
  return out;
7431
7755
  }
@@ -7452,7 +7776,7 @@ var GitCheckpoints = class {
7452
7776
  use(sessionId) {
7453
7777
  if (sessionId === this.session) return;
7454
7778
  this.session = sessionId;
7455
- if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log18.debug("re-point failed", e));
7779
+ if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log19.debug("re-point failed", e));
7456
7780
  }
7457
7781
  async begin(label) {
7458
7782
  if (!await this.start()) return;
@@ -7464,7 +7788,7 @@ var GitCheckpoints = class {
7464
7788
  try {
7465
7789
  await this.repos[i].commit(msg, forced);
7466
7790
  } catch (e) {
7467
- log18.debug("checkpoint commit failed", e);
7791
+ log19.debug("checkpoint commit failed", e);
7468
7792
  }
7469
7793
  }
7470
7794
  if (slow) clearTimeout(slow);
@@ -7494,7 +7818,7 @@ var GitCheckpoints = class {
7494
7818
  if (this.forced.has(abs)) continue;
7495
7819
  this.forced.add(abs);
7496
7820
  const i = this.repoIndexFor(abs);
7497
- if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log18.debug("amendForced failed", e));
7821
+ if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log19.debug("amendForced failed", e));
7498
7822
  }
7499
7823
  }
7500
7824
  };
@@ -7546,7 +7870,7 @@ var GitCheckpointsOptions = class {
7546
7870
  /** Real working tree to snapshot (the launch cwd). */
7547
7871
  workTree = process.cwd();
7548
7872
  /** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
7549
- gitDir = join7(process.cwd(), ".agent", "checkpoints.git");
7873
+ gitDir = join9(process.cwd(), ".agent", "checkpoints.git");
7550
7874
  /** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
7551
7875
  addDirs = [];
7552
7876
  /** Conversation id → per-session restore-point ref. */
@@ -7560,9 +7884,9 @@ var GitCheckpointsOptions = class {
7560
7884
  };
7561
7885
 
7562
7886
  // cli/permissions.ts
7563
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
7887
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "fs";
7564
7888
  import { homedir as homedir6 } from "os";
7565
- import { join as join8 } from "path";
7889
+ import { join as join10 } from "path";
7566
7890
  var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
7567
7891
  function parseOne(raw, decision) {
7568
7892
  const m = RULE_RE.exec(raw.trim());
@@ -7584,13 +7908,13 @@ function parsePermRules(perms) {
7584
7908
  function describeRule(r) {
7585
7909
  return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
7586
7910
  }
7587
- var PERM_FILE = (cwd) => join8(cwd, ".agent", "permissions.json");
7911
+ var PERM_FILE = (cwd) => join10(cwd, ".agent", "permissions.json");
7588
7912
  function loadPersistedRules(cwd) {
7589
7913
  const j = readJsonFile(PERM_FILE(cwd), null);
7590
7914
  return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
7591
7915
  }
7592
7916
  function loadClaudeSettings(cwd, home = homedir6()) {
7593
- const files = [join8(home, ".claude", "settings.json"), join8(cwd, ".claude", "settings.json"), join8(cwd, ".claude", "settings.local.json")];
7917
+ const files = [join10(home, ".claude", "settings.json"), join10(cwd, ".claude", "settings.json"), join10(cwd, ".claude", "settings.local.json")];
7594
7918
  let out = {};
7595
7919
  for (const p of files) {
7596
7920
  const perms = readJsonFile(p, null)?.permissions;
@@ -7603,8 +7927,8 @@ function persistRule(cwd, decision, ruleStr) {
7603
7927
  const list = cur[decision] ??= [];
7604
7928
  if (!list.includes(ruleStr)) list.push(ruleStr);
7605
7929
  try {
7606
- mkdirSync6(join8(cwd, ".agent"), { recursive: true });
7607
- writeFileSync5(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
7930
+ mkdirSync8(join10(cwd, ".agent"), { recursive: true });
7931
+ writeFileSync6(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
7608
7932
  } catch {
7609
7933
  }
7610
7934
  }
@@ -7617,7 +7941,7 @@ function mergePerms(a, b) {
7617
7941
  }
7618
7942
  return Object.keys(out).length ? out : void 0;
7619
7943
  }
7620
- var TRUST_FILE = join8(homedir6(), ".agent", "trusted.json");
7944
+ var TRUST_FILE = join10(homedir6(), ".agent", "trusted.json");
7621
7945
  function isTrusted(cwd, file = TRUST_FILE) {
7622
7946
  const list = readJsonFile(file, []);
7623
7947
  return Array.isArray(list) && list.includes(cwd);
@@ -7627,8 +7951,8 @@ function trustDir(cwd, file = TRUST_FILE) {
7627
7951
  if (!Array.isArray(list)) list = [];
7628
7952
  if (!list.includes(cwd)) list.push(cwd);
7629
7953
  try {
7630
- mkdirSync6(join8(file, ".."), { recursive: true });
7631
- writeFileSync5(file, JSON.stringify(list, null, 2) + "\n");
7954
+ mkdirSync8(join10(file, ".."), { recursive: true });
7955
+ writeFileSync6(file, JSON.stringify(list, null, 2) + "\n");
7632
7956
  } catch {
7633
7957
  }
7634
7958
  }
@@ -7687,10 +8011,10 @@ function completePath(listDir, ref) {
7687
8011
 
7688
8012
  // cli/lineEditor.ts
7689
8013
  import { emitKeypressEvents } from "readline";
7690
- import { spawnSync as spawnSync3 } from "child_process";
7691
- import { writeFileSync as writeFileSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync2 } from "fs";
8014
+ import { spawnSync as spawnSync4 } from "child_process";
8015
+ import { writeFileSync as writeFileSync7, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
7692
8016
  import { tmpdir as tmpdir2 } from "os";
7693
- import { join as join9 } from "path";
8017
+ import { join as join11 } from "path";
7694
8018
 
7695
8019
  // cli/bidi.ts
7696
8020
  var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
@@ -8767,14 +9091,14 @@ function createLineEditor(out) {
8767
9091
  function externalEdit(s) {
8768
9092
  const spec = process.env.VISUAL || process.env.EDITOR || "vi";
8769
9093
  const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
8770
- const file = join9(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
9094
+ const file = join11(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
8771
9095
  try {
8772
- writeFileSync6(file, s.buf);
9096
+ writeFileSync7(file, s.buf);
8773
9097
  process.stdin.setRawMode(false);
8774
9098
  out.write("\x1B[?2004l");
8775
- const r = spawnSync3(cmd, [...cargs, file], { stdio: "inherit" });
9099
+ const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
8776
9100
  if (r.status === 0) {
8777
- const text = readFileSync5(file, "utf8").replace(/\n$/, "");
9101
+ const text = readFileSync6(file, "utf8").replace(/\n$/, "");
8778
9102
  s.reset();
8779
9103
  if (text) s.insert(text);
8780
9104
  }
@@ -9196,24 +9520,24 @@ function readPlainLine() {
9196
9520
  }
9197
9521
 
9198
9522
  // cli/osScheduler.ts
9199
- import { spawnSync as spawnSync4 } from "child_process";
9200
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync8 } from "fs";
9523
+ import { spawnSync as spawnSync5 } from "child_process";
9524
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync8 } from "fs";
9201
9525
  import { homedir as homedir7 } from "os";
9202
- import { join as join10 } from "path";
9203
- var log19 = forComponent("os-sched");
9526
+ import { join as join12 } from "path";
9527
+ var log20 = forComponent("os-sched");
9204
9528
  var OsScheduler = class {
9205
9529
  options;
9206
9530
  constructor(options) {
9207
9531
  this.options = { ...new OsSchedulerOptions(), ...options };
9208
9532
  }
9209
9533
  get dir() {
9210
- return join10(this.options.home, ".agent", "sched");
9534
+ return join12(this.options.home, ".agent", "sched");
9211
9535
  }
9212
9536
  label(id) {
9213
9537
  return `cc.livx.agentx.sched-${id}`;
9214
9538
  }
9215
9539
  plistPath(id) {
9216
- return join10(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
9540
+ return join12(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
9217
9541
  }
9218
9542
  run(cmd, args, input) {
9219
9543
  return this.options.exec(cmd, args, input);
@@ -9224,16 +9548,16 @@ var OsScheduler = class {
9224
9548
  /** Register the job with the OS. Returns a human description of the mechanism used. Throws on failure. */
9225
9549
  schedule(spec) {
9226
9550
  if (!this.available()) throw new Error(`no OS scheduler on ${this.options.platform}`);
9227
- mkdirSync7(this.dir, { recursive: true });
9551
+ mkdirSync9(this.dir, { recursive: true });
9228
9552
  const oneOff = "at" in spec.trigger;
9229
9553
  const script = this.writeScript(spec, oneOff);
9230
9554
  const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
9231
9555
  const meta = { ...spec, created: Date.now(), mechanism };
9232
- writeFileSync7(join10(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
9556
+ writeFileSync8(join12(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
9233
9557
  return mechanism;
9234
9558
  }
9235
9559
  cancel(id) {
9236
- const meta = readJsonFile(join10(this.dir, `${id}.json`), null);
9560
+ const meta = readJsonFile(join12(this.dir, `${id}.json`), null);
9237
9561
  if (!meta) return false;
9238
9562
  try {
9239
9563
  if (this.options.platform === "darwin") {
@@ -9262,11 +9586,11 @@ var OsScheduler = class {
9262
9586
  }
9263
9587
  }
9264
9588
  } catch (e) {
9265
- log19.debug(`cancel ${id}`, e);
9589
+ log20.debug(`cancel ${id}`, e);
9266
9590
  }
9267
9591
  for (const f of [`${id}.json`, `${id}.sh`]) {
9268
9592
  try {
9269
- unlinkSync3(join10(this.dir, f));
9593
+ unlinkSync3(join12(this.dir, f));
9270
9594
  } catch {
9271
9595
  }
9272
9596
  }
@@ -9274,19 +9598,19 @@ var OsScheduler = class {
9274
9598
  }
9275
9599
  list() {
9276
9600
  if (!existsSync8(this.dir)) return [];
9277
- return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join10(this.dir, f), null)).filter(Boolean);
9601
+ return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join12(this.dir, f), null)).filter(Boolean);
9278
9602
  }
9279
9603
  /** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
9280
9604
  writeScript(spec, oneOff) {
9281
- const p = join10(this.dir, `${spec.id}.sh`);
9605
+ const p = join12(this.dir, `${spec.id}.sh`);
9282
9606
  const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
9283
- const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join10(this.dir, `${spec.id}.json`))} ${q2(p)}
9284
- ` : `rm -f ${q2(join10(this.dir, `${spec.id}.json`))} ${q2(p)}
9607
+ 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)}
9608
+ ` : `rm -f ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
9285
9609
  ` : "";
9286
- writeFileSync7(p, `#!/bin/sh
9610
+ writeFileSync8(p, `#!/bin/sh
9287
9611
  # agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
9288
9612
  cd ${q2(spec.cwd)} || exit 1
9289
- ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join10(this.dir, `${spec.id}.log`))} 2>&1
9613
+ ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join12(this.dir, `${spec.id}.log`))} 2>&1
9290
9614
  ${cleanup}`);
9291
9615
  chmodSync(p, 493);
9292
9616
  return p;
@@ -9323,8 +9647,8 @@ ${trigger}
9323
9647
  <key>RunAtLoad</key><false/>
9324
9648
  </dict></plist>
9325
9649
  `;
9326
- mkdirSync7(join10(this.options.home, "Library", "LaunchAgents"), { recursive: true });
9327
- writeFileSync7(this.plistPath(spec.id), plist);
9650
+ mkdirSync9(join12(this.options.home, "Library", "LaunchAgents"), { recursive: true });
9651
+ writeFileSync8(this.plistPath(spec.id), plist);
9328
9652
  this.run("launchctl", ["load", this.plistPath(spec.id)]);
9329
9653
  return `launchd:${this.label(spec.id)}`;
9330
9654
  }
@@ -9361,7 +9685,7 @@ var OsSchedulerOptions = class {
9361
9685
  /** Injectable executor for tests. Returns stdout+stderr merged — `at` reports "job N" on STDERR,
9362
9686
  * and the job id is what atrm needs for cancel. Throws on non-zero exit. */
9363
9687
  exec = (cmd, args, input) => {
9364
- const r = spawnSync4(cmd, args, { input, encoding: "utf8" });
9688
+ const r = spawnSync5(cmd, args, { input, encoding: "utf8" });
9365
9689
  if (r.error) throw r.error;
9366
9690
  if (r.status !== 0) throw new Error(`${cmd} exited ${r.status}: ${r.stderr || r.stdout}`);
9367
9691
  return `${r.stdout ?? ""}${r.stderr ?? ""}`;
@@ -9376,12 +9700,12 @@ function routeTrigger(trigger, backendHint, now5 = Date.now()) {
9376
9700
  // cli/remoteTrigger.ts
9377
9701
  import { createServer as createServer2, createConnection } from "net";
9378
9702
  import { spawn as spawn3 } from "child_process";
9379
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
9703
+ import { existsSync as existsSync9, mkdirSync as mkdirSync10, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
9380
9704
  import { homedir as homedir8 } from "os";
9381
- import { join as join11 } from "path";
9382
- var log20 = forComponent("remote-trigger");
9383
- var TRIGGER_DIR = () => join11(homedir8(), ".agent", "triggers");
9384
- var sockPath = (sessionId, dir = TRIGGER_DIR()) => join11(dir, `${sessionId}.sock`);
9705
+ import { join as join13 } from "path";
9706
+ var log21 = forComponent("remote-trigger");
9707
+ var TRIGGER_DIR = () => join13(homedir8(), ".agent", "triggers");
9708
+ var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
9385
9709
  var TriggerServer = class {
9386
9710
  constructor(onTrigger) {
9387
9711
  this.onTrigger = onTrigger;
@@ -9393,7 +9717,7 @@ var TriggerServer = class {
9393
9717
  start(sessionId, dir = TRIGGER_DIR()) {
9394
9718
  this.stop();
9395
9719
  try {
9396
- mkdirSync8(dir, { recursive: true, mode: 448 });
9720
+ mkdirSync10(dir, { recursive: true, mode: 448 });
9397
9721
  const p = sockPath(sessionId, dir);
9398
9722
  try {
9399
9723
  unlinkSync4(p);
@@ -9417,13 +9741,13 @@ var TriggerServer = class {
9417
9741
  conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
9418
9742
  }
9419
9743
  });
9420
- conn.on("error", (e) => log20.debug("trigger conn error", e));
9744
+ conn.on("error", (e) => log21.debug("trigger conn error", e));
9421
9745
  });
9422
- this.server.on("error", (e) => log20.debug("trigger server error", e));
9746
+ this.server.on("error", (e) => log21.debug("trigger server error", e));
9423
9747
  this.server.listen(p);
9424
9748
  this.path = p;
9425
9749
  } catch (e) {
9426
- log20.debug("trigger server unavailable", e);
9750
+ log21.debug("trigger server unavailable", e);
9427
9751
  }
9428
9752
  }
9429
9753
  /** Re-bind on /resume (the session id changed). */
@@ -9544,10 +9868,10 @@ var italic = C("3");
9544
9868
  var strike = C("9");
9545
9869
  var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
9546
9870
  var err = (s) => process.stderr.write(s);
9547
- var log21 = forComponent("cli");
9871
+ var log22 = forComponent("cli");
9548
9872
  var VERSION = (() => {
9549
9873
  try {
9550
- return JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
9874
+ return JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
9551
9875
  } catch {
9552
9876
  return "?";
9553
9877
  }
@@ -9659,6 +9983,7 @@ function parseArgs(argv) {
9659
9983
  else if (x === "--reasoning") a.reasoning = parseReasoning(val(++i, x));
9660
9984
  else if (x === "--session-id") a.sessionId = val(++i, x);
9661
9985
  else if (x === "--fork-session") a.fork = true;
9986
+ else if (x === "--worktree" || x === "-w") a.worktree = val(++i, x);
9662
9987
  else if (x === "--max-steps") a.maxSteps = numFlag(argv[++i], "--max-steps");
9663
9988
  else if (x === "--max-tokens") a.maxTokens = numFlag(argv[++i], "--max-tokens");
9664
9989
  else if (x === "--timeout") a.timeoutMs = numFlag(argv[++i], "--timeout") * 1e3;
@@ -9671,6 +9996,7 @@ function parseArgs(argv) {
9671
9996
  if (!a.task && rest.length) a.task = rest.join(" ");
9672
9997
  if (a.boddb && a.vfs) throw new Error("--boddb and --sandbox are mutually exclusive (both are non-disk filesystems; pick one)");
9673
9998
  if (a.seed && !a.boddb) throw new Error("--seed only applies with --boddb (it seeds the database from cwd on first run)");
9999
+ if (a.worktree && (a.vfs || a.boddb)) throw new Error("--worktree is a real-disk concept \u2014 incompatible with --vfs/--boddb");
9674
10000
  if (a.duplex && (a.task || a.print)) throw new Error("--duplex is interactive-only (a conversational mode) \u2014 drop the task/-p");
9675
10001
  if (a.duplex && a.plan) throw new Error("--plan is not supported in --duplex (workers are non-interactive; a plan could never be approved)");
9676
10002
  if (a.voiceModel && !a.duplex) throw new Error("--voice-model only applies with --duplex");
@@ -9722,6 +10048,7 @@ Flags:
9722
10048
  --reasoning <e> extended thinking: off|low|medium|high or a token budget (anthropic/openai)
9723
10049
  --session-id <id> use this id for the session (instead of an auto-generated one)
9724
10050
  --fork-session with -r/-c: branch the resumed session into a new id (source left untouched)
10051
+ -w, --worktree <slug> run in an isolated git worktree (.claude/worktrees/<slug>); auto-cleaned on exit if no changes
9725
10052
  --max-steps <n> step budget (default 30)
9726
10053
  --max-tokens <n> token budget kill-switch (default 200000)
9727
10054
  --timeout <sec> wall-clock kill-switch (default 120)
@@ -9769,11 +10096,11 @@ function resolveModelOrNewest(model) {
9769
10096
  var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
9770
10097
  function loadInstallEnv() {
9771
10098
  let dir = dirname4(import.meta.path);
9772
- for (let i = 0; i < 5 && !existsSync10(join12(dir, "package.json")); i++) dir = dirname4(dir);
10099
+ for (let i = 0; i < 5 && !existsSync10(join14(dir, "package.json")); i++) dir = dirname4(dir);
9773
10100
  for (const name of [".env", ".env.local"]) {
9774
- const file = join12(dir, name);
10101
+ const file = join14(dir, name);
9775
10102
  if (!existsSync10(file)) continue;
9776
- for (const line of readFileSync6(file, "utf8").split("\n")) {
10103
+ for (const line of readFileSync7(file, "utf8").split("\n")) {
9777
10104
  const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
9778
10105
  if (!m || m[1] in process.env) continue;
9779
10106
  let val = m[2].trim();
@@ -10132,7 +10459,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
10132
10459
  function turnCost(model, usage) {
10133
10460
  return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
10134
10461
  }
10135
- async function evaluateGoal(ai, condition, transcript, log22) {
10462
+ async function evaluateGoal(ai, condition, transcript, log23) {
10136
10463
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
10137
10464
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
10138
10465
  return text.slice(0, 600);
@@ -10152,7 +10479,7 @@ ${recent}` }
10152
10479
  const match = r.content.match(/\{[\s\S]*\}/);
10153
10480
  if (match) return JSON.parse(match[0]);
10154
10481
  } catch (e) {
10155
- log22(dim(` (goal evaluator error: ${e?.message ?? e})
10482
+ log23(dim(` (goal evaluator error: ${e?.message ?? e})
10156
10483
  `));
10157
10484
  }
10158
10485
  return { met: false, reason: "evaluation unclear" };
@@ -10359,13 +10686,13 @@ function mcpAgentTools(mounted, opts) {
10359
10686
  return tools;
10360
10687
  }
10361
10688
  async function closeMcp(mounted) {
10362
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log21.debug("mcp close failed", e))));
10689
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log22.debug("mcp close failed", e))));
10363
10690
  }
10364
10691
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
10365
10692
  function mentionRefs(line) {
10366
10693
  return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
10367
10694
  }
10368
- var untilde = (p) => p.startsWith("~/") ? join12(homedir9(), p.slice(2)) : p;
10695
+ var untilde = (p) => p.startsWith("~/") ? join14(homedir9(), p.slice(2)) : p;
10369
10696
  function readImageParts(cwd, line) {
10370
10697
  const refs = mentionRefs(line);
10371
10698
  const parts = [];
@@ -10374,7 +10701,7 @@ function readImageParts(cwd, line) {
10374
10701
  if (!mime) continue;
10375
10702
  const abs = ref.startsWith("~/") ? untilde(ref) : resolve3(cwd, ref);
10376
10703
  try {
10377
- parts.push(imagePart(`data:${mime};base64,${readFileSync6(abs).toString("base64")}`));
10704
+ parts.push(imagePart(`data:${mime};base64,${readFileSync7(abs).toString("base64")}`));
10378
10705
  } catch {
10379
10706
  }
10380
10707
  }
@@ -10388,7 +10715,7 @@ function pastePathClassifier(cwd) {
10388
10715
  if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
10389
10716
  const abs = t.startsWith("~/") ? untilde(t) : resolve3(cwd, t);
10390
10717
  try {
10391
- if (!statSync3(abs).isFile()) return null;
10718
+ if (!statSync4(abs).isFile()) return null;
10392
10719
  } catch {
10393
10720
  return null;
10394
10721
  }
@@ -10409,7 +10736,7 @@ async function expandMentions(fs, line) {
10409
10736
  if (loaded.includes(ref) || missing.includes(ref)) continue;
10410
10737
  if (ref.includes(":") && mcpMentionResolver) {
10411
10738
  const body = await mcpMentionResolver(ref).catch((e) => {
10412
- log21.debug("mcp mention resolve failed", e);
10739
+ log22.debug("mcp mention resolve failed", e);
10413
10740
  return null;
10414
10741
  });
10415
10742
  if (body != null) {
@@ -10492,7 +10819,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
10492
10819
  try {
10493
10820
  store.save(session);
10494
10821
  } catch (ex) {
10495
- log21.debug("mid-turn session flush failed", ex);
10822
+ log22.debug("mid-turn session flush failed", ex);
10496
10823
  }
10497
10824
  }
10498
10825
  return origNotify(e);
@@ -10605,25 +10932,25 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
10605
10932
  `;
10606
10933
  function initInstructions(cwd) {
10607
10934
  for (const f of ["AGENTS.md", "CLAUDE.md"]) {
10608
- if (existsSync10(join12(cwd, f))) {
10935
+ if (existsSync10(join14(cwd, f))) {
10609
10936
  err(yellow(` ${f} already exists \u2014 leaving it as-is
10610
10937
  `));
10611
10938
  return;
10612
10939
  }
10613
10940
  }
10614
- const path = join12(cwd, "AGENTS.md");
10615
- writeFileSync8(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
10941
+ const path = join14(cwd, "AGENTS.md");
10942
+ writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
10616
10943
  err(green(` created ${path}
10617
10944
  `) + dim(" edit it, then it auto-loads into every run.\n"));
10618
10945
  }
10619
10946
  function persistSetting(cwd, key, value) {
10620
- const path = join12(cwd, ".agent", "settings.json");
10947
+ const path = join14(cwd, ".agent", "settings.json");
10621
10948
  try {
10622
- const obj = existsSync10(path) ? JSON.parse(readFileSync6(path, "utf8")) : {};
10949
+ const obj = existsSync10(path) ? JSON.parse(readFileSync7(path, "utf8")) : {};
10623
10950
  if (obj[key] === value) return;
10624
10951
  obj[key] = value;
10625
- mkdirSync9(dirname4(path), { recursive: true });
10626
- writeFileSync8(path, JSON.stringify(obj, null, 2) + "\n");
10952
+ mkdirSync11(dirname4(path), { recursive: true });
10953
+ writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
10627
10954
  } catch (e) {
10628
10955
  err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
10629
10956
  `));
@@ -10639,14 +10966,14 @@ var isCancelTeardown = (e) => {
10639
10966
  function installCancelGuards(mounted) {
10640
10967
  process.on("unhandledRejection", (e) => {
10641
10968
  if (isCancelTeardown(e)) {
10642
- log21.debug("suppressed unhandledRejection (cursor stream cancel)", e);
10969
+ log22.debug("suppressed unhandledRejection (cursor stream cancel)", e);
10643
10970
  return;
10644
10971
  }
10645
- log21.error("unhandledRejection", e);
10972
+ log22.error("unhandledRejection", e);
10646
10973
  });
10647
10974
  process.on("uncaughtException", (e) => {
10648
10975
  if (isCancelTeardown(e)) {
10649
- log21.debug("suppressed uncaughtException (cursor stream cancel)", e);
10976
+ log22.debug("suppressed uncaughtException (cursor stream cancel)", e);
10650
10977
  return;
10651
10978
  }
10652
10979
  console.error(e);
@@ -10655,7 +10982,7 @@ function installCancelGuards(mounted) {
10655
10982
  });
10656
10983
  }
10657
10984
  async function repl(args, ai, cfg, cwd) {
10658
- const oauth = new McpOAuth({ storePath: join12(cwd, ".agent", "mcp-auth.json") });
10985
+ const oauth = new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") });
10659
10986
  const mounted = await mountMcp(cfg, oauth);
10660
10987
  let mcpToolNames = [];
10661
10988
  const initialMcpTools = mcpAgentTools(mounted);
@@ -10863,7 +11190,7 @@ async function repl(args, ai, cfg, cwd) {
10863
11190
  quickLook: {
10864
11191
  branch: () => {
10865
11192
  try {
10866
- const head = readFileSync6(join12(cwd, ".git", "HEAD"), "utf8").trim();
11193
+ const head = readFileSync7(join14(cwd, ".git", "HEAD"), "utf8").trim();
10867
11194
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
10868
11195
  } catch {
10869
11196
  return "not a git repository";
@@ -10992,9 +11319,9 @@ async function repl(args, ai, cfg, cwd) {
10992
11319
  const pendingImages = [];
10993
11320
  const bangContext = [];
10994
11321
  const grabClipboardAttachment = () => {
10995
- const dir = join12(tmpdir3(), "agentx-pasted");
11322
+ const dir = join14(tmpdir3(), "agentx-pasted");
10996
11323
  try {
10997
- mkdirSync9(dir, { recursive: true });
11324
+ mkdirSync11(dir, { recursive: true });
10998
11325
  } catch {
10999
11326
  }
11000
11327
  const img = grabClipboardImage(dir, String(Date.now()));
@@ -11045,7 +11372,7 @@ async function repl(args, ai, cfg, cwd) {
11045
11372
  err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
11046
11373
  `));
11047
11374
  }
11048
- const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join12(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
11375
+ 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 });
11049
11376
  const cpHooks = checkpoints.hooks?.();
11050
11377
  if (cpHooks) {
11051
11378
  agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
@@ -11097,14 +11424,14 @@ ${lines.join("\n")}
11097
11424
  Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
11098
11425
  </system-reminder>`;
11099
11426
  };
11100
- const histPath = join12(cwd, ".agent", "history");
11101
- const history = existsSync10(histPath) ? readFileSync6(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
11427
+ const histPath = join14(cwd, ".agent", "history");
11428
+ const history = existsSync10(histPath) ? readFileSync7(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
11102
11429
  const remember = (line) => {
11103
11430
  try {
11104
- mkdirSync9(join12(cwd, ".agent"), { recursive: true });
11431
+ mkdirSync11(join14(cwd, ".agent"), { recursive: true });
11105
11432
  appendFileSync(histPath, line + "\n");
11106
11433
  } catch (e) {
11107
- log21.debug("history write failed", e);
11434
+ log22.debug("history write failed", e);
11108
11435
  }
11109
11436
  };
11110
11437
  const ago = (t) => {
@@ -11175,7 +11502,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
11175
11502
  try {
11176
11503
  store.save(session);
11177
11504
  } catch (e) {
11178
- log21.debug("session save after rewind failed", e);
11505
+ log22.debug("session save after rewind failed", e);
11179
11506
  }
11180
11507
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
11181
11508
  `));
@@ -11209,7 +11536,7 @@ ${task}`;
11209
11536
  bangContext.length = 0;
11210
11537
  }
11211
11538
  const delta = await refreshCatalogs().catch((e) => {
11212
- log21.debug("catalog refresh failed", e);
11539
+ log22.debug("catalog refresh failed", e);
11213
11540
  return "";
11214
11541
  });
11215
11542
  if (delta) {
@@ -11401,8 +11728,8 @@ ${task}`;
11401
11728
  const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
11402
11729
  if (wasRaw) process.stdin.setRawMode(false);
11403
11730
  try {
11404
- const { spawnSync: spawnSync5 } = await import("child_process");
11405
- const r = spawnSync5("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
11731
+ const { spawnSync: spawnSync6 } = await import("child_process");
11732
+ const r = spawnSync6("less", ["-R"], { input: text, stdio: ["pipe", "inherit", "inherit"] });
11406
11733
  if (r.error) err(text);
11407
11734
  } finally {
11408
11735
  if (wasRaw) process.stdin.setRawMode(true);
@@ -11426,8 +11753,8 @@ ${task}`;
11426
11753
  cfgFiles.length ? ok(`config: ${cfgFiles.join(", ")}`) : warn("no .agent/config.* found (project or ~) \u2014 running on defaults");
11427
11754
  try {
11428
11755
  const probe = `${cwd}/.agent/sessions/.doctor-probe`;
11429
- mkdirSync9(`${cwd}/.agent/sessions`, { recursive: true });
11430
- writeFileSync8(probe, "ok");
11756
+ mkdirSync11(`${cwd}/.agent/sessions`, { recursive: true });
11757
+ writeFileSync9(probe, "ok");
11431
11758
  unlinkSync5(probe);
11432
11759
  ok(`session store writable (${cwd}/.agent/sessions)`);
11433
11760
  } catch (e) {
@@ -11455,7 +11782,7 @@ ${task}`;
11455
11782
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
11456
11783
  run: async () => {
11457
11784
  await refreshCatalogs().catch((e) => {
11458
- log21.debug("catalog refresh failed", e);
11785
+ log22.debug("catalog refresh failed", e);
11459
11786
  });
11460
11787
  face.reprepare();
11461
11788
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -11792,8 +12119,8 @@ ${task}`;
11792
12119
  const wasRaw = process.stdin.isTTY && process.stdin.isRaw;
11793
12120
  if (wasRaw) process.stdin.setRawMode(false);
11794
12121
  try {
11795
- const { spawnSync: spawnSync5 } = await import("child_process");
11796
- spawnSync5(ed, [idx], { stdio: "inherit" });
12122
+ const { spawnSync: spawnSync6 } = await import("child_process");
12123
+ spawnSync6(ed, [idx], { stdio: "inherit" });
11797
12124
  } finally {
11798
12125
  if (wasRaw) process.stdin.setRawMode(true);
11799
12126
  }
@@ -11912,7 +12239,7 @@ ${task}`;
11912
12239
  try {
11913
12240
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
11914
12241
  } catch (e) {
11915
- log21.debug(`loadAgents(${d}) failed`, e);
12242
+ log22.debug(`loadAgents(${d}) failed`, e);
11916
12243
  }
11917
12244
  }
11918
12245
  if (!seen.size) {
@@ -12000,7 +12327,7 @@ ${task}`;
12000
12327
  }
12001
12328
  if (idx >= 0) {
12002
12329
  const old = mounted.splice(idx, 1)[0];
12003
- await old.client.close().catch((e) => log21.debug("mcp close failed", e));
12330
+ await old.client.close().catch((e) => log22.debug("mcp close failed", e));
12004
12331
  }
12005
12332
  try {
12006
12333
  const m = await mountMcpServer(name, conf);
@@ -12028,7 +12355,7 @@ ${task}`;
12028
12355
  }
12029
12356
  const m = mounted.splice(idx, 1)[0];
12030
12357
  remountMcpTools();
12031
- await m.client.close().catch((e) => log21.debug("mcp close failed", e));
12358
+ await m.client.close().catch((e) => log22.debug("mcp close failed", e));
12032
12359
  err(dim(` removed "${name}"
12033
12360
  `));
12034
12361
  return;
@@ -12160,11 +12487,11 @@ ${task}`;
12160
12487
  return;
12161
12488
  }
12162
12489
  const md = exportMarkdown(session.meta, shown);
12163
- const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join12(".agent", "exports", `${session.meta.id}.md`);
12490
+ const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
12164
12491
  const path = resolve3(cwd, name);
12165
12492
  try {
12166
- mkdirSync9(dirname4(path), { recursive: true });
12167
- writeFileSync8(path, md);
12493
+ mkdirSync11(dirname4(path), { recursive: true });
12494
+ writeFileSync9(path, md);
12168
12495
  err(green(` \u2713 exported \u2192 ${path}
12169
12496
  `) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
12170
12497
  `));
@@ -12226,9 +12553,9 @@ ${task}`;
12226
12553
  if (dx) banner(dim(`\u25D1 duplex \u2014 reflex: ${dx.options.reflexModel} \xB7 act: ${work.model}${dx.options.thinkModel !== false ? ` \xB7 think: ${dx.options.thinkModel}` : ""} (real work runs in background tasks, re-voiced when done)`));
12227
12554
  const listDir = (absDir) => {
12228
12555
  try {
12229
- return readdirSync4(join12(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
12556
+ return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
12230
12557
  } catch (e) {
12231
- log21.debug("completion readdir failed", absDir, e);
12558
+ log22.debug("completion readdir failed", absDir, e);
12232
12559
  return null;
12233
12560
  }
12234
12561
  };
@@ -12355,18 +12682,18 @@ ${out}
12355
12682
  return;
12356
12683
  }
12357
12684
  await refreshCatalogs().catch((e) => {
12358
- log21.debug("catalog refresh failed", e);
12685
+ log22.debug("catalog refresh failed", e);
12359
12686
  });
12360
- const c = cmds.find((x) => x.name === name);
12361
- if (c) {
12362
- await runCommand(c, a.join(" "));
12363
- return;
12364
- }
12365
12687
  const sk = skills.find((x) => x.name === name);
12366
12688
  if (sk) {
12367
12689
  await runSkill(sk, a.join(" "));
12368
12690
  return;
12369
12691
  }
12692
+ const c = cmds.find((x) => x.name === name);
12693
+ if (c) {
12694
+ await runCommand(c, a.join(" "));
12695
+ return;
12696
+ }
12370
12697
  const known = Object.keys(builtins).filter((k) => k !== "quit").map((k) => "/" + k);
12371
12698
  const custom = [...cmds.map((x) => x.name), ...skills.map((x) => x.name)].map((n) => "/" + n);
12372
12699
  err(red(` unknown command /${name}
@@ -12697,6 +13024,19 @@ async function main() {
12697
13024
  }
12698
13025
  if (args.debug) process.env.DEBUG ||= "*";
12699
13026
  if (args.print && !args.task && !process.stdin.isTTY) args.task = (await readAllStdin()).trim();
13027
+ let worktreeCleanup;
13028
+ if (args.worktree) {
13029
+ const { enterWorktree: enterWorktree2, exitWorktree: exitWorktree2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
13030
+ const session = enterWorktree2(args.worktree, resolve3(args.cwd ?? process.cwd()));
13031
+ err(dim(` \u2295 worktree: ${session.worktreePath} (branch ${session.branch})
13032
+ `));
13033
+ worktreeCleanup = () => {
13034
+ const r = exitWorktree2();
13035
+ if (r?.removed) err(dim(" \u2296 worktree cleaned up (no changes)\n"));
13036
+ else if (r && !r.removed) err(dim(` \u2295 worktree kept: ${r.reason}
13037
+ `));
13038
+ };
13039
+ }
12700
13040
  const cwd = resolve3(args.cwd ?? process.cwd());
12701
13041
  const cfg = await loadConfig(cwd);
12702
13042
  cfg.permissions = mergePerms(loadClaudeSettings(cwd), mergePerms(loadPersistedRules(cwd), cfg.permissions));
@@ -12742,7 +13082,7 @@ async function main() {
12742
13082
  }
12743
13083
  });
12744
13084
  if (args.task) {
12745
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join12(cwd, ".agent", "mcp-auth.json") }));
13085
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") }));
12746
13086
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted));
12747
13087
  const store = new SessionStore(cwd);
12748
13088
  const session = startSession(args, store, agent, cwd);
@@ -12759,9 +13099,11 @@ async function main() {
12759
13099
  const obj = res ? jsonResult(res, session) : { ok: false, finishReason: "error", sessionId: session.meta.id };
12760
13100
  process.stdout.write(JSON.stringify(args.outputFormat === "stream-json" ? { type: "result", ...obj } : obj) + "\n");
12761
13101
  }
13102
+ worktreeCleanup?.();
12762
13103
  process.exit(ok ? 0 : 1);
12763
13104
  }
12764
13105
  await repl(args, ai, cfg, cwd);
13106
+ worktreeCleanup?.();
12765
13107
  }
12766
13108
  if (import.meta.main) main().catch((e) => {
12767
13109
  console.error(e?.message ?? e);