claude-task-worker 0.39.2 → 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 +134 -60
  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, path2, status, time, duration) => hasPath ? `\u2502 ${pad(id, colWidths.id)} \u2502 ${pad(title, colWidths.title, true)} \u2502 ${pad(worker, colWidths.worker)} \u2502 ${pad(path2, 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
 
@@ -1843,6 +1870,7 @@ async function assertRemoteTrackingExists(epicBranch) {
1843
1870
  // src/process-manager.ts
1844
1871
  import { spawn } from "node:child_process";
1845
1872
  import { basename } from "node:path";
1873
+ import { StringDecoder } from "node:string_decoder";
1846
1874
  init_table();
1847
1875
 
1848
1876
  // src/task-result.ts
@@ -2059,6 +2087,38 @@ var childProcesses = /* @__PURE__ */ new Map();
2059
2087
  var herdrTasks = /* @__PURE__ */ new Map();
2060
2088
  var herdrAbortSignal = { aborted: false };
2061
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
+ }
2062
2122
  var shuttingDown = false;
2063
2123
  function setShuttingDown() {
2064
2124
  shuttingDown = true;
@@ -2088,8 +2148,14 @@ function isWorkerAtCapacity(workerName) {
2088
2148
  return count >= getWorkerConfig(workerName).maxConcurrentTasks;
2089
2149
  }
2090
2150
  function renderTable() {
2091
- const lines = buildTaskTableLines([...tasks.values()]);
2092
- 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
+ }
2093
2159
  console.clear();
2094
2160
  console.log(lines.join("\n"));
2095
2161
  }
@@ -2116,6 +2182,7 @@ async function finishTask(id, result, onComplete) {
2116
2182
  task.finishedAt = /* @__PURE__ */ new Date();
2117
2183
  task.agentStatus = void 0;
2118
2184
  }
2185
+ pruneTaskHistory();
2119
2186
  renderTable();
2120
2187
  }
2121
2188
  function resolveProjectName(cwd = process.cwd()) {
@@ -2159,6 +2226,7 @@ async function runViaHerdr(args, id, onComplete, cwd, env) {
2159
2226
  herdrTasks.delete(id);
2160
2227
  }
2161
2228
  function run(command, args, id, title, workerName, path2, onComplete, cwd, env) {
2229
+ tasks.delete(id);
2162
2230
  tasks.set(id, {
2163
2231
  id,
2164
2232
  title,
@@ -2180,9 +2248,12 @@ function run(command, args, id, title, workerName, path2, onComplete, cwd, env)
2180
2248
  ...env ? { env: { ...process.env, ...env } } : {}
2181
2249
  });
2182
2250
  childProcesses.set(id, child);
2251
+ const stdoutFeeder = makeLogFeeder(id, "stdout");
2252
+ const stderrFeeder = makeLogFeeder(id, "stderr");
2183
2253
  const outputChunks = [];
2184
2254
  child.stdout?.on("data", (chunk) => {
2185
2255
  outputChunks.push(chunk);
2256
+ stdoutFeeder.feed(chunk);
2186
2257
  });
2187
2258
  const stderrChunks = [];
2188
2259
  let stderrLen = 0;
@@ -2193,8 +2264,11 @@ function run(command, args, id, title, workerName, path2, onComplete, cwd, env)
2193
2264
  stderrLen -= stderrChunks[0].length;
2194
2265
  stderrChunks.shift();
2195
2266
  }
2267
+ stderrFeeder.feed(chunk);
2196
2268
  });
2197
2269
  child.on("close", async (code) => {
2270
+ stdoutFeeder.flush();
2271
+ stderrFeeder.flush();
2198
2272
  const result = buildTaskResult(
2199
2273
  code,
2200
2274
  Buffer.concat(outputChunks).toString("utf-8"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.39.2",
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",