@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/index.js CHANGED
@@ -1588,7 +1588,7 @@ function makeRealShellTool(options) {
1588
1588
  proc.stderr?.on("data", collect);
1589
1589
  proc.on("error", (err) => {
1590
1590
  if (err?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
1591
- log4.debug("shell spawn error", err);
1591
+ log5.debug("shell spawn error", err);
1592
1592
  finish(`[exit 1] ${err?.message ?? err}${out ? "\n" + clean(out) : ""}`);
1593
1593
  });
1594
1594
  proc.on("close", (code) => {
@@ -1650,7 +1650,7 @@ ${clean(out) || "(no output yet)"}`;
1650
1650
  }
1651
1651
  ];
1652
1652
  }
1653
- var log4, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
1653
+ var log5, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
1654
1654
  var init_tools_shell = __esm({
1655
1655
  "src/tools.shell.ts"() {
1656
1656
  "use strict";
@@ -1658,7 +1658,7 @@ var init_tools_shell = __esm({
1658
1658
  init_redact();
1659
1659
  init_logging();
1660
1660
  init_shell_sandbox();
1661
- log4 = forComponent("shell");
1661
+ log5 = forComponent("shell");
1662
1662
  clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
1663
1663
  DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
1664
1664
  SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
@@ -2435,7 +2435,163 @@ ${sections.join("\n\n---\n\n")}`;
2435
2435
 
2436
2436
  // src/subagent.ts
2437
2437
  init_tools();
2438
+ import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
2439
+ import { join as join3 } from "path";
2438
2440
  init_OverlayFilesystem();
2441
+
2442
+ // src/worktree.ts
2443
+ import { spawnSync } from "child_process";
2444
+ import { symlinkSync, mkdirSync, statSync, readFileSync, rmSync } from "fs";
2445
+ import { join } from "path";
2446
+ var VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
2447
+ var MAX_SLUG_LENGTH = 64;
2448
+ var GIT_NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
2449
+ function validateWorktreeSlug(slug2) {
2450
+ if (!slug2) throw new Error("worktree slug must not be empty");
2451
+ if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
2452
+ for (const seg of slug2.split("/")) {
2453
+ if (seg === "." || seg === "..") throw new Error(`worktree slug must not contain "." or ".." segments`);
2454
+ if (!VALID_SEGMENT.test(seg)) throw new Error(`worktree slug segment "${seg}" contains invalid characters (allowed: a-z A-Z 0-9 . _ -)`);
2455
+ }
2456
+ }
2457
+ function flattenSlug(slug2) {
2458
+ return slug2.replaceAll("/", "+");
2459
+ }
2460
+ function worktreeBranchName(slug2) {
2461
+ return `worktree-${flattenSlug(slug2)}`;
2462
+ }
2463
+ function worktreesDir(repoRoot) {
2464
+ return join(repoRoot, ".claude", "worktrees");
2465
+ }
2466
+ function worktreePathFor(repoRoot, slug2) {
2467
+ return join(worktreesDir(repoRoot), flattenSlug(slug2));
2468
+ }
2469
+ function git(args, cwd) {
2470
+ const r = spawnSync("git", args, {
2471
+ cwd,
2472
+ stdio: ["ignore", "pipe", "pipe"],
2473
+ env: { ...process.env, ...GIT_NO_PROMPT_ENV }
2474
+ });
2475
+ return { ok: r.status === 0, stdout: (r.stdout ?? "").toString().trim(), stderr: (r.stderr ?? "").toString().trim() };
2476
+ }
2477
+ function findGitRoot(from) {
2478
+ const r = git(["rev-parse", "--show-toplevel"], from);
2479
+ return r.ok ? r.stdout : null;
2480
+ }
2481
+ function readWorktreeHead(worktreePath) {
2482
+ try {
2483
+ const gitFile = readFileSync(join(worktreePath, ".git"), "utf-8").trim();
2484
+ const m = gitFile.match(/^gitdir:\s*(.+)$/);
2485
+ if (!m) return null;
2486
+ const headPath = join(m[1], "HEAD");
2487
+ const head = readFileSync(headPath, "utf-8").trim();
2488
+ const refMatch = head.match(/^ref:\s*(.+)$/);
2489
+ if (!refMatch) return head.length === 40 ? head : null;
2490
+ const refPath = join(m[1], "..", refMatch[1]);
2491
+ try {
2492
+ return readFileSync(refPath, "utf-8").trim();
2493
+ } catch {
2494
+ }
2495
+ const r = git(["rev-parse", "HEAD"], worktreePath);
2496
+ return r.ok ? r.stdout : null;
2497
+ } catch {
2498
+ return null;
2499
+ }
2500
+ }
2501
+ function getOrCreateWorktree(repoRoot, slug2) {
2502
+ validateWorktreeSlug(slug2);
2503
+ const worktreePath = worktreePathFor(repoRoot, slug2);
2504
+ const branch = worktreeBranchName(slug2);
2505
+ const existingHead = readWorktreeHead(worktreePath);
2506
+ if (existingHead) return { worktreePath, branch, headCommit: existingHead, existed: true };
2507
+ mkdirSync(worktreesDir(repoRoot), { recursive: true });
2508
+ const defaultBranch = git(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoRoot);
2509
+ let baseBranch = defaultBranch.ok ? defaultBranch.stdout.replace(/^origin\//, "") : "HEAD";
2510
+ const originRef = `origin/${baseBranch}`;
2511
+ const hasOrigin = git(["rev-parse", "--verify", originRef], repoRoot);
2512
+ if (!hasOrigin.ok) {
2513
+ const fetched = git(["fetch", "origin", baseBranch], repoRoot);
2514
+ if (!fetched.ok) baseBranch = "HEAD";
2515
+ }
2516
+ const base = baseBranch === "HEAD" ? "HEAD" : originRef;
2517
+ const r = git(["worktree", "add", "-B", branch, worktreePath, base], repoRoot);
2518
+ if (!r.ok) throw new Error(`failed to create worktree: ${r.stderr}`);
2519
+ const headSha = git(["rev-parse", "HEAD"], worktreePath);
2520
+ symlinkDirs(repoRoot, worktreePath, ["node_modules", ".bun"]);
2521
+ return { worktreePath, branch, headCommit: headSha.stdout, existed: false, baseBranch };
2522
+ }
2523
+ function symlinkDirs(repoRoot, worktreePath, dirs) {
2524
+ for (const dir of dirs) {
2525
+ const src = join(repoRoot, dir);
2526
+ const dst = join(worktreePath, dir);
2527
+ try {
2528
+ statSync(src);
2529
+ } catch {
2530
+ continue;
2531
+ }
2532
+ try {
2533
+ symlinkSync(src, dst, "dir");
2534
+ } catch (e) {
2535
+ if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
2536
+ }
2537
+ }
2538
+ }
2539
+ function cleanupWorktree(repoRoot, slug2, opts) {
2540
+ validateWorktreeSlug(slug2);
2541
+ const worktreePath = worktreePathFor(repoRoot, slug2);
2542
+ const branch = worktreeBranchName(slug2);
2543
+ try {
2544
+ statSync(worktreePath);
2545
+ } catch {
2546
+ return { removed: false, reason: "not found" };
2547
+ }
2548
+ if (!opts?.force) {
2549
+ const status = git(["status", "--porcelain", "-uno"], worktreePath);
2550
+ if (status.ok && status.stdout) return { removed: false, reason: "uncommitted changes" };
2551
+ }
2552
+ for (const dir of ["node_modules", ".bun"]) {
2553
+ try {
2554
+ rmSync(join(worktreePath, dir));
2555
+ } catch {
2556
+ }
2557
+ }
2558
+ const rm = git(["worktree", "remove", "--force", worktreePath], repoRoot);
2559
+ if (!rm.ok) return { removed: false, reason: rm.stderr };
2560
+ git(["branch", "-D", branch], repoRoot);
2561
+ return { removed: true };
2562
+ }
2563
+ var _session = null;
2564
+ function getWorktreeSession() {
2565
+ return _session;
2566
+ }
2567
+ function enterWorktree(slug2, cwd) {
2568
+ const repoRoot = findGitRoot(cwd);
2569
+ if (!repoRoot) throw new Error("--worktree requires a git repository");
2570
+ const info = getOrCreateWorktree(repoRoot, slug2);
2571
+ process.chdir(info.worktreePath);
2572
+ _session = {
2573
+ originalCwd: cwd,
2574
+ worktreePath: info.worktreePath,
2575
+ slug: slug2,
2576
+ branch: info.branch,
2577
+ headCommit: info.headCommit
2578
+ };
2579
+ return _session;
2580
+ }
2581
+ function exitWorktree() {
2582
+ if (!_session) return null;
2583
+ const repoRoot = findGitRoot(_session.originalCwd);
2584
+ if (!repoRoot) return null;
2585
+ process.chdir(_session.originalCwd);
2586
+ const result = cleanupWorktree(repoRoot, _session.slug);
2587
+ _session = null;
2588
+ return result;
2589
+ }
2590
+
2591
+ // src/subagent.ts
2592
+ init_NodeDiskFilesystem();
2593
+ init_logging();
2594
+ var log3 = forComponent("subagent");
2439
2595
  async function boundedPool(items, limit, fn) {
2440
2596
  const out = new Array(items.length);
2441
2597
  let next = 0;
@@ -2448,13 +2604,86 @@ async function boundedPool(items, limit, fn) {
2448
2604
  await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
2449
2605
  return out;
2450
2606
  }
2607
+ function sanitizeSlug(raw) {
2608
+ return raw.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/-+/g, "-").slice(0, 40);
2609
+ }
2610
+ function createWorktreeFs(cwd, label, index) {
2611
+ const repoRoot = findGitRoot(cwd);
2612
+ if (!repoRoot) return 'isolation: "worktree" requires a git repository';
2613
+ const slug2 = `sub-${index}-${sanitizeSlug(label)}`;
2614
+ try {
2615
+ const info = getOrCreateWorktree(repoRoot, slug2);
2616
+ return { repoRoot, slug: slug2, worktreePath: info.worktreePath, branch: info.branch };
2617
+ } catch (e) {
2618
+ return `worktree creation failed: ${e instanceof Error ? e.message : String(e)}`;
2619
+ }
2620
+ }
2621
+ function releaseWorktree(handle) {
2622
+ return cleanupWorktree(handle.repoRoot, handle.slug, { force: true });
2623
+ }
2624
+ function worktreePromptPrefix(branch) {
2625
+ 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).
2626
+
2627
+ `;
2628
+ }
2629
+ var traceSeq = 0;
2630
+ function traceFileFor(dir, label, depth) {
2631
+ try {
2632
+ mkdirSync2(dir, { recursive: true });
2633
+ const id = `${Date.now().toString(36)}-${(traceSeq++).toString(36)}`;
2634
+ return join3(dir, `${id}-d${depth}-${sanitizeSlug(label) || "sub"}.json`);
2635
+ } catch (e) {
2636
+ log3.warn(`subagent trace dir unavailable (${dir}) \u2014 running without a trace: ${e instanceof Error ? e.message : String(e)}`);
2637
+ return void 0;
2638
+ }
2639
+ }
2640
+ function flushTrace(file, meta, messages, status) {
2641
+ try {
2642
+ writeFileSync(file, JSON.stringify({ ...meta, status, updated: Date.now(), messages }, null, 2));
2643
+ } catch (e) {
2644
+ log3.warn(`subagent trace flush failed (${file}): ${e instanceof Error ? e.message : String(e)}`);
2645
+ }
2646
+ }
2647
+ function installTrace(child, file, meta) {
2648
+ const prev = child.options.host;
2649
+ child.options.host = {
2650
+ ...prev,
2651
+ notify: (e) => {
2652
+ if (e.kind === "turn_start") flushTrace(file, meta, child.transcript, "running");
2653
+ prev?.notify?.(e);
2654
+ }
2655
+ };
2656
+ }
2657
+ function abortHint(label, res, file) {
2658
+ const last = (res.text || "").trim().slice(0, 500);
2659
+ const lines = [
2660
+ `[sub-task '${label}' did NOT finish \u2014 ${res.finishReason} after ${res.steps} step(s). Treat its work as INCOMPLETE.]`,
2661
+ last ? `Progress so far: ${last}` : "It produced no summary before stopping."
2662
+ ];
2663
+ if (file) lines.push(`Full transcript (every step it took): ${file} \u2014 Read it to see what was actually done.`);
2664
+ 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.");
2665
+ return lines.join("\n");
2666
+ }
2667
+ async function runChildTraced(args) {
2668
+ const { opts, label, agentType, prompt } = args;
2669
+ if (args.signal) args.childOpts.signal = args.signal;
2670
+ const file = opts.traceDir ? traceFileFor(opts.traceDir, label, args.childDepth) : void 0;
2671
+ const meta = { label, agentType, prompt };
2672
+ const child = new Agent(args.childOpts);
2673
+ if (file) installTrace(child, file, meta);
2674
+ const res = await child.run(prompt);
2675
+ if (file) flushTrace(file, meta, child.transcript, res.finishReason);
2676
+ const result = res.finishReason === "stop" ? res.text || `(child '${label}' finished with no summary; finishReason=${res.finishReason})` : abortHint(label, res, file);
2677
+ await opts.hooks?.onSubagentStop?.(result, { label, agentType });
2678
+ return { res, result };
2679
+ }
2451
2680
  function childOptionsFor(opts, fs, depth, maxDepth, agentType) {
2452
2681
  let def;
2453
2682
  if (agentType) {
2454
2683
  def = (opts.agents ?? []).find((a) => a.name === agentType);
2455
2684
  if (!def) return `no subagent type '${agentType}'. Available: ${(opts.agents ?? []).map((a) => a.name).join(", ") || "(none defined)"}`;
2456
2685
  }
2457
- const childOpts = { ai: opts.ai, fs, model: def?.model ?? opts.model, subagents: true, depth: depth + 1, maxDepth };
2686
+ const childOpts = { ai: opts.ai, fs, model: def?.model ?? opts.model, subagents: true, depth: depth + 1, maxDepth, traceDir: opts.traceDir };
2458
2687
  if (opts.maxSteps != null) childOpts.maxSteps = opts.maxSteps;
2459
2688
  if (def?.systemPrompt) childOpts.systemPrompt = def.systemPrompt;
2460
2689
  if (def?.tools?.length) {
@@ -2471,7 +2700,7 @@ function makeTaskTool(opts) {
2471
2700
  const maxDepth = opts.maxDepth ?? 2;
2472
2701
  return {
2473
2702
  name: "Task",
2474
- 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.",
2703
+ 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.',
2475
2704
  parameters: {
2476
2705
  type: "object",
2477
2706
  required: ["description", "prompt"],
@@ -2479,36 +2708,83 @@ function makeTaskTool(opts) {
2479
2708
  description: { type: "string", description: "a short (3-5 word) label for the sub-task" },
2480
2709
  prompt: { type: "string", description: "the full instructions the child agent should carry out" },
2481
2710
  agentType: { type: "string", description: "optional named subagent type (its persona, model, and scoped tools) \u2014 see the catalog" },
2482
- background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" }
2711
+ background: { type: "boolean", description: "run detached (overlay-isolated, commits on finish); returns a job id to poll with JobOutput instead of blocking" },
2712
+ isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode: "overlay" (default, VFS layer) or "worktree" (real git worktree for shell-heavy work)' }
2483
2713
  }
2484
2714
  },
2485
- async run({ description, prompt, agentType, background }, ctx) {
2715
+ async run({ description, prompt, agentType, background, isolation }, ctx) {
2486
2716
  if (depth >= maxDepth) {
2487
2717
  return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
2488
2718
  }
2489
2719
  const label = String(description ?? agentType ?? "sub-task");
2720
+ const useWorktree = isolation === "worktree";
2490
2721
  if (background && ctx.jobs) {
2491
2722
  const id = ctx.jobs.start(async ({ signal }) => {
2492
- const overlay = new OverlayFilesystem(opts.fs);
2493
- const childOpts2 = childOptionsFor(opts, overlay, depth, maxDepth, agentType);
2723
+ let fs2;
2724
+ let commit;
2725
+ let wtHandle2;
2726
+ if (useWorktree) {
2727
+ const h = createWorktreeFs(process.cwd(), label, 0);
2728
+ if (typeof h === "string") throw new Error(h);
2729
+ wtHandle2 = h;
2730
+ fs2 = new NodeDiskFilesystem(h.worktreePath);
2731
+ commit = async () => {
2732
+ };
2733
+ } else {
2734
+ const overlay = new OverlayFilesystem(opts.fs);
2735
+ fs2 = overlay;
2736
+ commit = () => overlay.commit();
2737
+ }
2738
+ const childOpts2 = childOptionsFor(opts, fs2, depth, maxDepth, agentType);
2494
2739
  if (typeof childOpts2 === "string") throw new Error(childOpts2);
2495
- childOpts2.signal = signal;
2496
- const res2 = await new Agent(childOpts2).run(String(prompt ?? ""));
2497
- if (signal.aborted) return "[killed before commit]";
2498
- await overlay.commit();
2499
- const summary2 = res2.text || `(child '${label}' finished with no summary; finishReason=${res2.finishReason})`;
2500
- await opts.hooks?.onSubagentStop?.(summary2, { label, agentType });
2501
- return summary2;
2740
+ const childPrompt = wtHandle2 ? worktreePromptPrefix(wtHandle2.branch) + String(prompt ?? "") : String(prompt ?? "");
2741
+ const { res, result } = await runChildTraced({ opts, childOpts: childOpts2, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal });
2742
+ if (res.finishReason === "aborted" || signal.aborted) {
2743
+ wtHandle2 && releaseWorktree(wtHandle2);
2744
+ return result;
2745
+ }
2746
+ await commit();
2747
+ let summary = result;
2748
+ if (wtHandle2) {
2749
+ const r = releaseWorktree(wtHandle2);
2750
+ if (!r.removed) summary += `
2751
+
2752
+ [worktree removal failed: ${r.reason}]`;
2753
+ }
2754
+ return summary;
2502
2755
  }, { kind: "agent", label });
2503
2756
  return `Started background sub-task ${id} ('${label}') \u2014 poll with JobOutput({id:"${id}"}).`;
2504
2757
  }
2505
- const childOpts = childOptionsFor(opts, opts.fs, depth, maxDepth, agentType);
2506
- if (typeof childOpts === "string") return `Error: ${childOpts}`;
2507
- const child = new Agent(childOpts);
2508
- const res = await child.run(String(prompt ?? ""));
2509
- const summary = res.text || `(child agent finished '${label}' with no summary; finishReason=${res.finishReason})`;
2510
- await opts.hooks?.onSubagentStop?.(summary, { label, agentType });
2511
- return summary;
2758
+ let fs;
2759
+ let wtHandle;
2760
+ if (useWorktree) {
2761
+ const h = createWorktreeFs(process.cwd(), label, 0);
2762
+ if (typeof h === "string") return `Error: ${h}`;
2763
+ wtHandle = h;
2764
+ fs = new NodeDiskFilesystem(h.worktreePath);
2765
+ } else {
2766
+ fs = opts.fs;
2767
+ }
2768
+ const childOpts = childOptionsFor(opts, fs, depth, maxDepth, agentType);
2769
+ if (typeof childOpts === "string") {
2770
+ wtHandle && releaseWorktree(wtHandle);
2771
+ return `Error: ${childOpts}`;
2772
+ }
2773
+ try {
2774
+ const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(prompt ?? "") : String(prompt ?? "");
2775
+ const { result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType, childDepth: depth + 1, signal: ctx.signal });
2776
+ let summary = result;
2777
+ if (wtHandle) {
2778
+ const r = releaseWorktree(wtHandle);
2779
+ if (!r.removed) summary += `
2780
+
2781
+ [worktree removal failed: ${r.reason}]`;
2782
+ }
2783
+ return summary;
2784
+ } catch (e) {
2785
+ if (wtHandle) releaseWorktree(wtHandle);
2786
+ throw e;
2787
+ }
2512
2788
  }
2513
2789
  };
2514
2790
  }
@@ -2518,7 +2794,7 @@ function makeTaskBatchTool(opts) {
2518
2794
  const maxParallel = opts.maxParallel ?? 4;
2519
2795
  return {
2520
2796
  name: "TaskBatch",
2521
- 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? }]`.",
2797
+ 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.',
2522
2798
  parameters: {
2523
2799
  type: "object",
2524
2800
  required: ["tasks"],
@@ -2535,30 +2811,53 @@ function makeTaskBatchTool(opts) {
2535
2811
  agentType: { type: "string", description: "optional named subagent type" }
2536
2812
  }
2537
2813
  }
2538
- }
2814
+ },
2815
+ isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
2539
2816
  }
2540
2817
  },
2541
- async run({ tasks }, _ctx) {
2818
+ async run({ tasks, isolation }, ctx) {
2542
2819
  if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
2543
2820
  const list = Array.isArray(tasks) ? tasks : [];
2544
2821
  if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
2822
+ const useWorktree = isolation === "worktree";
2545
2823
  const results = await boundedPool(list, maxParallel, async (t, i) => {
2546
2824
  const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
2547
- const overlay = new OverlayFilesystem(opts.fs);
2548
- const childOpts = childOptionsFor(opts, overlay, depth, maxDepth, t?.agentType);
2825
+ let fs;
2826
+ let overlay;
2827
+ let wtHandle;
2828
+ if (useWorktree) {
2829
+ const h = createWorktreeFs(process.cwd(), label, i);
2830
+ if (typeof h === "string") return { label, error: h, ok: false };
2831
+ wtHandle = h;
2832
+ fs = new NodeDiskFilesystem(h.worktreePath);
2833
+ } else {
2834
+ overlay = new OverlayFilesystem(opts.fs);
2835
+ fs = overlay;
2836
+ }
2837
+ const childOpts = childOptionsFor(opts, fs, depth, maxDepth, t?.agentType);
2549
2838
  if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
2550
2839
  try {
2551
- const res = await new Agent(childOpts).run(String(t?.prompt ?? ""));
2552
- await opts.hooks?.onSubagentStop?.(res.text, { label, agentType: t?.agentType });
2553
- return { label, text: res.text, overlay, ok: res.finishReason !== "error" };
2840
+ const childPrompt = wtHandle ? worktreePromptPrefix(wtHandle.branch) + String(t?.prompt ?? "") : String(t?.prompt ?? "");
2841
+ const { res, result } = await runChildTraced({ opts, childOpts, prompt: childPrompt, label, agentType: t?.agentType, childDepth: depth + 1, signal: ctx.signal });
2842
+ return { label, text: result, overlay, wtHandle, ok: res.finishReason !== "error" && res.finishReason !== "aborted" };
2554
2843
  } catch (e) {
2555
- return { label, error: e instanceof Error ? e.message : String(e), ok: false };
2844
+ return { label, error: e instanceof Error ? e.message : String(e), wtHandle, ok: false };
2556
2845
  }
2557
2846
  });
2558
- for (const r of results) if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
2559
- });
2560
- return results.map((r, i) => `### ${i + 1}. ${r.label}
2561
- ${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}`).join("\n\n");
2847
+ const lines = [];
2848
+ for (const r of results) {
2849
+ if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
2850
+ });
2851
+ let extra = "";
2852
+ if (r.wtHandle) {
2853
+ const wr = releaseWorktree(r.wtHandle);
2854
+ if (!wr.removed) extra = `
2855
+ [worktree removal failed: ${wr.reason}]`;
2856
+ }
2857
+ lines.push(`### ${lines.length + 1}. ${r.label}
2858
+ ${r.error ? `ERROR: ${r.error}` : r.text || "(no summary)"}${extra}`);
2859
+ }
2860
+ return lines.join("\n\n");
2562
2861
  }
2563
2862
  };
2564
2863
  }
@@ -2803,7 +3102,7 @@ function reasoningToChatFragment(model, effort) {
2803
3102
  }
2804
3103
 
2805
3104
  // src/Agent.ts
2806
- var log3 = forComponent("Agent");
3105
+ var log4 = forComponent("Agent");
2807
3106
  function isAbortError(err) {
2808
3107
  const e = err;
2809
3108
  const blob = `${e?.message ?? ""} ${e?.name ?? ""} ${e?.code ?? ""} ${e?.cause?.name ?? ""}`;
@@ -2879,6 +3178,11 @@ var AgentOptions = class {
2879
3178
  depth = 0;
2880
3179
  /** Hard ceiling on subagent nesting (beyond it the `Task` tool refuses to spawn). */
2881
3180
  maxDepth = 2;
3181
+ /** Real-disk dir where child agents stream their transcript as they run (origin-anchored — survives
3182
+ * worktree teardown / a child's own cwd). When set, an interrupted/failed child commits a STATUS HINT
3183
+ * + a pointer to its trace instead of a dangling tool_call, so the parent can read it and decide
3184
+ * whether to ignore, pick it up, or restart. Unset (library/sandbox default) = no tracing. */
3185
+ traceDir;
2882
3186
  /** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
2883
3187
  stream = false;
2884
3188
  /** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
@@ -2999,7 +3303,7 @@ var Agent = class _Agent {
2999
3303
  const disk = new NodeDiskFilesystem2(process.cwd());
3000
3304
  await disk.init();
3001
3305
  this.options.fs = new JailedFilesystem2(disk);
3002
- log3.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
3306
+ log4.info(`no fs provided \u2014 defaulting to jailed real disk at ${process.cwd()}`);
3003
3307
  }
3004
3308
  this.buildCtx();
3005
3309
  }
@@ -3053,7 +3357,7 @@ var Agent = class _Agent {
3053
3357
  agents = loaded.agents;
3054
3358
  if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
3055
3359
  }
3056
- const taskOpts = { ai: o.ai, model: o.model, fs, depth: o.depth, maxDepth: o.maxDepth, agents, hooks: o.hooks };
3360
+ const taskOpts = { ai: o.ai, model: o.model, fs, depth: o.depth, maxDepth: o.maxDepth, agents, hooks: o.hooks, traceDir: o.traceDir };
3057
3361
  tools = [...tools, makeTaskTool(taskOpts), makeTaskBatchTool(taskOpts)];
3058
3362
  }
3059
3363
  if (o.checkpoints) tools = [...tools, ...checkpointTools()];
@@ -3142,7 +3446,7 @@ var Agent = class _Agent {
3142
3446
  let lastFp = "";
3143
3447
  let repeats = 0;
3144
3448
  const kill = (finishReason) => {
3145
- 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` : ""})`);
3449
+ 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` : ""})`);
3146
3450
  this.ctx.jobs?.killAll();
3147
3451
  return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
3148
3452
  };
@@ -3188,7 +3492,7 @@ var Agent = class _Agent {
3188
3492
  const transient = !o.signal?.aborted && !isAbortError(err) && attempt < 2 && (network || serverSide);
3189
3493
  if (!transient) throw err;
3190
3494
  const waitMs = 1e3 * (attempt + 1);
3191
- log3.warn(`network drop mid-step (${err?.message ?? err}) \u2014 retrying in ${waitMs}ms`);
3495
+ log4.warn(`network drop mid-step (${err?.message ?? err}) \u2014 retrying in ${waitMs}ms`);
3192
3496
  o.host?.notify?.({ kind: "retry", message: `connection dropped \u2014 retrying step (#${attempt + 1})` });
3193
3497
  await new Promise((r) => setTimeout(r, waitMs));
3194
3498
  }
@@ -3204,7 +3508,7 @@ var Agent = class _Agent {
3204
3508
  bodyStr = void 0;
3205
3509
  }
3206
3510
  if (bodyStr && err instanceof Error && !err.message.includes(bodyStr)) err.detail = bodyStr;
3207
- log3.error(`chat() failed: ${err?.message ?? err}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err);
3511
+ log4.error(`chat() failed: ${err?.message ?? err}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err);
3208
3512
  return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err };
3209
3513
  }
3210
3514
  if (o.signal?.aborted) return kill("aborted");
@@ -3231,7 +3535,7 @@ var Agent = class _Agent {
3231
3535
  });
3232
3536
  }
3233
3537
  if (toolCalls.length === 0) {
3234
- log3.verbose(`completed in ${steps} step(s)`);
3538
+ log4.verbose(`completed in ${steps} step(s)`);
3235
3539
  await this.ctx.jobs?.drain();
3236
3540
  await this.activeHooks?.onStop?.(res.content ?? "");
3237
3541
  return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
@@ -3297,13 +3601,13 @@ var Agent = class _Agent {
3297
3601
  const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
3298
3602
  if (decision?.block) {
3299
3603
  const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
3300
- log3.debug(`${tc.function.name} -> ${blocked}`);
3604
+ log4.debug(`${tc.function.name} -> ${blocked}`);
3301
3605
  await hooks?.postToolUse?.(call, blocked, meta);
3302
3606
  return blocked;
3303
3607
  }
3304
3608
  this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
3305
3609
  if (earlyError) {
3306
- log3.debug(`${tc.function.name} -> ${earlyError}`);
3610
+ log4.debug(`${tc.function.name} -> ${earlyError}`);
3307
3611
  await hooks?.postToolUse?.(call, earlyError, meta);
3308
3612
  this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
3309
3613
  return earlyError;
@@ -3312,12 +3616,12 @@ var Agent = class _Agent {
3312
3616
  let images;
3313
3617
  let threw = false;
3314
3618
  try {
3315
- log3.debug(`${tc.function.name}(${tc.function.arguments})`);
3619
+ log4.debug(`${tc.function.name}(${tc.function.arguments})`);
3316
3620
  this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
3317
3621
  try {
3318
3622
  hooks.onToolOutput(call, chunk, meta);
3319
3623
  } catch (e) {
3320
- log3.debug(`onToolOutput hook error: ${e}`);
3624
+ log4.debug(`onToolOutput hook error: ${e}`);
3321
3625
  }
3322
3626
  } : void 0;
3323
3627
  const raw = await tool.run(args, this.ctx);
@@ -3329,7 +3633,7 @@ var Agent = class _Agent {
3329
3633
  }
3330
3634
  } catch (e) {
3331
3635
  const msg = e instanceof Error ? e.message : String(e);
3332
- log3.debug(`${tc.function.name} -> error: ${msg}`);
3636
+ log4.debug(`${tc.function.name} -> error: ${msg}`);
3333
3637
  result = `Error: ${msg}`;
3334
3638
  threw = true;
3335
3639
  } finally {
@@ -3455,7 +3759,7 @@ function fitTokenBudget(messages, maxTokens) {
3455
3759
  const ids = callIdSet(messages.slice(from));
3456
3760
  while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
3457
3761
  if (total > maxTokens)
3458
- log3.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
3762
+ log4.warn(`context ~${total} tok still over maxContextTokens=${maxTokens} after trimming (system head can't be dropped)`);
3459
3763
  return [...head, ...messages.slice(from)];
3460
3764
  }
3461
3765
  function compact(m, max, focus) {
@@ -4116,7 +4420,7 @@ init_tools_web();
4116
4420
  init_tools();
4117
4421
  init_tools_structured();
4118
4422
  init_logging();
4119
- var log5 = forComponent("scratch");
4423
+ var log6 = forComponent("scratch");
4120
4424
  var SCRATCH_DIR = "/scratch";
4121
4425
  function shortArgs(args) {
4122
4426
  try {
@@ -4156,7 +4460,7 @@ var Scratch = class {
4156
4460
  await (this.dirReady ??= mkdirp(this.fs, dir));
4157
4461
  await this.fs.writeFile(path, header + raw);
4158
4462
  } catch (e) {
4159
- log5.debug("scratch write failed; returning raw", e);
4463
+ log6.debug("scratch write failed; returning raw", e);
4160
4464
  return raw;
4161
4465
  }
4162
4466
  const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
@@ -4186,7 +4490,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
4186
4490
  await (this.dirReady ??= mkdirp(this.fs, dir));
4187
4491
  await this.fs.writeFile(path, header + full);
4188
4492
  } catch (e) {
4189
- log5.debug("scratch spill failed; cropping lossy", e);
4493
+ log6.debug("scratch spill failed; cropping lossy", e);
4190
4494
  return full.slice(0, pageBytes) + `
4191
4495
 
4192
4496
  [output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
@@ -4232,7 +4536,7 @@ Question: ${q}`);
4232
4536
  const answer = (res.text ?? "").trim();
4233
4537
  return answer || "(no answer found in scratch)";
4234
4538
  } catch (e) {
4235
- log5.debug("Ask peek failed", e);
4539
+ log6.debug("Ask peek failed", e);
4236
4540
  return `Error querying scratch: ${e?.message ?? e}`;
4237
4541
  }
4238
4542
  }
@@ -4241,7 +4545,7 @@ Question: ${q}`);
4241
4545
 
4242
4546
  // src/lessons.ts
4243
4547
  init_logging();
4244
- var log6 = forComponent("Lessons");
4548
+ var log7 = forComponent("Lessons");
4245
4549
  var LessonOptionsDefaults = class {
4246
4550
  minRepeats = 2;
4247
4551
  };
@@ -4266,15 +4570,15 @@ function lessonCapture(options) {
4266
4570
  counts.set(lesson.slug, n);
4267
4571
  if (n < o.minRepeats) return;
4268
4572
  written.add(lesson.slug);
4269
- await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log6.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
4270
- log6.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
4573
+ await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log7.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
4574
+ log7.debug(`captured lesson ${lesson.slug} (recurred ${n}\xD7)`);
4271
4575
  }
4272
4576
  };
4273
4577
  }
4274
4578
 
4275
4579
  // src/reflect.ts
4276
4580
  init_logging();
4277
- var log7 = forComponent("Reflect");
4581
+ var log8 = forComponent("Reflect");
4278
4582
  async function reflectOnRun(o) {
4279
4583
  const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
4280
4584
  if (!digest.trim()) return null;
@@ -4290,7 +4594,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
4290
4594
  const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
4291
4595
  text = r?.content ?? "";
4292
4596
  } catch (e) {
4293
- log7.warn(`reflection call failed: ${e?.message ?? e}`);
4597
+ log8.warn(`reflection call failed: ${e?.message ?? e}`);
4294
4598
  return null;
4295
4599
  }
4296
4600
  const m = text.match(/LESSON:\s*(.+)/i);
@@ -4300,10 +4604,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
4300
4604
  try {
4301
4605
  await writeFact(o.fs, o.dir, slug2, lesson);
4302
4606
  } catch (e) {
4303
- log7.warn(`could not persist lesson: ${e?.message ?? e}`);
4607
+ log8.warn(`could not persist lesson: ${e?.message ?? e}`);
4304
4608
  return null;
4305
4609
  }
4306
- log7.debug(`reflection persisted ${slug2}`);
4610
+ log8.debug(`reflection persisted ${slug2}`);
4307
4611
  return slug2;
4308
4612
  }
4309
4613
  function digestRun(messages, maxChars) {
@@ -4320,7 +4624,7 @@ function digestRun(messages, maxChars) {
4320
4624
  // src/duplex.ts
4321
4625
  import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
4322
4626
  init_logging();
4323
- var log8 = forComponent("DuplexAgent");
4627
+ var log9 = forComponent("DuplexAgent");
4324
4628
  function describeCall(call) {
4325
4629
  const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
4326
4630
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
@@ -4460,7 +4764,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4460
4764
  const m = this.reflexBuf.match(RESERVED_EVENT_MARKER);
4461
4765
  if (m) {
4462
4766
  this.fabricationCut = true;
4463
- log8.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
4767
+ log9.warn(`reflex fabricated a [task \u2026] event in its spoken stream \u2014 cutting it (kept ${m.index} chars)`);
4464
4768
  const safe = this.reflexBuf.slice(this.reflexForwarded, m.index);
4465
4769
  if (!safe) return;
4466
4770
  if (safe.trim()) this.spokeThisTurn = true;
@@ -4539,7 +4843,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4539
4843
  try {
4540
4844
  await this.voice.send("[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools.");
4541
4845
  } catch (e) {
4542
- log8.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
4846
+ log9.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
4543
4847
  } finally {
4544
4848
  this.nudging = false;
4545
4849
  }
@@ -4687,7 +4991,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4687
4991
  this.notify("task_verify", `task ${id}: verifying`, { id });
4688
4992
  const cres = await new Agent(agentOpts).run(checkBrief);
4689
4993
  if (cres.finishReason !== "stop") {
4690
- log8.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
4994
+ log9.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
4691
4995
  this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
4692
4996
  }
4693
4997
  const sum = (a = 0, b = 0) => a + b;
@@ -4790,7 +5094,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4790
5094
  }
4791
5095
  rec.status = "done";
4792
5096
  rec.result = res.text;
4793
- log8.verbose(`task ${id} done (${res.steps} steps)`);
5097
+ log9.verbose(`task ${id} done (${res.steps} steps)`);
4794
5098
  this.notify("task_done", `task ${id} (${rec.label}) completed`, {
4795
5099
  id,
4796
5100
  text: res.text,
@@ -4808,7 +5112,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4808
5112
  this.dropAsk(rec.id);
4809
5113
  rec.status = "error";
4810
5114
  rec.result = msg;
4811
- log8.warn(`task ${rec.id} failed: ${msg}`);
5115
+ log9.warn(`task ${rec.id} failed: ${msg}`);
4812
5116
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
4813
5117
  this.queueRevoice(`[task ${rec.id} failed] ${msg}`);
4814
5118
  }
@@ -5168,7 +5472,7 @@ init_logging();
5168
5472
 
5169
5473
  // src/voice/engine.ts
5170
5474
  init_logging();
5171
- var log9 = forComponent("VoiceEngine");
5475
+ var log10 = forComponent("VoiceEngine");
5172
5476
  var now = () => performance.now();
5173
5477
  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, ".");
5174
5478
  var VoiceEngineOptions = class {
@@ -5276,7 +5580,7 @@ var VoiceEngine = class _VoiceEngine {
5276
5580
  this.stt.onLevel = (rms) => this.handleLevel(rms);
5277
5581
  await Promise.all([this.tts.connect(), this.stt.start()]);
5278
5582
  this.setState("listening");
5279
- log9.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
5583
+ log10.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
5280
5584
  }
5281
5585
  get usingAec() {
5282
5586
  return this.stt.usingAec;
@@ -5328,7 +5632,7 @@ var VoiceEngine = class _VoiceEngine {
5328
5632
  this.reply += text;
5329
5633
  for (const w of this.words(this.reply)) this.echoWords.add(w);
5330
5634
  this.tts.speak(forSpeech(text), true);
5331
- if (!this.spokeDeltas && this.turnStartAt) log9.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
5635
+ if (!this.spokeDeltas && this.turnStartAt) log10.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
5332
5636
  this.spokeDeltas = true;
5333
5637
  this.setState("speaking");
5334
5638
  }
@@ -5349,7 +5653,7 @@ var VoiceEngine = class _VoiceEngine {
5349
5653
  }
5350
5654
  this.drainTimer = null;
5351
5655
  this.speaking = false;
5352
- if (this.turnStartAt) log9.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
5656
+ if (this.turnStartAt) log10.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
5353
5657
  this.echoUntil = now() + 2500;
5354
5658
  if (!this.usingAec) this.stt.reset();
5355
5659
  this.setState("listening");
@@ -5490,7 +5794,7 @@ var VoiceEngine = class _VoiceEngine {
5490
5794
  this.pendingUtt = this.pendingUtt ? `${this.pendingUtt} ${text}` : text;
5491
5795
  if (this.pendingTimer) clearTimeout(this.pendingTimer);
5492
5796
  if (this.options.incompleteMergeMs && this.looksIncomplete(this.pendingUtt)) {
5493
- log9.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
5797
+ log10.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
5494
5798
  this.options.onHold();
5495
5799
  if (this.options.holdFiller && !this.speaking) {
5496
5800
  this.beginSpeech();
@@ -5588,7 +5892,7 @@ async function resolveAuth(auth) {
5588
5892
  }
5589
5893
 
5590
5894
  // src/voice/soniox.ts
5591
- var log10 = forComponent("SonioxSTT");
5895
+ var log11 = forComponent("SonioxSTT");
5592
5896
  var now2 = () => performance.now();
5593
5897
  var SonioxSTTOptions = class {
5594
5898
  auth = "";
@@ -5645,9 +5949,9 @@ var SonioxSTT = class {
5645
5949
  this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
5646
5950
  this.ws.onclose = (ev) => {
5647
5951
  if (this.stopped) return;
5648
- log10.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
5952
+ log11.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
5649
5953
  this.reset();
5650
- this.connectWs().catch((e) => log10.error(`soniox reconnect failed: ${e.message}`));
5954
+ this.connectWs().catch((e) => log11.error(`soniox reconnect failed: ${e.message}`));
5651
5955
  };
5652
5956
  }
5653
5957
  async start() {
@@ -5657,7 +5961,7 @@ var SonioxSTT = class {
5657
5961
  this.endpointTimer = setInterval(() => {
5658
5962
  const combined = (this.finalText + this.partialText).trim();
5659
5963
  if (!combined || now2() - this.lastChangeAt < this.options.silenceEndpointMs) return;
5660
- if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
5964
+ if (this.firstTokenAt) log11.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
5661
5965
  this.reset();
5662
5966
  this.onUtterance(combined, now2());
5663
5967
  }, 120);
@@ -5674,7 +5978,7 @@ var SonioxSTT = class {
5674
5978
  });
5675
5979
  }
5676
5980
  handle(m) {
5677
- if (m.error_message) return log10.error(`soniox: ${m.error_message}`);
5981
+ if (m.error_message) return log11.error(`soniox: ${m.error_message}`);
5678
5982
  let endpoint = false;
5679
5983
  for (const t of m.tokens ?? []) {
5680
5984
  if (t.text === "<end>") endpoint = true;
@@ -5690,7 +5994,7 @@ var SonioxSTT = class {
5690
5994
  this.onPartial(combined);
5691
5995
  if (endpoint && this.finalText.trim()) {
5692
5996
  const utterance = this.finalText.trim();
5693
- if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
5997
+ if (this.firstTokenAt) log11.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
5694
5998
  this.reset();
5695
5999
  this.onUtterance(utterance, now2());
5696
6000
  }
@@ -5712,7 +6016,7 @@ var SonioxSTT = class {
5712
6016
 
5713
6017
  // src/voice/cartesia.ts
5714
6018
  init_logging();
5715
- var log11 = forComponent("CartesiaTTS");
6019
+ var log12 = forComponent("CartesiaTTS");
5716
6020
  var now3 = () => performance.now();
5717
6021
  var CartesiaTTSOptions = class {
5718
6022
  auth = "";
@@ -5762,9 +6066,9 @@ var CartesiaTTS = class _CartesiaTTS {
5762
6066
  this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
5763
6067
  });
5764
6068
  this.ws.onclose = (ev) => {
5765
- log11.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
6069
+ log12.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
5766
6070
  if (!this.closed) {
5767
- this.connecting = this.doConnect().catch((e) => log11.error(`cartesia reconnect failed: ${e.message}`));
6071
+ this.connecting = this.doConnect().catch((e) => log12.error(`cartesia reconnect failed: ${e.message}`));
5768
6072
  }
5769
6073
  };
5770
6074
  this.ws.onmessage = (ev) => {
@@ -5786,11 +6090,11 @@ var CartesiaTTS = class _CartesiaTTS {
5786
6090
  this.down = true;
5787
6091
  this.downAt = now3();
5788
6092
  this.consecutiveOk = 0;
5789
- log11.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
6093
+ log12.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
5790
6094
  this.onDone();
5791
6095
  this.startProbe();
5792
6096
  } else if (!this.down) {
5793
- log11.warn(`cartesia: ${JSON.stringify(m)}`);
6097
+ log12.warn(`cartesia: ${JSON.stringify(m)}`);
5794
6098
  }
5795
6099
  }
5796
6100
  };
@@ -5804,7 +6108,7 @@ var CartesiaTTS = class _CartesiaTTS {
5804
6108
  this.consecutiveOk = 0;
5805
6109
  this.stopProbe();
5806
6110
  const downMs = this.downAt ? now3() - this.downAt : 0;
5807
- (downMs < 2e3 ? log11.debug : log11.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
6111
+ (downMs < 2e3 ? log12.debug : log12.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
5808
6112
  }
5809
6113
  /** Ensure the WS is open before sending — reconnects if idle-closed. */
5810
6114
  async ensureConnected() {
@@ -5923,6 +6227,7 @@ export {
5923
6227
  buildMcpCatalog,
5924
6228
  checkpointTool,
5925
6229
  checkpointTools,
6230
+ cleanupWorktree,
5926
6231
  compileSynthesizedTool,
5927
6232
  composeHooks,
5928
6233
  contentText,
@@ -5931,12 +6236,17 @@ export {
5931
6236
  defaultTools,
5932
6237
  diskAgentOptions,
5933
6238
  editTool,
6239
+ enterWorktree,
5934
6240
  exitSessionTool,
6241
+ exitWorktree,
5935
6242
  expandCommand,
5936
6243
  expandEntry,
5937
6244
  expandTemplate,
6245
+ findGitRoot,
5938
6246
  forComponent,
5939
6247
  fullAgentOptions,
6248
+ getOrCreateWorktree,
6249
+ getWorktreeSession,
5940
6250
  globTool,
5941
6251
  grepTool,
5942
6252
  htmlToText,
@@ -5990,8 +6300,10 @@ export {
5990
6300
  toolsByName,
5991
6301
  topByRelevance,
5992
6302
  validateToolCode,
6303
+ validateWorktreeSlug,
5993
6304
  webFetchTool,
5994
6305
  webSearchTool,
6306
+ worktreeBranchName,
5995
6307
  writeFact,
5996
6308
  writeTool
5997
6309
  };