claude-task-worker 0.39.2 → 0.41.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 (3) hide show
  1. package/README.md +40 -20
  2. package/dist/index.js +201 -67
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -335,6 +335,7 @@ claude-task-worker yolo --epic 100 --epic 200 --label priority-high
335
335
  ```json
336
336
  {
337
337
  "mode": "default",
338
+ "advisor": false,
338
339
  "projects": {
339
340
  "app-a": "/Users/me/repos/app-a",
340
341
  "app-b": "/Users/me/repos/app-b",
@@ -346,7 +347,7 @@ claude-task-worker yolo --epic 100 --epic 200 --label priority-high
346
347
  }
347
348
  ```
348
349
 
349
- `mode` については [`mode`(タスクの実行形態)](#modeタスクの実行形態) を参照。
350
+ `mode` については [`mode`(タスクの実行形態)](#modeタスクの実行形態)、`advisor` については [`advisor`(アドバイザーモデル)](#advisorアドバイザーモデル) を参照。
350
351
 
351
352
  `--project` は繰り返し指定可能で、複数指定した場合は解決後のプロジェクト集合の和集合が対象になる(重複は一意化される)。`--epic` / `--label` と併用でき、ディスパッチ先の各プロジェクトで実行されるコマンドにそのまま引き継がれる。
352
353
 
@@ -405,6 +406,24 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
405
406
 
406
407
  適用は `herdr server reload-config`。この設定は herdr サーバー全体に効くため、ワーカー以外の対話セッションの完了音も鳴らなくなる(`[ui.sound.agents] claude = "off"` でも実質同じ範囲)。ワーカーだけを無音にしたい場合は、`HERDR_DISABLE_SOUND=1 herdr --session <name>` で別セッションを起動し、その中でディスパッチャーを動かす
407
408
 
409
+ ### `advisor`(アドバイザーモデル)
410
+
411
+ `config.json` のトップレベルに `advisor: true` を書くと、ワーカーがタスクを起動する際、Claude CLI へ `--advisor <model>` を渡すようになる。渡すモデルはリポジトリ直下の `claude-task-worker.json` の `workers.<ワーカー名>.advisorModel`([ワーカーごとの設定](#ワーカーごとの設定))で指定する。`mode` と同じくトップレベル一括で、プロジェクト単位・ワーカー単位のオン/オフはできない。
412
+
413
+ | `advisor` | 挙動 |
414
+ |---|---|
415
+ | `false`(既定) | `advisorModel` の指定に関わらず `--advisor` を渡さない |
416
+ | `true` | 各ワーカーの `advisorModel`(未指定時はワーカー既定値)を `--advisor` に渡す。空文字のワーカーには渡さない |
417
+
418
+ ```json
419
+ {
420
+ "advisor": true,
421
+ "projects": { "app-a": "/Users/me/repos/app-a" }
422
+ }
423
+ ```
424
+
425
+ advisor は main モデル以上の能力を持つモデルである必要がある(Claude CLI 側の制約)。そのため `advisorModel` の既定値は、`model` が `sonnet` のワーカーは `opus`、`model` が `opus` のワーカー(`answer-issue-questions`)は空文字(=advisor なし)にしてある。
426
+
408
427
  ### exec-issue
409
428
 
410
429
  `cc-exec-issue` ラベルが付いた自分にアサインされたIssueを定期取得し、Claude Codeで処理を実行する。(デフォルト1分間隔)
@@ -562,26 +581,26 @@ claude-task-worker -v
562
581
  |---|---|---|---|
563
582
  | `fixReviewPointCallbackCommentMessage` | string | - | fix-review-point 完了時にPRへ投稿するコメント(未設定の場合は投稿しない) |
564
583
  | `uiDesign` | object | `{ "enabled": false, "designDir": "designs" }` | UIデザイン先行ワークフローの設定(詳細は「[UIデザイン先行ワークフロー](#uiデザイン先行ワークフロー)」) |
565
- | `workers` | object | `{}` | ワーカーごとに Claude CLI に渡すスキル、`--model` / `--effort`、ポーリング間隔、クールダウン時間、最大同時実行数を上書きする設定(詳細は下記) |
584
+ | `workers` | object | `{}` | ワーカーごとに Claude CLI に渡すスキル、`--model` / `--advisor` / `--effort`、ポーリング間隔、クールダウン時間、最大同時実行数を上書きする設定(詳細は下記) |
566
585
 
567
586
  ### ワーカーごとの設定
568
587
 
569
- `workers` キーにワーカー名ごとの設定オブジェクトを指定することで、Claude CLI の `-p` に渡すスキル(スラッシュコマンド)、`--model` / `--effort`、ポーリング間隔、タスク完了後のクールダウン時間、最大同時実行数を個別に上書きできる。未指定のワーカー・フィールドは下記のワーカー別デフォルト値が使用される。
570
-
571
- | ワーカー名 | デフォルト `skill` | デフォルト `model` | デフォルト `effort` | デフォルト `pollingIntervalSeconds` | デフォルト `cooldownSeconds` | デフォルト `maxConcurrentTasks` |
572
- |---|---|---|---|---|---|---|
573
- | `answer-issue-questions` | `/claude-task-worker:answer-issue-questions` | `opus` | `xhigh` | 60 | 0 | 1 |
574
- | `create-issue` | `/claude-task-worker:create-issue-from-issue-number` | `sonnet` | `xhigh` | 60 | 0 | 1 |
575
- | `update-issue` | `/claude-task-worker:update-issue` | `sonnet` | `high` | 60 | 0 | 1 |
576
- | `exec-issue` | `/claude-task-worker:exec-issue` | `sonnet` | `high` | 60 | 0 | 1 |
577
- | `fix-review-point` | `/claude-task-worker:fix-review-point` | `sonnet` | `high` | 60 | 0 | 1 |
578
- | `triage-created-issue` | `/claude-task-worker:triage-created-issue` | `sonnet` | `high` | 60 | 0 | 1 |
579
- | `triage-pr` | `/claude-task-worker:triage-pr` | `sonnet` | `high` | 60 | 0 | 1 |
580
- | `resolve-conflict` | `/claude-task-worker:resolve-pr-conflict` | `sonnet` | `high` | 60 | 0 | 1 |
581
- | `check-dependabot` | `/claude-task-worker:check-dependabot` | `sonnet` | `high` | 3600 | 0 | 1 |
582
- | `epic-issue` | `/claude-task-worker:create-epic-pr` | `sonnet` | `high` | 300 | 0 | 1 |
583
- | `create-ui-design` | `/claude-task-worker:create-ui-design` | `sonnet` | `high` | 60 | 0 | 1 |
584
- | `apply-ui-design` | `/claude-task-worker:apply-ui-design` | `sonnet` | `high` | 60 | 0 | 1 |
588
+ `workers` キーにワーカー名ごとの設定オブジェクトを指定することで、Claude CLI の `-p` に渡すスキル(スラッシュコマンド)、`--model` / `--advisor` / `--effort`、ポーリング間隔、タスク完了後のクールダウン時間、最大同時実行数を個別に上書きできる。未指定のワーカー・フィールドは下記のワーカー別デフォルト値が使用される。
589
+
590
+ | ワーカー名 | デフォルト `skill` | デフォルト `model` | デフォルト `advisorModel` | デフォルト `effort` | デフォルト `pollingIntervalSeconds` | デフォルト `cooldownSeconds` | デフォルト `maxConcurrentTasks` |
591
+ |---|---|---|---|---|---|---|---|
592
+ | `answer-issue-questions` | `/claude-task-worker:answer-issue-questions` | `opus` | (なし) | `xhigh` | 60 | 0 | 1 |
593
+ | `create-issue` | `/claude-task-worker:create-issue-from-issue-number` | `sonnet` | `opus` | `xhigh` | 60 | 0 | 1 |
594
+ | `update-issue` | `/claude-task-worker:update-issue` | `sonnet` | `opus` | `high` | 60 | 0 | 1 |
595
+ | `exec-issue` | `/claude-task-worker:exec-issue` | `sonnet` | `opus` | `high` | 60 | 0 | 1 |
596
+ | `fix-review-point` | `/claude-task-worker:fix-review-point` | `sonnet` | `opus` | `high` | 60 | 0 | 1 |
597
+ | `triage-created-issue` | `/claude-task-worker:triage-created-issue` | `sonnet` | `opus` | `high` | 60 | 0 | 1 |
598
+ | `triage-pr` | `/claude-task-worker:triage-pr` | `sonnet` | `opus` | `high` | 60 | 0 | 1 |
599
+ | `resolve-conflict` | `/claude-task-worker:resolve-pr-conflict` | `sonnet` | `opus` | `high` | 60 | 0 | 1 |
600
+ | `check-dependabot` | `/claude-task-worker:check-dependabot` | `sonnet` | `opus` | `high` | 3600 | 0 | 1 |
601
+ | `epic-issue` | `/claude-task-worker:create-epic-pr` | `sonnet` | `opus` | `high` | 300 | 0 | 1 |
602
+ | `create-ui-design` | `/claude-task-worker:create-ui-design` | `sonnet` | `opus` | `high` | 60 | 0 | 1 |
603
+ | `apply-ui-design` | `/claude-task-worker:apply-ui-design` | `sonnet` | `opus` | `high` | 60 | 0 | 1 |
585
604
 
586
605
  各フィールドの値:
587
606
 
@@ -589,6 +608,7 @@ claude-task-worker -v
589
608
  |---|---|---|
590
609
  | `skill` | string | Claude CLI の `-p` に渡すスラッシュコマンド(例: `/claude-task-worker:exec-issue`, `/my-plugin:my-skill`)。ワーカーは `"<skill> <issue-or-pr-number>"` の形で Claude を起動する |
591
610
  | `model` | string | Claude CLI の `--model` に渡す値(例: `sonnet`, `opus`, `haiku`) |
611
+ | `advisorModel` | string | Claude CLI の `--advisor` に渡す値(例: `opus`)。空文字を指定するとそのワーカーでは advisor を使わない。`config.json` の `advisor` が `true` のときだけ参照される(詳細は「[`advisor`(アドバイザーモデル)](#advisorアドバイザーモデル)」) |
592
612
  | `effort` | string | Claude CLI の `--effort` に渡す値(例: `high`, `medium`, `low`) |
593
613
  | `pollingIntervalSeconds` | number | GitHub をポーリングする間隔(秒)。正の数を指定する |
594
614
  | `cooldownSeconds` | number | タスク完了後に次のポーリングを停止する時間(秒)。`0` でクールダウンなし |
@@ -599,8 +619,8 @@ claude-task-worker -v
599
619
  ```json
600
620
  {
601
621
  "workers": {
602
- "exec-issue": { "skill": "/my-plugin:exec-issue", "model": "opus", "effort": "high", "pollingIntervalSeconds": 60, "cooldownSeconds": 600, "maxConcurrentTasks": 3 },
603
- "fix-review-point": { "skill": "/claude-task-worker:fix-review-point", "model": "sonnet", "effort": "high", "maxConcurrentTasks": 2 },
622
+ "exec-issue": { "skill": "/my-plugin:exec-issue", "model": "opus", "advisorModel": "", "effort": "high", "pollingIntervalSeconds": 60, "cooldownSeconds": 600, "maxConcurrentTasks": 3 },
623
+ "fix-review-point": { "skill": "/claude-task-worker:fix-review-point", "model": "sonnet", "advisorModel": "opus", "effort": "high", "maxConcurrentTasks": 2 },
604
624
  "triage-pr": { "effort": "medium", "pollingIntervalSeconds": 120 },
605
625
  "check-dependabot": { "model": "haiku", "pollingIntervalSeconds": 7200 }
606
626
  }
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];
128
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]);
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
 
@@ -1515,7 +1542,8 @@ function systemPromptFilePath() {
1515
1542
  cachedSystemPromptFilePath = target;
1516
1543
  return target;
1517
1544
  }
1518
- function buildClaudeArgs({ mode, prompt, model, effort }) {
1545
+ function buildClaudeArgs({ mode, prompt, model, effort, advisorModel }) {
1546
+ const advisor = advisorModel?.trim() ?? "";
1519
1547
  return [
1520
1548
  ...mode === "herdr" ? [] : ["-p"],
1521
1549
  prompt,
@@ -1528,7 +1556,10 @@ function buildClaudeArgs({ mode, prompt, model, effort }) {
1528
1556
  "--model",
1529
1557
  model,
1530
1558
  "--effort",
1531
- effort
1559
+ effort,
1560
+ // advisor 未指定(空文字)ならフラグごと省く。値なしの `--advisor` を渡すと
1561
+ // 後続フラグを値として食われるため、必ずモデル名とセットでのみ付ける。
1562
+ ...advisor === "" ? [] : ["--advisor", advisor]
1532
1563
  ];
1533
1564
  }
1534
1565
  function buildClaudeExecution(invocation) {
@@ -1548,6 +1579,7 @@ var DEFAULT_UI_DESIGN_CONFIG = {
1548
1579
  var DEFAULT_WORKER_CONFIG = {
1549
1580
  skill: "",
1550
1581
  model: "sonnet",
1582
+ advisorModel: "opus",
1551
1583
  effort: "high",
1552
1584
  pollingIntervalSeconds: 60,
1553
1585
  cooldownSeconds: 0,
@@ -1557,6 +1589,7 @@ var WORKER_DEFAULTS = {
1557
1589
  "answer-issue-questions": {
1558
1590
  skill: "/claude-task-worker:answer-issue-questions",
1559
1591
  model: "opus",
1592
+ advisorModel: "",
1560
1593
  effort: "xhigh",
1561
1594
  pollingIntervalSeconds: 60,
1562
1595
  cooldownSeconds: 0,
@@ -1565,6 +1598,7 @@ var WORKER_DEFAULTS = {
1565
1598
  "create-issue": {
1566
1599
  skill: "/claude-task-worker:create-issue-from-issue-number",
1567
1600
  model: "sonnet",
1601
+ advisorModel: "opus",
1568
1602
  effort: "xhigh",
1569
1603
  pollingIntervalSeconds: 60,
1570
1604
  cooldownSeconds: 0,
@@ -1573,6 +1607,7 @@ var WORKER_DEFAULTS = {
1573
1607
  "update-issue": {
1574
1608
  skill: "/claude-task-worker:update-issue",
1575
1609
  model: "sonnet",
1610
+ advisorModel: "opus",
1576
1611
  effort: "high",
1577
1612
  pollingIntervalSeconds: 60,
1578
1613
  cooldownSeconds: 0,
@@ -1581,6 +1616,7 @@ var WORKER_DEFAULTS = {
1581
1616
  "exec-issue": {
1582
1617
  skill: "/claude-task-worker:exec-issue",
1583
1618
  model: "sonnet",
1619
+ advisorModel: "opus",
1584
1620
  effort: "high",
1585
1621
  pollingIntervalSeconds: 60,
1586
1622
  cooldownSeconds: 0,
@@ -1589,6 +1625,7 @@ var WORKER_DEFAULTS = {
1589
1625
  "fix-review-point": {
1590
1626
  skill: "/claude-task-worker:fix-review-point",
1591
1627
  model: "sonnet",
1628
+ advisorModel: "opus",
1592
1629
  effort: "high",
1593
1630
  pollingIntervalSeconds: 60,
1594
1631
  cooldownSeconds: 0,
@@ -1597,6 +1634,7 @@ var WORKER_DEFAULTS = {
1597
1634
  "triage-created-issue": {
1598
1635
  skill: "/claude-task-worker:triage-created-issue",
1599
1636
  model: "sonnet",
1637
+ advisorModel: "opus",
1600
1638
  effort: "high",
1601
1639
  pollingIntervalSeconds: 60,
1602
1640
  cooldownSeconds: 0,
@@ -1605,6 +1643,7 @@ var WORKER_DEFAULTS = {
1605
1643
  "triage-pr": {
1606
1644
  skill: "/claude-task-worker:triage-pr",
1607
1645
  model: "sonnet",
1646
+ advisorModel: "opus",
1608
1647
  effort: "high",
1609
1648
  pollingIntervalSeconds: 60,
1610
1649
  cooldownSeconds: 0,
@@ -1613,6 +1652,7 @@ var WORKER_DEFAULTS = {
1613
1652
  "resolve-conflict": {
1614
1653
  skill: "/claude-task-worker:resolve-pr-conflict",
1615
1654
  model: "sonnet",
1655
+ advisorModel: "opus",
1616
1656
  effort: "high",
1617
1657
  pollingIntervalSeconds: 60,
1618
1658
  cooldownSeconds: 0,
@@ -1621,6 +1661,7 @@ var WORKER_DEFAULTS = {
1621
1661
  "check-dependabot": {
1622
1662
  skill: "/claude-task-worker:check-dependabot",
1623
1663
  model: "sonnet",
1664
+ advisorModel: "opus",
1624
1665
  effort: "high",
1625
1666
  pollingIntervalSeconds: 3600,
1626
1667
  cooldownSeconds: 0,
@@ -1629,6 +1670,7 @@ var WORKER_DEFAULTS = {
1629
1670
  "epic-issue": {
1630
1671
  skill: "/claude-task-worker:create-epic-pr",
1631
1672
  model: "sonnet",
1673
+ advisorModel: "opus",
1632
1674
  effort: "high",
1633
1675
  pollingIntervalSeconds: 300,
1634
1676
  cooldownSeconds: 0,
@@ -1637,6 +1679,7 @@ var WORKER_DEFAULTS = {
1637
1679
  "create-ui-design": {
1638
1680
  skill: "/claude-task-worker:create-ui-design",
1639
1681
  model: "sonnet",
1682
+ advisorModel: "opus",
1640
1683
  effort: "high",
1641
1684
  pollingIntervalSeconds: 60,
1642
1685
  cooldownSeconds: 0,
@@ -1645,6 +1688,7 @@ var WORKER_DEFAULTS = {
1645
1688
  "apply-ui-design": {
1646
1689
  skill: "/claude-task-worker:apply-ui-design",
1647
1690
  model: "sonnet",
1691
+ advisorModel: "opus",
1648
1692
  effort: "high",
1649
1693
  pollingIntervalSeconds: 300,
1650
1694
  cooldownSeconds: 0,
@@ -1682,6 +1726,15 @@ function parseWorkerEntry(name, val) {
1682
1726
  console.warn(`[config] invalid workers.${name}.model: ${String(entry.model)}, using default ${base.model}`);
1683
1727
  }
1684
1728
  }
1729
+ if ("advisorModel" in entry) {
1730
+ if (typeof entry.advisorModel === "string") {
1731
+ result.advisorModel = entry.advisorModel;
1732
+ } else {
1733
+ console.warn(
1734
+ `[config] invalid workers.${name}.advisorModel: ${String(entry.advisorModel)}, using default ${JSON.stringify(base.advisorModel)}`
1735
+ );
1736
+ }
1737
+ }
1685
1738
  if ("effort" in entry) {
1686
1739
  if (typeof entry.effort === "string" && entry.effort.length > 0) {
1687
1740
  result.effort = entry.effort;
@@ -1843,6 +1896,7 @@ async function assertRemoteTrackingExists(epicBranch) {
1843
1896
  // src/process-manager.ts
1844
1897
  import { spawn } from "node:child_process";
1845
1898
  import { basename } from "node:path";
1899
+ import { StringDecoder } from "node:string_decoder";
1846
1900
  init_table();
1847
1901
 
1848
1902
  // src/task-result.ts
@@ -1870,6 +1924,7 @@ import { homedir } from "node:os";
1870
1924
  import { isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
1871
1925
  var RESERVED_ALL = "all";
1872
1926
  var DEFAULT_RUN_MODE = "default";
1927
+ var DEFAULT_ADVISOR_ENABLED = false;
1873
1928
  var UserConfigError = class extends Error {
1874
1929
  constructor(message) {
1875
1930
  super(message);
@@ -1918,6 +1973,13 @@ function parseMode(raw, path2) {
1918
1973
  console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path2}, using "${DEFAULT_RUN_MODE}"`);
1919
1974
  return DEFAULT_RUN_MODE;
1920
1975
  }
1976
+ function parseAdvisor(raw, path2) {
1977
+ if (!("advisor" in raw)) return DEFAULT_ADVISOR_ENABLED;
1978
+ const value = raw["advisor"];
1979
+ if (typeof value === "boolean") return value;
1980
+ console.warn(`[config] invalid advisor: ${JSON.stringify(value)} in ${path2}, using ${DEFAULT_ADVISOR_ENABLED}`);
1981
+ return DEFAULT_ADVISOR_ENABLED;
1982
+ }
1921
1983
  function loadUserConfig() {
1922
1984
  const path2 = getUserConfigPath();
1923
1985
  const raw = readRawConfig();
@@ -1934,6 +1996,7 @@ function loadUserConfig() {
1934
1996
  throw new UserConfigError(`config.json "projectGroups" must be an object: ${path2}`);
1935
1997
  }
1936
1998
  const mode = parseMode(raw, path2);
1999
+ const advisor = parseAdvisor(raw, path2);
1937
2000
  const rawProjects = raw["projects"];
1938
2001
  const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
1939
2002
  const projectKeys = Object.keys(rawProjects);
@@ -1984,7 +2047,7 @@ function loadUserConfig() {
1984
2047
  }
1985
2048
  projectGroups[groupName] = members;
1986
2049
  }
1987
- return { mode, projects, projectGroups };
2050
+ return { mode, advisor, projects, projectGroups };
1988
2051
  }
1989
2052
  var cachedRunMode;
1990
2053
  function getRunMode() {
@@ -1993,6 +2056,27 @@ function getRunMode() {
1993
2056
  }
1994
2057
  return cachedRunMode;
1995
2058
  }
2059
+ var cachedAdvisorEnabled;
2060
+ function isAdvisorEnabled() {
2061
+ if (cachedAdvisorEnabled === void 0) {
2062
+ cachedAdvisorEnabled = readAdvisorEnabled();
2063
+ }
2064
+ return cachedAdvisorEnabled;
2065
+ }
2066
+ function readAdvisorEnabled() {
2067
+ let raw;
2068
+ try {
2069
+ raw = readRawConfig();
2070
+ } catch (err) {
2071
+ console.warn(`[config] failed to read config file, advisor disabled: ${err}`);
2072
+ return DEFAULT_ADVISOR_ENABLED;
2073
+ }
2074
+ if (raw === void 0) return DEFAULT_ADVISOR_ENABLED;
2075
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2076
+ return DEFAULT_ADVISOR_ENABLED;
2077
+ }
2078
+ return parseAdvisor(raw, getUserConfigPath());
2079
+ }
1996
2080
  function readRunMode() {
1997
2081
  let raw;
1998
2082
  try {
@@ -2059,6 +2143,38 @@ var childProcesses = /* @__PURE__ */ new Map();
2059
2143
  var herdrTasks = /* @__PURE__ */ new Map();
2060
2144
  var herdrAbortSignal = { aborted: false };
2061
2145
  var tasks = /* @__PURE__ */ new Map();
2146
+ var logLines = [];
2147
+ function pushLogLine(id, stream, text) {
2148
+ const trimmed = text.replace(/\r$/, "");
2149
+ if (trimmed.trim().length === 0) return;
2150
+ logLines.push({ id, stream, text: trimmed, time: /* @__PURE__ */ new Date() });
2151
+ if (logLines.length > LOG_DISPLAY_LIMIT) {
2152
+ logLines.splice(0, logLines.length - LOG_DISPLAY_LIMIT);
2153
+ }
2154
+ }
2155
+ function makeLogFeeder(id, stream) {
2156
+ let partial = "";
2157
+ const decoder = new StringDecoder("utf-8");
2158
+ return {
2159
+ feed(chunk) {
2160
+ partial += decoder.write(chunk);
2161
+ const parts = partial.split("\n");
2162
+ partial = parts.pop() ?? "";
2163
+ for (const part of parts) pushLogLine(id, stream, part);
2164
+ },
2165
+ flush() {
2166
+ partial += decoder.end();
2167
+ if (partial.length > 0) {
2168
+ pushLogLine(id, stream, partial);
2169
+ partial = "";
2170
+ }
2171
+ }
2172
+ };
2173
+ }
2174
+ function pruneTaskHistory() {
2175
+ const finished = [...tasks.values()].filter((t) => t.status !== "running").sort((a, b) => (b.finishedAt ?? b.startedAt).getTime() - (a.finishedAt ?? a.startedAt).getTime());
2176
+ for (const t of finished.slice(TASK_DISPLAY_LIMIT)) tasks.delete(t.id);
2177
+ }
2062
2178
  var shuttingDown = false;
2063
2179
  function setShuttingDown() {
2064
2180
  shuttingDown = true;
@@ -2088,8 +2204,14 @@ function isWorkerAtCapacity(workerName) {
2088
2204
  return count >= getWorkerConfig(workerName).maxConcurrentTasks;
2089
2205
  }
2090
2206
  function renderTable() {
2091
- const lines = buildTaskTableLines([...tasks.values()]);
2092
- if (lines.length === 0) return;
2207
+ const taskLines = buildTaskTableLines(selectRecentTasks([...tasks.values()]));
2208
+ const logTableLines = buildLogTableLines(logLines);
2209
+ if (taskLines.length === 0 && logTableLines.length === 0) return;
2210
+ const lines = [...taskLines];
2211
+ if (logTableLines.length > 0) {
2212
+ if (lines.length > 0) lines.push("");
2213
+ lines.push("Logs", ...logTableLines);
2214
+ }
2093
2215
  console.clear();
2094
2216
  console.log(lines.join("\n"));
2095
2217
  }
@@ -2116,6 +2238,7 @@ async function finishTask(id, result, onComplete) {
2116
2238
  task.finishedAt = /* @__PURE__ */ new Date();
2117
2239
  task.agentStatus = void 0;
2118
2240
  }
2241
+ pruneTaskHistory();
2119
2242
  renderTable();
2120
2243
  }
2121
2244
  function resolveProjectName(cwd = process.cwd()) {
@@ -2159,6 +2282,7 @@ async function runViaHerdr(args, id, onComplete, cwd, env) {
2159
2282
  herdrTasks.delete(id);
2160
2283
  }
2161
2284
  function run(command, args, id, title, workerName, path2, onComplete, cwd, env) {
2285
+ tasks.delete(id);
2162
2286
  tasks.set(id, {
2163
2287
  id,
2164
2288
  title,
@@ -2180,9 +2304,12 @@ function run(command, args, id, title, workerName, path2, onComplete, cwd, env)
2180
2304
  ...env ? { env: { ...process.env, ...env } } : {}
2181
2305
  });
2182
2306
  childProcesses.set(id, child);
2307
+ const stdoutFeeder = makeLogFeeder(id, "stdout");
2308
+ const stderrFeeder = makeLogFeeder(id, "stderr");
2183
2309
  const outputChunks = [];
2184
2310
  child.stdout?.on("data", (chunk) => {
2185
2311
  outputChunks.push(chunk);
2312
+ stdoutFeeder.feed(chunk);
2186
2313
  });
2187
2314
  const stderrChunks = [];
2188
2315
  let stderrLen = 0;
@@ -2193,8 +2320,11 @@ function run(command, args, id, title, workerName, path2, onComplete, cwd, env)
2193
2320
  stderrLen -= stderrChunks[0].length;
2194
2321
  stderrChunks.shift();
2195
2322
  }
2323
+ stderrFeeder.feed(chunk);
2196
2324
  });
2197
2325
  child.on("close", async (code) => {
2326
+ stdoutFeeder.flush();
2327
+ stderrFeeder.flush();
2198
2328
  const result = buildTaskResult(
2199
2329
  code,
2200
2330
  Buffer.concat(outputChunks).toString("utf-8"),
@@ -2903,7 +3033,7 @@ function createIssuePollingWorker(config) {
2903
3033
  try {
2904
3034
  const issueUrl = `https://github.com/${owner}/${name}/issues/${issue.number}`;
2905
3035
  syncDefaultBranch(defaultBranch);
2906
- const { model, effort, skill } = getWorkerConfig(config.name);
3036
+ const { model, effort, skill, advisorModel } = getWorkerConfig(config.name);
2907
3037
  const command = skill || config.command;
2908
3038
  const parentNumber = issue.parent?.number;
2909
3039
  const mode = getRunMode();
@@ -2911,7 +3041,9 @@ function createIssuePollingWorker(config) {
2911
3041
  mode,
2912
3042
  prompt: `${command} ${issue.number}`,
2913
3043
  model,
2914
- effort
3044
+ effort,
3045
+ // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3046
+ advisorModel: isAdvisorEnabled() ? advisorModel : ""
2915
3047
  });
2916
3048
  let baseBranch = defaultBranch;
2917
3049
  if (parentNumber !== void 0) {
@@ -3075,14 +3207,16 @@ function createPrPollingWorker(config) {
3075
3207
  syncDefaultBranch(defaultBranch);
3076
3208
  await createWorktreeFromBranch(worktreeId, defaultBranch);
3077
3209
  const cwd = getWorktreePath(worktreeId);
3078
- const { model, effort, skill } = getWorkerConfig(config.name);
3210
+ const { model, effort, skill, advisorModel } = getWorkerConfig(config.name);
3079
3211
  const command = skill || config.command;
3080
3212
  const mode = getRunMode();
3081
3213
  const execution = buildClaudeExecution({
3082
3214
  mode,
3083
3215
  prompt: `${command} ${pr.number}`,
3084
3216
  model,
3085
- effort
3217
+ effort,
3218
+ // config.json の advisor が false なら advisorModel の指定に関わらず渡さない。
3219
+ advisorModel: isAdvisorEnabled() ? advisorModel : ""
3086
3220
  });
3087
3221
  run(
3088
3222
  execution.command,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.39.2",
3
+ "version": "0.41.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",