clawt 2.16.3 → 2.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var COMMON_MESSAGES = {
36
36
  /** worktree 创建成功 */
37
37
  WORKTREE_CREATED: (count) => `\u2713 \u5DF2\u521B\u5EFA ${count} \u4E2A worktree`,
38
38
  /** worktree 移除成功 */
39
- WORKTREE_REMOVED: (path) => `\u2713 \u5DF2\u79FB\u9664 worktree: ${path}`,
39
+ WORKTREE_REMOVED: (path2) => `\u2713 \u5DF2\u79FB\u9664 worktree: ${path2}`,
40
40
  /** 没有 worktree */
41
41
  NO_WORKTREES: "(\u65E0 worktree)",
42
42
  /** 目标 worktree 不存在 */
@@ -74,17 +74,17 @@ var RUN_MESSAGES = {
74
74
  /** 中断后保留 worktree */
75
75
  INTERRUPT_KEPT: "\u5DF2\u4FDD\u7559 worktree\uFF0C\u53EF\u7A0D\u540E\u4F7F\u7528 clawt remove \u624B\u52A8\u6E05\u7406",
76
76
  /** 非 TTY 环境降级输出:任务启动 */
77
- PROGRESS_TASK_STARTED: (index, total, branch, path) => `[${index}/${total}] ${branch} \u542F\u52A8 ${path}`,
77
+ PROGRESS_TASK_STARTED: (index, total, branch, path2) => `[${index}/${total}] ${branch} \u542F\u52A8 ${path2}`,
78
78
  /** 非 TTY 环境降级输出:任务完成 */
79
- PROGRESS_TASK_DONE: (index, total, branch, duration, cost, path) => `[${index}/${total}] ${branch} \u2713 \u5B8C\u6210 ${duration} ${cost} ${path}`,
79
+ PROGRESS_TASK_DONE: (index, total, branch, duration, cost, detail) => `[${index}/${total}] ${branch} \u2713 \u5B8C\u6210 ${duration} ${cost} ${detail}`,
80
80
  /** 非 TTY 环境降级输出:任务失败 */
81
- PROGRESS_TASK_FAILED: (index, total, branch, duration, path) => `[${index}/${total}] ${branch} \u2717 \u5931\u8D25 ${duration} ${path}`,
81
+ PROGRESS_TASK_FAILED: (index, total, branch, duration, detail) => `[${index}/${total}] ${branch} \u2717 \u5931\u8D25 ${duration} ${detail}`,
82
82
  /** 并发限制提示 */
83
83
  CONCURRENCY_INFO: (concurrency, total) => `\u5E76\u53D1\u9650\u5236: ${concurrency}\uFF0C\u5171 ${total} \u4E2A\u4EFB\u52A1`,
84
84
  /** 并发数无效提示 */
85
85
  CONCURRENCY_INVALID: "\u5E76\u53D1\u6570\u5FC5\u987B\u4E3A\u6B63\u6574\u6570",
86
86
  /** 任务文件不存在 */
87
- TASK_FILE_NOT_FOUND: (path) => `\u4EFB\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728: ${path}`,
87
+ TASK_FILE_NOT_FOUND: (path2) => `\u4EFB\u52A1\u6587\u4EF6\u4E0D\u5B58\u5728: ${path2}`,
88
88
  /** 任务文件中没有解析到有效任务 */
89
89
  TASK_FILE_EMPTY: "\u4EFB\u52A1\u6587\u4EF6\u4E2D\u6CA1\u6709\u89E3\u6790\u5230\u6709\u6548\u4EFB\u52A1",
90
90
  /** 任务文件中某个任务块缺少分支名 */
@@ -96,7 +96,7 @@ var RUN_MESSAGES = {
96
96
  /** --file 和 --tasks 不能同时使用 */
97
97
  FILE_AND_TASKS_CONFLICT: "--file \u548C --tasks \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528",
98
98
  /** 任务文件加载成功 */
99
- TASK_FILE_LOADED: (count, path) => `\u2713 \u4ECE ${path} \u52A0\u8F7D\u4E86 ${count} \u4E2A\u4EFB\u52A1`,
99
+ TASK_FILE_LOADED: (count, path2) => `\u2713 \u4ECE ${path2} \u52A0\u8F7D\u4E86 ${count} \u4E2A\u4EFB\u52A1`,
100
100
  /** 未指定 -b 或 -f */
101
101
  BRANCH_OR_FILE_REQUIRED: "\u8BF7\u6307\u5B9A -b \u5206\u652F\u540D\u6216 -f \u4EFB\u52A1\u6587\u4EF6",
102
102
  /** dry-run 预览标题 */
@@ -211,7 +211,16 @@ ${branches.map((b) => ` - ${b}`).join("\n")}`,
211
211
  /** 并行执行中单个命令失败 */
212
212
  VALIDATE_PARALLEL_CMD_FAILED: (command, exitCode) => ` \u2717 ${command}\uFF08\u9000\u51FA\u7801: ${exitCode}\uFF09`,
213
213
  /** 并行执行中单个命令启动失败 */
214
- VALIDATE_PARALLEL_CMD_ERROR: (command, errorMessage) => ` \u2717 ${command}\uFF08\u9519\u8BEF: ${errorMessage}\uFF09`
214
+ VALIDATE_PARALLEL_CMD_ERROR: (command, errorMessage) => ` \u2717 ${command}\uFF08\u9519\u8BEF: ${errorMessage}\uFF09`,
215
+ /** patch apply 失败后询问用户是否执行 sync */
216
+ VALIDATE_CONFIRM_AUTO_SYNC: (branch) => `\u662F\u5426\u7ACB\u5373\u6267\u884C sync \u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch}\uFF1F`,
217
+ /** 自动 sync 开始提示 */
218
+ VALIDATE_AUTO_SYNC_START: (branch) => `\u6B63\u5728\u81EA\u52A8\u540C\u6B65\u4E3B\u5206\u652F\u5230 ${branch} ...`,
219
+ /** 自动 sync 存在冲突,无法重试 */
220
+ VALIDATE_AUTO_SYNC_CONFLICT: (worktreePath) => `\u540C\u6B65\u5B58\u5728\u51B2\u7A81\uFF0C\u8BF7\u8FDB\u5165\u76EE\u6807 worktree \u624B\u52A8\u89E3\u51B3\u51B2\u7A81\u540E\u91CD\u8BD5
221
+ cd ${worktreePath}`,
222
+ /** 用户拒绝自动 sync */
223
+ VALIDATE_AUTO_SYNC_DECLINED: (branch) => `\u8BF7\u624B\u52A8\u6267\u884C clawt sync -b ${branch} \u540C\u6B65\u4E3B\u5206\u652F\u540E\u91CD\u8BD5`
215
224
  };
216
225
 
217
226
  // src/constants/messages/sync.ts
@@ -453,8 +462,6 @@ var DEBUG_TIMESTAMP_FORMAT = "HH:mm:ss.SSS";
453
462
  // src/constants/progress.ts
454
463
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
455
464
  var SPINNER_INTERVAL_MS = 100;
456
- var CURSOR_UP = (n) => `\x1B[${n}A`;
457
- var CLEAR_LINE = "\x1B[0K";
458
465
  var CURSOR_HIDE = "\x1B[?25l";
459
466
  var CURSOR_SHOW = "\x1B[?25h";
460
467
  var TASK_STATUS_ICONS = {
@@ -475,6 +482,18 @@ var TASK_STATUS_LABELS = {
475
482
  /** 失败 */
476
483
  FAILED: "\u5931\u8D25"
477
484
  };
485
+ var ACTIVITY_TEXT_MAX_LENGTH = 30;
486
+ var TEXT_ACTIVITY_PREFIX = "\u601D\u8003\u4E2D";
487
+ var RESULT_PREVIEW_MAX_LENGTH = 40;
488
+ var DEFAULT_TERMINAL_COLUMNS = 80;
489
+ var LINE_WRAP_DISABLE = "\x1B[?7l";
490
+ var LINE_WRAP_ENABLE = "\x1B[?7h";
491
+ var SYNC_OUTPUT_START = "\x1B[?2026h";
492
+ var SYNC_OUTPUT_END = "\x1B[?2026l";
493
+ var ALT_SCREEN_ENTER = "\x1B[?1049h";
494
+ var ALT_SCREEN_LEAVE = "\x1B[?1049l";
495
+ var CLEAR_SCREEN = "\x1B[2J";
496
+ var CURSOR_HOME = "\x1B[H";
478
497
 
479
498
  // src/constants/prompt.ts
480
499
  var SELECT_ALL_NAME = "__select_all__";
@@ -1425,28 +1444,59 @@ async function resolveTargetWorktree(worktrees, messages, branchName) {
1425
1444
 
1426
1445
  // src/utils/progress-render.ts
1427
1446
  import chalk3 from "chalk";
1428
- function getMaxBranchWidth(tasks) {
1429
- return Math.max(...tasks.map((t) => t.branch.length));
1447
+ import stringWidth from "string-width";
1448
+ var ANSI_RESET = "\x1B[0m";
1449
+ function truncateToTerminalWidth(text, maxWidth) {
1450
+ if (stringWidth(text) <= maxWidth) {
1451
+ return text;
1452
+ }
1453
+ let visibleWidth = 0;
1454
+ let result = "";
1455
+ let i = 0;
1456
+ while (i < text.length) {
1457
+ if (text[i] === "\x1B" && text[i + 1] === "[") {
1458
+ const match = text.slice(i).match(/^\x1B\[[0-9;]*m/);
1459
+ if (match) {
1460
+ result += match[0];
1461
+ i += match[0].length;
1462
+ continue;
1463
+ }
1464
+ }
1465
+ const charWidth = stringWidth(text[i]);
1466
+ if (visibleWidth + charWidth > maxWidth) {
1467
+ break;
1468
+ }
1469
+ result += text[i];
1470
+ visibleWidth += charWidth;
1471
+ i++;
1472
+ }
1473
+ return result + ANSI_RESET;
1474
+ }
1475
+ function getMaxPathWidth(tasks) {
1476
+ return Math.max(...tasks.map((t) => t.path.length));
1430
1477
  }
1431
- function renderTaskLine(task, total, maxBranchWidth, spinnerChar) {
1478
+ function renderTaskLine(task, total, maxPathWidth, spinnerChar) {
1432
1479
  const indexStr = `[${task.index}/${total}]`;
1433
- const branchStr = task.branch.padEnd(maxBranchWidth);
1480
+ const pathStr = task.path.padEnd(maxPathWidth);
1434
1481
  switch (task.status) {
1435
1482
  case "pending": {
1436
- return `${indexStr} ${branchStr} ${chalk3.gray(TASK_STATUS_ICONS.PENDING)} ${chalk3.gray(TASK_STATUS_LABELS.PENDING)} ${chalk3.dim(task.path)}`;
1483
+ return `${indexStr} ${pathStr} ${chalk3.gray(TASK_STATUS_ICONS.PENDING)} ${chalk3.gray(TASK_STATUS_LABELS.PENDING)}`;
1437
1484
  }
1438
1485
  case "running": {
1439
1486
  const elapsed = formatDuration(Date.now() - task.startedAt);
1440
- return `${indexStr} ${branchStr} ${chalk3.cyan(spinnerChar)} ${chalk3.cyan(TASK_STATUS_LABELS.RUNNING)} ${chalk3.gray(elapsed)} ${chalk3.dim(task.path)}`;
1487
+ const detail = task.activity ? ` ${chalk3.dim(task.activity)}` : "";
1488
+ return `${indexStr} ${pathStr} ${chalk3.cyan(spinnerChar)} ${chalk3.cyan(TASK_STATUS_LABELS.RUNNING)} ${chalk3.gray(elapsed)}${detail}`;
1441
1489
  }
1442
1490
  case "done": {
1443
1491
  const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
1444
1492
  const cost = task.costUsd != null ? `$${task.costUsd.toFixed(2)}` : "";
1445
- return `${indexStr} ${branchStr} ${chalk3.green(TASK_STATUS_ICONS.DONE)} ${chalk3.green(TASK_STATUS_LABELS.DONE)} ${chalk3.gray(duration)} ${chalk3.yellow(cost)} ${chalk3.dim(task.path)}`;
1493
+ const preview = task.resultPreview ? ` ${chalk3.dim(task.resultPreview)}` : "";
1494
+ return `${indexStr} ${pathStr} ${chalk3.green(TASK_STATUS_ICONS.DONE)} ${chalk3.green(TASK_STATUS_LABELS.DONE)} ${chalk3.gray(duration)} ${chalk3.yellow(cost)}${preview}`;
1446
1495
  }
1447
1496
  case "failed": {
1448
1497
  const duration = task.durationMs != null ? formatDuration(task.durationMs) : "N/A";
1449
- return `${indexStr} ${branchStr} ${chalk3.red(TASK_STATUS_ICONS.FAILED)} ${chalk3.red(TASK_STATUS_LABELS.FAILED)} ${chalk3.gray(duration)} ${chalk3.dim(task.path)}`;
1498
+ const preview = task.resultPreview ? ` ${chalk3.dim(task.resultPreview)}` : "";
1499
+ return `${indexStr} ${pathStr} ${chalk3.red(TASK_STATUS_ICONS.FAILED)} ${chalk3.red(TASK_STATUS_LABELS.FAILED)} ${chalk3.gray(duration)}${preview}`;
1450
1500
  }
1451
1501
  }
1452
1502
  }
@@ -1475,10 +1525,12 @@ var ProgressRenderer = class {
1475
1525
  timer;
1476
1526
  /** 是否为 TTY 环境 */
1477
1527
  isTTY;
1478
- /** 已渲染的行数(用于回退光标) */
1479
- renderedLineCount;
1528
+ /** resize 事件处理器引用(用于 stop 时移除监听) */
1529
+ resizeHandler;
1480
1530
  /** 是否已停止 */
1481
1531
  stopped;
1532
+ /** exit 兜底处理器(确保异常退出时终端状态被恢复) */
1533
+ exitHandler;
1482
1534
  /** 是否存在排队任务(启用汇总行渲染) */
1483
1535
  hasPendingTasks;
1484
1536
  /**
@@ -1493,8 +1545,9 @@ var ProgressRenderer = class {
1493
1545
  this.frameIndex = 0;
1494
1546
  this.timer = null;
1495
1547
  this.isTTY = !!process.stdout.isTTY;
1496
- this.renderedLineCount = 0;
1548
+ this.resizeHandler = null;
1497
1549
  this.stopped = false;
1550
+ this.exitHandler = null;
1498
1551
  this.hasPendingTasks = !allRunning;
1499
1552
  this.tasks = branches.map((branch, i) => ({
1500
1553
  index: i + 1,
@@ -1505,7 +1558,9 @@ var ProgressRenderer = class {
1505
1558
  finishedAt: null,
1506
1559
  lastActiveAt: allRunning ? now : 0,
1507
1560
  durationMs: null,
1508
- costUsd: null
1561
+ costUsd: null,
1562
+ activity: null,
1563
+ resultPreview: null
1509
1564
  }));
1510
1565
  }
1511
1566
  /**
@@ -1523,7 +1578,9 @@ var ProgressRenderer = class {
1523
1578
  }
1524
1579
  return;
1525
1580
  }
1581
+ process.stdout.write(ALT_SCREEN_ENTER);
1526
1582
  process.stdout.write(CURSOR_HIDE);
1583
+ process.stdout.write(LINE_WRAP_DISABLE);
1527
1584
  this.render();
1528
1585
  this.timer = setInterval(() => {
1529
1586
  this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
@@ -1532,6 +1589,18 @@ var ProgressRenderer = class {
1532
1589
  if (this.timer.unref) {
1533
1590
  this.timer.unref();
1534
1591
  }
1592
+ this.resizeHandler = () => {
1593
+ if (!this.stopped) {
1594
+ this.render();
1595
+ }
1596
+ };
1597
+ process.stdout.on("resize", this.resizeHandler);
1598
+ this.exitHandler = () => {
1599
+ process.stdout.write(LINE_WRAP_ENABLE);
1600
+ process.stdout.write(CURSOR_SHOW);
1601
+ process.stdout.write(ALT_SCREEN_LEAVE);
1602
+ };
1603
+ process.on("exit", this.exitHandler);
1535
1604
  }
1536
1605
  /**
1537
1606
  * 更新指定任务的最后活动时间戳
@@ -1541,6 +1610,17 @@ var ProgressRenderer = class {
1541
1610
  updateActivity(index) {
1542
1611
  this.tasks[index].lastActiveAt = Date.now();
1543
1612
  }
1613
+ /**
1614
+ * 更新指定任务的活动描述文本
1615
+ * 同时更新 lastActiveAt 时间戳
1616
+ * @param {number} index - 任务索引(从 0 开始)
1617
+ * @param {string} text - 活动描述文本
1618
+ */
1619
+ updateActivityText(index, text) {
1620
+ const task = this.tasks[index];
1621
+ task.activity = text;
1622
+ task.lastActiveAt = Date.now();
1623
+ }
1544
1624
  /**
1545
1625
  * 标记指定任务为运行中状态
1546
1626
  * 将 pending 任务标记为 running 并设置启动时间戳
@@ -1561,32 +1641,36 @@ var ProgressRenderer = class {
1561
1641
  * @param {number} index - 任务索引(从 0 开始)
1562
1642
  * @param {number} durationMs - 耗时(毫秒)
1563
1643
  * @param {number} costUsd - 费用(美元)
1644
+ * @param {string} [resultPreview] - 结果预览文本(可选)
1564
1645
  */
1565
- markDone(index, durationMs, costUsd) {
1646
+ markDone(index, durationMs, costUsd, resultPreview) {
1566
1647
  const task = this.tasks[index];
1567
1648
  task.status = "done";
1568
1649
  task.finishedAt = Date.now();
1569
1650
  task.durationMs = durationMs;
1570
1651
  task.costUsd = costUsd;
1652
+ task.resultPreview = resultPreview ?? null;
1571
1653
  if (!this.isTTY) {
1572
1654
  const duration = formatDuration(durationMs);
1573
1655
  const cost = `$${costUsd.toFixed(2)}`;
1574
- console.log(MESSAGES.PROGRESS_TASK_DONE(task.index, this.total, task.branch, duration, cost, task.path));
1656
+ console.log(MESSAGES.PROGRESS_TASK_DONE(task.index, this.total, task.branch, duration, cost, task.resultPreview ?? task.path));
1575
1657
  }
1576
1658
  }
1577
1659
  /**
1578
1660
  * 标记指定任务为失败状态
1579
1661
  * @param {number} index - 任务索引(从 0 开始)
1580
1662
  * @param {number} durationMs - 耗时(毫秒)
1663
+ * @param {string} [resultPreview] - 结果预览文本(可选)
1581
1664
  */
1582
- markFailed(index, durationMs) {
1665
+ markFailed(index, durationMs, resultPreview) {
1583
1666
  const task = this.tasks[index];
1584
1667
  task.status = "failed";
1585
1668
  task.finishedAt = Date.now();
1586
1669
  task.durationMs = durationMs;
1670
+ task.resultPreview = resultPreview ?? null;
1587
1671
  if (!this.isTTY) {
1588
1672
  const duration = formatDuration(durationMs);
1589
- console.log(MESSAGES.PROGRESS_TASK_FAILED(task.index, this.total, task.branch, duration, task.path));
1673
+ console.log(MESSAGES.PROGRESS_TASK_FAILED(task.index, this.total, task.branch, duration, task.resultPreview ?? task.path));
1590
1674
  }
1591
1675
  }
1592
1676
  /**
@@ -1600,30 +1684,58 @@ var ProgressRenderer = class {
1600
1684
  clearInterval(this.timer);
1601
1685
  this.timer = null;
1602
1686
  }
1687
+ if (this.resizeHandler) {
1688
+ process.stdout.removeListener("resize", this.resizeHandler);
1689
+ this.resizeHandler = null;
1690
+ }
1603
1691
  if (this.isTTY) {
1604
1692
  this.render();
1693
+ process.stdout.write(LINE_WRAP_ENABLE);
1605
1694
  process.stdout.write(CURSOR_SHOW);
1695
+ process.stdout.write(ALT_SCREEN_LEAVE);
1696
+ const cols = process.stdout.columns || DEFAULT_TERMINAL_COLUMNS;
1697
+ const finalLines = this.buildLines(cols);
1698
+ for (const line of finalLines) {
1699
+ process.stdout.write(`${line}
1700
+ `);
1701
+ }
1702
+ }
1703
+ if (this.exitHandler) {
1704
+ process.removeListener("exit", this.exitHandler);
1705
+ this.exitHandler = null;
1606
1706
  }
1607
1707
  }
1608
1708
  /**
1609
- * 执行一次完整的面板渲染
1610
- * 先回退光标到面板起始位置,再逐行输出
1709
+ * 构建当前帧的面板行内容
1710
+ * @param {number} cols - 终端列数
1711
+ * @returns {string[]} 截断后的面板行数组
1611
1712
  */
1612
- render() {
1613
- const maxBranchWidth = getMaxBranchWidth(this.tasks);
1713
+ buildLines(cols) {
1714
+ const maxPathWidth = getMaxPathWidth(this.tasks);
1614
1715
  const spinnerChar = SPINNER_FRAMES[this.frameIndex];
1615
- const lines = this.tasks.map((task) => renderTaskLine(task, this.total, maxBranchWidth, spinnerChar));
1716
+ const lines = this.tasks.map((task) => renderTaskLine(task, this.total, maxPathWidth, spinnerChar));
1616
1717
  if (this.hasPendingTasks) {
1617
1718
  lines.push(renderSummaryLine(this.tasks, this.total));
1618
1719
  }
1619
- if (this.renderedLineCount > 0) {
1620
- process.stdout.write(CURSOR_UP(this.renderedLineCount));
1621
- }
1720
+ return lines.map((line) => truncateToTerminalWidth(line, cols));
1721
+ }
1722
+ /**
1723
+ * 执行一次完整的面板渲染
1724
+ * 在备选屏幕缓冲区中,每次清屏+归位后完全重绘
1725
+ * 无需计算 CURSOR_UP 回退量,不受终端 reflow 影响
1726
+ * 使用 Synchronized Output 防止多行写入时的闪烁
1727
+ */
1728
+ render() {
1729
+ const cols = process.stdout.columns || DEFAULT_TERMINAL_COLUMNS;
1730
+ const lines = this.buildLines(cols);
1731
+ process.stdout.write(SYNC_OUTPUT_START);
1732
+ process.stdout.write(CLEAR_SCREEN);
1733
+ process.stdout.write(CURSOR_HOME);
1622
1734
  for (const line of lines) {
1623
- process.stdout.write(`${line}${CLEAR_LINE}
1735
+ process.stdout.write(`${line}
1624
1736
  `);
1625
1737
  }
1626
- this.renderedLineCount = lines.length;
1738
+ process.stdout.write(SYNC_OUTPUT_END);
1627
1739
  }
1628
1740
  };
1629
1741
 
@@ -1686,11 +1798,114 @@ function loadTaskFile(filePath, options) {
1686
1798
  return entries;
1687
1799
  }
1688
1800
 
1801
+ // src/utils/stream-parser.ts
1802
+ import path from "path";
1803
+ function createLineBuffer() {
1804
+ let buffer = "";
1805
+ return {
1806
+ push(chunk) {
1807
+ buffer += chunk;
1808
+ const lines = buffer.split("\n");
1809
+ buffer = lines.pop() ?? "";
1810
+ return lines;
1811
+ },
1812
+ flush() {
1813
+ const remaining = buffer;
1814
+ buffer = "";
1815
+ return remaining || null;
1816
+ }
1817
+ };
1818
+ }
1819
+ function parseStreamLine(line) {
1820
+ const trimmed = line.trim();
1821
+ if (!trimmed) return null;
1822
+ try {
1823
+ return JSON.parse(trimmed);
1824
+ } catch {
1825
+ return null;
1826
+ }
1827
+ }
1828
+ function truncateText(text, maxLength) {
1829
+ if (text.length <= maxLength) return text;
1830
+ return text.substring(0, maxLength - 1) + "\u2026";
1831
+ }
1832
+ function formatActivityText(kind, toolName, input, text) {
1833
+ let raw = "";
1834
+ if (kind === "tool_use" && toolName) {
1835
+ const filePath = input?.file_path;
1836
+ const command = input?.command;
1837
+ if (filePath) {
1838
+ const basename2 = path.basename(filePath);
1839
+ raw = `${toolName} ${basename2}`;
1840
+ } else if (command) {
1841
+ const cleaned = command.replace(/[\n\r\t]+/g, " ").trim();
1842
+ raw = `${toolName} ${cleaned}`;
1843
+ } else {
1844
+ raw = toolName;
1845
+ }
1846
+ } else if (kind === "text" && text) {
1847
+ const cleaned = text.replace(/[\n\r\t]/g, " ").trim();
1848
+ if (!cleaned) return "";
1849
+ raw = `${TEXT_ACTIVITY_PREFIX}: ${cleaned}`;
1850
+ }
1851
+ if (!raw) return "";
1852
+ return truncateText(raw, ACTIVITY_TEXT_MAX_LENGTH);
1853
+ }
1854
+ function parseStreamEvent(event) {
1855
+ if (event.type === "result") {
1856
+ const result = {
1857
+ type: event.type,
1858
+ subtype: event.subtype ?? "",
1859
+ is_error: event.is_error ?? false,
1860
+ duration_ms: event.duration_ms ?? 0,
1861
+ duration_api_ms: event.duration_api_ms ?? 0,
1862
+ num_turns: event.num_turns ?? 0,
1863
+ result: event.result ?? "",
1864
+ stop_reason: event.stop_reason ?? "",
1865
+ session_id: event.session_id ?? "",
1866
+ total_cost_usd: event.total_cost_usd ?? 0,
1867
+ usage: event.usage ?? {}
1868
+ };
1869
+ return { kind: "result", activityText: "", result };
1870
+ }
1871
+ if (event.type === "assistant") {
1872
+ const content = event.message?.content;
1873
+ if (!content || content.length === 0) return null;
1874
+ let lastToolUse = null;
1875
+ let lastText = null;
1876
+ for (const block of content) {
1877
+ if (block.type === "tool_use") {
1878
+ lastToolUse = block;
1879
+ } else if (block.type === "text") {
1880
+ lastText = block;
1881
+ }
1882
+ }
1883
+ if (lastToolUse) {
1884
+ const activityText = formatActivityText(
1885
+ "tool_use",
1886
+ lastToolUse.name,
1887
+ lastToolUse.input
1888
+ );
1889
+ if (activityText) {
1890
+ return { kind: "tool_use", activityText };
1891
+ }
1892
+ }
1893
+ if (lastText?.text) {
1894
+ const activityText = formatActivityText("text", void 0, void 0, lastText.text);
1895
+ if (activityText) {
1896
+ return { kind: "text", activityText };
1897
+ }
1898
+ }
1899
+ return null;
1900
+ }
1901
+ return null;
1902
+ }
1903
+
1689
1904
  // src/utils/task-executor.ts
1690
- function executeClaudeTask(worktree, task) {
1905
+ function executeClaudeTask(worktree, task, onActivity) {
1691
1906
  const child = spawnProcess(
1692
1907
  "claude",
1693
- ["-p", task, "--output-format", "json", "--permission-mode", "bypassPermissions"],
1908
+ ["-p", task, "--output-format", "stream-json", "--verbose", "--permission-mode", "bypassPermissions"],
1694
1909
  {
1695
1910
  cwd: worktree.path,
1696
1911
  // stdin 必须设置为 'ignore',不能用 'pipe'
@@ -1701,31 +1916,47 @@ function executeClaudeTask(worktree, task) {
1701
1916
  }
1702
1917
  );
1703
1918
  const promise = new Promise((resolve2) => {
1704
- let stdout = "";
1705
1919
  let stderr = "";
1920
+ let finalResult = null;
1921
+ const lineBuffer = createLineBuffer();
1706
1922
  child.stdout?.on("data", (data) => {
1707
- stdout += data.toString();
1923
+ const lines = lineBuffer.push(data.toString());
1924
+ for (const line of lines) {
1925
+ const event = parseStreamLine(line);
1926
+ if (!event) continue;
1927
+ const parsed = parseStreamEvent(event);
1928
+ if (!parsed) continue;
1929
+ if (parsed.kind === "result") {
1930
+ finalResult = parsed.result ?? null;
1931
+ } else if (parsed.activityText && onActivity) {
1932
+ onActivity(parsed.activityText);
1933
+ }
1934
+ }
1708
1935
  });
1709
1936
  child.stderr?.on("data", (data) => {
1710
1937
  stderr += data.toString();
1711
1938
  });
1712
1939
  child.on("close", (code) => {
1713
- let result = null;
1714
- let success = code === 0;
1715
- try {
1716
- if (stdout.trim()) {
1717
- result = JSON.parse(stdout.trim());
1718
- success = !result.is_error;
1940
+ const remaining = lineBuffer.flush();
1941
+ if (remaining) {
1942
+ const event = parseStreamLine(remaining);
1943
+ if (event) {
1944
+ const parsed = parseStreamEvent(event);
1945
+ if (parsed?.kind === "result") {
1946
+ finalResult = parsed.result ?? null;
1947
+ }
1719
1948
  }
1720
- } catch {
1721
- logger.warn(`\u89E3\u6790 Claude Code \u8F93\u51FA\u5931\u8D25: ${stdout.substring(0, 200)}`);
1949
+ }
1950
+ let success = code === 0;
1951
+ if (finalResult) {
1952
+ success = !finalResult.is_error;
1722
1953
  }
1723
1954
  resolve2({
1724
1955
  task,
1725
1956
  branch: worktree.branch,
1726
1957
  worktreePath: worktree.path,
1727
1958
  success,
1728
- result,
1959
+ result: finalResult,
1729
1960
  error: success ? void 0 : stderr || "\u4EFB\u52A1\u6267\u884C\u5931\u8D25"
1730
1961
  });
1731
1962
  });
@@ -1767,16 +1998,20 @@ async function handleInterruptCleanup(worktrees) {
1767
1998
  }
1768
1999
  }
1769
2000
  function updateRendererStatus(renderer, index, result, startTime) {
2001
+ const rawResultText = result.result?.result;
2002
+ const resultPreview = rawResultText ? truncateText(rawResultText.replace(/[\n\r\t]+/g, " ").trim(), RESULT_PREVIEW_MAX_LENGTH) : void 0;
1770
2003
  if (result.success) {
1771
2004
  renderer.markDone(
1772
2005
  index,
1773
2006
  result.result?.duration_ms ?? Date.now() - startTime,
1774
- result.result?.total_cost_usd ?? 0
2007
+ result.result?.total_cost_usd ?? 0,
2008
+ resultPreview
1775
2009
  );
1776
2010
  } else {
1777
2011
  renderer.markFailed(
1778
2012
  index,
1779
- result.result?.duration_ms ?? Date.now() - startTime
2013
+ result.result?.duration_ms ?? Date.now() - startTime,
2014
+ resultPreview
1780
2015
  );
1781
2016
  }
1782
2017
  }
@@ -1794,11 +2029,10 @@ async function executeWithConcurrency(worktrees, tasks, concurrency, renderer, s
1794
2029
  const task = tasks[index];
1795
2030
  logger.info(`\u542F\u52A8\u4EFB\u52A1 ${index + 1}: ${task} (worktree: ${wt.path})`);
1796
2031
  renderer.markRunning(index);
1797
- const handle = executeClaudeTask(wt, task);
1798
- childProcesses.push(handle.child);
1799
- handle.child.stderr?.on("data", () => {
1800
- renderer.updateActivity(index);
2032
+ const handle = executeClaudeTask(wt, task, (activityText) => {
2033
+ renderer.updateActivityText(index, activityText);
1801
2034
  });
2035
+ childProcesses.push(handle.child);
1802
2036
  handle.promise.then((result) => {
1803
2037
  results[index] = result;
1804
2038
  completedCount++;
@@ -1821,11 +2055,10 @@ async function executeAllParallel(worktrees, tasks, renderer, startTime, isInter
1821
2055
  const handles = worktrees.map((wt, index) => {
1822
2056
  const task = tasks[index];
1823
2057
  logger.info(`\u542F\u52A8\u4EFB\u52A1 ${index + 1}: ${task} (worktree: ${wt.path})`);
1824
- const handle = executeClaudeTask(wt, task);
1825
- childProcesses.push(handle.child);
1826
- handle.child.stderr?.on("data", () => {
1827
- renderer.updateActivity(index);
2058
+ const handle = executeClaudeTask(wt, task, (activityText) => {
2059
+ renderer.updateActivityText(index, activityText);
1828
2060
  });
2061
+ childProcesses.push(handle.child);
1829
2062
  return handle;
1830
2063
  });
1831
2064
  const results = await Promise.all(
@@ -2337,6 +2570,66 @@ async function handleBatchResume(worktrees) {
2337
2570
 
2338
2571
  // src/commands/validate.ts
2339
2572
  import Enquirer4 from "enquirer";
2573
+
2574
+ // src/commands/sync.ts
2575
+ function registerSyncCommand(program2) {
2576
+ program2.command("sync").description("\u5C06\u4E3B\u5206\u652F\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230\u76EE\u6807 worktree").option("-b, --branch <branchName>", "\u8981\u540C\u6B65\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
2577
+ await handleSync(options);
2578
+ });
2579
+ }
2580
+ var SYNC_RESOLVE_MESSAGES = {
2581
+ noWorktrees: MESSAGES.SYNC_NO_WORKTREES,
2582
+ selectBranch: MESSAGES.SYNC_SELECT_BRANCH,
2583
+ multipleMatches: MESSAGES.SYNC_MULTIPLE_MATCHES,
2584
+ noMatch: MESSAGES.SYNC_NO_MATCH
2585
+ };
2586
+ function autoSaveChanges(worktreePath, branch) {
2587
+ gitAddAll(worktreePath);
2588
+ gitCommit(AUTO_SAVE_COMMIT_MESSAGE, worktreePath);
2589
+ printInfo(MESSAGES.SYNC_AUTO_COMMITTED(branch));
2590
+ logger.info(`\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`);
2591
+ }
2592
+ function mergeMainBranch(worktreePath, mainBranch) {
2593
+ try {
2594
+ gitMerge(mainBranch, worktreePath);
2595
+ return false;
2596
+ } catch {
2597
+ if (hasMergeConflict(worktreePath)) {
2598
+ return true;
2599
+ }
2600
+ throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
2601
+ }
2602
+ }
2603
+ function executeSyncForBranch(targetWorktreePath, branch) {
2604
+ const mainWorktreePath = getGitTopLevel();
2605
+ const mainBranch = getCurrentBranch(mainWorktreePath);
2606
+ if (!isWorkingDirClean(targetWorktreePath)) {
2607
+ autoSaveChanges(targetWorktreePath, branch);
2608
+ }
2609
+ printInfo(MESSAGES.SYNC_MERGING(branch, mainBranch));
2610
+ const hasConflict = mergeMainBranch(targetWorktreePath, mainBranch);
2611
+ if (hasConflict) {
2612
+ printWarning(MESSAGES.SYNC_CONFLICT(targetWorktreePath));
2613
+ return { success: false, hasConflict: true };
2614
+ }
2615
+ const projectName = getProjectName();
2616
+ if (hasSnapshot(projectName, branch)) {
2617
+ removeSnapshot(projectName, branch);
2618
+ logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
2619
+ }
2620
+ printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
2621
+ return { success: true, hasConflict: false };
2622
+ }
2623
+ async function handleSync(options) {
2624
+ validateMainWorktree();
2625
+ logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
2626
+ const worktrees = getProjectWorktrees();
2627
+ const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
2628
+ const { path: targetWorktreePath, branch } = worktree;
2629
+ executeSyncForBranch(targetWorktreePath, branch);
2630
+ }
2631
+
2632
+ // src/commands/validate.ts
2340
2633
  var VALIDATE_RESOLVE_MESSAGES = {
2341
2634
  noWorktrees: MESSAGES.VALIDATE_NO_WORKTREES,
2342
2635
  selectBranch: MESSAGES.VALIDATE_SELECT_BRANCH,
@@ -2397,9 +2690,10 @@ function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName
2397
2690
  } catch (error) {
2398
2691
  logger.warn(`patch apply \u5931\u8D25: ${error}`);
2399
2692
  printWarning(MESSAGES.VALIDATE_PATCH_APPLY_FAILED(branchName));
2400
- throw error;
2693
+ return { success: false };
2401
2694
  }
2402
2695
  }
2696
+ return { success: true };
2403
2697
  } finally {
2404
2698
  if (didTempCommit) {
2405
2699
  try {
@@ -2415,6 +2709,18 @@ function migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName
2415
2709
  }
2416
2710
  }
2417
2711
  }
2712
+ async function handlePatchApplyFailure(targetWorktreePath, branchName) {
2713
+ const confirmed = await confirmAction(MESSAGES.VALIDATE_CONFIRM_AUTO_SYNC(branchName));
2714
+ if (!confirmed) {
2715
+ printWarning(MESSAGES.VALIDATE_AUTO_SYNC_DECLINED(branchName));
2716
+ return;
2717
+ }
2718
+ printInfo(MESSAGES.VALIDATE_AUTO_SYNC_START(branchName));
2719
+ const syncResult = executeSyncForBranch(targetWorktreePath, branchName);
2720
+ if (syncResult.hasConflict) {
2721
+ printWarning(MESSAGES.VALIDATE_AUTO_SYNC_CONFLICT(targetWorktreePath));
2722
+ }
2723
+ }
2418
2724
  function saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName) {
2419
2725
  gitAddAll(mainWorktreePath);
2420
2726
  const treeHash = gitWriteTree(mainWorktreePath);
@@ -2448,18 +2754,26 @@ async function handleValidateClean(options) {
2448
2754
  removeSnapshot(projectName, branchName);
2449
2755
  printSuccess(MESSAGES.VALIDATE_CLEANED(branchName));
2450
2756
  }
2451
- function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
2452
- migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
2757
+ async function handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
2758
+ const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
2759
+ if (!result.success) {
2760
+ await handlePatchApplyFailure(targetWorktreePath, branchName);
2761
+ return;
2762
+ }
2453
2763
  saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
2454
2764
  printSuccess(MESSAGES.VALIDATE_SUCCESS(branchName));
2455
2765
  }
2456
- function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
2766
+ async function handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted) {
2457
2767
  const { treeHash: oldTreeHash, headCommitHash: oldHeadCommitHash } = readSnapshot(projectName, branchName);
2458
2768
  if (!isWorkingDirClean(mainWorktreePath)) {
2459
2769
  gitResetHard(mainWorktreePath);
2460
2770
  gitCleanForce(mainWorktreePath);
2461
2771
  }
2462
- migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
2772
+ const result = migrateChangesViaPatch(targetWorktreePath, mainWorktreePath, branchName, hasUncommitted);
2773
+ if (!result.success) {
2774
+ await handlePatchApplyFailure(targetWorktreePath, branchName);
2775
+ return;
2776
+ }
2463
2777
  saveCurrentSnapshotTree(mainWorktreePath, projectName, branchName);
2464
2778
  try {
2465
2779
  const currentHeadCommitHash = getHeadCommitHash(mainWorktreePath);
@@ -2562,12 +2876,12 @@ async function handleValidate(options) {
2562
2876
  if (!isWorkingDirClean(mainWorktreePath)) {
2563
2877
  await handleDirtyMainWorktree(mainWorktreePath);
2564
2878
  }
2565
- handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
2879
+ await handleIncrementalValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
2566
2880
  } else {
2567
2881
  if (!isWorkingDirClean(mainWorktreePath)) {
2568
2882
  await handleDirtyMainWorktree(mainWorktreePath);
2569
2883
  }
2570
- handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
2884
+ await handleFirstValidate(targetWorktreePath, mainWorktreePath, projectName, branchName, hasUncommitted);
2571
2885
  }
2572
2886
  if (options.run) {
2573
2887
  await executeRunCommand(options.run, mainWorktreePath);
@@ -2780,60 +3094,6 @@ function handleConfigGet(key) {
2780
3094
  printInfo(MESSAGES.CONFIG_GET_VALUE(key, String(value)));
2781
3095
  }
2782
3096
 
2783
- // src/commands/sync.ts
2784
- function registerSyncCommand(program2) {
2785
- program2.command("sync").description("\u5C06\u4E3B\u5206\u652F\u6700\u65B0\u4EE3\u7801\u540C\u6B65\u5230\u76EE\u6807 worktree").option("-b, --branch <branchName>", "\u8981\u540C\u6B65\u7684\u5206\u652F\u540D\uFF08\u652F\u6301\u6A21\u7CCA\u5339\u914D\uFF0C\u4E0D\u4F20\u5219\u5217\u51FA\u6240\u6709\u5206\u652F\uFF09").action(async (options) => {
2786
- await handleSync(options);
2787
- });
2788
- }
2789
- var SYNC_RESOLVE_MESSAGES = {
2790
- noWorktrees: MESSAGES.SYNC_NO_WORKTREES,
2791
- selectBranch: MESSAGES.SYNC_SELECT_BRANCH,
2792
- multipleMatches: MESSAGES.SYNC_MULTIPLE_MATCHES,
2793
- noMatch: MESSAGES.SYNC_NO_MATCH
2794
- };
2795
- function autoSaveChanges(worktreePath, branch) {
2796
- gitAddAll(worktreePath);
2797
- gitCommit(AUTO_SAVE_COMMIT_MESSAGE, worktreePath);
2798
- printInfo(MESSAGES.SYNC_AUTO_COMMITTED(branch));
2799
- logger.info(`\u5DF2\u81EA\u52A8\u4FDD\u5B58 ${branch} \u5206\u652F\u7684\u672A\u63D0\u4EA4\u53D8\u66F4`);
2800
- }
2801
- function mergeMainBranch(worktreePath, mainBranch) {
2802
- try {
2803
- gitMerge(mainBranch, worktreePath);
2804
- return false;
2805
- } catch {
2806
- if (hasMergeConflict(worktreePath)) {
2807
- return true;
2808
- }
2809
- throw new ClawtError(`\u5408\u5E76 ${mainBranch} \u5931\u8D25`);
2810
- }
2811
- }
2812
- async function handleSync(options) {
2813
- validateMainWorktree();
2814
- logger.info(`sync \u547D\u4EE4\u6267\u884C\uFF0C\u5206\u652F: ${options.branch ?? "(\u672A\u6307\u5B9A)"}`);
2815
- const worktrees = getProjectWorktrees();
2816
- const worktree = await resolveTargetWorktree(worktrees, SYNC_RESOLVE_MESSAGES, options.branch);
2817
- const { path: targetWorktreePath, branch } = worktree;
2818
- const mainWorktreePath = getGitTopLevel();
2819
- const mainBranch = getCurrentBranch(mainWorktreePath);
2820
- if (!isWorkingDirClean(targetWorktreePath)) {
2821
- autoSaveChanges(targetWorktreePath, branch);
2822
- }
2823
- printInfo(MESSAGES.SYNC_MERGING(branch, mainBranch));
2824
- const hasConflict = mergeMainBranch(targetWorktreePath, mainBranch);
2825
- if (hasConflict) {
2826
- printWarning(MESSAGES.SYNC_CONFLICT(targetWorktreePath));
2827
- return;
2828
- }
2829
- const projectName = getProjectName();
2830
- if (hasSnapshot(projectName, branch)) {
2831
- removeSnapshot(projectName, branch);
2832
- logger.info(`\u5DF2\u6E05\u9664\u5206\u652F ${branch} \u7684 validate \u5FEB\u7167`);
2833
- }
2834
- printSuccess(MESSAGES.SYNC_SUCCESS(branch, mainBranch));
2835
- }
2836
-
2837
3097
  // src/commands/reset.ts
2838
3098
  function registerResetCommand(program2) {
2839
3099
  program2.command("reset").description("\u91CD\u7F6E\u4E3B worktree \u5DE5\u4F5C\u533A\u548C\u6682\u5B58\u533A\uFF08\u4FDD\u7559 validate \u5FEB\u7167\uFF09").action(async () => {