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.
- package/dist/index.js +209 -120
- 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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
184
|
-
if (!
|
|
210
|
+
const path2 = findTranscriptPath(sessionId, root);
|
|
211
|
+
if (!path2) return "";
|
|
185
212
|
try {
|
|
186
|
-
return extractFinalAssistantText(readFileSync3(
|
|
213
|
+
return extractFinalAssistantText(readFileSync3(path2, "utf-8"));
|
|
187
214
|
} catch (err) {
|
|
188
|
-
console.error(`[transcript] failed to read ${
|
|
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
|
-
|
|
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(
|
|
1915
|
+
function isDirectory(path2) {
|
|
1873
1916
|
try {
|
|
1874
|
-
return statSync(
|
|
1917
|
+
return statSync(path2).isDirectory();
|
|
1875
1918
|
} catch {
|
|
1876
1919
|
return false;
|
|
1877
1920
|
}
|
|
1878
1921
|
}
|
|
1879
|
-
function parseConfigFile(
|
|
1922
|
+
function parseConfigFile(path2) {
|
|
1880
1923
|
let content;
|
|
1881
1924
|
try {
|
|
1882
|
-
content = readFileSync2(
|
|
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: ${
|
|
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,
|
|
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 ${
|
|
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
|
|
1950
|
+
const path2 = getUserConfigPath();
|
|
1908
1951
|
const raw = readRawConfig();
|
|
1909
1952
|
if (raw === void 0) {
|
|
1910
|
-
throw new UserConfigError(`config.json not found: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
1962
|
+
throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
|
|
1920
1963
|
}
|
|
1921
|
-
const mode = parseMode(raw,
|
|
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": ${
|
|
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": ${
|
|
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(
|
|
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(
|
|
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
|
|
2077
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
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
|
-
|
|
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(
|
|
2697
|
-
return resolve2(
|
|
2785
|
+
function isManagedWorktreePath(path2) {
|
|
2786
|
+
return resolve2(path2).startsWith(resolve2(WORKTREES_DIR) + sep);
|
|
2698
2787
|
}
|
|
2699
|
-
async function pathExists(
|
|
2788
|
+
async function pathExists(path2) {
|
|
2700
2789
|
try {
|
|
2701
|
-
await stat(
|
|
2790
|
+
await stat(path2);
|
|
2702
2791
|
return true;
|
|
2703
2792
|
} catch {
|
|
2704
2793
|
return false;
|
|
2705
2794
|
}
|
|
2706
2795
|
}
|
|
2707
|
-
async function forceRemoveIfExists(
|
|
2708
|
-
if (!isManagedWorktreePath(
|
|
2709
|
-
console.error(`[worktree] Refusing to remove path outside ${WORKTREES_DIR}: ${
|
|
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(
|
|
2801
|
+
if (!await pathExists(path2)) return;
|
|
2713
2802
|
try {
|
|
2714
|
-
await rm(
|
|
2715
|
-
console.log(`[worktree] Force removed remaining directory: ${
|
|
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 ${
|
|
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
|
|
3297
|
-
if (
|
|
3298
|
-
return
|
|
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
|
|
3658
|
+
const path2 = globalGitIgnorePath();
|
|
3570
3659
|
try {
|
|
3571
3660
|
let current = "";
|
|
3572
3661
|
try {
|
|
3573
|
-
current = await readFile(
|
|
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} (${
|
|
3667
|
+
console.log(`[${logPrefix}] Already ignored: ${CODEGRAPH_IGNORE_ENTRY} (${path2})`);
|
|
3579
3668
|
return true;
|
|
3580
3669
|
}
|
|
3581
|
-
await mkdir(dirname2(
|
|
3582
|
-
await writeFile(
|
|
3583
|
-
console.log(`[${logPrefix}] Added ${CODEGRAPH_IGNORE_ENTRY} to ${
|
|
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 ${
|
|
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(
|
|
3736
|
+
async function writeFileWithMode(path2, content, force) {
|
|
3648
3737
|
try {
|
|
3649
|
-
await access(
|
|
3738
|
+
await access(path2);
|
|
3650
3739
|
if (!force) return "skipped";
|
|
3651
|
-
await writeFile2(
|
|
3740
|
+
await writeFile2(path2, content, "utf-8");
|
|
3652
3741
|
return "overwritten";
|
|
3653
3742
|
} catch {
|
|
3654
|
-
await writeFile2(
|
|
3743
|
+
await writeFile2(path2, content, "utf-8");
|
|
3655
3744
|
return "created";
|
|
3656
3745
|
}
|
|
3657
3746
|
}
|
|
3658
|
-
function logWriteResult(result,
|
|
3659
|
-
if (result === "created") console.log(`[init] Created: ${
|
|
3660
|
-
else if (result === "overwritten") console.log(`[init] Overwritten: ${
|
|
3661
|
-
else console.log(`[init] Already exists: ${
|
|
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.
|
|
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",
|