@livx.cc/agentx 0.95.1 → 0.95.3

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, {
@@ -1420,172 +1586,6 @@ var init_JailedFilesystem = __esm({
1420
1586
  }
1421
1587
  });
1422
1588
 
1423
- // src/worktree.ts
1424
- var worktree_exports = {};
1425
- __export(worktree_exports, {
1426
- cleanupWorktree: () => cleanupWorktree,
1427
- enterWorktree: () => enterWorktree,
1428
- exitWorktree: () => exitWorktree,
1429
- findGitRoot: () => findGitRoot,
1430
- getOrCreateWorktree: () => getOrCreateWorktree,
1431
- getWorktreeSession: () => getWorktreeSession,
1432
- validateWorktreeSlug: () => validateWorktreeSlug,
1433
- worktreeBranchName: () => worktreeBranchName
1434
- });
1435
- import { spawnSync } from "child_process";
1436
- import { symlinkSync, mkdirSync, statSync as statSync2, readFileSync, rmSync } from "fs";
1437
- import { join as join3 } from "path";
1438
- function validateWorktreeSlug(slug2) {
1439
- if (!slug2) throw new Error("worktree slug must not be empty");
1440
- if (slug2.length > MAX_SLUG_LENGTH) throw new Error(`worktree slug must be \u2264${MAX_SLUG_LENGTH} chars (got ${slug2.length})`);
1441
- for (const seg of slug2.split("/")) {
1442
- if (seg === "." || seg === "..") throw new Error(`worktree slug must not contain "." or ".." segments`);
1443
- if (!VALID_SEGMENT.test(seg)) throw new Error(`worktree slug segment "${seg}" contains invalid characters (allowed: a-z A-Z 0-9 . _ -)`);
1444
- }
1445
- }
1446
- function flattenSlug(slug2) {
1447
- return slug2.replaceAll("/", "+");
1448
- }
1449
- function worktreeBranchName(slug2) {
1450
- return `worktree-${flattenSlug(slug2)}`;
1451
- }
1452
- function worktreesDir(repoRoot) {
1453
- return join3(repoRoot, ".claude", "worktrees");
1454
- }
1455
- function worktreePathFor(repoRoot, slug2) {
1456
- return join3(worktreesDir(repoRoot), flattenSlug(slug2));
1457
- }
1458
- function git(args, cwd) {
1459
- const r = spawnSync("git", args, {
1460
- cwd,
1461
- stdio: ["ignore", "pipe", "pipe"],
1462
- env: { ...process.env, ...GIT_NO_PROMPT_ENV }
1463
- });
1464
- return { ok: r.status === 0, stdout: (r.stdout ?? "").toString().trim(), stderr: (r.stderr ?? "").toString().trim() };
1465
- }
1466
- function findGitRoot(from) {
1467
- const r = git(["rev-parse", "--show-toplevel"], from);
1468
- return r.ok ? r.stdout : null;
1469
- }
1470
- function readWorktreeHead(worktreePath) {
1471
- try {
1472
- const gitFile = readFileSync(join3(worktreePath, ".git"), "utf-8").trim();
1473
- const m = gitFile.match(/^gitdir:\s*(.+)$/);
1474
- if (!m) return null;
1475
- const headPath = join3(m[1], "HEAD");
1476
- const head = readFileSync(headPath, "utf-8").trim();
1477
- const refMatch = head.match(/^ref:\s*(.+)$/);
1478
- if (!refMatch) return head.length === 40 ? head : null;
1479
- const refPath = join3(m[1], "..", refMatch[1]);
1480
- try {
1481
- return readFileSync(refPath, "utf-8").trim();
1482
- } catch {
1483
- }
1484
- const r = git(["rev-parse", "HEAD"], worktreePath);
1485
- return r.ok ? r.stdout : null;
1486
- } catch {
1487
- return null;
1488
- }
1489
- }
1490
- function getOrCreateWorktree(repoRoot, slug2) {
1491
- validateWorktreeSlug(slug2);
1492
- const worktreePath = worktreePathFor(repoRoot, slug2);
1493
- const branch = worktreeBranchName(slug2);
1494
- const existingHead = readWorktreeHead(worktreePath);
1495
- if (existingHead) return { worktreePath, branch, headCommit: existingHead, existed: true };
1496
- mkdirSync(worktreesDir(repoRoot), { recursive: true });
1497
- const defaultBranch = git(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoRoot);
1498
- let baseBranch = defaultBranch.ok ? defaultBranch.stdout.replace(/^origin\//, "") : "HEAD";
1499
- const originRef = `origin/${baseBranch}`;
1500
- const hasOrigin = git(["rev-parse", "--verify", originRef], repoRoot);
1501
- if (!hasOrigin.ok) {
1502
- const fetched = git(["fetch", "origin", baseBranch], repoRoot);
1503
- if (!fetched.ok) baseBranch = "HEAD";
1504
- }
1505
- const base = baseBranch === "HEAD" ? "HEAD" : originRef;
1506
- const r = git(["worktree", "add", "-B", branch, worktreePath, base], repoRoot);
1507
- if (!r.ok) throw new Error(`failed to create worktree: ${r.stderr}`);
1508
- const headSha = git(["rev-parse", "HEAD"], worktreePath);
1509
- symlinkDirs(repoRoot, worktreePath, ["node_modules", ".bun"]);
1510
- return { worktreePath, branch, headCommit: headSha.stdout, existed: false, baseBranch };
1511
- }
1512
- function symlinkDirs(repoRoot, worktreePath, dirs) {
1513
- for (const dir of dirs) {
1514
- const src = join3(repoRoot, dir);
1515
- const dst = join3(worktreePath, dir);
1516
- try {
1517
- statSync2(src);
1518
- } catch {
1519
- continue;
1520
- }
1521
- try {
1522
- symlinkSync(src, dst, "dir");
1523
- } catch (e) {
1524
- if (e?.code !== "EEXIST") console.error(`[worktree] symlink ${dir}: ${e?.message ?? e}`);
1525
- }
1526
- }
1527
- }
1528
- function cleanupWorktree(repoRoot, slug2, opts) {
1529
- validateWorktreeSlug(slug2);
1530
- const worktreePath = worktreePathFor(repoRoot, slug2);
1531
- const branch = worktreeBranchName(slug2);
1532
- try {
1533
- statSync2(worktreePath);
1534
- } catch {
1535
- return { removed: false, reason: "not found" };
1536
- }
1537
- if (!opts?.force) {
1538
- const status = git(["status", "--porcelain", "-uno"], worktreePath);
1539
- if (status.ok && status.stdout) return { removed: false, reason: "uncommitted changes" };
1540
- }
1541
- for (const dir of ["node_modules", ".bun"]) {
1542
- try {
1543
- rmSync(join3(worktreePath, dir));
1544
- } catch {
1545
- }
1546
- }
1547
- const rm = git(["worktree", "remove", "--force", worktreePath], repoRoot);
1548
- if (!rm.ok) return { removed: false, reason: rm.stderr };
1549
- git(["branch", "-D", branch], repoRoot);
1550
- return { removed: true };
1551
- }
1552
- function getWorktreeSession() {
1553
- return _session;
1554
- }
1555
- function enterWorktree(slug2, cwd) {
1556
- const repoRoot = findGitRoot(cwd);
1557
- if (!repoRoot) throw new Error("--worktree requires a git repository");
1558
- const info = getOrCreateWorktree(repoRoot, slug2);
1559
- process.chdir(info.worktreePath);
1560
- _session = {
1561
- originalCwd: cwd,
1562
- worktreePath: info.worktreePath,
1563
- slug: slug2,
1564
- branch: info.branch,
1565
- headCommit: info.headCommit
1566
- };
1567
- return _session;
1568
- }
1569
- function exitWorktree() {
1570
- if (!_session) return null;
1571
- const repoRoot = findGitRoot(_session.originalCwd);
1572
- if (!repoRoot) return null;
1573
- process.chdir(_session.originalCwd);
1574
- const result = cleanupWorktree(repoRoot, _session.slug);
1575
- _session = null;
1576
- return result;
1577
- }
1578
- var VALID_SEGMENT, MAX_SLUG_LENGTH, GIT_NO_PROMPT_ENV, _session;
1579
- var init_worktree = __esm({
1580
- "src/worktree.ts"() {
1581
- "use strict";
1582
- VALID_SEGMENT = /^[a-zA-Z0-9._-]+$/;
1583
- MAX_SLUG_LENGTH = 64;
1584
- GIT_NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "" };
1585
- _session = null;
1586
- }
1587
- });
1588
-
1589
1589
  // src/shell.sandbox.ts
1590
1590
  function writable(cwd, o, tmpDir) {
1591
1591
  const set = /* @__PURE__ */ new Set([cwd, "/tmp", "/private/tmp", "/private/var/folders", "/dev", ...tmpDir ? [tmpDir] : [], ...o.writePaths]);
@@ -1755,7 +1755,7 @@ function makeRealShellTool(options) {
1755
1755
  proc.stderr?.on("data", collect);
1756
1756
  proc.on("error", (err2) => {
1757
1757
  if (err2?.name === "AbortError" || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));
1758
- log12.debug("shell spawn error", err2);
1758
+ log13.debug("shell spawn error", err2);
1759
1759
  finish(`[exit 1] ${err2?.message ?? err2}${out ? "\n" + clean(out) : ""}`);
1760
1760
  });
1761
1761
  proc.on("close", (code) => {
@@ -1817,7 +1817,7 @@ ${clean(out) || "(no output yet)"}`;
1817
1817
  }
1818
1818
  ];
1819
1819
  }
1820
- var log12, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
1820
+ var log13, clean, DETACHED, SECRET_ENV_RE, _spawn, ShellJobRegistry, NO_JOB2;
1821
1821
  var init_tools_shell = __esm({
1822
1822
  "src/tools.shell.ts"() {
1823
1823
  "use strict";
@@ -1825,7 +1825,7 @@ var init_tools_shell = __esm({
1825
1825
  init_redact();
1826
1826
  init_logging();
1827
1827
  init_shell_sandbox();
1828
- log12 = forComponent("shell");
1828
+ log13 = forComponent("shell");
1829
1829
  clean = (s) => truncateOutput(redactSecrets(s.replace(/\n+$/, "")));
1830
1830
  DETACHED = { stdio: ["ignore", "pipe", "pipe"], detached: true };
1831
1831
  SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;
@@ -1907,7 +1907,7 @@ var init_tools_shell = __esm({
1907
1907
 
1908
1908
  // cli/cli.ts
1909
1909
  import { createInterface } from "readline/promises";
1910
- import { existsSync as existsSync10, readFileSync as readFileSync7, appendFileSync, mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, readdirSync as readdirSync4, statSync as statSync4, 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";
1911
1911
  import { homedir as homedir9, tmpdir as tmpdir3 } from "os";
1912
1912
 
1913
1913
  // cli/clipboard.ts
@@ -1975,7 +1975,7 @@ function copyTextToClipboard(text, platform2 = process.platform) {
1975
1975
  }
1976
1976
 
1977
1977
  // cli/cli.ts
1978
- import { join as join13, 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";
1979
1979
  import { AIClient, listModels, listProviders, getProviderFromModel, getModelInfo, resolveModel, isModelSupported, disposeCursorSessions } from "ai.libx.js";
1980
1980
 
1981
1981
  // src/llm.ts
@@ -2622,7 +2622,13 @@ ${sections.join("\n\n---\n\n")}`;
2622
2622
 
2623
2623
  // src/subagent.ts
2624
2624
  init_tools();
2625
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2626
+ import { join as join4 } from "path";
2625
2627
  init_OverlayFilesystem();
2628
+ init_worktree();
2629
+ init_NodeDiskFilesystem();
2630
+ init_logging();
2631
+ var log3 = forComponent("subagent");
2626
2632
  async function boundedPool(items, limit, fn) {
2627
2633
  const out = new Array(items.length);
2628
2634
  let next = 0;
@@ -2635,13 +2641,86 @@ async function boundedPool(items, limit, fn) {
2635
2641
  await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
2636
2642
  return out;
2637
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
+ }
2638
2717
  function childOptionsFor(opts, fs, depth, maxDepth, agentType) {
2639
2718
  let def;
2640
2719
  if (agentType) {
2641
2720
  def = (opts.agents ?? []).find((a) => a.name === agentType);
2642
2721
  if (!def) return `no subagent type '${agentType}'. Available: ${(opts.agents ?? []).map((a) => a.name).join(", ") || "(none defined)"}`;
2643
2722
  }
2644
- 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 };
2645
2724
  if (opts.maxSteps != null) childOpts.maxSteps = opts.maxSteps;
2646
2725
  if (def?.systemPrompt) childOpts.systemPrompt = def.systemPrompt;
2647
2726
  if (def?.tools?.length) {
@@ -2658,7 +2737,7 @@ function makeTaskTool(opts) {
2658
2737
  const maxDepth = opts.maxDepth ?? 2;
2659
2738
  return {
2660
2739
  name: "Task",
2661
- 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.',
2662
2741
  parameters: {
2663
2742
  type: "object",
2664
2743
  required: ["description", "prompt"],
@@ -2666,36 +2745,83 @@ function makeTaskTool(opts) {
2666
2745
  description: { type: "string", description: "a short (3-5 word) label for the sub-task" },
2667
2746
  prompt: { type: "string", description: "the full instructions the child agent should carry out" },
2668
2747
  agentType: { type: "string", description: "optional named subagent type (its persona, model, and scoped tools) \u2014 see the catalog" },
2669
- 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)' }
2670
2750
  }
2671
2751
  },
2672
- async run({ description, prompt, agentType, background }, ctx) {
2752
+ async run({ description, prompt, agentType, background, isolation }, ctx) {
2673
2753
  if (depth >= maxDepth) {
2674
2754
  return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn another child agent \u2014 do this work directly instead.`;
2675
2755
  }
2676
2756
  const label = String(description ?? agentType ?? "sub-task");
2757
+ const useWorktree = isolation === "worktree";
2677
2758
  if (background && ctx.jobs) {
2678
2759
  const id = ctx.jobs.start(async ({ signal }) => {
2679
- const overlay = new OverlayFilesystem(opts.fs);
2680
- 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);
2681
2776
  if (typeof childOpts2 === "string") throw new Error(childOpts2);
2682
- childOpts2.signal = signal;
2683
- const res2 = await new Agent(childOpts2).run(String(prompt ?? ""));
2684
- if (signal.aborted) return "[killed before commit]";
2685
- await overlay.commit();
2686
- const summary2 = res2.text || `(child '${label}' finished with no summary; finishReason=${res2.finishReason})`;
2687
- await opts.hooks?.onSubagentStop?.(summary2, { label, agentType });
2688
- 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;
2689
2792
  }, { kind: "agent", label });
2690
2793
  return `Started background sub-task ${id} ('${label}') \u2014 poll with JobOutput({id:"${id}"}).`;
2691
2794
  }
2692
- const childOpts = childOptionsFor(opts, opts.fs, depth, maxDepth, agentType);
2693
- if (typeof childOpts === "string") return `Error: ${childOpts}`;
2694
- const child = new Agent(childOpts);
2695
- const res = await child.run(String(prompt ?? ""));
2696
- const summary = res.text || `(child agent finished '${label}' with no summary; finishReason=${res.finishReason})`;
2697
- await opts.hooks?.onSubagentStop?.(summary, { label, agentType });
2698
- 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
+ }
2699
2825
  }
2700
2826
  };
2701
2827
  }
@@ -2705,7 +2831,7 @@ function makeTaskBatchTool(opts) {
2705
2831
  const maxParallel = opts.maxParallel ?? 4;
2706
2832
  return {
2707
2833
  name: "TaskBatch",
2708
- 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.',
2709
2835
  parameters: {
2710
2836
  type: "object",
2711
2837
  required: ["tasks"],
@@ -2722,30 +2848,53 @@ function makeTaskBatchTool(opts) {
2722
2848
  agentType: { type: "string", description: "optional named subagent type" }
2723
2849
  }
2724
2850
  }
2725
- }
2851
+ },
2852
+ isolation: { type: "string", enum: ["overlay", "worktree"], description: 'isolation mode for ALL children: "overlay" (default) or "worktree" (real git worktree each)' }
2726
2853
  }
2727
2854
  },
2728
- async run({ tasks }, _ctx) {
2855
+ async run({ tasks, isolation }, ctx) {
2729
2856
  if (depth >= maxDepth) return `Error: Task depth limit reached (maxDepth ${maxDepth}). Cannot spawn child agents \u2014 do this work directly instead.`;
2730
2857
  const list = Array.isArray(tasks) ? tasks : [];
2731
2858
  if (!list.length) return "Error: TaskBatch needs a non-empty `tasks` array.";
2859
+ const useWorktree = isolation === "worktree";
2732
2860
  const results = await boundedPool(list, maxParallel, async (t, i) => {
2733
2861
  const label = String(t?.description ?? t?.agentType ?? `task ${i + 1}`);
2734
- const overlay = new OverlayFilesystem(opts.fs);
2735
- 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);
2736
2875
  if (typeof childOpts === "string") return { label, error: childOpts, ok: false };
2737
2876
  try {
2738
- const res = await new Agent(childOpts).run(String(t?.prompt ?? ""));
2739
- await opts.hooks?.onSubagentStop?.(res.text, { label, agentType: t?.agentType });
2740
- 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" };
2741
2880
  } catch (e) {
2742
- 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 };
2743
2882
  }
2744
2883
  });
2745
- for (const r of results) if (r.ok && r.overlay) await r.overlay.commit().catch(() => {
2746
- });
2747
- return results.map((r, i) => `### ${i + 1}. ${r.label}
2748
- ${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");
2749
2898
  }
2750
2899
  };
2751
2900
  }
@@ -2990,7 +3139,7 @@ function reasoningToChatFragment(model, effort) {
2990
3139
  }
2991
3140
 
2992
3141
  // src/Agent.ts
2993
- var log3 = forComponent("Agent");
3142
+ var log4 = forComponent("Agent");
2994
3143
  function isAbortError(err2) {
2995
3144
  const e = err2;
2996
3145
  const blob = `${e?.message ?? ""} ${e?.name ?? ""} ${e?.code ?? ""} ${e?.cause?.name ?? ""}`;
@@ -3066,6 +3215,11 @@ var AgentOptions = class {
3066
3215
  depth = 0;
3067
3216
  /** Hard ceiling on subagent nesting (beyond it the `Task` tool refuses to spawn). */
3068
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;
3069
3223
  /** Stream tokens from the model. Takes effect only with a `host.notify`; off => current (non-stream) behavior. */
3070
3224
  stream = false;
3071
3225
  /** Fold the dropped middle of an over-long transcript into a synthetic summary (edge-safe, no LLM). Off => drop-oldest. */
@@ -3186,7 +3340,7 @@ var Agent = class _Agent {
3186
3340
  const disk = new NodeDiskFilesystem2(process.cwd());
3187
3341
  await disk.init();
3188
3342
  this.options.fs = new JailedFilesystem2(disk);
3189
- 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()}`);
3190
3344
  }
3191
3345
  this.buildCtx();
3192
3346
  }
@@ -3240,7 +3394,7 @@ var Agent = class _Agent {
3240
3394
  agents = loaded.agents;
3241
3395
  if (loaded.catalog) systemPrompt += "\n\n" + loaded.catalog;
3242
3396
  }
3243
- 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 };
3244
3398
  tools = [...tools, makeTaskTool(taskOpts), makeTaskBatchTool(taskOpts)];
3245
3399
  }
3246
3400
  if (o.checkpoints) tools = [...tools, ...checkpointTools()];
@@ -3329,7 +3483,7 @@ var Agent = class _Agent {
3329
3483
  let lastFp = "";
3330
3484
  let repeats = 0;
3331
3485
  const kill = (finishReason) => {
3332
- 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` : ""})`);
3333
3487
  this.ctx.jobs?.killAll();
3334
3488
  return { text: lastAssistantText(this.transcript), steps, finishReason, messages: this.transcript, usage, usageEstimated };
3335
3489
  };
@@ -3375,7 +3529,7 @@ var Agent = class _Agent {
3375
3529
  const transient = !o.signal?.aborted && !isAbortError(err2) && attempt < 2 && (network || serverSide);
3376
3530
  if (!transient) throw err2;
3377
3531
  const waitMs = 1e3 * (attempt + 1);
3378
- 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`);
3379
3533
  o.host?.notify?.({ kind: "retry", message: `connection dropped \u2014 retrying step (#${attempt + 1})` });
3380
3534
  await new Promise((r) => setTimeout(r, waitMs));
3381
3535
  }
@@ -3391,7 +3545,7 @@ var Agent = class _Agent {
3391
3545
  bodyStr = void 0;
3392
3546
  }
3393
3547
  if (bodyStr && err2 instanceof Error && !err2.message.includes(bodyStr)) err2.detail = bodyStr;
3394
- log3.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err2);
3548
+ log4.error(`chat() failed: ${err2?.message ?? err2}${bodyStr ? ` \u2014 ${bodyStr}` : ""}`, err2);
3395
3549
  return { text: "", steps, finishReason: "error", messages: this.transcript, usage, usageEstimated, error: err2 };
3396
3550
  }
3397
3551
  if (o.signal?.aborted) return kill("aborted");
@@ -3418,7 +3572,7 @@ var Agent = class _Agent {
3418
3572
  });
3419
3573
  }
3420
3574
  if (toolCalls.length === 0) {
3421
- log3.verbose(`completed in ${steps} step(s)`);
3575
+ log4.verbose(`completed in ${steps} step(s)`);
3422
3576
  await this.ctx.jobs?.drain();
3423
3577
  await this.activeHooks?.onStop?.(res.content ?? "");
3424
3578
  return { text: res.content ?? "", steps, finishReason: "stop", messages: this.transcript, usage, usageEstimated };
@@ -3484,13 +3638,13 @@ var Agent = class _Agent {
3484
3638
  const decision = await this.park(Promise.resolve(hooks?.preToolUse?.(call, meta)));
3485
3639
  if (decision?.block) {
3486
3640
  const blocked = `Blocked by hook: ${decision.reason ?? "no reason given"}`;
3487
- log3.debug(`${tc.function.name} -> ${blocked}`);
3641
+ log4.debug(`${tc.function.name} -> ${blocked}`);
3488
3642
  await hooks?.postToolUse?.(call, blocked, meta);
3489
3643
  return blocked;
3490
3644
  }
3491
3645
  this.options.host?.notify?.({ kind: "tool_use", id: tc.id ?? "", name: tc.function.name, input: args });
3492
3646
  if (earlyError) {
3493
- log3.debug(`${tc.function.name} -> ${earlyError}`);
3647
+ log4.debug(`${tc.function.name} -> ${earlyError}`);
3494
3648
  await hooks?.postToolUse?.(call, earlyError, meta);
3495
3649
  this.options.host?.notify?.({ kind: "tool_result", id: tc.id ?? "", output: earlyError, isError: true });
3496
3650
  return earlyError;
@@ -3499,12 +3653,12 @@ var Agent = class _Agent {
3499
3653
  let images;
3500
3654
  let threw = false;
3501
3655
  try {
3502
- log3.debug(`${tc.function.name}(${tc.function.arguments})`);
3656
+ log4.debug(`${tc.function.name}(${tc.function.arguments})`);
3503
3657
  this.ctx.emit = hooks?.onToolOutput ? (chunk) => {
3504
3658
  try {
3505
3659
  hooks.onToolOutput(call, chunk, meta);
3506
3660
  } catch (e) {
3507
- log3.debug(`onToolOutput hook error: ${e}`);
3661
+ log4.debug(`onToolOutput hook error: ${e}`);
3508
3662
  }
3509
3663
  } : void 0;
3510
3664
  const raw = await tool.run(args, this.ctx);
@@ -3516,7 +3670,7 @@ var Agent = class _Agent {
3516
3670
  }
3517
3671
  } catch (e) {
3518
3672
  const msg = e instanceof Error ? e.message : String(e);
3519
- log3.debug(`${tc.function.name} -> error: ${msg}`);
3673
+ log4.debug(`${tc.function.name} -> error: ${msg}`);
3520
3674
  result = `Error: ${msg}`;
3521
3675
  threw = true;
3522
3676
  } finally {
@@ -3642,7 +3796,7 @@ function fitTokenBudget(messages, maxTokens) {
3642
3796
  const ids = callIdSet(messages.slice(from));
3643
3797
  while (from < messages.length && messages[from].role === "tool" && !ids.has(messages[from].tool_call_id ?? "")) total -= per[from++];
3644
3798
  if (total > maxTokens)
3645
- 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)`);
3646
3800
  return [...head, ...messages.slice(from)];
3647
3801
  }
3648
3802
  function compact(m, max, focus) {
@@ -4199,7 +4353,7 @@ init_tools_web();
4199
4353
  init_tools();
4200
4354
  init_tools_structured();
4201
4355
  init_logging();
4202
- var log4 = forComponent("scratch");
4356
+ var log5 = forComponent("scratch");
4203
4357
  var SCRATCH_DIR = "/scratch";
4204
4358
  function shortArgs(args) {
4205
4359
  try {
@@ -4239,7 +4393,7 @@ var Scratch = class {
4239
4393
  await (this.dirReady ??= mkdirp(this.fs, dir));
4240
4394
  await this.fs.writeFile(path, header + raw);
4241
4395
  } catch (e) {
4242
- log4.debug("scratch write failed; returning raw", e);
4396
+ log5.debug("scratch write failed; returning raw", e);
4243
4397
  return raw;
4244
4398
  }
4245
4399
  const preview = raw.slice(0, previewChars).replace(/\s+/g, " ").trim();
@@ -4269,7 +4423,7 @@ To pull a specific detail, Grep/Read ${path}, or call Ask({ question: "\u2026",
4269
4423
  await (this.dirReady ??= mkdirp(this.fs, dir));
4270
4424
  await this.fs.writeFile(path, header + full);
4271
4425
  } catch (e) {
4272
- log4.debug("scratch spill failed; cropping lossy", e);
4426
+ log5.debug("scratch spill failed; cropping lossy", e);
4273
4427
  return full.slice(0, pageBytes) + `
4274
4428
 
4275
4429
  [output cropped to ${pageBytes} of ${full.length} bytes; full output unavailable (scratch write failed) \u2014 refine your query]`;
@@ -4315,7 +4469,7 @@ Question: ${q2}`);
4315
4469
  const answer = (res.text ?? "").trim();
4316
4470
  return answer || "(no answer found in scratch)";
4317
4471
  } catch (e) {
4318
- log4.debug("Ask peek failed", e);
4472
+ log5.debug("Ask peek failed", e);
4319
4473
  return `Error querying scratch: ${e?.message ?? e}`;
4320
4474
  }
4321
4475
  }
@@ -4324,7 +4478,7 @@ Question: ${q2}`);
4324
4478
 
4325
4479
  // src/lessons.ts
4326
4480
  init_logging();
4327
- var log5 = forComponent("Lessons");
4481
+ var log6 = forComponent("Lessons");
4328
4482
  var LessonOptionsDefaults = class {
4329
4483
  minRepeats = 2;
4330
4484
  };
@@ -4349,15 +4503,15 @@ function lessonCapture(options) {
4349
4503
  counts.set(lesson.slug, n);
4350
4504
  if (n < o.minRepeats) return;
4351
4505
  written.add(lesson.slug);
4352
- await writeFact(o.fs, o.dir, lesson.slug, lesson.body).catch((e) => log5.warn(`could not persist ${lesson.slug}: ${e?.message ?? e}`));
4353
- 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)`);
4354
4508
  }
4355
4509
  };
4356
4510
  }
4357
4511
 
4358
4512
  // src/reflect.ts
4359
4513
  init_logging();
4360
- var log6 = forComponent("Reflect");
4514
+ var log7 = forComponent("Reflect");
4361
4515
  async function reflectOnRun(o) {
4362
4516
  const digest = digestRun(o.result.messages, o.maxDigestChars ?? 6e3);
4363
4517
  if (!digest.trim()) return null;
@@ -4373,7 +4527,7 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
4373
4527
  const r = await o.ai.chat({ model: o.model, messages: [{ role: "user", content: prompt }], stream: false });
4374
4528
  text = r?.content ?? "";
4375
4529
  } catch (e) {
4376
- log6.warn(`reflection call failed: ${e?.message ?? e}`);
4530
+ log7.warn(`reflection call failed: ${e?.message ?? e}`);
4377
4531
  return null;
4378
4532
  }
4379
4533
  const m = text.match(/LESSON:\s*(.+)/i);
@@ -4383,10 +4537,10 @@ If the run was fine or the issue was purely task-specific (not generalizable), r
4383
4537
  try {
4384
4538
  await writeFact(o.fs, o.dir, slug2, lesson);
4385
4539
  } catch (e) {
4386
- log6.warn(`could not persist lesson: ${e?.message ?? e}`);
4540
+ log7.warn(`could not persist lesson: ${e?.message ?? e}`);
4387
4541
  return null;
4388
4542
  }
4389
- log6.debug(`reflection persisted ${slug2}`);
4543
+ log7.debug(`reflection persisted ${slug2}`);
4390
4544
  return slug2;
4391
4545
  }
4392
4546
  function digestRun(messages, maxChars) {
@@ -4403,7 +4557,7 @@ function digestRun(messages, maxChars) {
4403
4557
  // src/duplex.ts
4404
4558
  import { MemFilesystem as MemFilesystem2 } from "@livx.cc/wcli/core";
4405
4559
  init_logging();
4406
- var log7 = forComponent("DuplexAgent");
4560
+ var log8 = forComponent("DuplexAgent");
4407
4561
  function describeCall(call) {
4408
4562
  const v = call.args && Object.values(call.args).find((x) => typeof x === "string" && x.trim());
4409
4563
  const hint = v ? ` (${String(v).replace(/\s+/g, " ").trim().slice(0, 48)})` : "";
@@ -4543,7 +4697,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4543
4697
  const m = this.reflexBuf.match(RESERVED_EVENT_MARKER);
4544
4698
  if (m) {
4545
4699
  this.fabricationCut = true;
4546
- 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)`);
4547
4701
  const safe = this.reflexBuf.slice(this.reflexForwarded, m.index);
4548
4702
  if (!safe) return;
4549
4703
  if (safe.trim()) this.spokeThisTurn = true;
@@ -4610,23 +4764,28 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4610
4764
  }
4611
4765
  };
4612
4766
  }
4613
- /** True when the just-finished turn dispatched a task but voiced nothing — dead air to repair.
4767
+ /** True when the just-finished turn voiced NOTHING — dead air to repair. Two ways this happens:
4768
+ * (a) a dispatch with no spoken ack, and (b) an INLINE turn whose `final` channel came back empty —
4769
+ * gpt-oss harmony sometimes puts the whole reply in `analysis` (→ thinking_delta, suppressed in
4770
+ * voice) and emits an empty `final`, so no text_delta ever streams. Both ship silence; both repair.
4614
4771
  * Requires a host: without one there's no stream to detect speech on (and no one to speak to). */
4615
- get silentDispatch() {
4616
- return !!this.options.host && this.turnDispatched && !this.spokeThisTurn;
4772
+ get silentTurn() {
4773
+ return !!this.options.host && !this.spokeThisTurn;
4617
4774
  }
4618
- /** A dispatch with no spoken text is dead air. Re-prompt the reflex ONCE so the LLM itself voices a
4619
- * short ack (no template). If it STILL says nothing, fall back to a minimal line so silence never ships. */
4775
+ /** A turn that voiced nothing is dead air. Re-prompt the reflex ONCE so the LLM itself voices a short
4776
+ * line (no template). If it STILL says nothing, fall back to a minimal line so silence never ships.
4777
+ * Wording adapts to whether work was dispatched (an ack) or the inline reply was simply lost. */
4620
4778
  async ackIfSilent() {
4779
+ const dispatched = this.turnDispatched;
4621
4780
  this.nudging = true;
4622
4781
  try {
4623
- await this.voice.send("[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools.");
4782
+ await this.voice.send(dispatched ? "[reminder] You dispatched a task but said nothing to the user. Say ONE short spoken acknowledgement now \u2014 no tools." : "[reminder] You said nothing to the user this turn. Give your ONE short spoken reply now \u2014 no tools.");
4624
4783
  } catch (e) {
4625
- log7.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
4784
+ log8.warn(`ack nudge failed: ${e instanceof Error ? e.message : e}`);
4626
4785
  } finally {
4627
4786
  this.nudging = false;
4628
4787
  }
4629
- if (!this.spokeThisTurn) this.options.host?.notify?.({ kind: "text_delta", message: "Okay, on it." });
4788
+ if (!this.spokeThisTurn) this.options.host?.notify?.({ kind: "text_delta", message: dispatched ? "Okay, on it." : "Sorry, could you say that again?" });
4630
4789
  }
4631
4790
  /** One user turn: the voice agent streams the reply (and may Act/Think). Serialized with re-voice turns. */
4632
4791
  send(content) {
@@ -4634,7 +4793,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4634
4793
  await this.initMemory();
4635
4794
  this.resetTurn();
4636
4795
  const res = await this.voice.send(content);
4637
- if (this.silentDispatch) await this.ackIfSilent();
4796
+ if (this.silentTurn) await this.ackIfSilent();
4638
4797
  return res;
4639
4798
  });
4640
4799
  }
@@ -4679,7 +4838,7 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
4679
4838
  if (!events.length) return;
4680
4839
  this.resetTurn();
4681
4840
  await this.voice.send(events.join("\n"));
4682
- if (this.silentDispatch) await this.ackIfSilent();
4841
+ if (this.silentTurn) await this.ackIfSilent();
4683
4842
  this.notify("revoice_done", "");
4684
4843
  });
4685
4844
  }
@@ -4770,7 +4929,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4770
4929
  this.notify("task_verify", `task ${id}: verifying`, { id });
4771
4930
  const cres = await new Agent(agentOpts).run(checkBrief);
4772
4931
  if (cres.finishReason !== "stop") {
4773
- log7.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
4932
+ log8.warn(`task ${id}: verify inconclusive (${cres.finishReason})`);
4774
4933
  this.notify("task_verify", `task ${id}: verify inconclusive (${cres.finishReason})`, { id, finishReason: cres.finishReason });
4775
4934
  }
4776
4935
  const sum = (a = 0, b = 0) => a + b;
@@ -4873,7 +5032,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4873
5032
  }
4874
5033
  rec.status = "done";
4875
5034
  rec.result = res.text;
4876
- log7.verbose(`task ${id} done (${res.steps} steps)`);
5035
+ log8.verbose(`task ${id} done (${res.steps} steps)`);
4877
5036
  this.notify("task_done", `task ${id} (${rec.label}) completed`, {
4878
5037
  id,
4879
5038
  text: res.text,
@@ -4891,7 +5050,7 @@ Another agent just implemented the above. Independently check the CURRENT state
4891
5050
  this.dropAsk(rec.id);
4892
5051
  rec.status = "error";
4893
5052
  rec.result = msg;
4894
- log7.warn(`task ${rec.id} failed: ${msg}`);
5053
+ log8.warn(`task ${rec.id} failed: ${msg}`);
4895
5054
  this.notify("task_error", `task ${rec.id} (${rec.label}) failed: ${msg}`);
4896
5055
  this.queueRevoice(`[task ${rec.id} failed] ${msg}`);
4897
5056
  }
@@ -5193,7 +5352,7 @@ init_logging();
5193
5352
 
5194
5353
  // src/voice/engine.ts
5195
5354
  init_logging();
5196
- var log8 = forComponent("VoiceEngine");
5355
+ var log9 = forComponent("VoiceEngine");
5197
5356
  var now = () => performance.now();
5198
5357
  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, ".");
5199
5358
  var VoiceEngineOptions = class {
@@ -5301,7 +5460,7 @@ var VoiceEngine = class _VoiceEngine {
5301
5460
  this.stt.onLevel = (rms) => this.handleLevel(rms);
5302
5461
  await Promise.all([this.tts.connect(), this.stt.start()]);
5303
5462
  this.setState("listening");
5304
- log8.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
5463
+ log9.debug(`voice I/O up (${this.stt.usingAec ? "AEC" : "heuristic echo"} capture)`);
5305
5464
  }
5306
5465
  get usingAec() {
5307
5466
  return this.stt.usingAec;
@@ -5353,7 +5512,7 @@ var VoiceEngine = class _VoiceEngine {
5353
5512
  this.reply += text;
5354
5513
  for (const w of this.words(this.reply)) this.echoWords.add(w);
5355
5514
  this.tts.speak(forSpeech(text), true);
5356
- if (!this.spokeDeltas && this.turnStartAt) log8.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
5515
+ if (!this.spokeDeltas && this.turnStartAt) log9.debug(`ttft: ${Math.round(now() - this.turnStartAt)}ms`);
5357
5516
  this.spokeDeltas = true;
5358
5517
  this.setState("speaking");
5359
5518
  }
@@ -5374,7 +5533,7 @@ var VoiceEngine = class _VoiceEngine {
5374
5533
  }
5375
5534
  this.drainTimer = null;
5376
5535
  this.speaking = false;
5377
- if (this.turnStartAt) log8.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
5536
+ if (this.turnStartAt) log9.debug(`turn: ${Math.round(now() - this.turnStartAt)}ms (incl. playback)`);
5378
5537
  this.echoUntil = now() + 2500;
5379
5538
  if (!this.usingAec) this.stt.reset();
5380
5539
  this.setState("listening");
@@ -5515,7 +5674,7 @@ var VoiceEngine = class _VoiceEngine {
5515
5674
  this.pendingUtt = this.pendingUtt ? `${this.pendingUtt} ${text}` : text;
5516
5675
  if (this.pendingTimer) clearTimeout(this.pendingTimer);
5517
5676
  if (this.options.incompleteMergeMs && this.looksIncomplete(this.pendingUtt)) {
5518
- log8.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
5677
+ log9.verbose(`hold: incomplete utterance "${this.pendingUtt.slice(-40)}"`);
5519
5678
  this.options.onHold();
5520
5679
  if (this.options.holdFiller && !this.speaking) {
5521
5680
  this.beginSpeech();
@@ -5613,7 +5772,7 @@ async function resolveAuth(auth) {
5613
5772
  }
5614
5773
 
5615
5774
  // src/voice/soniox.ts
5616
- var log9 = forComponent("SonioxSTT");
5775
+ var log10 = forComponent("SonioxSTT");
5617
5776
  var now2 = () => performance.now();
5618
5777
  var SonioxSTTOptions = class {
5619
5778
  auth = "";
@@ -5670,9 +5829,9 @@ var SonioxSTT = class {
5670
5829
  this.ws.onmessage = (ev) => this.handle(JSON.parse(String(ev.data)));
5671
5830
  this.ws.onclose = (ev) => {
5672
5831
  if (this.stopped) return;
5673
- log9.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
5832
+ log10.warn(`soniox ws closed (${ev.code} ${ev.reason || ""}) \u2014 reconnecting`);
5674
5833
  this.reset();
5675
- this.connectWs().catch((e) => log9.error(`soniox reconnect failed: ${e.message}`));
5834
+ this.connectWs().catch((e) => log10.error(`soniox reconnect failed: ${e.message}`));
5676
5835
  };
5677
5836
  }
5678
5837
  async start() {
@@ -5682,7 +5841,7 @@ var SonioxSTT = class {
5682
5841
  this.endpointTimer = setInterval(() => {
5683
5842
  const combined = (this.finalText + this.partialText).trim();
5684
5843
  if (!combined || now2() - this.lastChangeAt < this.options.silenceEndpointMs) return;
5685
- if (this.firstTokenAt) log9.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
5844
+ if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192silence-endpoint, "${combined.slice(0, 60)}"`);
5686
5845
  this.reset();
5687
5846
  this.onUtterance(combined, now2());
5688
5847
  }, 120);
@@ -5699,7 +5858,7 @@ var SonioxSTT = class {
5699
5858
  });
5700
5859
  }
5701
5860
  handle(m) {
5702
- if (m.error_message) return log9.error(`soniox: ${m.error_message}`);
5861
+ if (m.error_message) return log10.error(`soniox: ${m.error_message}`);
5703
5862
  let endpoint = false;
5704
5863
  for (const t of m.tokens ?? []) {
5705
5864
  if (t.text === "<end>") endpoint = true;
@@ -5715,7 +5874,7 @@ var SonioxSTT = class {
5715
5874
  this.onPartial(combined);
5716
5875
  if (endpoint && this.finalText.trim()) {
5717
5876
  const utterance = this.finalText.trim();
5718
- if (this.firstTokenAt) log9.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
5877
+ if (this.firstTokenAt) log10.debug(`stt: ${Math.round(now2() - this.firstTokenAt)}ms first-token\u2192endpoint, "${utterance.slice(0, 60)}"`);
5719
5878
  this.reset();
5720
5879
  this.onUtterance(utterance, now2());
5721
5880
  }
@@ -5737,7 +5896,7 @@ var SonioxSTT = class {
5737
5896
 
5738
5897
  // src/voice/cartesia.ts
5739
5898
  init_logging();
5740
- var log10 = forComponent("CartesiaTTS");
5899
+ var log11 = forComponent("CartesiaTTS");
5741
5900
  var now3 = () => performance.now();
5742
5901
  var CartesiaTTSOptions = class {
5743
5902
  auth = "";
@@ -5787,9 +5946,9 @@ var CartesiaTTS = class _CartesiaTTS {
5787
5946
  this.ws.onerror = (e) => rej(new Error(`cartesia ws: ${e.message || "connect failed"}`));
5788
5947
  });
5789
5948
  this.ws.onclose = (ev) => {
5790
- log10.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
5949
+ log11.warn(`cartesia ws closed (${ev.code} ${ev.reason || ""})`);
5791
5950
  if (!this.closed) {
5792
- this.connecting = this.doConnect().catch((e) => log10.error(`cartesia reconnect failed: ${e.message}`));
5951
+ this.connecting = this.doConnect().catch((e) => log11.error(`cartesia reconnect failed: ${e.message}`));
5793
5952
  }
5794
5953
  };
5795
5954
  this.ws.onmessage = (ev) => {
@@ -5811,11 +5970,11 @@ var CartesiaTTS = class _CartesiaTTS {
5811
5970
  this.down = true;
5812
5971
  this.downAt = now3();
5813
5972
  this.consecutiveOk = 0;
5814
- log10.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
5973
+ log11.warn(`TTS circuit breaker open \u2014 ${this.consecutiveErrors} consecutive errors, switching to text-only`);
5815
5974
  this.onDone();
5816
5975
  this.startProbe();
5817
5976
  } else if (!this.down) {
5818
- log10.warn(`cartesia: ${JSON.stringify(m)}`);
5977
+ log11.warn(`cartesia: ${JSON.stringify(m)}`);
5819
5978
  }
5820
5979
  }
5821
5980
  };
@@ -5829,7 +5988,7 @@ var CartesiaTTS = class _CartesiaTTS {
5829
5988
  this.consecutiveOk = 0;
5830
5989
  this.stopProbe();
5831
5990
  const downMs = this.downAt ? now3() - this.downAt : 0;
5832
- (downMs < 2e3 ? log10.debug : log10.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
5991
+ (downMs < 2e3 ? log11.debug : log11.info)(`TTS recovered${downMs ? ` (down ${downMs}ms)` : ""}`);
5833
5992
  }
5834
5993
  /** Ensure the WS is open before sending — reconnects if idle-closed. */
5835
5994
  async ensureConnected() {
@@ -5909,7 +6068,7 @@ import { MemFilesystem as MemFilesystem3, IndexedDbFilesystem, CommandExecutor a
5909
6068
  init_logging();
5910
6069
  import { spawn } from "child_process";
5911
6070
  import { createHash } from "crypto";
5912
- var log11 = forComponent("mcp");
6071
+ var log12 = forComponent("mcp");
5913
6072
  var PROTOCOL_VERSION = "2025-06-18";
5914
6073
  var DEFAULT_TIMEOUT_MS = 3e4;
5915
6074
  var StdioTransport = class {
@@ -5928,7 +6087,7 @@ var StdioTransport = class {
5928
6087
  proc.stdout.setEncoding("utf8");
5929
6088
  proc.stdout.on("data", (chunk) => this.onData(chunk));
5930
6089
  proc.stderr.setEncoding("utf8");
5931
- proc.stderr.on("data", (chunk) => log11.debug(`[${command}] stderr:`, chunk.trimEnd()));
6090
+ proc.stderr.on("data", (chunk) => log12.debug(`[${command}] stderr:`, chunk.trimEnd()));
5932
6091
  proc.on("exit", (code) => this.failAll(new Error(`MCP server "${command}" exited (code ${code})`)));
5933
6092
  proc.on("error", (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));
5934
6093
  }
@@ -5942,7 +6101,7 @@ var StdioTransport = class {
5942
6101
  try {
5943
6102
  this.dispatch(JSON.parse(line));
5944
6103
  } catch (e) {
5945
- log11.debug("dropping non-JSON line from MCP server:", line, e);
6104
+ log12.debug("dropping non-JSON line from MCP server:", line, e);
5946
6105
  }
5947
6106
  }
5948
6107
  }
@@ -5991,7 +6150,7 @@ var StdioTransport = class {
5991
6150
  try {
5992
6151
  this.proc?.stdin?.end();
5993
6152
  } catch (e) {
5994
- log11.debug("stdin end failed", e);
6153
+ log12.debug("stdin end failed", e);
5995
6154
  }
5996
6155
  this.proc?.kill();
5997
6156
  }
@@ -6060,7 +6219,7 @@ function parseSseResponse(body) {
6060
6219
  const obj = JSON.parse(trimmed.slice(5).trim());
6061
6220
  if (obj && (obj.result !== void 0 || obj.error !== void 0)) return obj;
6062
6221
  } catch (e) {
6063
- log11.debug("skipping unparseable SSE data line", e);
6222
+ log12.debug("skipping unparseable SSE data line", e);
6064
6223
  }
6065
6224
  }
6066
6225
  return {};
@@ -6128,7 +6287,7 @@ async function mountWithDeadline(name, cfg, mountTimeoutMs) {
6128
6287
  return { name, client, tools, specs, serverInfo: init?.serverInfo, config: cfg };
6129
6288
  })(), mountTimeoutMs, name);
6130
6289
  } catch (e) {
6131
- await client.close().catch((err2) => log11.debug(`close after failed mount of "${name}": ${err2}`));
6290
+ await client.close().catch((err2) => log12.debug(`close after failed mount of "${name}": ${err2}`));
6132
6291
  throw e;
6133
6292
  }
6134
6293
  }
@@ -6139,15 +6298,15 @@ function validEntries(servers) {
6139
6298
  return Object.entries(servers).filter(([name, cfg]) => {
6140
6299
  if (!cfg || cfg.disabled) return false;
6141
6300
  if (!cfg.command && !cfg.url) {
6142
- log11.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
6301
+ log12.warn(`MCP server "${name}" needs a command (stdio) or url (http) \u2014 skipping`);
6143
6302
  return false;
6144
6303
  }
6145
6304
  return true;
6146
6305
  });
6147
6306
  }
6148
6307
  function logMountFailure(name, e) {
6149
- if (e instanceof McpAuthError) log11.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
6150
- else log11.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
6308
+ if (e instanceof McpAuthError) log12.warn(`MCP "${name}" needs-auth: HTTP ${e.status} \u2014 set bearerToken or headers in its config; skipping`);
6309
+ else log12.error(`MCP server "${name}" failed to mount: ${e?.message ?? e}`);
6151
6310
  }
6152
6311
  async function mountMcpServers(servers = {}, opts = {}) {
6153
6312
  const entries = validEntries(servers);
@@ -6157,7 +6316,7 @@ async function mountMcpServers(servers = {}, opts = {}) {
6157
6316
  const name = entries[i][0];
6158
6317
  if (r.status === "fulfilled") {
6159
6318
  out.push(r.value);
6160
- log11.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
6319
+ log12.debug(`MCP "${name}" mounted \u2014 ${r.value.tools.length} tool(s)${r.value.serverInfo?.name ? ` from ${r.value.serverInfo.name}` : ""}`);
6161
6320
  } else logMountFailure(name, r.reason);
6162
6321
  });
6163
6322
  return out;
@@ -6197,7 +6356,7 @@ var McpPool = class {
6197
6356
  const prev = this.warm.get(key);
6198
6357
  if (prev) {
6199
6358
  clearTimeout(prev.timer);
6200
- if (prev.client !== client) void prev.client.close().catch((err2) => log11.debug(`warm-pool replace close failed: ${err2}`));
6359
+ if (prev.client !== client) void prev.client.close().catch((err2) => log12.debug(`warm-pool replace close failed: ${err2}`));
6201
6360
  }
6202
6361
  const e = { client, timer: void 0 };
6203
6362
  this.warm.set(key, e);
@@ -6214,7 +6373,7 @@ var McpPool = class {
6214
6373
  const e = this.warm.get(key);
6215
6374
  if (!e) return;
6216
6375
  this.warm.delete(key);
6217
- await e.client.close().catch((err2) => log11.debug(`warm-pool evict close failed: ${err2}`));
6376
+ await e.client.close().catch((err2) => log12.debug(`warm-pool evict close failed: ${err2}`));
6218
6377
  }
6219
6378
  async closeAll() {
6220
6379
  for (const e of this.warm.values()) {
@@ -6231,7 +6390,7 @@ var defaultPool = new McpPool();
6231
6390
  // cli/mcpOAuth.ts
6232
6391
  import { createServer } from "http";
6233
6392
  import { randomBytes, createHash as createHash2 } from "crypto";
6234
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync } from "fs";
6393
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync } from "fs";
6235
6394
  import { dirname as dirname2 } from "path";
6236
6395
  var McpOAuthOptions = class {
6237
6396
  /** Where to persist tokens. Mode 0600. */
@@ -6265,8 +6424,8 @@ var McpOAuth = class {
6265
6424
  }
6266
6425
  }
6267
6426
  save(store) {
6268
- mkdirSync2(dirname2(this.options.storePath), { recursive: true });
6269
- writeFileSync2(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
6427
+ mkdirSync3(dirname2(this.options.storePath), { recursive: true });
6428
+ writeFileSync3(this.options.storePath, JSON.stringify(store, null, 2), { mode: 384 });
6270
6429
  }
6271
6430
  // -- ATTENDED: one-time authorization ----------------------------------------
6272
6431
  /** Run the authorization-code + PKCE flow and persist the resulting tokens for `serverUrl`. */
@@ -6424,15 +6583,15 @@ function defaultOpenBrowser(url) {
6424
6583
  // cli/core.ts
6425
6584
  import { randomUUID } from "crypto";
6426
6585
  import { execFile as execFile2 } from "child_process";
6427
- import { resolve, basename, join as join4 } from "path";
6428
- import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
6586
+ import { resolve, basename, join as join5 } from "path";
6587
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
6429
6588
  import { platform, arch, release, userInfo, homedir as homedir2, tmpdir } from "os";
6430
6589
  init_tools_shell();
6431
6590
 
6432
6591
  // src/tools.notify.ts
6433
6592
  init_logging();
6434
6593
  import { execFile } from "child_process";
6435
- var log13 = forComponent("notify");
6594
+ var log14 = forComponent("notify");
6436
6595
  function makeNotifyTool(opts = {}) {
6437
6596
  const platform2 = opts.platform ?? process.platform;
6438
6597
  const run = opts.exec ?? execFile;
@@ -6456,7 +6615,7 @@ function makeNotifyTool(opts = {}) {
6456
6615
  return new Promise((resolve4) => {
6457
6616
  run(argv[0], argv[1], { timeout: 5e3 }, (e) => {
6458
6617
  if (e) {
6459
- log13.debug("notification failed", e);
6618
+ log14.debug("notification failed", e);
6460
6619
  resolve4(`Notification failed: ${e.message}`);
6461
6620
  } else resolve4("Notification shown.");
6462
6621
  });
@@ -6472,7 +6631,7 @@ import { BodDB as BodDB2 } from "@bod.ee/db";
6472
6631
  init_logging();
6473
6632
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
6474
6633
  import { homedir } from "os";
6475
- var log14 = forComponent("cli-util");
6634
+ var log15 = forComponent("cli-util");
6476
6635
  function dotDirs(base, sub, opts = {}) {
6477
6636
  const home = opts.home ?? homedir();
6478
6637
  const dirs = [`${base}/.agent/${sub}`, `${base}/.claude/${sub}`, `${home}/.agent/${sub}`, `${home}/.claude/${sub}`];
@@ -6489,7 +6648,7 @@ function parseJson(text, fallback, what = "json") {
6489
6648
  try {
6490
6649
  return JSON.parse(text);
6491
6650
  } catch (e) {
6492
- log14.debug(`parseJson(${what}) failed: ${e.message}`);
6651
+ log15.debug(`parseJson(${what}) failed: ${e.message}`);
6493
6652
  return fallback;
6494
6653
  }
6495
6654
  }
@@ -6499,7 +6658,7 @@ function readJsonFile(path, fallback) {
6499
6658
  try {
6500
6659
  text = readFileSync3(path, "utf8");
6501
6660
  } catch (e) {
6502
- log14.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
6661
+ log15.debug(`readJsonFile(${path}) unreadable: ${e.message}`);
6503
6662
  return fallback;
6504
6663
  }
6505
6664
  return parseJson(text, fallback, path);
@@ -6583,8 +6742,8 @@ async function buildAgent(o) {
6583
6742
  fs = mem;
6584
6743
  } else if (o.boddb) {
6585
6744
  const root = resolve(o.boddb);
6586
- mkdirSync3(root, { recursive: true });
6587
- const db = new BodDB2({ path: join4(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join4(root, "files") } });
6745
+ mkdirSync4(root, { recursive: true });
6746
+ const db = new BodDB2({ path: join5(root, "meta.db"), sweepInterval: 0, vfs: { storageRoot: join5(root, "files") } });
6588
6747
  const bod = new BodDbFilesystem(db);
6589
6748
  const firstRun = (await bod.readDir("/").catch(() => [])).length === 0;
6590
6749
  await mkdirp(bod, cwd);
@@ -6711,6 +6870,9 @@ The filesystem root '/' is the real machine root \u2014 you have full filesystem
6711
6870
  ...scratch ? { capToolResult: (full, info) => scratch.spill(full, info) } : {},
6712
6871
  backgroundJobs: o.backgroundJobs ?? virtual,
6713
6872
  // default ON in virtual modes (no real shell there); disk uses ShellJobRegistry
6873
+ // Subagent transcript traces, origin-anchored under .agent/traces (disk mode only — sandbox/db modes
6874
+ // must never write the real disk). Lets an interrupted/failed child leave a readable trail + pointer.
6875
+ ...!virtual ? { traceDir: `${cwd}/.agent/traces` } : {},
6714
6876
  skillsDir: dots("skills"),
6715
6877
  commandsDir: dots("commands"),
6716
6878
  memoryDir,
@@ -6744,11 +6906,11 @@ var trunc = (s, n) => (s == null ? "" : String(s).length > n ? String(s).slice(0
6744
6906
  // cli/voice.ts
6745
6907
  init_logging();
6746
6908
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
6747
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
6909
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, statSync as statSync3 } from "fs";
6748
6910
  import { homedir as homedir3 } from "os";
6749
- import { dirname as dirname3, join as join5 } from "path";
6911
+ import { dirname as dirname3, join as join6 } from "path";
6750
6912
  import { fileURLToPath } from "url";
6751
- var log15 = forComponent("VoiceIO");
6913
+ var log16 = forComponent("VoiceIO");
6752
6914
  var now4 = () => performance.now();
6753
6915
  var Player = class {
6754
6916
  proc = null;
@@ -6762,7 +6924,7 @@ var Player = class {
6762
6924
  ["-loglevel", "quiet", "-nodisp", "-fflags", "nobuffer", "-flags", "low_delay", "-probesize", "32", "-f", "s16le", "-ar", String(TTS_SAMPLE_RATE), "-ch_layout", "mono", "-i", "-"],
6763
6925
  { stdio: ["pipe", "ignore", "ignore"] }
6764
6926
  );
6765
- this.proc.on("error", (e) => log15.warn(`ffplay error: ${e.message}`));
6927
+ this.proc.on("error", (e) => log16.warn(`ffplay error: ${e.message}`));
6766
6928
  this.proc.stdin.on("error", () => {
6767
6929
  });
6768
6930
  this.bytesWritten = 0;
@@ -6789,7 +6951,7 @@ var Player = class {
6789
6951
  this.proc = null;
6790
6952
  }
6791
6953
  };
6792
- var nativeDir = () => join5(dirname3(fileURLToPath(import.meta.url)), "native");
6954
+ var nativeDir = () => join6(dirname3(fileURLToPath(import.meta.url)), "native");
6793
6955
  function detectFfmpegMic() {
6794
6956
  if (process.env.MIC_DEVICE) return process.env.MIC_DEVICE;
6795
6957
  const out = spawnSync2("ffmpeg", ["-f", "avfoundation", "-list_devices", "true", "-i", ""], { encoding: "utf8" }).stderr;
@@ -6797,7 +6959,7 @@ function detectFfmpegMic() {
6797
6959
  const devices = [...audio.matchAll(/\[(\d+)\] (.+)/g)].map(([, idx, name]) => ({ idx, name: name.trim() }));
6798
6960
  const mic = devices.find((d) => /microphone|built-in/i.test(d.name) && !/teams|blackhole|loopback/i.test(d.name)) ?? devices[0];
6799
6961
  if (!mic) throw new Error("no audio input device found");
6800
- log15.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
6962
+ log16.debug(`ffmpeg mic: [${mic.idx}] ${mic.name}`);
6801
6963
  return `:${mic.idx}`;
6802
6964
  }
6803
6965
  function detectedInputDevice() {
@@ -6825,23 +6987,23 @@ function detectedInputDevice() {
6825
6987
  }
6826
6988
  function resolveAecBinary() {
6827
6989
  if (process.env.MIC_AEC === "0" || process.platform !== "darwin") return null;
6828
- const src = join5(nativeDir(), "mic-aec.swift");
6829
- const plist = join5(nativeDir(), "Info.plist");
6990
+ const src = join6(nativeDir(), "mic-aec.swift");
6991
+ const plist = join6(nativeDir(), "Info.plist");
6830
6992
  if (!existsSync4(src)) return null;
6831
- const cacheDir = join5(homedir3(), ".agent", "cache");
6832
- const bin = join5(cacheDir, "mic-aec");
6993
+ const cacheDir = join6(homedir3(), ".agent", "cache");
6994
+ const bin = join6(cacheDir, "mic-aec");
6833
6995
  if (existsSync4(bin) && statSync3(bin).mtimeMs >= statSync3(src).mtimeMs) return bin;
6834
6996
  if (spawnSync2("which", ["swiftc"]).status !== 0) return null;
6835
- mkdirSync4(cacheDir, { recursive: true });
6836
- log15.info("compiling AEC mic helper (first run)\u2026");
6997
+ mkdirSync5(cacheDir, { recursive: true });
6998
+ log16.info("compiling AEC mic helper (first run)\u2026");
6837
6999
  const build = spawnSync2("swiftc", ["-O", "-o", bin, src, "-Xlinker", "-sectcreate", "-Xlinker", "__TEXT", "-Xlinker", "__info_plist", "-Xlinker", plist], { encoding: "utf8" });
6838
7000
  if (build.status !== 0) {
6839
- log15.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
7001
+ log16.warn(`AEC build failed: ${build.stderr?.slice(0, 400)}`);
6840
7002
  return null;
6841
7003
  }
6842
7004
  const sign = spawnSync2("codesign", ["-fs", "-", bin], { encoding: "utf8" });
6843
7005
  if (sign.status !== 0) {
6844
- log15.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
7006
+ log16.warn(`codesign failed: ${sign.stderr?.slice(0, 200)}`);
6845
7007
  return null;
6846
7008
  }
6847
7009
  return bin;
@@ -6860,16 +7022,16 @@ var NodeMicSource = class {
6860
7022
  this.proc = spawn2(this.bin, [], { stdio: ["ignore", "pipe", "ignore"] });
6861
7023
  } else {
6862
7024
  if (spawnSync2("which", ["ffmpeg"]).status !== 0) throw new Error("voice I/O unavailable: no AEC helper and no ffmpeg on PATH");
6863
- log15.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
7025
+ log16.info("mic: raw capture (no AEC) \u2014 echo handled heuristically; headphones recommended");
6864
7026
  this.proc = spawn2(
6865
7027
  "ffmpeg",
6866
7028
  ["-loglevel", "error", "-f", "avfoundation", "-i", detectFfmpegMic(), "-ar", String(STT_SAMPLE_RATE), "-ac", "1", "-f", "s16le", "-"],
6867
7029
  { stdio: ["ignore", "pipe", "pipe"] }
6868
7030
  );
6869
- this.proc.stderr.on("data", (d) => log15.warn(`ffmpeg: ${String(d).trim()}`));
7031
+ this.proc.stderr.on("data", (d) => log16.warn(`ffmpeg: ${String(d).trim()}`));
6870
7032
  }
6871
7033
  this.proc.on("exit", (c) => {
6872
- if (c && !this.stopped) log15.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
7034
+ if (c && !this.stopped) log16.error(`mic capture exited (${c}) \u2014 check mic permission / MIC_DEVICE / MIC_AEC=0`);
6873
7035
  });
6874
7036
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
6875
7037
  }
@@ -6903,11 +7065,11 @@ var AecDuplexAudio = class {
6903
7065
  this.proc.stdin.on("error", () => {
6904
7066
  });
6905
7067
  this.proc.on("exit", (c) => {
6906
- if (c && !this.stopped) log15.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
7068
+ if (c && !this.stopped) log16.error(`aec duplex audio exited (${c}) \u2014 check mic permission / MIC_AEC=0`);
6907
7069
  });
6908
7070
  this.proc.stdout.on("data", (chunk) => onChunk(chunk));
6909
7071
  this.proc.stderr.on("data", (d) => {
6910
- for (const ln of String(d).split("\n")) if (ln.trim()) log15.debug(`mic-aec: ${ln.trim()}`);
7072
+ for (const ln of String(d).split("\n")) if (ln.trim()) log16.debug(`mic-aec: ${ln.trim()}`);
6911
7073
  });
6912
7074
  }
6913
7075
  stop() {
@@ -7029,12 +7191,12 @@ var VoiceIO = class extends VoiceEngine {
7029
7191
  // cli/config.ts
7030
7192
  import { homedir as homedir4 } from "os";
7031
7193
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
7032
- import { join as join6 } from "path";
7194
+ import { join as join7 } from "path";
7033
7195
  import { pathToFileURL } from "url";
7034
7196
  var FILES = ["config.ts", "config.js", "config.mjs", "config.json"];
7035
7197
  async function loadFrom(dir) {
7036
7198
  for (const f of FILES) {
7037
- const p = join6(dir, ".agent", f);
7199
+ const p = join7(dir, ".agent", f);
7038
7200
  if (!existsSync5(p)) continue;
7039
7201
  try {
7040
7202
  const mod = await import(pathToFileURL(p).href, f.endsWith(".json") ? { with: { type: "json" } } : void 0);
@@ -7047,7 +7209,7 @@ async function loadFrom(dir) {
7047
7209
  return {};
7048
7210
  }
7049
7211
  function loadSettings(dir) {
7050
- const p = join6(dir, ".agent", "settings.json");
7212
+ const p = join7(dir, ".agent", "settings.json");
7051
7213
  if (!existsSync5(p)) return {};
7052
7214
  try {
7053
7215
  const raw = JSON.parse(readFileSync4(p, "utf8"));
@@ -7084,7 +7246,7 @@ async function loadConfig(cwd) {
7084
7246
 
7085
7247
  // cli/hooks-config.ts
7086
7248
  import { spawnSync as spawnSync3 } from "child_process";
7087
- var log16 = forComponent("hooks");
7249
+ var log17 = forComponent("hooks");
7088
7250
  var escapeRegex = (s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
7089
7251
  function ruleMatches(rule, toolName) {
7090
7252
  if (!rule.tool || rule.tool === "*") return true;
@@ -7101,7 +7263,7 @@ function runCmd(rule, env) {
7101
7263
  });
7102
7264
  return { code: r.status ?? 1, out: ((r.stdout ?? "") + (r.stderr ?? "")).trim() };
7103
7265
  } catch (e) {
7104
- log16.debug(`hook command failed: ${rule.command}`, e);
7266
+ log17.debug(`hook command failed: ${rule.command}`, e);
7105
7267
  return { code: 1, out: String(e?.message ?? e) };
7106
7268
  }
7107
7269
  }
@@ -7205,15 +7367,15 @@ function formatDiff(ops, opts = {}) {
7205
7367
  }
7206
7368
 
7207
7369
  // cli/session.ts
7208
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
7370
+ import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4, readdirSync, renameSync, symlinkSync as symlinkSync2, unlinkSync, readlinkSync } from "fs";
7209
7371
  import { homedir as homedir5 } from "os";
7210
- import { join as join7 } from "path";
7211
- var log17 = forComponent("session");
7212
- var globalDir = () => join7(homedir5(), ".agent", "sessions");
7372
+ import { join as join8 } from "path";
7373
+ var log18 = forComponent("session");
7374
+ var globalDir = () => join8(homedir5(), ".agent", "sessions");
7213
7375
  var SessionStore = class {
7214
7376
  dir;
7215
7377
  constructor(cwd) {
7216
- this.dir = join7(cwd, ".agent", "sessions");
7378
+ this.dir = join8(cwd, ".agent", "sessions");
7217
7379
  }
7218
7380
  /** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
7219
7381
  newId(now5 = Date.now(), cwd) {
@@ -7221,10 +7383,10 @@ var SessionStore = class {
7221
7383
  const p = (n, w = 2) => String(n).padStart(w, "0");
7222
7384
  const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
7223
7385
  let id = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
7224
- if (existsSync6(this.dir) && existsSync6(join7(this.dir, `${id}.json`))) {
7386
+ if (existsSync6(this.dir) && existsSync6(join8(this.dir, `${id}.json`))) {
7225
7387
  for (let i = 2; i <= 99; i++) {
7226
7388
  const c = `${id}-${i}`;
7227
- if (!existsSync6(join7(this.dir, `${c}.json`))) {
7389
+ if (!existsSync6(join8(this.dir, `${c}.json`))) {
7228
7390
  id = c;
7229
7391
  break;
7230
7392
  }
@@ -7238,30 +7400,30 @@ var SessionStore = class {
7238
7400
  }
7239
7401
  save(data) {
7240
7402
  if (!this.safeId(data.meta.id)) throw new Error(`unsafe session id: ${data.meta.id}`);
7241
- if (!existsSync6(this.dir)) mkdirSync5(this.dir, { recursive: true });
7242
- const path = join7(this.dir, `${data.meta.id}.json`);
7403
+ if (!existsSync6(this.dir)) mkdirSync6(this.dir, { recursive: true });
7404
+ const path = join8(this.dir, `${data.meta.id}.json`);
7243
7405
  const tmp = `${path}.${process.pid}.tmp`;
7244
- writeFileSync3(tmp, JSON.stringify(data));
7406
+ writeFileSync4(tmp, JSON.stringify(data));
7245
7407
  renameSync(tmp, path);
7246
7408
  try {
7247
7409
  const gd = globalDir();
7248
- if (!existsSync6(gd)) mkdirSync5(gd, { recursive: true });
7249
- const link2 = join7(gd, `${data.meta.id}.json`);
7410
+ if (!existsSync6(gd)) mkdirSync6(gd, { recursive: true });
7411
+ const link2 = join8(gd, `${data.meta.id}.json`);
7250
7412
  if (!existsSync6(link2)) symlinkSync2(path, link2);
7251
7413
  } catch {
7252
7414
  }
7253
7415
  }
7254
7416
  load(id) {
7255
7417
  if (!this.safeId(id)) {
7256
- log17.debug(`rejecting unsafe session id: ${id}`);
7418
+ log18.debug(`rejecting unsafe session id: ${id}`);
7257
7419
  return void 0;
7258
7420
  }
7259
- const path = join7(this.dir, `${id}.json`);
7421
+ const path = join8(this.dir, `${id}.json`);
7260
7422
  if (!existsSync6(path)) return void 0;
7261
7423
  try {
7262
7424
  return JSON.parse(readFileSync5(path, "utf8"));
7263
7425
  } catch (e) {
7264
- log17.debug(`unreadable session ${id} \u2014 ignoring`, e);
7426
+ log18.debug(`unreadable session ${id} \u2014 ignoring`, e);
7265
7427
  return void 0;
7266
7428
  }
7267
7429
  }
@@ -7272,9 +7434,9 @@ var SessionStore = class {
7272
7434
  for (const f of readdirSync(this.dir)) {
7273
7435
  if (!f.endsWith(".json")) continue;
7274
7436
  try {
7275
- metas.push(JSON.parse(readFileSync5(join7(this.dir, f), "utf8")).meta);
7437
+ metas.push(JSON.parse(readFileSync5(join8(this.dir, f), "utf8")).meta);
7276
7438
  } catch (e) {
7277
- log17.debug(`skipping unreadable session file ${f}`, e);
7439
+ log18.debug(`skipping unreadable session file ${f}`, e);
7278
7440
  }
7279
7441
  }
7280
7442
  return metas.sort((a, b) => b.updated - a.updated);
@@ -7291,7 +7453,7 @@ var SessionStore = class {
7291
7453
  function globalSessionLoad(idOrPrefix) {
7292
7454
  const gd = globalDir();
7293
7455
  if (!existsSync6(gd)) return void 0;
7294
- const exact = join7(gd, `${idOrPrefix}.json`);
7456
+ const exact = join8(gd, `${idOrPrefix}.json`);
7295
7457
  if (existsSync6(exact)) {
7296
7458
  try {
7297
7459
  const target = readlinkSync(exact);
@@ -7305,7 +7467,7 @@ function globalSessionLoad(idOrPrefix) {
7305
7467
  if (!f.endsWith(".json")) continue;
7306
7468
  const base = f.slice(0, -5);
7307
7469
  if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
7308
- const target = readlinkSync(join7(gd, f));
7470
+ const target = readlinkSync(join8(gd, f));
7309
7471
  return JSON.parse(readFileSync5(target, "utf8"));
7310
7472
  }
7311
7473
  }
@@ -7320,10 +7482,10 @@ function globalSessionList() {
7320
7482
  for (const f of readdirSync(gd)) {
7321
7483
  if (!f.endsWith(".json")) continue;
7322
7484
  try {
7323
- const target = readlinkSync(join7(gd, f));
7485
+ const target = readlinkSync(join8(gd, f));
7324
7486
  if (!existsSync6(target)) {
7325
7487
  try {
7326
- unlinkSync(join7(gd, f));
7488
+ unlinkSync(join8(gd, f));
7327
7489
  } catch {
7328
7490
  }
7329
7491
  continue;
@@ -7440,9 +7602,9 @@ ${formatDiff(ops)}`);
7440
7602
  // cli/gitCheckpoints.ts
7441
7603
  import { execFile as execFile3 } from "child_process";
7442
7604
  import { promisify } from "util";
7443
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, existsSync as existsSync7 } from "fs";
7444
- import { join as join8, resolve as resolve2, sep as sep2 } from "path";
7445
- var log18 = forComponent("checkpoints");
7605
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7, existsSync as existsSync7 } from "fs";
7606
+ import { join as join9, resolve as resolve2, sep as sep2 } from "path";
7607
+ var log19 = forComponent("checkpoints");
7446
7608
  var exec = promisify(execFile3);
7447
7609
  var DEFAULT_EXCLUDE = [".agent/", ".git/", "node_modules/", "dist/", "build/", ".next/", "target/", ".venv/", "__pycache__/", "*.log"];
7448
7610
  var ShadowRepo = class {
@@ -7474,13 +7636,13 @@ var ShadowRepo = class {
7474
7636
  try {
7475
7637
  await exec(this.git, ["--version"]);
7476
7638
  if (!existsSync7(this.gitDir)) {
7477
- mkdirSync6(this.gitDir, { recursive: true });
7639
+ mkdirSync7(this.gitDir, { recursive: true });
7478
7640
  await this.run("init", "-q");
7479
7641
  }
7480
- writeFileSync4(join8(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
7642
+ writeFileSync5(join9(this.gitDir, "info", "exclude"), this.exclude.join("\n") + "\n");
7481
7643
  this.ready = true;
7482
7644
  } catch (e) {
7483
- log18.debug(`git checkpoints unavailable for ${this.workTree}`, e);
7645
+ log19.debug(`git checkpoints unavailable for ${this.workTree}`, e);
7484
7646
  this.ready = false;
7485
7647
  }
7486
7648
  return this.ready;
@@ -7491,7 +7653,7 @@ var ShadowRepo = class {
7491
7653
  }
7492
7654
  async commit(label, forced = []) {
7493
7655
  await this.run("add", "-A");
7494
- for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log18.debug(`force-add failed: ${p}`, e));
7656
+ for (const p of forced) await this.run("add", "-f", "--", p).catch((e) => log19.debug(`force-add failed: ${p}`, e));
7495
7657
  await this.run("commit", "--allow-empty", "-q", "-m", label);
7496
7658
  }
7497
7659
  /** Inject the CURRENT (pre-edit) content of `paths` into the turn-open restore point by amending it.
@@ -7499,8 +7661,8 @@ var ShadowRepo = class {
7499
7661
  * turn-boundary `add -A` would never have captured it. Amend (vs a new commit) keeps one restore
7500
7662
  * point per turn, so the REPL's turn↔frame mapping stays intact. */
7501
7663
  async amendForced(paths) {
7502
- for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log18.debug(`force-capture failed: ${p}`, e));
7503
- await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log18.debug("amend failed", e));
7664
+ for (const p of paths) await this.run("add", "-f", "--", p).catch((e) => log19.debug(`force-capture failed: ${p}`, e));
7665
+ await this.run("commit", "--amend", "--no-edit", "-q", "--allow-empty").catch((e) => log19.debug("amend failed", e));
7504
7666
  }
7505
7667
  /** Commits on `ref`, oldest-first (canonical index space). */
7506
7668
  async log(ref) {
@@ -7560,7 +7722,7 @@ var ShadowRepo = class {
7560
7722
  await this.run("gc", "--auto", "-q").catch(() => {
7561
7723
  });
7562
7724
  } catch (e) {
7563
- log18.debug("checkpoint prune failed", e);
7725
+ log19.debug("checkpoint prune failed", e);
7564
7726
  }
7565
7727
  }
7566
7728
  };
@@ -7592,7 +7754,7 @@ var GitCheckpoints = class {
7592
7754
  const abs = resolve2(d);
7593
7755
  if (abs === cwd || abs.startsWith(cwd + sep2)) continue;
7594
7756
  if (cwd.startsWith(abs + sep2)) continue;
7595
- out.push({ workTree: abs, gitDir: join8(abs, ".agent", "checkpoints.git") });
7757
+ out.push({ workTree: abs, gitDir: join9(abs, ".agent", "checkpoints.git") });
7596
7758
  }
7597
7759
  return out;
7598
7760
  }
@@ -7619,7 +7781,7 @@ var GitCheckpoints = class {
7619
7781
  use(sessionId) {
7620
7782
  if (sessionId === this.session) return;
7621
7783
  this.session = sessionId;
7622
- if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log18.debug("re-point failed", e));
7784
+ if (this.started) for (const r of this.repos) void r.point(this.ref()).catch((e) => log19.debug("re-point failed", e));
7623
7785
  }
7624
7786
  async begin(label) {
7625
7787
  if (!await this.start()) return;
@@ -7631,7 +7793,7 @@ var GitCheckpoints = class {
7631
7793
  try {
7632
7794
  await this.repos[i].commit(msg, forced);
7633
7795
  } catch (e) {
7634
- log18.debug("checkpoint commit failed", e);
7796
+ log19.debug("checkpoint commit failed", e);
7635
7797
  }
7636
7798
  }
7637
7799
  if (slow) clearTimeout(slow);
@@ -7661,7 +7823,7 @@ var GitCheckpoints = class {
7661
7823
  if (this.forced.has(abs)) continue;
7662
7824
  this.forced.add(abs);
7663
7825
  const i = this.repoIndexFor(abs);
7664
- if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log18.debug("amendForced failed", e));
7826
+ if (i >= 0) await this.repos[i].amendForced([abs]).catch((e) => log19.debug("amendForced failed", e));
7665
7827
  }
7666
7828
  }
7667
7829
  };
@@ -7713,7 +7875,7 @@ var GitCheckpointsOptions = class {
7713
7875
  /** Real working tree to snapshot (the launch cwd). */
7714
7876
  workTree = process.cwd();
7715
7877
  /** Isolated git dir for the cwd shadow repo (kept out of the user's real .git). */
7716
- gitDir = join8(process.cwd(), ".agent", "checkpoints.git");
7878
+ gitDir = join9(process.cwd(), ".agent", "checkpoints.git");
7717
7879
  /** Extra mounted dirs (`--add-dir`); those outside cwd each get their own shadow repo. */
7718
7880
  addDirs = [];
7719
7881
  /** Conversation id → per-session restore-point ref. */
@@ -7727,9 +7889,9 @@ var GitCheckpointsOptions = class {
7727
7889
  };
7728
7890
 
7729
7891
  // cli/permissions.ts
7730
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "fs";
7892
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync8 } from "fs";
7731
7893
  import { homedir as homedir6 } from "os";
7732
- import { join as join9 } from "path";
7894
+ import { join as join10 } from "path";
7733
7895
  var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
7734
7896
  function parseOne(raw, decision) {
7735
7897
  const m = RULE_RE.exec(raw.trim());
@@ -7751,13 +7913,13 @@ function parsePermRules(perms) {
7751
7913
  function describeRule(r) {
7752
7914
  return `${r.decision.padEnd(5)} ${r.tool ?? "*"}${r.pathGlob ? `(${r.pathGlob})` : ""}`;
7753
7915
  }
7754
- var PERM_FILE = (cwd) => join9(cwd, ".agent", "permissions.json");
7916
+ var PERM_FILE = (cwd) => join10(cwd, ".agent", "permissions.json");
7755
7917
  function loadPersistedRules(cwd) {
7756
7918
  const j = readJsonFile(PERM_FILE(cwd), null);
7757
7919
  return j ? { allow: j.allow ?? [], ask: j.ask ?? [], deny: j.deny ?? [] } : {};
7758
7920
  }
7759
7921
  function loadClaudeSettings(cwd, home = homedir6()) {
7760
- const files = [join9(home, ".claude", "settings.json"), join9(cwd, ".claude", "settings.json"), join9(cwd, ".claude", "settings.local.json")];
7922
+ const files = [join10(home, ".claude", "settings.json"), join10(cwd, ".claude", "settings.json"), join10(cwd, ".claude", "settings.local.json")];
7761
7923
  let out = {};
7762
7924
  for (const p of files) {
7763
7925
  const perms = readJsonFile(p, null)?.permissions;
@@ -7770,8 +7932,8 @@ function persistRule(cwd, decision, ruleStr) {
7770
7932
  const list = cur[decision] ??= [];
7771
7933
  if (!list.includes(ruleStr)) list.push(ruleStr);
7772
7934
  try {
7773
- mkdirSync7(join9(cwd, ".agent"), { recursive: true });
7774
- writeFileSync5(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
7935
+ mkdirSync8(join10(cwd, ".agent"), { recursive: true });
7936
+ writeFileSync6(PERM_FILE(cwd), JSON.stringify(cur, null, 2) + "\n");
7775
7937
  } catch {
7776
7938
  }
7777
7939
  }
@@ -7784,7 +7946,7 @@ function mergePerms(a, b) {
7784
7946
  }
7785
7947
  return Object.keys(out).length ? out : void 0;
7786
7948
  }
7787
- var TRUST_FILE = join9(homedir6(), ".agent", "trusted.json");
7949
+ var TRUST_FILE = join10(homedir6(), ".agent", "trusted.json");
7788
7950
  function isTrusted(cwd, file = TRUST_FILE) {
7789
7951
  const list = readJsonFile(file, []);
7790
7952
  return Array.isArray(list) && list.includes(cwd);
@@ -7794,8 +7956,8 @@ function trustDir(cwd, file = TRUST_FILE) {
7794
7956
  if (!Array.isArray(list)) list = [];
7795
7957
  if (!list.includes(cwd)) list.push(cwd);
7796
7958
  try {
7797
- mkdirSync7(join9(file, ".."), { recursive: true });
7798
- writeFileSync5(file, JSON.stringify(list, null, 2) + "\n");
7959
+ mkdirSync8(join10(file, ".."), { recursive: true });
7960
+ writeFileSync6(file, JSON.stringify(list, null, 2) + "\n");
7799
7961
  } catch {
7800
7962
  }
7801
7963
  }
@@ -7855,9 +8017,9 @@ function completePath(listDir, ref) {
7855
8017
  // cli/lineEditor.ts
7856
8018
  import { emitKeypressEvents } from "readline";
7857
8019
  import { spawnSync as spawnSync4 } from "child_process";
7858
- import { writeFileSync as writeFileSync6, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
8020
+ import { writeFileSync as writeFileSync7, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
7859
8021
  import { tmpdir as tmpdir2 } from "os";
7860
- import { join as join10 } from "path";
8022
+ import { join as join11 } from "path";
7861
8023
 
7862
8024
  // cli/bidi.ts
7863
8025
  var RTL_RE = /[\u0590-\u05ff\u0600-\u06ff\u0750-\u077f\u08a0-\u08ff\ufb1d-\ufdff\ufe70-\ufeff]/;
@@ -8934,9 +9096,9 @@ function createLineEditor(out) {
8934
9096
  function externalEdit(s) {
8935
9097
  const spec = process.env.VISUAL || process.env.EDITOR || "vi";
8936
9098
  const [cmd, ...cargs] = spec.split(" ").filter(Boolean);
8937
- const file = join10(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
9099
+ const file = join11(tmpdir2(), `agentx-edit-${process.pid}-${Date.now()}.md`);
8938
9100
  try {
8939
- writeFileSync6(file, s.buf);
9101
+ writeFileSync7(file, s.buf);
8940
9102
  process.stdin.setRawMode(false);
8941
9103
  out.write("\x1B[?2004l");
8942
9104
  const r = spawnSync4(cmd, [...cargs, file], { stdio: "inherit" });
@@ -9364,23 +9526,23 @@ function readPlainLine() {
9364
9526
 
9365
9527
  // cli/osScheduler.ts
9366
9528
  import { spawnSync as spawnSync5 } from "child_process";
9367
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync8, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync8 } from "fs";
9529
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3, chmodSync, existsSync as existsSync8 } from "fs";
9368
9530
  import { homedir as homedir7 } from "os";
9369
- import { join as join11 } from "path";
9370
- var log19 = forComponent("os-sched");
9531
+ import { join as join12 } from "path";
9532
+ var log20 = forComponent("os-sched");
9371
9533
  var OsScheduler = class {
9372
9534
  options;
9373
9535
  constructor(options) {
9374
9536
  this.options = { ...new OsSchedulerOptions(), ...options };
9375
9537
  }
9376
9538
  get dir() {
9377
- return join11(this.options.home, ".agent", "sched");
9539
+ return join12(this.options.home, ".agent", "sched");
9378
9540
  }
9379
9541
  label(id) {
9380
9542
  return `cc.livx.agentx.sched-${id}`;
9381
9543
  }
9382
9544
  plistPath(id) {
9383
- return join11(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
9545
+ return join12(this.options.home, "Library", "LaunchAgents", `${this.label(id)}.plist`);
9384
9546
  }
9385
9547
  run(cmd, args, input) {
9386
9548
  return this.options.exec(cmd, args, input);
@@ -9391,16 +9553,16 @@ var OsScheduler = class {
9391
9553
  /** Register the job with the OS. Returns a human description of the mechanism used. Throws on failure. */
9392
9554
  schedule(spec) {
9393
9555
  if (!this.available()) throw new Error(`no OS scheduler on ${this.options.platform}`);
9394
- mkdirSync8(this.dir, { recursive: true });
9556
+ mkdirSync9(this.dir, { recursive: true });
9395
9557
  const oneOff = "at" in spec.trigger;
9396
9558
  const script = this.writeScript(spec, oneOff);
9397
9559
  const mechanism = this.options.platform === "darwin" ? this.scheduleDarwin(spec, script) : this.scheduleLinux(spec, script, oneOff);
9398
9560
  const meta = { ...spec, created: Date.now(), mechanism };
9399
- writeFileSync7(join11(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
9561
+ writeFileSync8(join12(this.dir, `${spec.id}.json`), JSON.stringify(meta, null, 2));
9400
9562
  return mechanism;
9401
9563
  }
9402
9564
  cancel(id) {
9403
- const meta = readJsonFile(join11(this.dir, `${id}.json`), null);
9565
+ const meta = readJsonFile(join12(this.dir, `${id}.json`), null);
9404
9566
  if (!meta) return false;
9405
9567
  try {
9406
9568
  if (this.options.platform === "darwin") {
@@ -9429,11 +9591,11 @@ var OsScheduler = class {
9429
9591
  }
9430
9592
  }
9431
9593
  } catch (e) {
9432
- log19.debug(`cancel ${id}`, e);
9594
+ log20.debug(`cancel ${id}`, e);
9433
9595
  }
9434
9596
  for (const f of [`${id}.json`, `${id}.sh`]) {
9435
9597
  try {
9436
- unlinkSync3(join11(this.dir, f));
9598
+ unlinkSync3(join12(this.dir, f));
9437
9599
  } catch {
9438
9600
  }
9439
9601
  }
@@ -9441,19 +9603,19 @@ var OsScheduler = class {
9441
9603
  }
9442
9604
  list() {
9443
9605
  if (!existsSync8(this.dir)) return [];
9444
- return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join11(this.dir, f), null)).filter(Boolean);
9606
+ return readdirSync2(this.dir).filter((f) => f.endsWith(".json")).map((f) => readJsonFile(join12(this.dir, f), null)).filter(Boolean);
9445
9607
  }
9446
9608
  /** The per-job runner script: cd to the project, headless-resume the session, log, notify. */
9447
9609
  writeScript(spec, oneOff) {
9448
- const p = join11(this.dir, `${spec.id}.sh`);
9610
+ const p = join12(this.dir, `${spec.id}.sh`);
9449
9611
  const q2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
9450
- const cleanup = oneOff ? this.options.platform === "darwin" ? `launchctl remove ${this.label(spec.id)} 2>/dev/null; rm -f ${q2(this.plistPath(spec.id))} ${q2(join11(this.dir, `${spec.id}.json`))} ${q2(p)}
9451
- ` : `rm -f ${q2(join11(this.dir, `${spec.id}.json`))} ${q2(p)}
9612
+ 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)}
9613
+ ` : `rm -f ${q2(join12(this.dir, `${spec.id}.json`))} ${q2(p)}
9452
9614
  ` : "";
9453
- writeFileSync7(p, `#!/bin/sh
9615
+ writeFileSync8(p, `#!/bin/sh
9454
9616
  # agentx scheduled job ${spec.id}${spec.label ? ` \u2014 ${spec.label}` : ""}
9455
9617
  cd ${q2(spec.cwd)} || exit 1
9456
- ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join11(this.dir, `${spec.id}.log`))} 2>&1
9618
+ ${this.options.agentx} -p ${q2(spec.prompt)} --resume ${q2(spec.sessionId)} --yes >> ${q2(join12(this.dir, `${spec.id}.log`))} 2>&1
9457
9619
  ${cleanup}`);
9458
9620
  chmodSync(p, 493);
9459
9621
  return p;
@@ -9490,8 +9652,8 @@ ${trigger}
9490
9652
  <key>RunAtLoad</key><false/>
9491
9653
  </dict></plist>
9492
9654
  `;
9493
- mkdirSync8(join11(this.options.home, "Library", "LaunchAgents"), { recursive: true });
9494
- writeFileSync7(this.plistPath(spec.id), plist);
9655
+ mkdirSync9(join12(this.options.home, "Library", "LaunchAgents"), { recursive: true });
9656
+ writeFileSync8(this.plistPath(spec.id), plist);
9495
9657
  this.run("launchctl", ["load", this.plistPath(spec.id)]);
9496
9658
  return `launchd:${this.label(spec.id)}`;
9497
9659
  }
@@ -9543,12 +9705,12 @@ function routeTrigger(trigger, backendHint, now5 = Date.now()) {
9543
9705
  // cli/remoteTrigger.ts
9544
9706
  import { createServer as createServer2, createConnection } from "net";
9545
9707
  import { spawn as spawn3 } from "child_process";
9546
- import { existsSync as existsSync9, mkdirSync as mkdirSync9, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
9708
+ import { existsSync as existsSync9, mkdirSync as mkdirSync10, unlinkSync as unlinkSync4, readdirSync as readdirSync3 } from "fs";
9547
9709
  import { homedir as homedir8 } from "os";
9548
- import { join as join12 } from "path";
9549
- var log20 = forComponent("remote-trigger");
9550
- var TRIGGER_DIR = () => join12(homedir8(), ".agent", "triggers");
9551
- var sockPath = (sessionId, dir = TRIGGER_DIR()) => join12(dir, `${sessionId}.sock`);
9710
+ import { join as join13 } from "path";
9711
+ var log21 = forComponent("remote-trigger");
9712
+ var TRIGGER_DIR = () => join13(homedir8(), ".agent", "triggers");
9713
+ var sockPath = (sessionId, dir = TRIGGER_DIR()) => join13(dir, `${sessionId}.sock`);
9552
9714
  var TriggerServer = class {
9553
9715
  constructor(onTrigger) {
9554
9716
  this.onTrigger = onTrigger;
@@ -9560,7 +9722,7 @@ var TriggerServer = class {
9560
9722
  start(sessionId, dir = TRIGGER_DIR()) {
9561
9723
  this.stop();
9562
9724
  try {
9563
- mkdirSync9(dir, { recursive: true, mode: 448 });
9725
+ mkdirSync10(dir, { recursive: true, mode: 448 });
9564
9726
  const p = sockPath(sessionId, dir);
9565
9727
  try {
9566
9728
  unlinkSync4(p);
@@ -9584,13 +9746,13 @@ var TriggerServer = class {
9584
9746
  conn.end(JSON.stringify({ ok: false, error: String(e) }) + "\n");
9585
9747
  }
9586
9748
  });
9587
- conn.on("error", (e) => log20.debug("trigger conn error", e));
9749
+ conn.on("error", (e) => log21.debug("trigger conn error", e));
9588
9750
  });
9589
- this.server.on("error", (e) => log20.debug("trigger server error", e));
9751
+ this.server.on("error", (e) => log21.debug("trigger server error", e));
9590
9752
  this.server.listen(p);
9591
9753
  this.path = p;
9592
9754
  } catch (e) {
9593
- log20.debug("trigger server unavailable", e);
9755
+ log21.debug("trigger server unavailable", e);
9594
9756
  }
9595
9757
  }
9596
9758
  /** Re-bind on /resume (the session id changed). */
@@ -9711,7 +9873,7 @@ var italic = C("3");
9711
9873
  var strike = C("9");
9712
9874
  var link = (text, url) => useColor ? `\x1B]8;;${url}\x1B\\${cyan(text)}\x1B]8;;\x1B\\` : `${text} (${url})`;
9713
9875
  var err = (s) => process.stderr.write(s);
9714
- var log21 = forComponent("cli");
9876
+ var log22 = forComponent("cli");
9715
9877
  var VERSION = (() => {
9716
9878
  try {
9717
9879
  return JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8")).version ?? "?";
@@ -9939,9 +10101,9 @@ function resolveModelOrNewest(model) {
9939
10101
  var ENV_KEY_ALIASES = { google: ["GEMINI_API_KEY"] };
9940
10102
  function loadInstallEnv() {
9941
10103
  let dir = dirname4(import.meta.path);
9942
- for (let i = 0; i < 5 && !existsSync10(join13(dir, "package.json")); i++) dir = dirname4(dir);
10104
+ for (let i = 0; i < 5 && !existsSync10(join14(dir, "package.json")); i++) dir = dirname4(dir);
9943
10105
  for (const name of [".env", ".env.local"]) {
9944
- const file = join13(dir, name);
10106
+ const file = join14(dir, name);
9945
10107
  if (!existsSync10(file)) continue;
9946
10108
  for (const line of readFileSync7(file, "utf8").split("\n")) {
9947
10109
  const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
@@ -10302,7 +10464,7 @@ function costOf(pricing, promptTokens = 0, completionTokens = 0, cacheCreationTo
10302
10464
  function turnCost(model, usage) {
10303
10465
  return costOf(getModelInfo(model)?.pricing, usage?.promptTokens ?? 0, usage?.completionTokens ?? 0, usage?.cacheCreationTokens ?? 0, usage?.cacheReadTokens ?? 0, model);
10304
10466
  }
10305
- async function evaluateGoal(ai, condition, transcript, log22) {
10467
+ async function evaluateGoal(ai, condition, transcript, log23) {
10306
10468
  const recent = transcript.filter((m) => m.role === "assistant").slice(-8).map((m) => {
10307
10469
  const text = typeof m.content === "string" ? m.content : m.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
10308
10470
  return text.slice(0, 600);
@@ -10322,7 +10484,7 @@ ${recent}` }
10322
10484
  const match = r.content.match(/\{[\s\S]*\}/);
10323
10485
  if (match) return JSON.parse(match[0]);
10324
10486
  } catch (e) {
10325
- log22(dim(` (goal evaluator error: ${e?.message ?? e})
10487
+ log23(dim(` (goal evaluator error: ${e?.message ?? e})
10326
10488
  `));
10327
10489
  }
10328
10490
  return { met: false, reason: "evaluation unclear" };
@@ -10529,13 +10691,13 @@ function mcpAgentTools(mounted, opts) {
10529
10691
  return tools;
10530
10692
  }
10531
10693
  async function closeMcp(mounted) {
10532
- await Promise.all(mounted.map((m) => m.client.close().catch((e) => log21.debug("mcp close failed", e))));
10694
+ await Promise.all(mounted.map((m) => m.client.close().catch((e) => log22.debug("mcp close failed", e))));
10533
10695
  }
10534
10696
  var IMG_EXT = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
10535
10697
  function mentionRefs(line) {
10536
10698
  return [...line.matchAll(/(?:^|\s)@(?:"([^"]+)"|(\S+))/g)].map((m) => m[1] ?? m[2].replace(/[?!.,;:)\]}'">]+$/, "")).filter(Boolean);
10537
10699
  }
10538
- var untilde = (p) => p.startsWith("~/") ? join13(homedir9(), p.slice(2)) : p;
10700
+ var untilde = (p) => p.startsWith("~/") ? join14(homedir9(), p.slice(2)) : p;
10539
10701
  function readImageParts(cwd, line) {
10540
10702
  const refs = mentionRefs(line);
10541
10703
  const parts = [];
@@ -10579,7 +10741,7 @@ async function expandMentions(fs, line) {
10579
10741
  if (loaded.includes(ref) || missing.includes(ref)) continue;
10580
10742
  if (ref.includes(":") && mcpMentionResolver) {
10581
10743
  const body = await mcpMentionResolver(ref).catch((e) => {
10582
- log21.debug("mcp mention resolve failed", e);
10744
+ log22.debug("mcp mention resolve failed", e);
10583
10745
  return null;
10584
10746
  });
10585
10747
  if (body != null) {
@@ -10662,7 +10824,7 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
10662
10824
  try {
10663
10825
  store.save(session);
10664
10826
  } catch (ex) {
10665
- log21.debug("mid-turn session flush failed", ex);
10827
+ log22.debug("mid-turn session flush failed", ex);
10666
10828
  }
10667
10829
  }
10668
10830
  return origNotify(e);
@@ -10775,25 +10937,25 @@ var AGENTS_MD_TEMPLATE = `# ${"${name}"}
10775
10937
  `;
10776
10938
  function initInstructions(cwd) {
10777
10939
  for (const f of ["AGENTS.md", "CLAUDE.md"]) {
10778
- if (existsSync10(join13(cwd, f))) {
10940
+ if (existsSync10(join14(cwd, f))) {
10779
10941
  err(yellow(` ${f} already exists \u2014 leaving it as-is
10780
10942
  `));
10781
10943
  return;
10782
10944
  }
10783
10945
  }
10784
- const path = join13(cwd, "AGENTS.md");
10785
- writeFileSync8(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
10946
+ const path = join14(cwd, "AGENTS.md");
10947
+ writeFileSync9(path, AGENTS_MD_TEMPLATE.replace("${name}", basename2(cwd)));
10786
10948
  err(green(` created ${path}
10787
10949
  `) + dim(" edit it, then it auto-loads into every run.\n"));
10788
10950
  }
10789
10951
  function persistSetting(cwd, key, value) {
10790
- const path = join13(cwd, ".agent", "settings.json");
10952
+ const path = join14(cwd, ".agent", "settings.json");
10791
10953
  try {
10792
10954
  const obj = existsSync10(path) ? JSON.parse(readFileSync7(path, "utf8")) : {};
10793
10955
  if (obj[key] === value) return;
10794
10956
  obj[key] = value;
10795
- mkdirSync10(dirname4(path), { recursive: true });
10796
- writeFileSync8(path, JSON.stringify(obj, null, 2) + "\n");
10957
+ mkdirSync11(dirname4(path), { recursive: true });
10958
+ writeFileSync9(path, JSON.stringify(obj, null, 2) + "\n");
10797
10959
  } catch (e) {
10798
10960
  err(yellow(` \u26A0 couldn't persist ${key} to ${path} \u2014 ${e?.message ?? e}
10799
10961
  `));
@@ -10809,14 +10971,14 @@ var isCancelTeardown = (e) => {
10809
10971
  function installCancelGuards(mounted) {
10810
10972
  process.on("unhandledRejection", (e) => {
10811
10973
  if (isCancelTeardown(e)) {
10812
- log21.debug("suppressed unhandledRejection (cursor stream cancel)", e);
10974
+ log22.debug("suppressed unhandledRejection (cursor stream cancel)", e);
10813
10975
  return;
10814
10976
  }
10815
- log21.error("unhandledRejection", e);
10977
+ log22.error("unhandledRejection", e);
10816
10978
  });
10817
10979
  process.on("uncaughtException", (e) => {
10818
10980
  if (isCancelTeardown(e)) {
10819
- log21.debug("suppressed uncaughtException (cursor stream cancel)", e);
10981
+ log22.debug("suppressed uncaughtException (cursor stream cancel)", e);
10820
10982
  return;
10821
10983
  }
10822
10984
  console.error(e);
@@ -10825,7 +10987,7 @@ function installCancelGuards(mounted) {
10825
10987
  });
10826
10988
  }
10827
10989
  async function repl(args, ai, cfg, cwd) {
10828
- const oauth = new McpOAuth({ storePath: join13(cwd, ".agent", "mcp-auth.json") });
10990
+ const oauth = new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") });
10829
10991
  const mounted = await mountMcp(cfg, oauth);
10830
10992
  let mcpToolNames = [];
10831
10993
  const initialMcpTools = mcpAgentTools(mounted);
@@ -11033,7 +11195,7 @@ async function repl(args, ai, cfg, cwd) {
11033
11195
  quickLook: {
11034
11196
  branch: () => {
11035
11197
  try {
11036
- const head = readFileSync7(join13(cwd, ".git", "HEAD"), "utf8").trim();
11198
+ const head = readFileSync7(join14(cwd, ".git", "HEAD"), "utf8").trim();
11037
11199
  return head.startsWith("ref: refs/heads/") ? `branch: ${head.slice("ref: refs/heads/".length)}` : `detached HEAD at ${head.slice(0, 12)}`;
11038
11200
  } catch {
11039
11201
  return "not a git repository";
@@ -11162,9 +11324,9 @@ async function repl(args, ai, cfg, cwd) {
11162
11324
  const pendingImages = [];
11163
11325
  const bangContext = [];
11164
11326
  const grabClipboardAttachment = () => {
11165
- const dir = join13(tmpdir3(), "agentx-pasted");
11327
+ const dir = join14(tmpdir3(), "agentx-pasted");
11166
11328
  try {
11167
- mkdirSync10(dir, { recursive: true });
11329
+ mkdirSync11(dir, { recursive: true });
11168
11330
  } catch {
11169
11331
  }
11170
11332
  const img = grabClipboardImage(dir, String(Date.now()));
@@ -11215,7 +11377,7 @@ async function repl(args, ai, cfg, cwd) {
11215
11377
  err(dim(` \u23F0 ${scheduler.size} scheduled job(s) re-armed
11216
11378
  `));
11217
11379
  }
11218
- const checkpoints = args.vfs || args.boddb ? new CheckpointStack(agent.options.fs) : new GitCheckpoints({ workTree: cwd, gitDir: join13(cwd, ".agent", "checkpoints.git"), addDirs: args.addDirs, sessionId: session.meta.id });
11380
+ 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 });
11219
11381
  const cpHooks = checkpoints.hooks?.();
11220
11382
  if (cpHooks) {
11221
11383
  agent.options.hooks = composeHooks(agent.options.hooks, cpHooks);
@@ -11267,14 +11429,14 @@ ${lines.join("\n")}
11267
11429
  Added entries are loadable now via the Skill/SlashCommand tools; removed ones are gone even if still listed in the system prompt.
11268
11430
  </system-reminder>`;
11269
11431
  };
11270
- const histPath = join13(cwd, ".agent", "history");
11432
+ const histPath = join14(cwd, ".agent", "history");
11271
11433
  const history = existsSync10(histPath) ? readFileSync7(histPath, "utf8").split("\n").filter(Boolean).reverse().slice(0, 500) : [];
11272
11434
  const remember = (line) => {
11273
11435
  try {
11274
- mkdirSync10(join13(cwd, ".agent"), { recursive: true });
11436
+ mkdirSync11(join14(cwd, ".agent"), { recursive: true });
11275
11437
  appendFileSync(histPath, line + "\n");
11276
11438
  } catch (e) {
11277
- log21.debug("history write failed", e);
11439
+ log22.debug("history write failed", e);
11278
11440
  }
11279
11441
  };
11280
11442
  const ago = (t) => {
@@ -11345,7 +11507,7 @@ Added entries are loadable now via the Skill/SlashCommand tools; removed ones ar
11345
11507
  try {
11346
11508
  store.save(session);
11347
11509
  } catch (e) {
11348
- log21.debug("session save after rewind failed", e);
11510
+ log22.debug("session save after rewind failed", e);
11349
11511
  }
11350
11512
  err(green(" \u27F2 jumped back") + dim(` \u2014 ${face.transcript.length} message(s) kept; edit + resend
11351
11513
  `));
@@ -11379,7 +11541,7 @@ ${task}`;
11379
11541
  bangContext.length = 0;
11380
11542
  }
11381
11543
  const delta = await refreshCatalogs().catch((e) => {
11382
- log21.debug("catalog refresh failed", e);
11544
+ log22.debug("catalog refresh failed", e);
11383
11545
  return "";
11384
11546
  });
11385
11547
  if (delta) {
@@ -11596,8 +11758,8 @@ ${task}`;
11596
11758
  cfgFiles.length ? ok(`config: ${cfgFiles.join(", ")}`) : warn("no .agent/config.* found (project or ~) \u2014 running on defaults");
11597
11759
  try {
11598
11760
  const probe = `${cwd}/.agent/sessions/.doctor-probe`;
11599
- mkdirSync10(`${cwd}/.agent/sessions`, { recursive: true });
11600
- writeFileSync8(probe, "ok");
11761
+ mkdirSync11(`${cwd}/.agent/sessions`, { recursive: true });
11762
+ writeFileSync9(probe, "ok");
11601
11763
  unlinkSync5(probe);
11602
11764
  ok(`session store writable (${cwd}/.agent/sessions)`);
11603
11765
  } catch (e) {
@@ -11625,7 +11787,7 @@ ${task}`;
11625
11787
  desc: "rescan skills/commands dirs and rebuild the system prompt (one cache miss) \u2014 picks up entries created mid-session",
11626
11788
  run: async () => {
11627
11789
  await refreshCatalogs().catch((e) => {
11628
- log21.debug("catalog refresh failed", e);
11790
+ log22.debug("catalog refresh failed", e);
11629
11791
  });
11630
11792
  face.reprepare();
11631
11793
  err(green(` \u2713 reloaded \u2014 ${skills.length} skill(s), ${cmds.length} command(s); system prompt rebuilds on next message
@@ -12082,7 +12244,7 @@ ${task}`;
12082
12244
  try {
12083
12245
  for (const def of (await loadAgents(fs2, d)).agents) if (!seen.has(def.name)) seen.set(def.name, { def, from: d });
12084
12246
  } catch (e) {
12085
- log21.debug(`loadAgents(${d}) failed`, e);
12247
+ log22.debug(`loadAgents(${d}) failed`, e);
12086
12248
  }
12087
12249
  }
12088
12250
  if (!seen.size) {
@@ -12170,7 +12332,7 @@ ${task}`;
12170
12332
  }
12171
12333
  if (idx >= 0) {
12172
12334
  const old = mounted.splice(idx, 1)[0];
12173
- await old.client.close().catch((e) => log21.debug("mcp close failed", e));
12335
+ await old.client.close().catch((e) => log22.debug("mcp close failed", e));
12174
12336
  }
12175
12337
  try {
12176
12338
  const m = await mountMcpServer(name, conf);
@@ -12198,7 +12360,7 @@ ${task}`;
12198
12360
  }
12199
12361
  const m = mounted.splice(idx, 1)[0];
12200
12362
  remountMcpTools();
12201
- await m.client.close().catch((e) => log21.debug("mcp close failed", e));
12363
+ await m.client.close().catch((e) => log22.debug("mcp close failed", e));
12202
12364
  err(dim(` removed "${name}"
12203
12365
  `));
12204
12366
  return;
@@ -12330,11 +12492,11 @@ ${task}`;
12330
12492
  return;
12331
12493
  }
12332
12494
  const md = exportMarkdown(session.meta, shown);
12333
- const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join13(".agent", "exports", `${session.meta.id}.md`);
12495
+ const name = a[0] ? extname(a[0]) ? a[0] : a[0] + ".md" : join14(".agent", "exports", `${session.meta.id}.md`);
12334
12496
  const path = resolve3(cwd, name);
12335
12497
  try {
12336
- mkdirSync10(dirname4(path), { recursive: true });
12337
- writeFileSync8(path, md);
12498
+ mkdirSync11(dirname4(path), { recursive: true });
12499
+ writeFileSync9(path, md);
12338
12500
  err(green(` \u2713 exported \u2192 ${path}
12339
12501
  `) + dim(` ${shown.length} message(s) \xB7 ${md.length} chars
12340
12502
  `));
@@ -12396,9 +12558,9 @@ ${task}`;
12396
12558
  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)`));
12397
12559
  const listDir = (absDir) => {
12398
12560
  try {
12399
- return readdirSync4(join13(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
12561
+ return readdirSync4(join14(cwd, absDir.replace(/^\/+/, "")), { withFileTypes: true }).map((d) => ({ name: d.name, dir: d.isDirectory() }));
12400
12562
  } catch (e) {
12401
- log21.debug("completion readdir failed", absDir, e);
12563
+ log22.debug("completion readdir failed", absDir, e);
12402
12564
  return null;
12403
12565
  }
12404
12566
  };
@@ -12525,18 +12687,18 @@ ${out}
12525
12687
  return;
12526
12688
  }
12527
12689
  await refreshCatalogs().catch((e) => {
12528
- log21.debug("catalog refresh failed", e);
12690
+ log22.debug("catalog refresh failed", e);
12529
12691
  });
12530
- const c = cmds.find((x) => x.name === name);
12531
- if (c) {
12532
- await runCommand(c, a.join(" "));
12533
- return;
12534
- }
12535
12692
  const sk = skills.find((x) => x.name === name);
12536
12693
  if (sk) {
12537
12694
  await runSkill(sk, a.join(" "));
12538
12695
  return;
12539
12696
  }
12697
+ const c = cmds.find((x) => x.name === name);
12698
+ if (c) {
12699
+ await runCommand(c, a.join(" "));
12700
+ return;
12701
+ }
12540
12702
  const known = Object.keys(builtins).filter((k) => k !== "quit").map((k) => "/" + k);
12541
12703
  const custom = [...cmds.map((x) => x.name), ...skills.map((x) => x.name)].map((n) => "/" + n);
12542
12704
  err(red(` unknown command /${name}
@@ -12925,7 +13087,7 @@ async function main() {
12925
13087
  }
12926
13088
  });
12927
13089
  if (args.task) {
12928
- const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join13(cwd, ".agent", "mcp-auth.json") }));
13090
+ const mounted = await mountMcp(cfg, new McpOAuth({ storePath: join14(cwd, ".agent", "mcp-auth.json") }));
12929
13091
  const agent = await makeAgent(args, ai, cfg, mcpAgentTools(mounted));
12930
13092
  const store = new SessionStore(cwd);
12931
13093
  const session = startSession(args, store, agent, cwd);