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.
- package/README.md +2 -31
- package/dist/index.js +83 -180
- 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タスクの実行形態)
|
|
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番号>`
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
{
|
|
264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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, {
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1508
|
+
model,
|
|
1569
1509
|
"--effort",
|
|
1570
1510
|
effort
|
|
1571
1511
|
];
|
|
1572
1512
|
}
|
|
1573
1513
|
function buildClaudeExecution(invocation) {
|
|
1574
|
-
|
|
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
|
|
1584
|
-
|
|
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
|
-
|
|
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,
|
|
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, ${
|
|
2060
|
-
return
|
|
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
|
|
1983
|
+
if (raw === void 0) return DEFAULT_RUN_MODE;
|
|
2063
1984
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2064
|
-
return
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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 () => {
|