claude-task-worker 0.39.1 → 0.40.0

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.
Files changed (2) hide show
  1. package/dist/index.js +209 -120
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -12,9 +12,13 @@ var __export = (target, all) => {
12
12
  // src/table.ts
13
13
  var table_exports = {};
14
14
  __export(table_exports, {
15
+ LOG_DISPLAY_LIMIT: () => LOG_DISPLAY_LIMIT,
16
+ TASK_DISPLAY_LIMIT: () => TASK_DISPLAY_LIMIT,
17
+ buildLogTableLines: () => buildLogTableLines,
15
18
  buildTaskTableLines: () => buildTaskTableLines,
16
19
  getDisplayWidth: () => getDisplayWidth,
17
20
  padToWidth: () => padToWidth,
21
+ selectRecentTasks: () => selectRecentTasks,
18
22
  truncateToWidth: () => truncateToWidth
19
23
  });
20
24
  function getDisplayWidth(str) {
@@ -61,74 +65,97 @@ function formatTime(date) {
61
65
  const s = String(date.getSeconds()).padStart(2, "0");
62
66
  return `${h}:${m}:${s}`;
63
67
  }
68
+ function renderBoxTable(headers, groups) {
69
+ const allRows = groups.flat();
70
+ const widths = headers.map(
71
+ (h, i) => Math.max(1, getDisplayWidth(h), ...allRows.map((r) => getDisplayWidth(r[i] ?? "")))
72
+ );
73
+ const border = (l, m, r) => `${l}${widths.map((w) => "\u2500".repeat(w + 2)).join(m)}${r}`;
74
+ const row = (cells) => `\u2502 ${cells.map((c, i) => padToWidth(c ?? "", widths[i])).join(" \u2502 ")} \u2502`;
75
+ const lines = [];
76
+ lines.push(border("\u250C", "\u252C", "\u2510"));
77
+ lines.push(row(headers));
78
+ lines.push(border("\u251C", "\u253C", "\u2524"));
79
+ const nonEmpty = groups.filter((g) => g.length > 0);
80
+ nonEmpty.forEach((group, gi) => {
81
+ if (gi > 0) lines.push(border("\u251C", "\u253C", "\u2524"));
82
+ for (const r of group) lines.push(row(r));
83
+ });
84
+ lines.push(border("\u2514", "\u2534", "\u2518"));
85
+ return lines;
86
+ }
87
+ function taskRecency(t) {
88
+ return (t.finishedAt ?? t.startedAt).getTime();
89
+ }
90
+ function selectRecentTasks(entries, limit = TASK_DISPLAY_LIMIT) {
91
+ const byId = /* @__PURE__ */ new Map();
92
+ for (const e of entries) byId.set(e.id, e);
93
+ const unique = [...byId.values()];
94
+ const running2 = unique.filter((t) => t.status === "running").sort((a, b) => taskRecency(b) - taskRecency(a));
95
+ const finished = unique.filter((t) => t.status !== "running").sort((a, b) => taskRecency(b) - taskRecency(a));
96
+ return [...running2, ...finished].slice(0, limit);
97
+ }
64
98
  function buildTaskTableLines(entries, now = /* @__PURE__ */ new Date()) {
65
99
  if (entries.length === 0) return [];
66
100
  const runningTasks = entries.filter((t) => t.status === "running");
67
101
  const finishedTasks = entries.filter((t) => t.status !== "running");
68
102
  const maxTitleWidth = 40;
69
103
  const maxPathWidth = 40;
70
- const runningRows = runningTasks.map((t) => ({
71
- id: `#${t.id}`,
72
- title: getDisplayWidth(t.title) > maxTitleWidth ? truncateToWidth(t.title, maxTitleWidth) : t.title,
73
- worker: t.workerName,
74
- path: t.path ? t.path.length > maxPathWidth ? truncateToWidth(t.path, maxPathWidth) : t.path : "",
75
- // herdr モードでは agent ステータス(working / blocked 等)を併記し、
76
- // 人の介入が必要な blocked にも気づけるようにする。
77
- status: t.agentStatus ? `${t.status}:${t.agentStatus}` : t.status,
78
- time: formatTime(t.startedAt),
79
- duration: formatDuration(t.startedAt, now)
80
- }));
81
- const finishedRows = finishedTasks.map((t) => ({
82
- id: `#${t.id}`,
83
- title: getDisplayWidth(t.title) > maxTitleWidth ? truncateToWidth(t.title, maxTitleWidth) : t.title,
84
- worker: t.workerName,
85
- path: t.path ? t.path.length > maxPathWidth ? truncateToWidth(t.path, maxPathWidth) : t.path : "",
86
- status: t.status,
87
- time: formatTime(t.finishedAt ?? t.startedAt),
88
- duration: formatDuration(t.startedAt, t.finishedAt ?? now)
89
- }));
90
- const allRows = [...runningRows, ...finishedRows];
91
- const hasPath = allRows.some((r) => r.path !== "");
92
- const colWidths = {
93
- id: Math.max(3, ...allRows.map((r) => r.id.length)),
94
- title: Math.max(5, ...allRows.map((r) => getDisplayWidth(r.title))),
95
- worker: Math.max(6, ...allRows.map((r) => r.worker.length)),
96
- ...hasPath ? { path: Math.max(8, ...allRows.map((r) => r.path.length)) } : {},
97
- status: Math.max(6, ...allRows.map((r) => r.status.length)),
98
- time: Math.max(4, ...allRows.map((r) => r.time.length)),
99
- duration: Math.max(8, ...allRows.map((r) => r.duration.length))
100
- };
101
- const pad = (s, w, useDisplayWidth = false) => useDisplayWidth ? padToWidth(s, w) : s + " ".repeat(w - s.length);
102
- const cols = hasPath ? [
103
- colWidths.id,
104
- colWidths.title,
105
- colWidths.worker,
106
- colWidths.path,
107
- colWidths.status,
108
- colWidths.time,
109
- colWidths.duration
110
- ] : [colWidths.id, colWidths.title, colWidths.worker, colWidths.status, colWidths.time, colWidths.duration];
111
- const line = (l, m, r, f) => `${l}${cols.map((w) => f.repeat(w + 2)).join(m)}${r}`;
112
- const row = (id, title, worker, path, status, time, duration) => hasPath ? `\u2502 ${pad(id, colWidths.id)} \u2502 ${pad(title, colWidths.title, true)} \u2502 ${pad(worker, colWidths.worker)} \u2502 ${pad(path, colWidths.path)} \u2502 ${pad(status, colWidths.status)} \u2502 ${pad(time, colWidths.time)} \u2502 ${pad(duration, colWidths.duration)} \u2502` : `\u2502 ${pad(id, colWidths.id)} \u2502 ${pad(title, colWidths.title, true)} \u2502 ${pad(worker, colWidths.worker)} \u2502 ${pad(status, colWidths.status)} \u2502 ${pad(time, colWidths.time)} \u2502 ${pad(duration, colWidths.duration)} \u2502`;
113
- const lines = [];
114
- lines.push(line("\u250C", "\u252C", "\u2510", "\u2500"));
115
- lines.push(row("#", "Title", "Worker", "Worktree", "Status", "Time", "Duration"));
116
- lines.push(line("\u251C", "\u253C", "\u2524", "\u2500"));
117
- for (const r of runningRows) {
118
- lines.push(row(r.id, r.title, r.worker, r.path, r.status, r.time, r.duration));
119
- }
120
- if (runningRows.length > 0 && finishedRows.length > 0) {
121
- lines.push(line("\u251C", "\u253C", "\u2524", "\u2500"));
122
- }
123
- for (const r of finishedRows) {
124
- lines.push(row(r.id, r.title, r.worker, r.path, r.status, r.time, r.duration));
125
- }
126
- lines.push(line("\u2514", "\u2534", "\u2518", "\u2500"));
127
- return lines;
104
+ const truncTitle = (s) => getDisplayWidth(s) > maxTitleWidth ? truncateToWidth(s, maxTitleWidth) : s;
105
+ const truncPath = (p) => p ? getDisplayWidth(p) > maxPathWidth ? truncateToWidth(p, maxPathWidth) : p : "";
106
+ const hasPath = entries.some((t) => truncPath(t.path) !== "");
107
+ const runningRows = runningTasks.map(
108
+ (t) => taskRow(
109
+ t,
110
+ truncTitle(t.title),
111
+ truncPath(t.path),
112
+ hasPath,
113
+ t.agentStatus ? `${t.status}:${t.agentStatus}` : t.status,
114
+ formatTime(t.startedAt),
115
+ formatDuration(t.startedAt, now)
116
+ )
117
+ );
118
+ const finishedRows = finishedTasks.map(
119
+ (t) => taskRow(
120
+ t,
121
+ truncTitle(t.title),
122
+ truncPath(t.path),
123
+ hasPath,
124
+ t.status,
125
+ formatTime(t.finishedAt ?? t.startedAt),
126
+ formatDuration(t.startedAt, t.finishedAt ?? now)
127
+ )
128
+ );
129
+ const headers = hasPath ? ["#", "Title", "Worker", "Worktree", "Status", "Time", "Duration"] : ["#", "Title", "Worker", "Status", "Time", "Duration"];
130
+ return renderBoxTable(headers, [runningRows, finishedRows]);
131
+ }
132
+ function taskRow(t, title, path2, hasPath, status, time, duration) {
133
+ return hasPath ? [`#${t.id}`, title, t.workerName, path2, status, time, duration] : [`#${t.id}`, title, t.workerName, status, time, duration];
134
+ }
135
+ function sanitizeLogText(text) {
136
+ return text.replace(CONTROL_CHARS, " ").replace(/\t/g, " ");
137
+ }
138
+ function buildLogTableLines(entries) {
139
+ if (entries.length === 0) return [];
140
+ const maxTextWidth = 100;
141
+ const rows = entries.map((e) => {
142
+ const text = sanitizeLogText(e.text);
143
+ return [
144
+ formatTime(e.time),
145
+ `#${e.id}`,
146
+ e.stream,
147
+ getDisplayWidth(text) > maxTextWidth ? truncateToWidth(text, maxTextWidth) : text
148
+ ];
149
+ });
150
+ return renderBoxTable(["Time", "#", "Stream", "Log"], [rows]);
128
151
  }
152
+ var TASK_DISPLAY_LIMIT, LOG_DISPLAY_LIMIT, CONTROL_CHARS;
129
153
  var init_table = __esm({
130
154
  "src/table.ts"() {
131
155
  "use strict";
156
+ TASK_DISPLAY_LIMIT = 20;
157
+ LOG_DISPLAY_LIMIT = 20;
158
+ CONTROL_CHARS = /\x1b\[[0-9;?]*[ -/]*[@-~]|[\x00-\x08\x0b-\x1f\x7f]/g;
132
159
  }
133
160
  });
134
161
 
@@ -180,12 +207,12 @@ function extractFinalAssistantText(jsonl) {
180
207
  }
181
208
  function readFinalReport(sessionId, root = transcriptRoot()) {
182
209
  if (!sessionId) return "";
183
- const path = findTranscriptPath(sessionId, root);
184
- if (!path) return "";
210
+ const path2 = findTranscriptPath(sessionId, root);
211
+ if (!path2) return "";
185
212
  try {
186
- return extractFinalAssistantText(readFileSync3(path, "utf-8"));
213
+ return extractFinalAssistantText(readFileSync3(path2, "utf-8"));
187
214
  } catch (err) {
188
- console.error(`[transcript] failed to read ${path}: ${err}`);
215
+ console.error(`[transcript] failed to read ${path2}: ${err}`);
189
216
  return "";
190
217
  }
191
218
  }
@@ -1459,6 +1486,9 @@ async function createLabel(name, color, force) {
1459
1486
  }
1460
1487
 
1461
1488
  // src/claude-args.ts
1489
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
1490
+ import os from "node:os";
1491
+ import path from "node:path";
1462
1492
  var DISALLOWED_TOOLS = [
1463
1493
  // 遅延 / yield: 後続ウェイクアップ前提。print モードではウェイクアップが発火せず、
1464
1494
  // 呼ぶと処理未完のままプロセスが終了する。
@@ -1500,6 +1530,18 @@ var SYSTEM_PROMPT = `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F \`claude-t
1500
1530
  - \u8A2D\u5B9A\u30D5\u30A1\u30A4\u30EB\u30FB\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u30FB\u30B3\u30E1\u30F3\u30C8/\u6587\u5B57\u5217\u30EA\u30C6\u30E9\u30EB\u30FB\u672A\u5BFE\u5FDC\u8A00\u8A9E\u306A\u3069 CodeGraph \u304C\u6271\u308F\u306A\u3044\u5BFE\u8C61\u306F\u3001\u5F93\u6765\u3069\u304A\u308A\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u3067\u88DC\u3046
1501
1531
  - \u63A2\u7D22\u3092\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u59D4\u8B72\u3059\u308B\u5834\u5408\u306F\u3001\u3053\u306E\u65B9\u91DD\u3082\u59D4\u8B72\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u660E\u8A18\u3057\u3066\u4F1D\u3048\u308B`;
1502
1532
  var CLAUDE_COMMAND = "claude";
1533
+ var cachedSystemPromptFilePath;
1534
+ function systemPromptFilePath() {
1535
+ if (cachedSystemPromptFilePath) return cachedSystemPromptFilePath;
1536
+ const dir = path.join(os.tmpdir(), "claude-task-worker");
1537
+ mkdirSync(dir, { recursive: true });
1538
+ const target = path.join(dir, `append-system-prompt-${process.pid}.txt`);
1539
+ const tmp = `${target}.tmp`;
1540
+ writeFileSync(tmp, SYSTEM_PROMPT, "utf8");
1541
+ renameSync(tmp, target);
1542
+ cachedSystemPromptFilePath = target;
1543
+ return target;
1544
+ }
1503
1545
  function buildClaudeArgs({ mode, prompt, model, effort }) {
1504
1546
  return [
1505
1547
  ...mode === "herdr" ? [] : ["-p"],
@@ -1508,8 +1550,8 @@ function buildClaudeArgs({ mode, prompt, model, effort }) {
1508
1550
  "--chrome",
1509
1551
  "--disallowedTools",
1510
1552
  DISALLOWED_TOOLS_ARG,
1511
- "--append-system-prompt",
1512
- SYSTEM_PROMPT,
1553
+ "--append-system-prompt-file",
1554
+ systemPromptFilePath(),
1513
1555
  "--model",
1514
1556
  model,
1515
1557
  "--effort",
@@ -1828,6 +1870,7 @@ async function assertRemoteTrackingExists(epicBranch) {
1828
1870
  // src/process-manager.ts
1829
1871
  import { spawn } from "node:child_process";
1830
1872
  import { basename } from "node:path";
1873
+ import { StringDecoder } from "node:string_decoder";
1831
1874
  init_table();
1832
1875
 
1833
1876
  // src/task-result.ts
@@ -1869,17 +1912,17 @@ function getConfigDir() {
1869
1912
  function getUserConfigPath() {
1870
1913
  return join2(getConfigDir(), "config.json");
1871
1914
  }
1872
- function isDirectory(path) {
1915
+ function isDirectory(path2) {
1873
1916
  try {
1874
- return statSync(path).isDirectory();
1917
+ return statSync(path2).isDirectory();
1875
1918
  } catch {
1876
1919
  return false;
1877
1920
  }
1878
1921
  }
1879
- function parseConfigFile(path) {
1922
+ function parseConfigFile(path2) {
1880
1923
  let content;
1881
1924
  try {
1882
- content = readFileSync2(path, "utf-8");
1925
+ content = readFileSync2(path2, "utf-8");
1883
1926
  } catch (err) {
1884
1927
  if (err.code === "ENOENT") return void 0;
1885
1928
  throw err;
@@ -1888,7 +1931,7 @@ function parseConfigFile(path) {
1888
1931
  return JSON.parse(content);
1889
1932
  } catch (err) {
1890
1933
  if (err instanceof SyntaxError) {
1891
- throw new UserConfigError(`config file contains invalid JSON: ${path}: ${err.message}`);
1934
+ throw new UserConfigError(`config file contains invalid JSON: ${path2}: ${err.message}`);
1892
1935
  }
1893
1936
  throw err;
1894
1937
  }
@@ -1896,29 +1939,29 @@ function parseConfigFile(path) {
1896
1939
  function readRawConfig() {
1897
1940
  return parseConfigFile(getUserConfigPath());
1898
1941
  }
1899
- function parseMode(raw, path) {
1942
+ function parseMode(raw, path2) {
1900
1943
  if (!("mode" in raw)) return DEFAULT_RUN_MODE;
1901
1944
  const value = raw["mode"];
1902
1945
  if (value === "default" || value === "herdr") return value;
1903
- console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path}, using "${DEFAULT_RUN_MODE}"`);
1946
+ console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_RUN_MODE}"`);
1904
1947
  return DEFAULT_RUN_MODE;
1905
1948
  }
1906
1949
  function loadUserConfig() {
1907
- const path = getUserConfigPath();
1950
+ const path2 = getUserConfigPath();
1908
1951
  const raw = readRawConfig();
1909
1952
  if (raw === void 0) {
1910
- throw new UserConfigError(`config.json not found: ${path}`);
1953
+ throw new UserConfigError(`config.json not found: ${path2}`);
1911
1954
  }
1912
1955
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1913
- throw new UserConfigError(`config.json must contain a JSON object: ${path}`);
1956
+ throw new UserConfigError(`config.json must contain a JSON object: ${path2}`);
1914
1957
  }
1915
1958
  if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
1916
- throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path}`);
1959
+ throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path2}`);
1917
1960
  }
1918
1961
  if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
1919
- throw new UserConfigError(`config.json "projectGroups" must be an object: ${path}`);
1962
+ throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
1920
1963
  }
1921
- const mode = parseMode(raw, path);
1964
+ const mode = parseMode(raw, path2);
1922
1965
  const rawProjects = raw["projects"];
1923
1966
  const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
1924
1967
  const projectKeys = Object.keys(rawProjects);
@@ -1938,7 +1981,7 @@ function loadUserConfig() {
1938
1981
  const projects = {};
1939
1982
  for (const [name, value] of Object.entries(rawProjects)) {
1940
1983
  if (name === "__proto__") {
1941
- throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path}`);
1984
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path2}`);
1942
1985
  }
1943
1986
  if (typeof value !== "string" || !isAbsolute2(value)) {
1944
1987
  console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
@@ -1953,7 +1996,7 @@ function loadUserConfig() {
1953
1996
  const projectGroups = {};
1954
1997
  for (const [groupName, value] of Object.entries(rawProjectGroups)) {
1955
1998
  if (groupName === "__proto__") {
1956
- throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path}`);
1999
+ throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path2}`);
1957
2000
  }
1958
2001
  if (!Array.isArray(value)) {
1959
2002
  console.warn(`[config] invalid projectGroups.${groupName}: expected an array, skipping`);
@@ -1992,14 +2035,14 @@ function readRunMode() {
1992
2035
  }
1993
2036
  return parseMode(raw, getUserConfigPath());
1994
2037
  }
1995
- function findProjectNameByPath(path) {
2038
+ function findProjectNameByPath(path2) {
1996
2039
  let config;
1997
2040
  try {
1998
2041
  config = loadUserConfig();
1999
2042
  } catch {
2000
2043
  return void 0;
2001
2044
  }
2002
- const target = resolve(path);
2045
+ const target = resolve(path2);
2003
2046
  for (const [name, projectPath] of Object.entries(config.projects)) {
2004
2047
  if (resolve(projectPath) === target) return name;
2005
2048
  }
@@ -2044,6 +2087,38 @@ var childProcesses = /* @__PURE__ */ new Map();
2044
2087
  var herdrTasks = /* @__PURE__ */ new Map();
2045
2088
  var herdrAbortSignal = { aborted: false };
2046
2089
  var tasks = /* @__PURE__ */ new Map();
2090
+ var logLines = [];
2091
+ function pushLogLine(id, stream, text) {
2092
+ const trimmed = text.replace(/\r$/, "");
2093
+ if (trimmed.trim().length === 0) return;
2094
+ logLines.push({ id, stream, text: trimmed, time: /* @__PURE__ */ new Date() });
2095
+ if (logLines.length > LOG_DISPLAY_LIMIT) {
2096
+ logLines.splice(0, logLines.length - LOG_DISPLAY_LIMIT);
2097
+ }
2098
+ }
2099
+ function makeLogFeeder(id, stream) {
2100
+ let partial = "";
2101
+ const decoder = new StringDecoder("utf-8");
2102
+ return {
2103
+ feed(chunk) {
2104
+ partial += decoder.write(chunk);
2105
+ const parts = partial.split("\n");
2106
+ partial = parts.pop() ?? "";
2107
+ for (const part of parts) pushLogLine(id, stream, part);
2108
+ },
2109
+ flush() {
2110
+ partial += decoder.end();
2111
+ if (partial.length > 0) {
2112
+ pushLogLine(id, stream, partial);
2113
+ partial = "";
2114
+ }
2115
+ }
2116
+ };
2117
+ }
2118
+ function pruneTaskHistory() {
2119
+ const finished = [...tasks.values()].filter((t) => t.status !== "running").sort((a, b) => (b.finishedAt ?? b.startedAt).getTime() - (a.finishedAt ?? a.startedAt).getTime());
2120
+ for (const t of finished.slice(TASK_DISPLAY_LIMIT)) tasks.delete(t.id);
2121
+ }
2047
2122
  var shuttingDown = false;
2048
2123
  function setShuttingDown() {
2049
2124
  shuttingDown = true;
@@ -2073,8 +2148,14 @@ function isWorkerAtCapacity(workerName) {
2073
2148
  return count >= getWorkerConfig(workerName).maxConcurrentTasks;
2074
2149
  }
2075
2150
  function renderTable() {
2076
- const lines = buildTaskTableLines([...tasks.values()]);
2077
- if (lines.length === 0) return;
2151
+ const taskLines = buildTaskTableLines(selectRecentTasks([...tasks.values()]));
2152
+ const logTableLines = buildLogTableLines(logLines);
2153
+ if (taskLines.length === 0 && logTableLines.length === 0) return;
2154
+ const lines = [...taskLines];
2155
+ if (logTableLines.length > 0) {
2156
+ if (lines.length > 0) lines.push("");
2157
+ lines.push("Logs", ...logTableLines);
2158
+ }
2078
2159
  console.clear();
2079
2160
  console.log(lines.join("\n"));
2080
2161
  }
@@ -2101,6 +2182,7 @@ async function finishTask(id, result, onComplete) {
2101
2182
  task.finishedAt = /* @__PURE__ */ new Date();
2102
2183
  task.agentStatus = void 0;
2103
2184
  }
2185
+ pruneTaskHistory();
2104
2186
  renderTable();
2105
2187
  }
2106
2188
  function resolveProjectName(cwd = process.cwd()) {
@@ -2143,13 +2225,14 @@ async function runViaHerdr(args, id, onComplete, cwd, env) {
2143
2225
  await finishTask(id, result, onComplete);
2144
2226
  herdrTasks.delete(id);
2145
2227
  }
2146
- function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
2228
+ function run(command, args, id, title, workerName, path2, onComplete, cwd, env) {
2229
+ tasks.delete(id);
2147
2230
  tasks.set(id, {
2148
2231
  id,
2149
2232
  title,
2150
2233
  status: "running",
2151
2234
  workerName,
2152
- path,
2235
+ path: path2,
2153
2236
  startedAt: /* @__PURE__ */ new Date()
2154
2237
  });
2155
2238
  ensureRenderInterval();
@@ -2165,9 +2248,12 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
2165
2248
  ...env ? { env: { ...process.env, ...env } } : {}
2166
2249
  });
2167
2250
  childProcesses.set(id, child);
2251
+ const stdoutFeeder = makeLogFeeder(id, "stdout");
2252
+ const stderrFeeder = makeLogFeeder(id, "stderr");
2168
2253
  const outputChunks = [];
2169
2254
  child.stdout?.on("data", (chunk) => {
2170
2255
  outputChunks.push(chunk);
2256
+ stdoutFeeder.feed(chunk);
2171
2257
  });
2172
2258
  const stderrChunks = [];
2173
2259
  let stderrLen = 0;
@@ -2178,8 +2264,11 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
2178
2264
  stderrLen -= stderrChunks[0].length;
2179
2265
  stderrChunks.shift();
2180
2266
  }
2267
+ stderrFeeder.feed(chunk);
2181
2268
  });
2182
2269
  child.on("close", async (code) => {
2270
+ stdoutFeeder.flush();
2271
+ stderrFeeder.flush();
2183
2272
  const result = buildTaskResult(
2184
2273
  code,
2185
2274
  Buffer.concat(outputChunks).toString("utf-8"),
@@ -2472,13 +2561,13 @@ function isGeneratedWorktreeName(name) {
2472
2561
 
2473
2562
  // src/slack.ts
2474
2563
  import { exec } from "node:child_process";
2475
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
2564
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2476
2565
  import { homedir as homedir4 } from "node:os";
2477
2566
  import { join as join5 } from "node:path";
2478
2567
  import { promisify as promisify2 } from "node:util";
2479
2568
 
2480
2569
  // src/runcat.ts
2481
- import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
2570
+ import { mkdirSync as mkdirSync2, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
2482
2571
  import { homedir as homedir3 } from "node:os";
2483
2572
  import { dirname, join as join4 } from "node:path";
2484
2573
  var RUNCAT_OUT_FILE = process.env.RUNCAT_OUT_FILE ?? join4(process.env.HOME ?? homedir3(), ".claude", "runcat-usage.json");
@@ -2547,9 +2636,9 @@ function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
2547
2636
  const dir = dirname(outFile);
2548
2637
  const tmp = join4(dir, `.runcat-${process.pid}-${Date.now()}.json`);
2549
2638
  try {
2550
- mkdirSync(dir, { recursive: true });
2551
- writeFileSync(tmp, JSON.stringify(snapshot), "utf-8");
2552
- renameSync(tmp, outFile);
2639
+ mkdirSync2(dir, { recursive: true });
2640
+ writeFileSync2(tmp, JSON.stringify(snapshot), "utf-8");
2641
+ renameSync2(tmp, outFile);
2553
2642
  } catch (err) {
2554
2643
  rmSync(tmp, { force: true });
2555
2644
  console.error(`[runcat] Failed to write ${outFile}: ${err}`);
@@ -2599,7 +2688,7 @@ function readUsageCache() {
2599
2688
  }
2600
2689
  function writeUsageCache(data) {
2601
2690
  try {
2602
- writeFileSync2(USAGE_CACHE_PATH, JSON.stringify({ timestamp: Date.now(), data }));
2691
+ writeFileSync3(USAGE_CACHE_PATH, JSON.stringify({ timestamp: Date.now(), data }));
2603
2692
  } catch {
2604
2693
  }
2605
2694
  }
@@ -2693,28 +2782,28 @@ import { readdir, rm, stat } from "node:fs/promises";
2693
2782
  import { basename as basename2, resolve as resolve2, sep } from "node:path";
2694
2783
  var execFileAsync2 = promisify3(execFile2);
2695
2784
  var WORKTREES_DIR = ".claude/worktrees";
2696
- function isManagedWorktreePath(path) {
2697
- return resolve2(path).startsWith(resolve2(WORKTREES_DIR) + sep);
2785
+ function isManagedWorktreePath(path2) {
2786
+ return resolve2(path2).startsWith(resolve2(WORKTREES_DIR) + sep);
2698
2787
  }
2699
- async function pathExists(path) {
2788
+ async function pathExists(path2) {
2700
2789
  try {
2701
- await stat(path);
2790
+ await stat(path2);
2702
2791
  return true;
2703
2792
  } catch {
2704
2793
  return false;
2705
2794
  }
2706
2795
  }
2707
- async function forceRemoveIfExists(path) {
2708
- if (!isManagedWorktreePath(path)) {
2709
- console.error(`[worktree] Refusing to remove path outside ${WORKTREES_DIR}: ${path}`);
2796
+ async function forceRemoveIfExists(path2) {
2797
+ if (!isManagedWorktreePath(path2)) {
2798
+ console.error(`[worktree] Refusing to remove path outside ${WORKTREES_DIR}: ${path2}`);
2710
2799
  return;
2711
2800
  }
2712
- if (!await pathExists(path)) return;
2801
+ if (!await pathExists(path2)) return;
2713
2802
  try {
2714
- await rm(path, { recursive: true, force: true });
2715
- console.log(`[worktree] Force removed remaining directory: ${path}`);
2803
+ await rm(path2, { recursive: true, force: true });
2804
+ console.log(`[worktree] Force removed remaining directory: ${path2}`);
2716
2805
  } catch (error) {
2717
- console.error(`[worktree] Failed to remove directory ${path}:`, error);
2806
+ console.error(`[worktree] Failed to remove directory ${path2}:`, error);
2718
2807
  }
2719
2808
  }
2720
2809
  async function listWorktreeEntries() {
@@ -3293,9 +3382,9 @@ function extractDesignFilePath(body) {
3293
3382
  if (section === null) return null;
3294
3383
  const match = DESIGN_FILE_LINE.exec(section);
3295
3384
  if (match === null) return null;
3296
- const path = match[1].trim();
3297
- if (path.length === 0 || path.includes("<") || path.includes(">") || !path.endsWith(".pen")) return null;
3298
- return path;
3385
+ const path2 = match[1].trim();
3386
+ if (path2.length === 0 || path2.includes("<") || path2.includes(">") || !path2.endsWith(".pen")) return null;
3387
+ return path2;
3299
3388
  }
3300
3389
  function classifyDesignPr(pr) {
3301
3390
  if (pr === null) return "needs-human";
@@ -3566,24 +3655,24 @@ async function runCodegraphInit(logPrefix) {
3566
3655
  }
3567
3656
  }
3568
3657
  async function ensureCodegraphGitIgnore(logPrefix) {
3569
- const path = globalGitIgnorePath();
3658
+ const path2 = globalGitIgnorePath();
3570
3659
  try {
3571
3660
  let current = "";
3572
3661
  try {
3573
- current = await readFile(path, "utf-8");
3662
+ current = await readFile(path2, "utf-8");
3574
3663
  } catch {
3575
3664
  }
3576
3665
  const next = appendIgnoreEntry(current, CODEGRAPH_IGNORE_ENTRY);
3577
3666
  if (next === null) {
3578
- console.log(`[${logPrefix}] Already ignored: ${CODEGRAPH_IGNORE_ENTRY} (${path})`);
3667
+ console.log(`[${logPrefix}] Already ignored: ${CODEGRAPH_IGNORE_ENTRY} (${path2})`);
3579
3668
  return true;
3580
3669
  }
3581
- await mkdir(dirname2(path), { recursive: true });
3582
- await writeFile(path, next, "utf-8");
3583
- console.log(`[${logPrefix}] Added ${CODEGRAPH_IGNORE_ENTRY} to ${path}`);
3670
+ await mkdir(dirname2(path2), { recursive: true });
3671
+ await writeFile(path2, next, "utf-8");
3672
+ console.log(`[${logPrefix}] Added ${CODEGRAPH_IGNORE_ENTRY} to ${path2}`);
3584
3673
  return true;
3585
3674
  } catch (err) {
3586
- console.error(`[${logPrefix}] Failed to update ${path}: ${err.message}`);
3675
+ console.error(`[${logPrefix}] Failed to update ${path2}: ${err.message}`);
3587
3676
  return false;
3588
3677
  }
3589
3678
  }
@@ -3644,21 +3733,21 @@ jobs:
3644
3733
  assignees: [context.payload.issue.user.login]
3645
3734
  });
3646
3735
  `;
3647
- async function writeFileWithMode(path, content, force) {
3736
+ async function writeFileWithMode(path2, content, force) {
3648
3737
  try {
3649
- await access(path);
3738
+ await access(path2);
3650
3739
  if (!force) return "skipped";
3651
- await writeFile2(path, content, "utf-8");
3740
+ await writeFile2(path2, content, "utf-8");
3652
3741
  return "overwritten";
3653
3742
  } catch {
3654
- await writeFile2(path, content, "utf-8");
3743
+ await writeFile2(path2, content, "utf-8");
3655
3744
  return "created";
3656
3745
  }
3657
3746
  }
3658
- function logWriteResult(result, path) {
3659
- if (result === "created") console.log(`[init] Created: ${path}`);
3660
- else if (result === "overwritten") console.log(`[init] Overwritten: ${path}`);
3661
- else console.log(`[init] Already exists: ${path}`);
3747
+ function logWriteResult(result, path2) {
3748
+ if (result === "created") console.log(`[init] Created: ${path2}`);
3749
+ else if (result === "overwritten") console.log(`[init] Overwritten: ${path2}`);
3750
+ else console.log(`[init] Already exists: ${path2}`);
3662
3751
  }
3663
3752
  async function createConfig(force) {
3664
3753
  const initialConfig = { ...DEFAULT_CONFIG, workers: { ...WORKER_DEFAULTS } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.39.1",
3
+ "version": "0.40.0",
4
4
  "description": "CLI tool that polls GitHub Issues/PRs and delegates work to Claude CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,7 @@
12
12
  "dev": "tsc --noEmit --watch",
13
13
  "lint": "eslint .",
14
14
  "lint:fix": "eslint . --fix",
15
- "test": "node --experimental-strip-types --test \"src/**/*.test.ts\" \"plugin/scripts/**/*.test.mjs\"",
15
+ "test": "node --experimental-strip-types --import ./scripts/test-resolver.mjs --test \"src/**/*.test.ts\" \"plugin/scripts/**/*.test.mjs\"",
16
16
  "format": "prettier --write .",
17
17
  "format:check": "prettier --check .",
18
18
  "version": "node scripts/sync-plugin-version.mjs && git add plugin/.claude-plugin/plugin.json",