claude-task-worker 0.38.0 → 0.39.1

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 +89 -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,
@@ -546,6 +523,7 @@ __export(herdr_runner_exports, {
546
523
  startHerdrTask: () => startHerdrTask,
547
524
  stopHerdrTask: () => stopHerdrTask,
548
525
  taskTabLabel: () => taskTabLabel,
526
+ toAgentName: () => toAgentName,
549
527
  waitForHerdrTask: () => waitForHerdrTask
550
528
  });
551
529
  async function loadHerdr() {
@@ -554,6 +532,11 @@ async function loadHerdr() {
554
532
  function taskTabLabel(projectName, number) {
555
533
  return `ctw:${projectName}:#${number}`;
556
534
  }
535
+ function toAgentName(label) {
536
+ const sanitized = label.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^[-_]+/, "");
537
+ const withLeadingLetter = /^[a-z]/.test(sanitized) ? sanitized : `ctw-${sanitized}`;
538
+ return withLeadingLetter.slice(0, 32).replace(/[-_]+$/, "");
539
+ }
557
540
  function createCompletionTracker() {
558
541
  return { seenWorking: false, warnedBlocked: false };
559
542
  }
@@ -578,8 +561,7 @@ function buildHerdrTaskResult(paneOutput, options) {
578
561
  if (report !== "") {
579
562
  return { status: "completed", output: report };
580
563
  }
581
- const meaningful = options?.headroom ? stripHeadroomBanner(paneOutput) : paneOutput;
582
- if (meaningful.trim() === "") {
564
+ if (paneOutput.trim() === "") {
583
565
  return {
584
566
  status: "failed",
585
567
  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 +572,7 @@ function buildHerdrTaskResult(paneOutput, options) {
590
572
  async function startHerdrTask({
591
573
  label,
592
574
  cwd,
593
- argv,
575
+ args,
594
576
  env,
595
577
  workspaceId,
596
578
  herdr,
@@ -606,16 +588,7 @@ async function startHerdrTask({
606
588
  if (!ready) {
607
589
  console.warn(`[herdr-runner] pane ${paneId} produced no prompt before the timeout, launching anyway`);
608
590
  }
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
- }
591
+ await mod.agentStart(paneId, { name: toAgentName(label), args, readyTimeoutMs: timing?.agentStartReadyTimeoutMs });
619
592
  } catch (err) {
620
593
  await mod.tabClose(tabId).catch(() => {
621
594
  });
@@ -639,24 +612,6 @@ async function waitForPaneReady(paneId, mod, options) {
639
612
  await sleep(pollIntervalMs);
640
613
  }
641
614
  }
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
615
  async function waitForHerdrTask(paneId, options) {
661
616
  const mod = options?.herdr ?? await loadHerdr();
662
617
  const pollIntervalMs = options?.pollIntervalMs ?? AGENT_POLL_INTERVAL_MS;
@@ -688,7 +643,7 @@ async function waitForHerdrTask(paneId, options) {
688
643
  if (observed.decision === "completed") {
689
644
  const output = await readPaneOutput(paneId, mod);
690
645
  const report = (options?.readReport ?? readFinalReport)(sessionId);
691
- return buildHerdrTaskResult(output, { headroom: options?.headroom, report });
646
+ return buildHerdrTaskResult(output, { report });
692
647
  }
693
648
  if (observed.decision === "blocked-first-seen") {
694
649
  options?.onBlocked?.();
@@ -746,18 +701,15 @@ async function stopHerdrTask(task, herdr, options) {
746
701
  function sleep(ms) {
747
702
  return new Promise((resolve3) => setTimeout(resolve3, ms));
748
703
  }
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;
704
+ 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
705
  var init_herdr_runner = __esm({
751
706
  "src/herdr-runner.ts"() {
752
707
  "use strict";
753
- init_task_result();
754
708
  init_transcript();
755
709
  AGENT_POLL_INTERVAL_MS = 3 * 1e3;
756
710
  PANE_OUTPUT_LINES = 300;
757
711
  PANE_READY_TIMEOUT_MS = 30 * 1e3;
758
712
  PANE_READY_POLL_INTERVAL_MS = 200;
759
- AGENT_DETECT_TIMEOUT_MS = 120 * 1e3;
760
- AGENT_DETECT_POLL_INTERVAL_MS = 500;
761
713
  CLAUDE_EXIT_TIMEOUT_MS = 15 * 1e3;
762
714
  CLAUDE_EXIT_POLL_INTERVAL_MS = 200;
763
715
  }
@@ -1548,13 +1500,7 @@ var SYSTEM_PROMPT = `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F \`claude-t
1548
1500
  - \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
1501
  - \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
1502
  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 }) {
1503
+ function buildClaudeArgs({ mode, prompt, model, effort }) {
1558
1504
  return [
1559
1505
  ...mode === "herdr" ? [] : ["-p"],
1560
1506
  prompt,
@@ -1565,27 +1511,16 @@ function buildClaudeArgs({ mode, prompt, model, effort, headroom }) {
1565
1511
  "--append-system-prompt",
1566
1512
  SYSTEM_PROMPT,
1567
1513
  "--model",
1568
- headroom ? withContext1mSuffix(model) : model,
1514
+ model,
1569
1515
  "--effort",
1570
1516
  effort
1571
1517
  ];
1572
1518
  }
1573
1519
  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
- };
1520
+ return { command: CLAUDE_COMMAND, args: buildClaudeArgs(invocation) };
1582
1521
  }
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;
1522
+ function buildClaudeEnv(mode) {
1523
+ return mode === "herdr" ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
1589
1524
  }
1590
1525
 
1591
1526
  // src/config.ts
@@ -1894,7 +1829,25 @@ async function assertRemoteTrackingExists(epicBranch) {
1894
1829
  import { spawn } from "node:child_process";
1895
1830
  import { basename } from "node:path";
1896
1831
  init_table();
1897
- init_task_result();
1832
+
1833
+ // src/task-result.ts
1834
+ var STDERR_TAIL_LIMIT = 8 * 1024;
1835
+ function buildTaskResult(code, stdout, stderrTail) {
1836
+ const emptyOutput = stdout.trim() === "";
1837
+ const completed = code === 0 && !emptyOutput;
1838
+ let output = stdout;
1839
+ if (code === 0 && emptyOutput) {
1840
+ output += "[worker] claude exited with code 0 but produced no output (session aborted before the model ran; e.g. a skill preamble command failed)";
1841
+ } else if (!completed) {
1842
+ output += `
1843
+ [worker] claude exited with code ${code}`;
1844
+ }
1845
+ if (!completed && stderrTail.trim() !== "") {
1846
+ output += `
1847
+ [stderr] ${stderrTail.trim()}`;
1848
+ }
1849
+ return { status: completed ? "completed" : "failed", output };
1850
+ }
1898
1851
 
1899
1852
  // src/user-config.ts
1900
1853
  import { readFileSync as readFileSync2, statSync } from "node:fs";
@@ -1902,7 +1855,6 @@ import { homedir } from "node:os";
1902
1855
  import { isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
1903
1856
  var RESERVED_ALL = "all";
1904
1857
  var DEFAULT_RUN_MODE = "default";
1905
- var DEFAULT_HEADROOM = false;
1906
1858
  var UserConfigError = class extends Error {
1907
1859
  constructor(message) {
1908
1860
  super(message);
@@ -1951,13 +1903,6 @@ function parseMode(raw, path) {
1951
1903
  console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path}, using "${DEFAULT_RUN_MODE}"`);
1952
1904
  return DEFAULT_RUN_MODE;
1953
1905
  }
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
1906
  function loadUserConfig() {
1962
1907
  const path = getUserConfigPath();
1963
1908
  const raw = readRawConfig();
@@ -1974,7 +1919,6 @@ function loadUserConfig() {
1974
1919
  throw new UserConfigError(`config.json "projectGroups" must be an object: ${path}`);
1975
1920
  }
1976
1921
  const mode = parseMode(raw, path);
1977
- const headroom = parseHeadroom(raw, path);
1978
1922
  const rawProjects = raw["projects"];
1979
1923
  const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
1980
1924
  const projectKeys = Object.keys(rawProjects);
@@ -2025,7 +1969,7 @@ function loadUserConfig() {
2025
1969
  }
2026
1970
  projectGroups[groupName] = members;
2027
1971
  }
2028
- return { mode, headroom, projects, projectGroups };
1972
+ return { mode, projects, projectGroups };
2029
1973
  }
2030
1974
  var cachedRunMode;
2031
1975
  function getRunMode() {
@@ -2035,35 +1979,18 @@ function getRunMode() {
2035
1979
  return cachedRunMode;
2036
1980
  }
2037
1981
  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
1982
  let raw;
2056
1983
  try {
2057
1984
  raw = readRawConfig();
2058
1985
  } catch (err) {
2059
- console.warn(`[config] failed to read config file, ${fallbackDescription}: ${err}`);
2060
- return void 0;
1986
+ console.warn(`[config] failed to read config file, using "${DEFAULT_RUN_MODE}" mode: ${err}`);
1987
+ return DEFAULT_RUN_MODE;
2061
1988
  }
2062
- if (raw === void 0) return void 0;
1989
+ if (raw === void 0) return DEFAULT_RUN_MODE;
2063
1990
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2064
- return void 0;
1991
+ return DEFAULT_RUN_MODE;
2065
1992
  }
2066
- return raw;
1993
+ return parseMode(raw, getUserConfigPath());
2067
1994
  }
2068
1995
  function findProjectNameByPath(path) {
2069
1996
  let config;
@@ -2181,7 +2108,7 @@ function resolveProjectName(cwd = process.cwd()) {
2181
2108
  if (injected && injected.length > 0) return injected;
2182
2109
  return findProjectNameByPath(cwd) ?? basename(cwd);
2183
2110
  }
2184
- async function runViaHerdr(command, args, id, onComplete, cwd, env) {
2111
+ async function runViaHerdr(args, id, onComplete, cwd, env) {
2185
2112
  const { startHerdrTask: startHerdrTask2, stopHerdrTask: stopHerdrTask2, taskTabLabel: taskTabLabel2, waitForHerdrTask: waitForHerdrTask2 } = await Promise.resolve().then(() => (init_herdr_runner(), herdr_runner_exports));
2186
2113
  const { getCurrentWorkspaceId: getCurrentWorkspaceId2 } = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
2187
2114
  const label = taskTabLabel2(resolveProjectName(), id);
@@ -2192,14 +2119,13 @@ async function runViaHerdr(command, args, id, onComplete, cwd, env) {
2192
2119
  task = await startHerdrTask2({
2193
2120
  label,
2194
2121
  cwd: cwd ?? process.cwd(),
2195
- argv: [command, ...args],
2122
+ args,
2196
2123
  env,
2197
2124
  workspaceId: getCurrentWorkspaceId2()
2198
2125
  });
2199
2126
  herdrTasks.set(id, task);
2200
2127
  result = await waitForHerdrTask2(task.paneId, {
2201
2128
  signal: herdrAbortSignal,
2202
- headroom: getHeadroomEnabled(),
2203
2129
  onBlocked: () => console.warn(`[worker] #${id} is blocked and waiting for input in herdr tab "${label}"`),
2204
2130
  onStatus: (status) => {
2205
2131
  const task2 = tasks.get(id);
@@ -2229,7 +2155,7 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
2229
2155
  ensureRenderInterval();
2230
2156
  renderTable();
2231
2157
  if (getRunMode() === "herdr") {
2232
- void runViaHerdr(command, args, id, onComplete, cwd, env);
2158
+ void runViaHerdr(args, id, onComplete, cwd, env);
2233
2159
  return;
2234
2160
  }
2235
2161
  const child = spawn(command, args, {
@@ -2257,8 +2183,7 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
2257
2183
  const result = buildTaskResult(
2258
2184
  code,
2259
2185
  Buffer.concat(outputChunks).toString("utf-8"),
2260
- Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT),
2261
- { headroom: getHeadroomEnabled() }
2186
+ Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT)
2262
2187
  );
2263
2188
  await finishTask(id, result, onComplete);
2264
2189
  childProcesses.delete(id);
@@ -2971,8 +2896,7 @@ function createIssuePollingWorker(config) {
2971
2896
  mode,
2972
2897
  prompt: `${command} ${issue.number}`,
2973
2898
  model,
2974
- effort,
2975
- headroom: getHeadroomEnabled()
2899
+ effort
2976
2900
  });
2977
2901
  let baseBranch = defaultBranch;
2978
2902
  if (parentNumber !== void 0) {
@@ -3029,7 +2953,7 @@ function createIssuePollingWorker(config) {
3029
2953
  }
3030
2954
  },
3031
2955
  cwd,
3032
- buildClaudeEnv(mode, getHeadroomEnabled())
2956
+ buildClaudeEnv(mode)
3033
2957
  );
3034
2958
  } catch (err) {
3035
2959
  console.error(`[${config.name}] setup error for #${issue.number}: ${err}`);
@@ -3143,8 +3067,7 @@ function createPrPollingWorker(config) {
3143
3067
  mode,
3144
3068
  prompt: `${command} ${pr.number}`,
3145
3069
  model,
3146
- effort,
3147
- headroom: getHeadroomEnabled()
3070
+ effort
3148
3071
  });
3149
3072
  run(
3150
3073
  execution.command,
@@ -3191,7 +3114,7 @@ function createPrPollingWorker(config) {
3191
3114
  }
3192
3115
  },
3193
3116
  cwd,
3194
- buildClaudeEnv(mode, getHeadroomEnabled())
3117
+ buildClaudeEnv(mode)
3195
3118
  );
3196
3119
  } catch (err) {
3197
3120
  console.error(`[${config.name}] setup error for PR #${pr.number}: ${err}`);
@@ -4045,22 +3968,8 @@ async function assertRunModeAvailable() {
4045
3968
  }
4046
3969
  console.log("[worker] run mode: herdr (each task runs as a TUI session in its own herdr tab)");
4047
3970
  }
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
3971
  async function assertRunPrerequisites() {
4062
3972
  await assertRunModeAvailable();
4063
- await assertHeadroomAvailable();
4064
3973
  }
4065
3974
  if (!hasProjectFilter()) {
4066
3975
  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.1",
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",