claude-task-worker 0.38.0 → 0.39.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 +2 -31
  2. package/dist/index.js +83 -180
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -138,7 +138,6 @@ CLI 本体に npm の実行時依存パッケージはない(ビルド時に e
138
138
  | [CodeGraph (`codegraph`)](https://www.npmjs.com/package/@colbymchenry/codegraph) | - | コード探索用のインデックス。MCP サーバー(`codegraph serve --mcp`)としてプラグインから起動される。`install` / `update` / `init` が面倒を見る。未インストールでもワーカーは動作する(探索がテキスト検索に落ちるだけ) |
139
139
  | [Pencil CLI (`pencil`)](https://docs.pencil.dev/for-developers/pencil-cli) | - | `.pen` デザインファイルの編集・参照・コンフリクト解消(`edit-pencil-design` / `inspect-pencil-node` / `resolve-pencil-conflict` スキルで使用) |
140
140
  | [herdr](https://herdr.dev) | - | `--project` 使用時と `mode: "herdr"` 使用時にのみ必要。複数プロジェクトへのディスパッチ・セッション監視・一括停止、タスクのTUI実行に使用 |
141
- | [Headroom (`headroom`)](https://github.com/headroom-ai/headroom) | - | `headroom: true` 使用時にのみ必要。タスクの claude をコンテキスト圧縮プロキシ経由で起動する |
142
141
  | `claude-task-worker` プラグイン | - | 各ワーカーが呼び出すスキル群(本リポジトリの `plugin/`) |
143
142
 
144
143
  Slack通知(任意)を使う場合は Slack Incoming Webhook URL が必要。Claude API使用状況の取得には、macOSでは `security` コマンド(Keychain)を使用し、それ以外の環境では `~/.claude/.credentials.json` にフォールバックする。
@@ -176,7 +175,6 @@ Slack通知(任意)を使う場合は Slack Incoming Webhook URL が必要
176
175
  - [Claude Code (`claude`)](https://docs.anthropic.com/en/docs/claude-code) がインストール済みであること
177
176
  - `claude-task-worker` プラグインがインストール済みであること(下記インストール手順を参照)
178
177
  - `--project` オプション・`mode: "herdr"` を使う場合のみ、[herdr](https://herdr.dev) がインストール済みであること
179
- - `headroom: true` を使う場合のみ、[Headroom](https://github.com/headroom-ai/headroom) がインストール済みであること
180
178
  - CodeGraph はコード探索の精度向上のために推奨(`install` / `init` が導入・インデックス構築を行う)。未導入でもワーカーは動作する
181
179
 
182
180
  詳細は上記「[必要なライブラリ](#必要なライブラリ)」を参照。
@@ -348,7 +346,7 @@ claude-task-worker yolo --epic 100 --epic 200 --label priority-high
348
346
  }
349
347
  ```
350
348
 
351
- `mode` については [`mode`(タスクの実行形態)](#modeタスクの実行形態) を、`headroom` については [`headroom`(Headroom 経由でのタスク実行)](#headroomheadroom-経由でのタスク実行) を参照。
349
+ `mode` については [`mode`(タスクの実行形態)](#modeタスクの実行形態) を参照。
352
350
 
353
351
  `--project` は繰り返し指定可能で、複数指定した場合は解決後のプロジェクト集合の和集合が対象になる(重複は一意化される)。`--epic` / `--label` と併用でき、ディスパッチ先の各プロジェクトで実行されるコマンドにそのまま引き継がれる。
354
352
 
@@ -388,7 +386,7 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
388
386
 
389
387
  `mode: "herdr"` のときの1タスクの流れ:
390
388
 
391
- 1. worktree を作成し、`ctw:<プロジェクト名>:#<Issue/PR番号>` ラベルのタブで claude を TUI 起動する(タブを先に作ってからその中で起動するため、ユーザーが見ているタブにペインが割り込むことはない)
389
+ 1. worktree を作成し、`ctw:<プロジェクト名>:#<Issue/PR番号>` ラベルのタブを作ってから、そのルートペインで `herdr agent start --kind claude` を使って claude を TUI 起動する(タブを先に作ってからその中で起動するため、ユーザーが見ているタブにペインが割り込むことはない)。`agent start` は claude が検出され入力待ちになるまで同期的にブロックするため、起動確認の別ポーリングは不要
392
390
  2. herdr が持つ agent ステータスを監視し、`done`(未確認完了)または `working` → `idle` の遷移をタスク完了とみなす。ワーカーのタスクタブは誰も開かないため、完了したタスクは通常 `done` として観測される
393
391
  3. 完了したら claude のセッション transcript(`~/.claude/projects/*/<sessionId>.jsonl`)から最終レポートを回収して通知に使い、タブを閉じてラベル・worktree を後片付けする。transcript を引けない場合のみペインの内容にフォールバックする
394
392
 
@@ -407,33 +405,6 @@ claude-task-worker exec-issue --project app-a --epic 100 --label priority-high
407
405
 
408
406
  適用は `herdr server reload-config`。この設定は herdr サーバー全体に効くため、ワーカー以外の対話セッションの完了音も鳴らなくなる(`[ui.sound.agents] claude = "off"` でも実質同じ範囲)。ワーカーだけを無音にしたい場合は、`HERDR_DISABLE_SOUND=1 herdr --session <name>` で別セッションを起動し、その中でディスパッチャーを動かす
409
407
 
410
- ### `headroom`(Headroom 経由でのタスク実行)
411
-
412
- `config.json` のトップレベルに `"headroom": true` を書くと、ワーカーは各タスクの claude を [Headroom](https://github.com/headroom-ai/headroom) 経由(`headroom wrap claude`)で起動する。Headroom がローカルプロキシを立ててコンテキストを圧縮し、`ANTHROPIC_BASE_URL` を差し替えた状態で claude を起動する。既定は `false`(`claude` を直接起動する)。
413
-
414
- `mode` と同じくトップレベル一括の設定で、プロジェクト単位・ワーカー単位の指定はできない。
415
-
416
- ```json
417
- {
418
- "headroom": true,
419
- "projects": { "app-a": "/Users/me/repos/app-a" }
420
- }
421
- ```
422
-
423
- | `headroom` | 実行されるコマンド |
424
- |-----------|------------------|
425
- | `false`(既定) | `claude <引数>` |
426
- | `true` | `headroom wrap claude --1m --memory --no-tokensave --no-serena -- <引数>` |
427
-
428
- 補足:
429
-
430
- - `mode` とは独立して組み合わせられる(`mode: "herdr"` の TUI 起動も `headroom wrap claude` になる)
431
- - claude へ渡す引数はすべて `--` の後ろに置かれる。`headroom wrap claude` 自身も `--port` / `--memory` などのオプションを持ち、`-p` のように衝突しうるフラグは `--` の後ろでないと claude に届かないため。逆に headroom 自身へのオプションは `--` の前に置く
432
- - 1M コンテキストウィンドウは `--model` の値へ `[1m]` サフィックスを付けることで解放する(`--model 'sonnet[1m]'` のようになる)。headroom の `--1m` は `ANTHROPIC_MODEL` をセットするだけの実装で、CLI の `--model` が環境変数に勝つため、`--model` を明示するワーカー起動では単体では効かない
433
- - `--no-tokensave` / `--no-serena` で headroom が既定で登録するコードグラフ系 MCP を止める。コード探索はプラグインの codegraph MCP が担うため機能面の損失はなく、タスク起動のたびに走る再登録・再インデックスと、ユーザーのグローバル設定(`~/.claude.json`)の書き換えを避けられる
434
- - `headroom: true` で headroom コマンドが PATH に無い場合、ワーカーは起動時にエラー終了する(`mode: "herdr"` と同じく、サイレントに直接起動へフォールバックはしない)
435
- - `headroom wrap claude` は claude を起動する前に起動バナー(枠線・`ANTHROPIC_BASE_URL=...` など)を stdout へ出力し、これを抑止するオプションは無い。ワーカーは空振りセッション検知(exit 0 かつ無出力を失敗とみなす判定)の前にこのバナーを取り除くため、検知は `headroom: true` でも従来どおり機能する。ただし Slack 通知の本文にはバナーがそのまま含まれる
436
-
437
408
  ### exec-issue
438
409
 
439
410
  `cc-exec-issue` ラベルが付いた自分にアサインされたIssueを定期取得し、Claude Codeで処理を実行する。(デフォルト1分間隔)
package/dist/index.js CHANGED
@@ -132,38 +132,6 @@ var init_table = __esm({
132
132
  }
133
133
  });
134
134
 
135
- // src/task-result.ts
136
- function stripHeadroomBanner(stdout) {
137
- const lines = stdout.split("\n");
138
- let i = 0;
139
- while (i < lines.length && (lines[i].trim() === "" || lines[i].startsWith(" "))) i++;
140
- return lines.slice(i).join("\n");
141
- }
142
- function buildTaskResult(code, stdout, stderrTail, options) {
143
- const meaningful = options?.headroom ? stripHeadroomBanner(stdout) : stdout;
144
- const emptyOutput = meaningful.trim() === "";
145
- const completed = code === 0 && !emptyOutput;
146
- let output = stdout;
147
- if (code === 0 && emptyOutput) {
148
- output += "[worker] claude exited with code 0 but produced no output (session aborted before the model ran; e.g. a skill preamble command failed)";
149
- } else if (!completed) {
150
- output += `
151
- [worker] claude exited with code ${code}`;
152
- }
153
- if (!completed && stderrTail.trim() !== "") {
154
- output += `
155
- [stderr] ${stderrTail.trim()}`;
156
- }
157
- return { status: completed ? "completed" : "failed", output };
158
- }
159
- var STDERR_TAIL_LIMIT;
160
- var init_task_result = __esm({
161
- "src/task-result.ts"() {
162
- "use strict";
163
- STDERR_TAIL_LIMIT = 8 * 1024;
164
- }
165
- });
166
-
167
135
  // src/transcript.ts
168
136
  import { existsSync, readFileSync as readFileSync3, readdirSync } from "node:fs";
169
137
  import { homedir as homedir2 } from "node:os";
@@ -230,20 +198,21 @@ var init_transcript = __esm({
230
198
  // src/herdr.ts
231
199
  var herdr_exports = {};
232
200
  __export(herdr_exports, {
201
+ AGENT_KIND: () => AGENT_KIND,
202
+ AGENT_START_READY_TIMEOUT_MS: () => AGENT_START_READY_TIMEOUT_MS,
233
203
  HERDR_TIMEOUT_MS: () => HERDR_TIMEOUT_MS,
234
204
  HerdrError: () => HerdrError,
235
205
  HerdrUnavailableError: () => HerdrUnavailableError,
236
206
  agentGet: () => agentGet,
207
+ agentStart: () => agentStart,
237
208
  checkHerdrAvailable: () => checkHerdrAvailable,
238
209
  getCurrentWorkspaceId: () => getCurrentWorkspaceId,
239
- launchAgentInPane: () => launchAgentInPane,
240
210
  paneClose: () => paneClose,
241
211
  paneGet: () => paneGet,
242
212
  paneProcessInfo: () => paneProcessInfo,
243
213
  paneRead: () => paneRead,
244
214
  paneSendKeys: () => paneSendKeys,
245
215
  paneSendText: () => paneSendText,
246
- shellQuoteArgv: () => shellQuoteArgv,
247
216
  tabClose: () => tabClose,
248
217
  tabCreate: () => tabCreate,
249
218
  tabList: () => tabList,
@@ -255,18 +224,13 @@ __export(herdr_exports, {
255
224
  });
256
225
  import { createRequire as createRequire2 } from "node:module";
257
226
  import { resolve as resolvePath } from "node:path";
258
- function runHerdr(args) {
227
+ function runHerdr(args, timeoutMs = HERDR_TIMEOUT_MS) {
259
228
  return new Promise((resolve3) => {
260
- childProcess2.execFile(
261
- "herdr",
262
- args,
263
- { timeout: HERDR_TIMEOUT_MS, killSignal: "SIGKILL" },
264
- (error, stdout, stderr) => {
265
- const parsed = parseHerdrResponse(stdout);
266
- const stderrError = parsed?.error ? void 0 : parseHerdrResponse(stderr)?.error;
267
- resolve3({ execError: error, parsed, stderrError, stdout, stderr });
268
- }
269
- );
229
+ childProcess2.execFile("herdr", args, { timeout: timeoutMs, killSignal: "SIGKILL" }, (error, stdout, stderr) => {
230
+ const parsed = parseHerdrResponse(stdout);
231
+ const stderrError = parsed?.error ? void 0 : parseHerdrResponse(stderr)?.error;
232
+ resolve3({ execError: error, parsed, stderrError, stdout, stderr });
233
+ });
270
234
  });
271
235
  }
272
236
  function parseHerdrResponse(output) {
@@ -279,7 +243,7 @@ function parseHerdrResponse(output) {
279
243
  }
280
244
  }
281
245
  async function execHerdr(args, options) {
282
- const { execError, parsed, stderrError, stdout, stderr } = await runHerdr(args);
246
+ const { execError, parsed, stderrError, stdout, stderr } = await runHerdr(args, options?.timeoutMs);
283
247
  const stderrSuffix = stderr.trim() ? `: ${stderr.trim()}` : "";
284
248
  const errorPayload = parsed?.error ?? stderrError;
285
249
  if (errorPayload) {
@@ -396,32 +360,44 @@ async function workspaceClose(workspaceId) {
396
360
  async function workspaceFocus(workspaceId) {
397
361
  await execHerdr(["workspace", "focus", workspaceId]);
398
362
  }
399
- function shellQuoteArgv(argv) {
400
- return argv.map((arg) => `'${arg.replace(/'/g, `'\\''`)}'`).join(" ");
401
- }
402
- async function launchAgentInPane(paneId, argv) {
403
- await paneSendText(paneId, shellQuoteArgv(argv));
404
- await paneSendKeys(paneId, "enter");
405
- }
406
363
  function toAgentStatus(value) {
407
364
  return value === "working" || value === "idle" || value === "blocked" || value === "done" ? value : "unknown";
408
365
  }
409
- async function agentGet(target) {
410
- const result = await execHerdr(["agent", "get", target]);
411
- const agent = result?.agent;
412
- if (!agent?.pane_id) {
413
- throw new Error(`Failed to get agent info for ${target}: invalid response structure from herdr`);
414
- }
366
+ function toAgentInfo(agent) {
415
367
  const session = agent.agent_session;
416
368
  const sessionId = session?.kind === "id" && typeof session.value === "string" ? session.value : void 0;
417
369
  return {
418
- paneId: agent.pane_id,
370
+ paneId: agent.pane_id ?? "",
419
371
  tabId: agent.tab_id ?? "",
420
372
  workspaceId: agent.workspace_id ?? "",
421
373
  agentStatus: toAgentStatus(agent.agent_status),
422
374
  sessionId
423
375
  };
424
376
  }
377
+ async function agentStart(paneId, {
378
+ name,
379
+ args,
380
+ kind = AGENT_KIND,
381
+ readyTimeoutMs = AGENT_START_READY_TIMEOUT_MS
382
+ }) {
383
+ const result = await execHerdr(
384
+ ["agent", "start", name, "--kind", kind, "--pane", paneId, "--timeout", String(readyTimeoutMs), "--", ...args],
385
+ { timeoutMs: readyTimeoutMs + AGENT_START_EXEC_BUFFER_MS }
386
+ );
387
+ const agent = result?.agent;
388
+ if (!agent?.pane_id) {
389
+ throw new Error(`Failed to start agent in pane ${paneId}: invalid response structure from herdr`);
390
+ }
391
+ return toAgentInfo(agent);
392
+ }
393
+ async function agentGet(target) {
394
+ const result = await execHerdr(["agent", "get", target]);
395
+ const agent = result?.agent;
396
+ if (!agent?.pane_id) {
397
+ throw new Error(`Failed to get agent info for ${target}: invalid response structure from herdr`);
398
+ }
399
+ return toAgentInfo(agent);
400
+ }
425
401
  function getCurrentWorkspaceId() {
426
402
  const id = process.env.HERDR_WORKSPACE_ID;
427
403
  return id && id.length > 0 ? id : void 0;
@@ -503,7 +479,7 @@ async function checkHerdrAvailable() {
503
479
  );
504
480
  }
505
481
  }
506
- var childProcess2, HerdrUnavailableError, HerdrError, HERDR_TIMEOUT_MS, PANE_READ_ERROR_ALLOWED_KEYS;
482
+ var childProcess2, HerdrUnavailableError, HerdrError, HERDR_TIMEOUT_MS, AGENT_KIND, AGENT_START_READY_TIMEOUT_MS, AGENT_START_EXEC_BUFFER_MS, PANE_READ_ERROR_ALLOWED_KEYS;
507
483
  var init_herdr = __esm({
508
484
  "src/herdr.ts"() {
509
485
  "use strict";
@@ -525,6 +501,9 @@ var init_herdr = __esm({
525
501
  }
526
502
  };
527
503
  HERDR_TIMEOUT_MS = 15 * 1e3;
504
+ AGENT_KIND = "claude";
505
+ AGENT_START_READY_TIMEOUT_MS = 120 * 1e3;
506
+ AGENT_START_EXEC_BUFFER_MS = 15 * 1e3;
528
507
  PANE_READ_ERROR_ALLOWED_KEYS = /* @__PURE__ */ new Set(["code", "message"]);
529
508
  }
530
509
  });
@@ -532,8 +511,6 @@ var init_herdr = __esm({
532
511
  // src/herdr-runner.ts
533
512
  var herdr_runner_exports = {};
534
513
  __export(herdr_runner_exports, {
535
- AGENT_DETECT_POLL_INTERVAL_MS: () => AGENT_DETECT_POLL_INTERVAL_MS,
536
- AGENT_DETECT_TIMEOUT_MS: () => AGENT_DETECT_TIMEOUT_MS,
537
514
  AGENT_POLL_INTERVAL_MS: () => AGENT_POLL_INTERVAL_MS,
538
515
  CLAUDE_EXIT_POLL_INTERVAL_MS: () => CLAUDE_EXIT_POLL_INTERVAL_MS,
539
516
  CLAUDE_EXIT_TIMEOUT_MS: () => CLAUDE_EXIT_TIMEOUT_MS,
@@ -578,8 +555,7 @@ function buildHerdrTaskResult(paneOutput, options) {
578
555
  if (report !== "") {
579
556
  return { status: "completed", output: report };
580
557
  }
581
- const meaningful = options?.headroom ? stripHeadroomBanner(paneOutput) : paneOutput;
582
- if (meaningful.trim() === "") {
558
+ if (paneOutput.trim() === "") {
583
559
  return {
584
560
  status: "failed",
585
561
  output: "[worker] the claude session became idle but its pane produced no output (session aborted before the model ran; e.g. a skill preamble command failed)"
@@ -590,7 +566,7 @@ function buildHerdrTaskResult(paneOutput, options) {
590
566
  async function startHerdrTask({
591
567
  label,
592
568
  cwd,
593
- argv,
569
+ args,
594
570
  env,
595
571
  workspaceId,
596
572
  herdr,
@@ -606,16 +582,7 @@ async function startHerdrTask({
606
582
  if (!ready) {
607
583
  console.warn(`[herdr-runner] pane ${paneId} produced no prompt before the timeout, launching anyway`);
608
584
  }
609
- await mod.launchAgentInPane(paneId, argv);
610
- const detected = await waitForAgentDetected(paneId, mod, {
611
- timeoutMs: timing?.agentDetectTimeoutMs,
612
- pollIntervalMs: timing?.agentDetectPollIntervalMs
613
- });
614
- if (!detected) {
615
- throw new Error(
616
- `claude was not detected in pane ${paneId} after launch (it may have failed to start; e.g. a skill preamble command failed)`
617
- );
618
- }
585
+ await mod.agentStart(paneId, { name: label, args, readyTimeoutMs: timing?.agentStartReadyTimeoutMs });
619
586
  } catch (err) {
620
587
  await mod.tabClose(tabId).catch(() => {
621
588
  });
@@ -639,24 +606,6 @@ async function waitForPaneReady(paneId, mod, options) {
639
606
  await sleep(pollIntervalMs);
640
607
  }
641
608
  }
642
- async function waitForAgentDetected(paneId, mod, options) {
643
- const timeoutMs = options?.timeoutMs ?? AGENT_DETECT_TIMEOUT_MS;
644
- const pollIntervalMs = options?.pollIntervalMs ?? AGENT_DETECT_POLL_INTERVAL_MS;
645
- const deadline = Date.now() + timeoutMs;
646
- for (; ; ) {
647
- try {
648
- await mod.agentGet(paneId);
649
- return true;
650
- } catch (err) {
651
- if (err instanceof mod.HerdrError && err.code === "pane_not_found") return false;
652
- if (!(err instanceof mod.HerdrError && err.code === "agent_not_found")) {
653
- console.error(`[herdr-runner] unexpected error while waiting for agent detection on pane ${paneId}: ${err}`);
654
- }
655
- }
656
- if (Date.now() >= deadline) return false;
657
- await sleep(pollIntervalMs);
658
- }
659
- }
660
609
  async function waitForHerdrTask(paneId, options) {
661
610
  const mod = options?.herdr ?? await loadHerdr();
662
611
  const pollIntervalMs = options?.pollIntervalMs ?? AGENT_POLL_INTERVAL_MS;
@@ -688,7 +637,7 @@ async function waitForHerdrTask(paneId, options) {
688
637
  if (observed.decision === "completed") {
689
638
  const output = await readPaneOutput(paneId, mod);
690
639
  const report = (options?.readReport ?? readFinalReport)(sessionId);
691
- return buildHerdrTaskResult(output, { headroom: options?.headroom, report });
640
+ return buildHerdrTaskResult(output, { report });
692
641
  }
693
642
  if (observed.decision === "blocked-first-seen") {
694
643
  options?.onBlocked?.();
@@ -746,18 +695,15 @@ async function stopHerdrTask(task, herdr, options) {
746
695
  function sleep(ms) {
747
696
  return new Promise((resolve3) => setTimeout(resolve3, ms));
748
697
  }
749
- var AGENT_POLL_INTERVAL_MS, PANE_OUTPUT_LINES, PANE_READY_TIMEOUT_MS, PANE_READY_POLL_INTERVAL_MS, AGENT_DETECT_TIMEOUT_MS, AGENT_DETECT_POLL_INTERVAL_MS, CLAUDE_EXIT_TIMEOUT_MS, CLAUDE_EXIT_POLL_INTERVAL_MS;
698
+ var AGENT_POLL_INTERVAL_MS, PANE_OUTPUT_LINES, PANE_READY_TIMEOUT_MS, PANE_READY_POLL_INTERVAL_MS, CLAUDE_EXIT_TIMEOUT_MS, CLAUDE_EXIT_POLL_INTERVAL_MS;
750
699
  var init_herdr_runner = __esm({
751
700
  "src/herdr-runner.ts"() {
752
701
  "use strict";
753
- init_task_result();
754
702
  init_transcript();
755
703
  AGENT_POLL_INTERVAL_MS = 3 * 1e3;
756
704
  PANE_OUTPUT_LINES = 300;
757
705
  PANE_READY_TIMEOUT_MS = 30 * 1e3;
758
706
  PANE_READY_POLL_INTERVAL_MS = 200;
759
- AGENT_DETECT_TIMEOUT_MS = 120 * 1e3;
760
- AGENT_DETECT_POLL_INTERVAL_MS = 500;
761
707
  CLAUDE_EXIT_TIMEOUT_MS = 15 * 1e3;
762
708
  CLAUDE_EXIT_POLL_INTERVAL_MS = 200;
763
709
  }
@@ -1548,13 +1494,7 @@ var SYSTEM_PROMPT = `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F \`claude-t
1548
1494
  - \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
1549
1495
  - \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`;
1550
1496
  var CLAUDE_COMMAND = "claude";
1551
- var HEADROOM_COMMAND = "headroom";
1552
- var HEADROOM_WRAP_OPTIONS = ["--1m", "--memory", "--no-tokensave", "--no-serena"];
1553
- var CONTEXT_1M_SUFFIX = "[1m]";
1554
- function withContext1mSuffix(model) {
1555
- return model.endsWith(CONTEXT_1M_SUFFIX) ? model : `${model}${CONTEXT_1M_SUFFIX}`;
1556
- }
1557
- function buildClaudeArgs({ mode, prompt, model, effort, headroom }) {
1497
+ function buildClaudeArgs({ mode, prompt, model, effort }) {
1558
1498
  return [
1559
1499
  ...mode === "herdr" ? [] : ["-p"],
1560
1500
  prompt,
@@ -1565,27 +1505,16 @@ function buildClaudeArgs({ mode, prompt, model, effort, headroom }) {
1565
1505
  "--append-system-prompt",
1566
1506
  SYSTEM_PROMPT,
1567
1507
  "--model",
1568
- headroom ? withContext1mSuffix(model) : model,
1508
+ model,
1569
1509
  "--effort",
1570
1510
  effort
1571
1511
  ];
1572
1512
  }
1573
1513
  function buildClaudeExecution(invocation) {
1574
- const args = buildClaudeArgs(invocation);
1575
- if (!invocation.headroom) {
1576
- return { command: CLAUDE_COMMAND, args };
1577
- }
1578
- return {
1579
- command: HEADROOM_COMMAND,
1580
- args: ["wrap", CLAUDE_COMMAND, ...HEADROOM_WRAP_OPTIONS, "--", ...args]
1581
- };
1514
+ return { command: CLAUDE_COMMAND, args: buildClaudeArgs(invocation) };
1582
1515
  }
1583
- function buildClaudeEnv(mode, headroom) {
1584
- const env = mode === "herdr" ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
1585
- if (headroom) {
1586
- env.HEADROOM_LOSSLESS = "1";
1587
- }
1588
- return env;
1516
+ function buildClaudeEnv(mode) {
1517
+ return mode === "herdr" ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
1589
1518
  }
1590
1519
 
1591
1520
  // src/config.ts
@@ -1894,7 +1823,25 @@ async function assertRemoteTrackingExists(epicBranch) {
1894
1823
  import { spawn } from "node:child_process";
1895
1824
  import { basename } from "node:path";
1896
1825
  init_table();
1897
- init_task_result();
1826
+
1827
+ // src/task-result.ts
1828
+ var STDERR_TAIL_LIMIT = 8 * 1024;
1829
+ function buildTaskResult(code, stdout, stderrTail) {
1830
+ const emptyOutput = stdout.trim() === "";
1831
+ const completed = code === 0 && !emptyOutput;
1832
+ let output = stdout;
1833
+ if (code === 0 && emptyOutput) {
1834
+ output += "[worker] claude exited with code 0 but produced no output (session aborted before the model ran; e.g. a skill preamble command failed)";
1835
+ } else if (!completed) {
1836
+ output += `
1837
+ [worker] claude exited with code ${code}`;
1838
+ }
1839
+ if (!completed && stderrTail.trim() !== "") {
1840
+ output += `
1841
+ [stderr] ${stderrTail.trim()}`;
1842
+ }
1843
+ return { status: completed ? "completed" : "failed", output };
1844
+ }
1898
1845
 
1899
1846
  // src/user-config.ts
1900
1847
  import { readFileSync as readFileSync2, statSync } from "node:fs";
@@ -1902,7 +1849,6 @@ import { homedir } from "node:os";
1902
1849
  import { isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
1903
1850
  var RESERVED_ALL = "all";
1904
1851
  var DEFAULT_RUN_MODE = "default";
1905
- var DEFAULT_HEADROOM = false;
1906
1852
  var UserConfigError = class extends Error {
1907
1853
  constructor(message) {
1908
1854
  super(message);
@@ -1951,13 +1897,6 @@ function parseMode(raw, path) {
1951
1897
  console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path}, using "${DEFAULT_RUN_MODE}"`);
1952
1898
  return DEFAULT_RUN_MODE;
1953
1899
  }
1954
- function parseHeadroom(raw, path) {
1955
- if (!("headroom" in raw)) return DEFAULT_HEADROOM;
1956
- const value = raw["headroom"];
1957
- if (typeof value === "boolean") return value;
1958
- console.warn(`[config] invalid headroom: ${JSON.stringify(value)} in ${path}, using ${DEFAULT_HEADROOM}`);
1959
- return DEFAULT_HEADROOM;
1960
- }
1961
1900
  function loadUserConfig() {
1962
1901
  const path = getUserConfigPath();
1963
1902
  const raw = readRawConfig();
@@ -1974,7 +1913,6 @@ function loadUserConfig() {
1974
1913
  throw new UserConfigError(`config.json "projectGroups" must be an object: ${path}`);
1975
1914
  }
1976
1915
  const mode = parseMode(raw, path);
1977
- const headroom = parseHeadroom(raw, path);
1978
1916
  const rawProjects = raw["projects"];
1979
1917
  const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
1980
1918
  const projectKeys = Object.keys(rawProjects);
@@ -2025,7 +1963,7 @@ function loadUserConfig() {
2025
1963
  }
2026
1964
  projectGroups[groupName] = members;
2027
1965
  }
2028
- return { mode, headroom, projects, projectGroups };
1966
+ return { mode, projects, projectGroups };
2029
1967
  }
2030
1968
  var cachedRunMode;
2031
1969
  function getRunMode() {
@@ -2035,35 +1973,18 @@ function getRunMode() {
2035
1973
  return cachedRunMode;
2036
1974
  }
2037
1975
  function readRunMode() {
2038
- const raw = readTopLevelConfig(`using "${DEFAULT_RUN_MODE}" mode`);
2039
- if (raw === void 0) return DEFAULT_RUN_MODE;
2040
- return parseMode(raw, getUserConfigPath());
2041
- }
2042
- var cachedHeadroom;
2043
- function getHeadroomEnabled() {
2044
- if (cachedHeadroom === void 0) {
2045
- cachedHeadroom = readHeadroom();
2046
- }
2047
- return cachedHeadroom;
2048
- }
2049
- function readHeadroom() {
2050
- const raw = readTopLevelConfig(`using headroom=${DEFAULT_HEADROOM}`);
2051
- if (raw === void 0) return DEFAULT_HEADROOM;
2052
- return parseHeadroom(raw, getUserConfigPath());
2053
- }
2054
- function readTopLevelConfig(fallbackDescription) {
2055
1976
  let raw;
2056
1977
  try {
2057
1978
  raw = readRawConfig();
2058
1979
  } catch (err) {
2059
- console.warn(`[config] failed to read config file, ${fallbackDescription}: ${err}`);
2060
- return void 0;
1980
+ console.warn(`[config] failed to read config file, using "${DEFAULT_RUN_MODE}" mode: ${err}`);
1981
+ return DEFAULT_RUN_MODE;
2061
1982
  }
2062
- if (raw === void 0) return void 0;
1983
+ if (raw === void 0) return DEFAULT_RUN_MODE;
2063
1984
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2064
- return void 0;
1985
+ return DEFAULT_RUN_MODE;
2065
1986
  }
2066
- return raw;
1987
+ return parseMode(raw, getUserConfigPath());
2067
1988
  }
2068
1989
  function findProjectNameByPath(path) {
2069
1990
  let config;
@@ -2181,7 +2102,7 @@ function resolveProjectName(cwd = process.cwd()) {
2181
2102
  if (injected && injected.length > 0) return injected;
2182
2103
  return findProjectNameByPath(cwd) ?? basename(cwd);
2183
2104
  }
2184
- async function runViaHerdr(command, args, id, onComplete, cwd, env) {
2105
+ async function runViaHerdr(args, id, onComplete, cwd, env) {
2185
2106
  const { startHerdrTask: startHerdrTask2, stopHerdrTask: stopHerdrTask2, taskTabLabel: taskTabLabel2, waitForHerdrTask: waitForHerdrTask2 } = await Promise.resolve().then(() => (init_herdr_runner(), herdr_runner_exports));
2186
2107
  const { getCurrentWorkspaceId: getCurrentWorkspaceId2 } = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
2187
2108
  const label = taskTabLabel2(resolveProjectName(), id);
@@ -2192,14 +2113,13 @@ async function runViaHerdr(command, args, id, onComplete, cwd, env) {
2192
2113
  task = await startHerdrTask2({
2193
2114
  label,
2194
2115
  cwd: cwd ?? process.cwd(),
2195
- argv: [command, ...args],
2116
+ args,
2196
2117
  env,
2197
2118
  workspaceId: getCurrentWorkspaceId2()
2198
2119
  });
2199
2120
  herdrTasks.set(id, task);
2200
2121
  result = await waitForHerdrTask2(task.paneId, {
2201
2122
  signal: herdrAbortSignal,
2202
- headroom: getHeadroomEnabled(),
2203
2123
  onBlocked: () => console.warn(`[worker] #${id} is blocked and waiting for input in herdr tab "${label}"`),
2204
2124
  onStatus: (status) => {
2205
2125
  const task2 = tasks.get(id);
@@ -2229,7 +2149,7 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
2229
2149
  ensureRenderInterval();
2230
2150
  renderTable();
2231
2151
  if (getRunMode() === "herdr") {
2232
- void runViaHerdr(command, args, id, onComplete, cwd, env);
2152
+ void runViaHerdr(args, id, onComplete, cwd, env);
2233
2153
  return;
2234
2154
  }
2235
2155
  const child = spawn(command, args, {
@@ -2257,8 +2177,7 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
2257
2177
  const result = buildTaskResult(
2258
2178
  code,
2259
2179
  Buffer.concat(outputChunks).toString("utf-8"),
2260
- Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT),
2261
- { headroom: getHeadroomEnabled() }
2180
+ Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT)
2262
2181
  );
2263
2182
  await finishTask(id, result, onComplete);
2264
2183
  childProcesses.delete(id);
@@ -2971,8 +2890,7 @@ function createIssuePollingWorker(config) {
2971
2890
  mode,
2972
2891
  prompt: `${command} ${issue.number}`,
2973
2892
  model,
2974
- effort,
2975
- headroom: getHeadroomEnabled()
2893
+ effort
2976
2894
  });
2977
2895
  let baseBranch = defaultBranch;
2978
2896
  if (parentNumber !== void 0) {
@@ -3029,7 +2947,7 @@ function createIssuePollingWorker(config) {
3029
2947
  }
3030
2948
  },
3031
2949
  cwd,
3032
- buildClaudeEnv(mode, getHeadroomEnabled())
2950
+ buildClaudeEnv(mode)
3033
2951
  );
3034
2952
  } catch (err) {
3035
2953
  console.error(`[${config.name}] setup error for #${issue.number}: ${err}`);
@@ -3143,8 +3061,7 @@ function createPrPollingWorker(config) {
3143
3061
  mode,
3144
3062
  prompt: `${command} ${pr.number}`,
3145
3063
  model,
3146
- effort,
3147
- headroom: getHeadroomEnabled()
3064
+ effort
3148
3065
  });
3149
3066
  run(
3150
3067
  execution.command,
@@ -3191,7 +3108,7 @@ function createPrPollingWorker(config) {
3191
3108
  }
3192
3109
  },
3193
3110
  cwd,
3194
- buildClaudeEnv(mode, getHeadroomEnabled())
3111
+ buildClaudeEnv(mode)
3195
3112
  );
3196
3113
  } catch (err) {
3197
3114
  console.error(`[${config.name}] setup error for PR #${pr.number}: ${err}`);
@@ -4045,22 +3962,8 @@ async function assertRunModeAvailable() {
4045
3962
  }
4046
3963
  console.log("[worker] run mode: herdr (each task runs as a TUI session in its own herdr tab)");
4047
3964
  }
4048
- async function assertHeadroomAvailable() {
4049
- if (!getHeadroomEnabled()) return;
4050
- const { execFile: execFile3 } = await import("node:child_process");
4051
- const { promisify: promisify4 } = await import("node:util");
4052
- try {
4053
- await promisify4(execFile3)(HEADROOM_COMMAND, ["--version"]);
4054
- } catch (err) {
4055
- const message = err instanceof Error ? err.message : String(err);
4056
- console.error(`[worker] config.json has headroom: true but the headroom CLI is unavailable: ${message}`);
4057
- process.exit(1);
4058
- }
4059
- console.log("[worker] headroom: enabled (each task runs as `headroom wrap claude`)");
4060
- }
4061
3965
  async function assertRunPrerequisites() {
4062
3966
  await assertRunModeAvailable();
4063
- await assertHeadroomAvailable();
4064
3967
  }
4065
3968
  if (!hasProjectFilter()) {
4066
3969
  process.on("SIGTERM", async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.38.0",
3
+ "version": "0.39.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",