claude-task-worker 0.37.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 +80 -178
- 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,28 +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
1516
|
function buildClaudeEnv(mode) {
|
|
1584
|
-
|
|
1585
|
-
return {
|
|
1586
|
-
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS
|
|
1587
|
-
};
|
|
1588
|
-
}
|
|
1589
|
-
return { ...CLAUDE_SPAWN_ENV };
|
|
1517
|
+
return mode === "herdr" ? { CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS } : { ...CLAUDE_SPAWN_ENV };
|
|
1590
1518
|
}
|
|
1591
1519
|
|
|
1592
1520
|
// src/config.ts
|
|
@@ -1895,7 +1823,25 @@ async function assertRemoteTrackingExists(epicBranch) {
|
|
|
1895
1823
|
import { spawn } from "node:child_process";
|
|
1896
1824
|
import { basename } from "node:path";
|
|
1897
1825
|
init_table();
|
|
1898
|
-
|
|
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
|
+
}
|
|
1899
1845
|
|
|
1900
1846
|
// src/user-config.ts
|
|
1901
1847
|
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
@@ -1903,7 +1849,6 @@ import { homedir } from "node:os";
|
|
|
1903
1849
|
import { isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
|
|
1904
1850
|
var RESERVED_ALL = "all";
|
|
1905
1851
|
var DEFAULT_RUN_MODE = "default";
|
|
1906
|
-
var DEFAULT_HEADROOM = false;
|
|
1907
1852
|
var UserConfigError = class extends Error {
|
|
1908
1853
|
constructor(message) {
|
|
1909
1854
|
super(message);
|
|
@@ -1952,13 +1897,6 @@ function parseMode(raw, path) {
|
|
|
1952
1897
|
console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path}, using "${DEFAULT_RUN_MODE}"`);
|
|
1953
1898
|
return DEFAULT_RUN_MODE;
|
|
1954
1899
|
}
|
|
1955
|
-
function parseHeadroom(raw, path) {
|
|
1956
|
-
if (!("headroom" in raw)) return DEFAULT_HEADROOM;
|
|
1957
|
-
const value = raw["headroom"];
|
|
1958
|
-
if (typeof value === "boolean") return value;
|
|
1959
|
-
console.warn(`[config] invalid headroom: ${JSON.stringify(value)} in ${path}, using ${DEFAULT_HEADROOM}`);
|
|
1960
|
-
return DEFAULT_HEADROOM;
|
|
1961
|
-
}
|
|
1962
1900
|
function loadUserConfig() {
|
|
1963
1901
|
const path = getUserConfigPath();
|
|
1964
1902
|
const raw = readRawConfig();
|
|
@@ -1975,7 +1913,6 @@ function loadUserConfig() {
|
|
|
1975
1913
|
throw new UserConfigError(`config.json "projectGroups" must be an object: ${path}`);
|
|
1976
1914
|
}
|
|
1977
1915
|
const mode = parseMode(raw, path);
|
|
1978
|
-
const headroom = parseHeadroom(raw, path);
|
|
1979
1916
|
const rawProjects = raw["projects"];
|
|
1980
1917
|
const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
|
|
1981
1918
|
const projectKeys = Object.keys(rawProjects);
|
|
@@ -2026,7 +1963,7 @@ function loadUserConfig() {
|
|
|
2026
1963
|
}
|
|
2027
1964
|
projectGroups[groupName] = members;
|
|
2028
1965
|
}
|
|
2029
|
-
return { mode,
|
|
1966
|
+
return { mode, projects, projectGroups };
|
|
2030
1967
|
}
|
|
2031
1968
|
var cachedRunMode;
|
|
2032
1969
|
function getRunMode() {
|
|
@@ -2036,35 +1973,18 @@ function getRunMode() {
|
|
|
2036
1973
|
return cachedRunMode;
|
|
2037
1974
|
}
|
|
2038
1975
|
function readRunMode() {
|
|
2039
|
-
const raw = readTopLevelConfig(`using "${DEFAULT_RUN_MODE}" mode`);
|
|
2040
|
-
if (raw === void 0) return DEFAULT_RUN_MODE;
|
|
2041
|
-
return parseMode(raw, getUserConfigPath());
|
|
2042
|
-
}
|
|
2043
|
-
var cachedHeadroom;
|
|
2044
|
-
function getHeadroomEnabled() {
|
|
2045
|
-
if (cachedHeadroom === void 0) {
|
|
2046
|
-
cachedHeadroom = readHeadroom();
|
|
2047
|
-
}
|
|
2048
|
-
return cachedHeadroom;
|
|
2049
|
-
}
|
|
2050
|
-
function readHeadroom() {
|
|
2051
|
-
const raw = readTopLevelConfig(`using headroom=${DEFAULT_HEADROOM}`);
|
|
2052
|
-
if (raw === void 0) return DEFAULT_HEADROOM;
|
|
2053
|
-
return parseHeadroom(raw, getUserConfigPath());
|
|
2054
|
-
}
|
|
2055
|
-
function readTopLevelConfig(fallbackDescription) {
|
|
2056
1976
|
let raw;
|
|
2057
1977
|
try {
|
|
2058
1978
|
raw = readRawConfig();
|
|
2059
1979
|
} catch (err) {
|
|
2060
|
-
console.warn(`[config] failed to read config file, ${
|
|
2061
|
-
return
|
|
1980
|
+
console.warn(`[config] failed to read config file, using "${DEFAULT_RUN_MODE}" mode: ${err}`);
|
|
1981
|
+
return DEFAULT_RUN_MODE;
|
|
2062
1982
|
}
|
|
2063
|
-
if (raw === void 0) return
|
|
1983
|
+
if (raw === void 0) return DEFAULT_RUN_MODE;
|
|
2064
1984
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2065
|
-
return
|
|
1985
|
+
return DEFAULT_RUN_MODE;
|
|
2066
1986
|
}
|
|
2067
|
-
return raw;
|
|
1987
|
+
return parseMode(raw, getUserConfigPath());
|
|
2068
1988
|
}
|
|
2069
1989
|
function findProjectNameByPath(path) {
|
|
2070
1990
|
let config;
|
|
@@ -2182,7 +2102,7 @@ function resolveProjectName(cwd = process.cwd()) {
|
|
|
2182
2102
|
if (injected && injected.length > 0) return injected;
|
|
2183
2103
|
return findProjectNameByPath(cwd) ?? basename(cwd);
|
|
2184
2104
|
}
|
|
2185
|
-
async function runViaHerdr(
|
|
2105
|
+
async function runViaHerdr(args, id, onComplete, cwd, env) {
|
|
2186
2106
|
const { startHerdrTask: startHerdrTask2, stopHerdrTask: stopHerdrTask2, taskTabLabel: taskTabLabel2, waitForHerdrTask: waitForHerdrTask2 } = await Promise.resolve().then(() => (init_herdr_runner(), herdr_runner_exports));
|
|
2187
2107
|
const { getCurrentWorkspaceId: getCurrentWorkspaceId2 } = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
|
|
2188
2108
|
const label = taskTabLabel2(resolveProjectName(), id);
|
|
@@ -2193,14 +2113,13 @@ async function runViaHerdr(command, args, id, onComplete, cwd, env) {
|
|
|
2193
2113
|
task = await startHerdrTask2({
|
|
2194
2114
|
label,
|
|
2195
2115
|
cwd: cwd ?? process.cwd(),
|
|
2196
|
-
|
|
2116
|
+
args,
|
|
2197
2117
|
env,
|
|
2198
2118
|
workspaceId: getCurrentWorkspaceId2()
|
|
2199
2119
|
});
|
|
2200
2120
|
herdrTasks.set(id, task);
|
|
2201
2121
|
result = await waitForHerdrTask2(task.paneId, {
|
|
2202
2122
|
signal: herdrAbortSignal,
|
|
2203
|
-
headroom: getHeadroomEnabled(),
|
|
2204
2123
|
onBlocked: () => console.warn(`[worker] #${id} is blocked and waiting for input in herdr tab "${label}"`),
|
|
2205
2124
|
onStatus: (status) => {
|
|
2206
2125
|
const task2 = tasks.get(id);
|
|
@@ -2230,7 +2149,7 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
|
|
|
2230
2149
|
ensureRenderInterval();
|
|
2231
2150
|
renderTable();
|
|
2232
2151
|
if (getRunMode() === "herdr") {
|
|
2233
|
-
void runViaHerdr(
|
|
2152
|
+
void runViaHerdr(args, id, onComplete, cwd, env);
|
|
2234
2153
|
return;
|
|
2235
2154
|
}
|
|
2236
2155
|
const child = spawn(command, args, {
|
|
@@ -2258,8 +2177,7 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
|
|
|
2258
2177
|
const result = buildTaskResult(
|
|
2259
2178
|
code,
|
|
2260
2179
|
Buffer.concat(outputChunks).toString("utf-8"),
|
|
2261
|
-
Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT)
|
|
2262
|
-
{ headroom: getHeadroomEnabled() }
|
|
2180
|
+
Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT)
|
|
2263
2181
|
);
|
|
2264
2182
|
await finishTask(id, result, onComplete);
|
|
2265
2183
|
childProcesses.delete(id);
|
|
@@ -2972,8 +2890,7 @@ function createIssuePollingWorker(config) {
|
|
|
2972
2890
|
mode,
|
|
2973
2891
|
prompt: `${command} ${issue.number}`,
|
|
2974
2892
|
model,
|
|
2975
|
-
effort
|
|
2976
|
-
headroom: getHeadroomEnabled()
|
|
2893
|
+
effort
|
|
2977
2894
|
});
|
|
2978
2895
|
let baseBranch = defaultBranch;
|
|
2979
2896
|
if (parentNumber !== void 0) {
|
|
@@ -3144,8 +3061,7 @@ function createPrPollingWorker(config) {
|
|
|
3144
3061
|
mode,
|
|
3145
3062
|
prompt: `${command} ${pr.number}`,
|
|
3146
3063
|
model,
|
|
3147
|
-
effort
|
|
3148
|
-
headroom: getHeadroomEnabled()
|
|
3064
|
+
effort
|
|
3149
3065
|
});
|
|
3150
3066
|
run(
|
|
3151
3067
|
execution.command,
|
|
@@ -4046,22 +3962,8 @@ async function assertRunModeAvailable() {
|
|
|
4046
3962
|
}
|
|
4047
3963
|
console.log("[worker] run mode: herdr (each task runs as a TUI session in its own herdr tab)");
|
|
4048
3964
|
}
|
|
4049
|
-
async function assertHeadroomAvailable() {
|
|
4050
|
-
if (!getHeadroomEnabled()) return;
|
|
4051
|
-
const { execFile: execFile3 } = await import("node:child_process");
|
|
4052
|
-
const { promisify: promisify4 } = await import("node:util");
|
|
4053
|
-
try {
|
|
4054
|
-
await promisify4(execFile3)(HEADROOM_COMMAND, ["--version"]);
|
|
4055
|
-
} catch (err) {
|
|
4056
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
4057
|
-
console.error(`[worker] config.json has headroom: true but the headroom CLI is unavailable: ${message}`);
|
|
4058
|
-
process.exit(1);
|
|
4059
|
-
}
|
|
4060
|
-
console.log("[worker] headroom: enabled (each task runs as `headroom wrap claude`)");
|
|
4061
|
-
}
|
|
4062
3965
|
async function assertRunPrerequisites() {
|
|
4063
3966
|
await assertRunModeAvailable();
|
|
4064
|
-
await assertHeadroomAvailable();
|
|
4065
3967
|
}
|
|
4066
3968
|
if (!hasProjectFilter()) {
|
|
4067
3969
|
process.on("SIGTERM", async () => {
|