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.
- package/README.md +2 -31
- package/dist/index.js +89 -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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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, {
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1514
|
+
model,
|
|
1569
1515
|
"--effort",
|
|
1570
1516
|
effort
|
|
1571
1517
|
];
|
|
1572
1518
|
}
|
|
1573
1519
|
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
|
-
};
|
|
1520
|
+
return { command: CLAUDE_COMMAND, args: buildClaudeArgs(invocation) };
|
|
1582
1521
|
}
|
|
1583
|
-
function buildClaudeEnv(mode
|
|
1584
|
-
|
|
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
|
-
|
|
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,
|
|
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, ${
|
|
2060
|
-
return
|
|
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
|
|
1989
|
+
if (raw === void 0) return DEFAULT_RUN_MODE;
|
|
2063
1990
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2064
|
-
return
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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 () => {
|