claude-task-worker 0.26.1 → 0.27.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 +39 -8
- package/dist/index.js +720 -307
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,41 +59,57 @@ __export(herdr_exports, {
|
|
|
59
59
|
HERDR_TIMEOUT_MS: () => HERDR_TIMEOUT_MS,
|
|
60
60
|
HerdrError: () => HerdrError,
|
|
61
61
|
HerdrUnavailableError: () => HerdrUnavailableError,
|
|
62
|
+
agentGet: () => agentGet,
|
|
63
|
+
agentStart: () => agentStart,
|
|
62
64
|
checkHerdrAvailable: () => checkHerdrAvailable,
|
|
65
|
+
getCurrentWorkspaceId: () => getCurrentWorkspaceId,
|
|
66
|
+
paneClose: () => paneClose,
|
|
67
|
+
paneGet: () => paneGet,
|
|
63
68
|
paneProcessInfo: () => paneProcessInfo,
|
|
64
69
|
paneRead: () => paneRead,
|
|
65
70
|
paneSendKeys: () => paneSendKeys,
|
|
66
71
|
paneSendText: () => paneSendText,
|
|
67
72
|
tabClose: () => tabClose,
|
|
68
73
|
tabCreate: () => tabCreate,
|
|
69
|
-
tabList: () => tabList
|
|
74
|
+
tabList: () => tabList,
|
|
75
|
+
tabRename: () => tabRename,
|
|
76
|
+
workspaceClose: () => workspaceClose,
|
|
77
|
+
workspaceCreate: () => workspaceCreate,
|
|
78
|
+
workspaceList: () => workspaceList
|
|
70
79
|
});
|
|
71
80
|
import { createRequire as createRequire2 } from "node:module";
|
|
81
|
+
import { resolve as resolvePath } from "node:path";
|
|
72
82
|
function runHerdr(args) {
|
|
73
|
-
return new Promise((
|
|
83
|
+
return new Promise((resolve3) => {
|
|
74
84
|
childProcess2.execFile(
|
|
75
85
|
"herdr",
|
|
76
86
|
args,
|
|
77
87
|
{ timeout: HERDR_TIMEOUT_MS, killSignal: "SIGKILL" },
|
|
78
88
|
(error, stdout, stderr) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} catch {
|
|
83
|
-
parsed = void 0;
|
|
84
|
-
}
|
|
85
|
-
resolve2({ execError: error, parsed, stdout, stderr });
|
|
89
|
+
const parsed = parseHerdrResponse(stdout);
|
|
90
|
+
const stderrError = parsed?.error ? void 0 : parseHerdrResponse(stderr)?.error;
|
|
91
|
+
resolve3({ execError: error, parsed, stderrError, stdout, stderr });
|
|
86
92
|
}
|
|
87
93
|
);
|
|
88
94
|
});
|
|
89
95
|
}
|
|
96
|
+
function parseHerdrResponse(output) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(output);
|
|
99
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
100
|
+
return parsed;
|
|
101
|
+
} catch {
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
90
105
|
async function execHerdr(args, options) {
|
|
91
|
-
const { execError, parsed, stdout, stderr } = await runHerdr(args);
|
|
106
|
+
const { execError, parsed, stderrError, stdout, stderr } = await runHerdr(args);
|
|
92
107
|
const stderrSuffix = stderr.trim() ? `: ${stderr.trim()}` : "";
|
|
93
|
-
|
|
108
|
+
const errorPayload = parsed?.error ?? stderrError;
|
|
109
|
+
if (errorPayload) {
|
|
94
110
|
throw new HerdrError(
|
|
95
|
-
`herdr ${args.join(" ")} failed: [${
|
|
96
|
-
|
|
111
|
+
`herdr ${args.join(" ")} failed: [${errorPayload.code}] ${errorPayload.message}`,
|
|
112
|
+
errorPayload.code
|
|
97
113
|
);
|
|
98
114
|
}
|
|
99
115
|
if (execError) {
|
|
@@ -107,8 +123,29 @@ async function execHerdr(args, options) {
|
|
|
107
123
|
}
|
|
108
124
|
return parsed.result;
|
|
109
125
|
}
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
function envArgs(env) {
|
|
127
|
+
if (!env) return [];
|
|
128
|
+
return Object.entries(env).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
|
|
129
|
+
}
|
|
130
|
+
function cwdArgs(cwd) {
|
|
131
|
+
return ["--cwd", resolvePath(cwd)];
|
|
132
|
+
}
|
|
133
|
+
async function tabCreate({
|
|
134
|
+
label,
|
|
135
|
+
cwd,
|
|
136
|
+
workspaceId,
|
|
137
|
+
env
|
|
138
|
+
}) {
|
|
139
|
+
const result = await execHerdr([
|
|
140
|
+
"tab",
|
|
141
|
+
"create",
|
|
142
|
+
...workspaceId ? ["--workspace", workspaceId] : [],
|
|
143
|
+
"--label",
|
|
144
|
+
label,
|
|
145
|
+
...cwdArgs(cwd),
|
|
146
|
+
...envArgs(env),
|
|
147
|
+
"--no-focus"
|
|
148
|
+
]);
|
|
112
149
|
const paneId = result?.root_pane?.pane_id;
|
|
113
150
|
const tabId = result?.tab?.tab_id;
|
|
114
151
|
if (!paneId || !tabId) {
|
|
@@ -116,6 +153,20 @@ async function tabCreate({ label, cwd }) {
|
|
|
116
153
|
}
|
|
117
154
|
return { paneId, tabId };
|
|
118
155
|
}
|
|
156
|
+
async function tabRename(tabId, label) {
|
|
157
|
+
await execHerdr(["tab", "rename", tabId, label]);
|
|
158
|
+
}
|
|
159
|
+
async function paneClose(paneId) {
|
|
160
|
+
await execHerdr(["pane", "close", paneId]);
|
|
161
|
+
}
|
|
162
|
+
async function paneGet(paneId) {
|
|
163
|
+
const result = await execHerdr(["pane", "get", paneId]);
|
|
164
|
+
const pane = result?.pane;
|
|
165
|
+
if (!pane?.pane_id) {
|
|
166
|
+
throw new Error(`Failed to get pane ${paneId}: invalid response structure from herdr`);
|
|
167
|
+
}
|
|
168
|
+
return { paneId: pane.pane_id, tabId: pane.tab_id ?? "" };
|
|
169
|
+
}
|
|
119
170
|
async function tabClose(tabId) {
|
|
120
171
|
await execHerdr(["tab", "close", tabId]);
|
|
121
172
|
}
|
|
@@ -130,6 +181,88 @@ async function tabList() {
|
|
|
130
181
|
workspaceId: tab.workspace_id
|
|
131
182
|
}));
|
|
132
183
|
}
|
|
184
|
+
async function workspaceCreate({
|
|
185
|
+
label,
|
|
186
|
+
cwd,
|
|
187
|
+
env
|
|
188
|
+
}) {
|
|
189
|
+
const result = await execHerdr([
|
|
190
|
+
"workspace",
|
|
191
|
+
"create",
|
|
192
|
+
"--label",
|
|
193
|
+
label,
|
|
194
|
+
...cwdArgs(cwd),
|
|
195
|
+
...envArgs(env),
|
|
196
|
+
"--no-focus"
|
|
197
|
+
]);
|
|
198
|
+
const workspaceId = result?.workspace?.workspace_id;
|
|
199
|
+
const paneId = result?.root_pane?.pane_id;
|
|
200
|
+
const tabId = result?.root_pane?.tab_id;
|
|
201
|
+
if (!workspaceId || !paneId || !tabId) {
|
|
202
|
+
throw new Error("Failed to create workspace: invalid response structure from herdr");
|
|
203
|
+
}
|
|
204
|
+
return { workspaceId, paneId, tabId };
|
|
205
|
+
}
|
|
206
|
+
async function workspaceList() {
|
|
207
|
+
const result = await execHerdr(["workspace", "list"]);
|
|
208
|
+
if (!result || !Array.isArray(result.workspaces)) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
return result.workspaces.map((workspace) => ({
|
|
212
|
+
workspaceId: workspace.workspace_id,
|
|
213
|
+
label: workspace.label ?? ""
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
async function workspaceClose(workspaceId) {
|
|
217
|
+
await execHerdr(["workspace", "close", workspaceId]);
|
|
218
|
+
}
|
|
219
|
+
async function agentStart({
|
|
220
|
+
name,
|
|
221
|
+
cwd,
|
|
222
|
+
argv,
|
|
223
|
+
workspaceId,
|
|
224
|
+
tabId: targetTabId,
|
|
225
|
+
env
|
|
226
|
+
}) {
|
|
227
|
+
const result = await execHerdr([
|
|
228
|
+
"agent",
|
|
229
|
+
"start",
|
|
230
|
+
name,
|
|
231
|
+
...workspaceId ? ["--workspace", workspaceId] : [],
|
|
232
|
+
...targetTabId ? ["--tab", targetTabId] : [],
|
|
233
|
+
...cwdArgs(cwd),
|
|
234
|
+
...envArgs(env),
|
|
235
|
+
"--no-focus",
|
|
236
|
+
"--",
|
|
237
|
+
...argv
|
|
238
|
+
]);
|
|
239
|
+
const paneId = result?.agent?.pane_id;
|
|
240
|
+
const tabId = result?.agent?.tab_id;
|
|
241
|
+
if (!paneId || !tabId) {
|
|
242
|
+
throw new Error("Failed to start agent: invalid response structure from herdr");
|
|
243
|
+
}
|
|
244
|
+
return { paneId, tabId };
|
|
245
|
+
}
|
|
246
|
+
function toAgentStatus(value) {
|
|
247
|
+
return value === "working" || value === "idle" || value === "blocked" ? value : "unknown";
|
|
248
|
+
}
|
|
249
|
+
async function agentGet(target) {
|
|
250
|
+
const result = await execHerdr(["agent", "get", target]);
|
|
251
|
+
const agent = result?.agent;
|
|
252
|
+
if (!agent?.pane_id) {
|
|
253
|
+
throw new Error(`Failed to get agent info for ${target}: invalid response structure from herdr`);
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
paneId: agent.pane_id,
|
|
257
|
+
tabId: agent.tab_id ?? "",
|
|
258
|
+
workspaceId: agent.workspace_id ?? "",
|
|
259
|
+
agentStatus: toAgentStatus(agent.agent_status)
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function getCurrentWorkspaceId() {
|
|
263
|
+
const id = process.env.HERDR_WORKSPACE_ID;
|
|
264
|
+
return id && id.length > 0 ? id : void 0;
|
|
265
|
+
}
|
|
133
266
|
function isPaneReadErrorPayload(value) {
|
|
134
267
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
135
268
|
return false;
|
|
@@ -233,16 +366,179 @@ var init_herdr = __esm({
|
|
|
233
366
|
}
|
|
234
367
|
});
|
|
235
368
|
|
|
369
|
+
// src/herdr-runner.ts
|
|
370
|
+
var herdr_runner_exports = {};
|
|
371
|
+
__export(herdr_runner_exports, {
|
|
372
|
+
AGENT_POLL_INTERVAL_MS: () => AGENT_POLL_INTERVAL_MS,
|
|
373
|
+
CLAUDE_EXIT_POLL_INTERVAL_MS: () => CLAUDE_EXIT_POLL_INTERVAL_MS,
|
|
374
|
+
CLAUDE_EXIT_TIMEOUT_MS: () => CLAUDE_EXIT_TIMEOUT_MS,
|
|
375
|
+
PANE_OUTPUT_LINES: () => PANE_OUTPUT_LINES,
|
|
376
|
+
buildHerdrTaskResult: () => buildHerdrTaskResult,
|
|
377
|
+
createCompletionTracker: () => createCompletionTracker,
|
|
378
|
+
observeAgentStatus: () => observeAgentStatus,
|
|
379
|
+
startHerdrTask: () => startHerdrTask,
|
|
380
|
+
stopHerdrTask: () => stopHerdrTask,
|
|
381
|
+
taskTabLabel: () => taskTabLabel,
|
|
382
|
+
waitForHerdrTask: () => waitForHerdrTask
|
|
383
|
+
});
|
|
384
|
+
async function loadHerdr() {
|
|
385
|
+
return await Promise.resolve().then(() => (init_herdr(), herdr_exports));
|
|
386
|
+
}
|
|
387
|
+
function taskTabLabel(projectName, number) {
|
|
388
|
+
return `ctw:${projectName}:#${number}`;
|
|
389
|
+
}
|
|
390
|
+
function createCompletionTracker() {
|
|
391
|
+
return { seenWorking: false, warnedBlocked: false };
|
|
392
|
+
}
|
|
393
|
+
function observeAgentStatus(tracker, status) {
|
|
394
|
+
if (status === "working") {
|
|
395
|
+
return { tracker: { ...tracker, seenWorking: true }, decision: "running" };
|
|
396
|
+
}
|
|
397
|
+
if (status === "blocked") {
|
|
398
|
+
if (tracker.warnedBlocked) return { tracker, decision: "running" };
|
|
399
|
+
return { tracker: { ...tracker, warnedBlocked: true }, decision: "blocked-first-seen" };
|
|
400
|
+
}
|
|
401
|
+
if (status === "idle" && tracker.seenWorking) {
|
|
402
|
+
return { tracker, decision: "completed" };
|
|
403
|
+
}
|
|
404
|
+
return { tracker, decision: "running" };
|
|
405
|
+
}
|
|
406
|
+
function buildHerdrTaskResult(paneOutput) {
|
|
407
|
+
if (paneOutput.trim() === "") {
|
|
408
|
+
return {
|
|
409
|
+
status: "failed",
|
|
410
|
+
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)"
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
return { status: "completed", output: paneOutput };
|
|
414
|
+
}
|
|
415
|
+
async function startHerdrTask({
|
|
416
|
+
label,
|
|
417
|
+
cwd,
|
|
418
|
+
argv,
|
|
419
|
+
env,
|
|
420
|
+
workspaceId,
|
|
421
|
+
herdr
|
|
422
|
+
}) {
|
|
423
|
+
const mod = herdr ?? await loadHerdr();
|
|
424
|
+
const { tabId, paneId: shellPaneId } = await mod.tabCreate({ label, cwd, workspaceId, env });
|
|
425
|
+
let paneId;
|
|
426
|
+
try {
|
|
427
|
+
({ paneId } = await mod.agentStart({ name: label, cwd, argv, env, workspaceId, tabId }));
|
|
428
|
+
} catch (err) {
|
|
429
|
+
await mod.tabClose(tabId).catch(() => {
|
|
430
|
+
});
|
|
431
|
+
throw err;
|
|
432
|
+
}
|
|
433
|
+
await mod.paneClose(shellPaneId).catch((err) => {
|
|
434
|
+
console.error(`[herdr-runner] failed to close the placeholder shell pane ${shellPaneId}: ${err}`);
|
|
435
|
+
});
|
|
436
|
+
return { paneId, tabId };
|
|
437
|
+
}
|
|
438
|
+
async function waitForHerdrTask(paneId, options) {
|
|
439
|
+
const mod = options?.herdr ?? await loadHerdr();
|
|
440
|
+
const pollIntervalMs = options?.pollIntervalMs ?? AGENT_POLL_INTERVAL_MS;
|
|
441
|
+
let tracker = createCompletionTracker();
|
|
442
|
+
for (; ; ) {
|
|
443
|
+
if (options?.signal?.aborted) {
|
|
444
|
+
return { status: "failed", output: "[worker] the worker is shutting down; the task was interrupted" };
|
|
445
|
+
}
|
|
446
|
+
let status;
|
|
447
|
+
try {
|
|
448
|
+
status = (await mod.agentGet(paneId)).agentStatus;
|
|
449
|
+
} catch (err) {
|
|
450
|
+
if (err instanceof mod.HerdrError && err.code === "pane_not_found") {
|
|
451
|
+
return {
|
|
452
|
+
status: "failed",
|
|
453
|
+
output: "[worker] the claude pane disappeared before the task completed (claude died or the tab was closed)"
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
console.error(`[herdr-runner] failed to read agent status for pane ${paneId}: ${err}`);
|
|
457
|
+
await sleep(pollIntervalMs);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
options?.onStatus?.(status);
|
|
461
|
+
const observed = observeAgentStatus(tracker, status);
|
|
462
|
+
tracker = observed.tracker;
|
|
463
|
+
if (observed.decision === "completed") {
|
|
464
|
+
const output = await readPaneOutput(paneId, mod);
|
|
465
|
+
return buildHerdrTaskResult(output);
|
|
466
|
+
}
|
|
467
|
+
if (observed.decision === "blocked-first-seen") {
|
|
468
|
+
options?.onBlocked?.();
|
|
469
|
+
}
|
|
470
|
+
await sleep(pollIntervalMs);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function readPaneOutput(paneId, mod) {
|
|
474
|
+
try {
|
|
475
|
+
return await mod.paneRead(paneId, { source: "recent", lines: PANE_OUTPUT_LINES });
|
|
476
|
+
} catch (err) {
|
|
477
|
+
console.error(`[herdr-runner] failed to read pane ${paneId}: ${err}`);
|
|
478
|
+
return "";
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async function waitForPaneGone(paneId, mod, options) {
|
|
482
|
+
const timeoutMs = options?.timeoutMs ?? CLAUDE_EXIT_TIMEOUT_MS;
|
|
483
|
+
const pollIntervalMs = options?.pollIntervalMs ?? CLAUDE_EXIT_POLL_INTERVAL_MS;
|
|
484
|
+
const deadline = Date.now() + timeoutMs;
|
|
485
|
+
for (; ; ) {
|
|
486
|
+
try {
|
|
487
|
+
await mod.paneGet(paneId);
|
|
488
|
+
} catch (err) {
|
|
489
|
+
if (err instanceof mod.HerdrError && err.code === "pane_not_found") return true;
|
|
490
|
+
}
|
|
491
|
+
if (Date.now() >= deadline) return false;
|
|
492
|
+
await sleep(pollIntervalMs);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async function stopHerdrTask(task, herdr, options) {
|
|
496
|
+
const mod = herdr ?? await loadHerdr();
|
|
497
|
+
try {
|
|
498
|
+
await mod.paneSendKeys(task.paneId, "ctrl+c", "ctrl+c");
|
|
499
|
+
const exited = await waitForPaneGone(task.paneId, mod, {
|
|
500
|
+
timeoutMs: options?.exitTimeoutMs,
|
|
501
|
+
pollIntervalMs: options?.exitPollIntervalMs
|
|
502
|
+
});
|
|
503
|
+
if (!exited) {
|
|
504
|
+
console.warn(
|
|
505
|
+
`[herdr-runner] claude did not exit in pane ${task.paneId} after ctrl-c, closing the tab forcefully`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
} catch (err) {
|
|
509
|
+
console.error(`[herdr-runner] failed to send ctrl-c to pane ${task.paneId}: ${err}`);
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
await mod.tabClose(task.tabId);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
if (err instanceof mod.HerdrError && (err.code === "tab_not_found" || err.code === "pane_not_found")) return;
|
|
515
|
+
console.error(`[herdr-runner] failed to close tab ${task.tabId}: ${err}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function sleep(ms) {
|
|
519
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
520
|
+
}
|
|
521
|
+
var AGENT_POLL_INTERVAL_MS, PANE_OUTPUT_LINES, CLAUDE_EXIT_TIMEOUT_MS, CLAUDE_EXIT_POLL_INTERVAL_MS;
|
|
522
|
+
var init_herdr_runner = __esm({
|
|
523
|
+
"src/herdr-runner.ts"() {
|
|
524
|
+
"use strict";
|
|
525
|
+
AGENT_POLL_INTERVAL_MS = 3 * 1e3;
|
|
526
|
+
PANE_OUTPUT_LINES = 300;
|
|
527
|
+
CLAUDE_EXIT_TIMEOUT_MS = 15 * 1e3;
|
|
528
|
+
CLAUDE_EXIT_POLL_INTERVAL_MS = 200;
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
236
532
|
// src/dispatcher.ts
|
|
237
533
|
var dispatcher_exports = {};
|
|
238
534
|
__export(dispatcher_exports, {
|
|
535
|
+
LABEL_PREFIX: () => LABEL_PREFIX,
|
|
239
536
|
PANE_READY_POLL_INTERVAL_MS: () => PANE_READY_POLL_INTERVAL_MS,
|
|
240
537
|
PANE_READY_TIMEOUT_MS: () => PANE_READY_TIMEOUT_MS,
|
|
241
538
|
POLL_INTERVAL_MS: () => POLL_INTERVAL_MS,
|
|
242
539
|
SEND_MAX_ATTEMPTS: () => SEND_MAX_ATTEMPTS,
|
|
243
540
|
SHUTDOWN_RETRY_TIMEOUT_MS: () => SHUTDOWN_RETRY_TIMEOUT_MS,
|
|
244
541
|
SHUTDOWN_TIMEOUT_MS: () => SHUTDOWN_TIMEOUT_MS,
|
|
245
|
-
TAB_LABEL_PREFIX: () => TAB_LABEL_PREFIX,
|
|
246
542
|
WORKER_STARTUP_POLL_INTERVAL_MS: () => WORKER_STARTUP_POLL_INTERVAL_MS,
|
|
247
543
|
WORKER_STARTUP_TIMEOUT_MS: () => WORKER_STARTUP_TIMEOUT_MS,
|
|
248
544
|
createDispatcherShutdownHandler: () => createDispatcherShutdownHandler,
|
|
@@ -256,21 +552,21 @@ __export(dispatcher_exports, {
|
|
|
256
552
|
runDispatcher: () => runDispatcher,
|
|
257
553
|
shutdownDispatcher: () => shutdownDispatcher,
|
|
258
554
|
startWorkerInPane: () => startWorkerInPane,
|
|
259
|
-
tabLabelFor: () => tabLabelFor,
|
|
260
555
|
waitForPaneReady: () => waitForPaneReady,
|
|
261
|
-
waitForWorkerStartup: () => waitForWorkerStartup
|
|
556
|
+
waitForWorkerStartup: () => waitForWorkerStartup,
|
|
557
|
+
workspaceLabelFor: () => workspaceLabelFor
|
|
262
558
|
});
|
|
263
|
-
async function
|
|
559
|
+
async function loadHerdr2() {
|
|
264
560
|
return await Promise.resolve().then(() => (init_herdr(), herdr_exports));
|
|
265
561
|
}
|
|
266
562
|
async function loadTable() {
|
|
267
563
|
return await Promise.resolve().then(() => (init_table(), table_exports));
|
|
268
564
|
}
|
|
269
|
-
function
|
|
270
|
-
return `${
|
|
565
|
+
function workspaceLabelFor(projectName) {
|
|
566
|
+
return `${LABEL_PREFIX}${projectName}`;
|
|
271
567
|
}
|
|
272
|
-
function
|
|
273
|
-
return new Promise((
|
|
568
|
+
function sleep2(ms) {
|
|
569
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
274
570
|
}
|
|
275
571
|
function isWorkerProcess(process2) {
|
|
276
572
|
return process2.cmdline?.includes("claude-task-worker") ?? false;
|
|
@@ -286,7 +582,7 @@ async function waitForPaneReady(paneId, herdr, options) {
|
|
|
286
582
|
const content = await herdr.paneRead(paneId, { source: "visible" });
|
|
287
583
|
if (content.trim() !== "") return true;
|
|
288
584
|
if (Date.now() >= deadline) return false;
|
|
289
|
-
await
|
|
585
|
+
await sleep2(pollIntervalMs);
|
|
290
586
|
}
|
|
291
587
|
}
|
|
292
588
|
async function waitForWorkerStartup(paneId, herdr, options) {
|
|
@@ -298,7 +594,7 @@ async function waitForWorkerStartup(paneId, herdr, options) {
|
|
|
298
594
|
if (foregroundProcesses.some(isWorkerProcess)) return "started";
|
|
299
595
|
if (foregroundProcesses.length > 0 && !foregroundProcesses.every(isShellProcess)) return "other";
|
|
300
596
|
if (Date.now() >= deadline) return "shell";
|
|
301
|
-
await
|
|
597
|
+
await sleep2(pollIntervalMs);
|
|
302
598
|
}
|
|
303
599
|
}
|
|
304
600
|
async function startWorkerInPane(paneId, forwardedCommand, herdr, options) {
|
|
@@ -333,8 +629,8 @@ async function startWorkerInPane(paneId, forwardedCommand, herdr, options) {
|
|
|
333
629
|
return false;
|
|
334
630
|
}
|
|
335
631
|
async function runDispatcher(projects, forwardedCommand, timing) {
|
|
336
|
-
const herdr = await
|
|
337
|
-
const { checkHerdrAvailable: checkHerdrAvailable2,
|
|
632
|
+
const herdr = await loadHerdr2();
|
|
633
|
+
const { checkHerdrAvailable: checkHerdrAvailable2, workspaceCreate: workspaceCreate2, workspaceClose: workspaceClose2, workspaceList: workspaceList2, tabRename: tabRename2 } = herdr;
|
|
338
634
|
try {
|
|
339
635
|
await checkHerdrAvailable2();
|
|
340
636
|
} catch (error) {
|
|
@@ -342,20 +638,29 @@ async function runDispatcher(projects, forwardedCommand, timing) {
|
|
|
342
638
|
console.error(`[dispatcher] ${message}`);
|
|
343
639
|
throw error;
|
|
344
640
|
}
|
|
345
|
-
const
|
|
346
|
-
const existingLabels = new Set(
|
|
641
|
+
const existingWorkspaces = await workspaceList2();
|
|
642
|
+
const existingLabels = new Set(existingWorkspaces.map((workspace) => workspace.label));
|
|
347
643
|
const sessions = /* @__PURE__ */ new Map();
|
|
348
644
|
for (const project of projects) {
|
|
349
|
-
|
|
350
|
-
|
|
645
|
+
const label = workspaceLabelFor(project.name);
|
|
646
|
+
if (existingLabels.has(label)) {
|
|
647
|
+
console.warn(`[dispatcher] project "${project.name}" already has a running workspace, skipping`);
|
|
351
648
|
continue;
|
|
352
649
|
}
|
|
353
|
-
let
|
|
650
|
+
let createdWorkspaceId;
|
|
354
651
|
try {
|
|
355
|
-
const {
|
|
356
|
-
|
|
652
|
+
const { workspaceId, tabId, paneId } = await workspaceCreate2({
|
|
653
|
+
label,
|
|
654
|
+
cwd: project.path,
|
|
655
|
+
env: { CTW_PROJECT_NAME: project.name }
|
|
656
|
+
});
|
|
657
|
+
createdWorkspaceId = workspaceId;
|
|
658
|
+
await tabRename2(tabId, label).catch(
|
|
659
|
+
(error) => console.error(`[dispatcher] failed to rename the root tab for project "${project.name}": ${error}`)
|
|
660
|
+
);
|
|
357
661
|
sessions.set(project.name, {
|
|
358
662
|
name: project.name,
|
|
663
|
+
workspaceId,
|
|
359
664
|
tabId,
|
|
360
665
|
paneId,
|
|
361
666
|
startedAt: /* @__PURE__ */ new Date(),
|
|
@@ -370,24 +675,24 @@ async function runDispatcher(projects, forwardedCommand, timing) {
|
|
|
370
675
|
} catch (error) {
|
|
371
676
|
console.error(`[dispatcher] failed to dispatch project "${project.name}": ${error}`);
|
|
372
677
|
sessions.delete(project.name);
|
|
373
|
-
if (
|
|
678
|
+
if (createdWorkspaceId !== void 0) {
|
|
374
679
|
try {
|
|
375
|
-
await
|
|
680
|
+
await workspaceClose2(createdWorkspaceId);
|
|
376
681
|
} catch (closeError) {
|
|
377
|
-
console.error(`[dispatcher] failed to close dangling
|
|
682
|
+
console.error(`[dispatcher] failed to close dangling workspace for project "${project.name}": ${closeError}`);
|
|
378
683
|
}
|
|
379
684
|
}
|
|
380
685
|
}
|
|
381
686
|
}
|
|
382
687
|
return sessions;
|
|
383
688
|
}
|
|
384
|
-
async function removeSession(sessions, name, {
|
|
689
|
+
async function removeSession(sessions, name, { closeWorkspace }) {
|
|
385
690
|
const session = sessions.get(name);
|
|
386
691
|
if (!session) return;
|
|
387
692
|
sessions.delete(name);
|
|
388
|
-
if (
|
|
389
|
-
const {
|
|
390
|
-
await
|
|
693
|
+
if (closeWorkspace) {
|
|
694
|
+
const { workspaceClose: workspaceClose2 } = await loadHerdr2();
|
|
695
|
+
await workspaceClose2(session.workspaceId);
|
|
391
696
|
}
|
|
392
697
|
}
|
|
393
698
|
async function pollOnce(sessions, herdr) {
|
|
@@ -395,11 +700,11 @@ async function pollOnce(sessions, herdr) {
|
|
|
395
700
|
try {
|
|
396
701
|
const { foregroundProcesses } = await herdr.paneProcessInfo(session.paneId);
|
|
397
702
|
if (!foregroundProcesses.some(isWorkerProcess)) {
|
|
398
|
-
await removeSession(sessions, name, {
|
|
703
|
+
await removeSession(sessions, name, { closeWorkspace: true });
|
|
399
704
|
}
|
|
400
705
|
} catch (error) {
|
|
401
706
|
if (error instanceof herdr.HerdrError && error.code === "pane_not_found") {
|
|
402
|
-
await removeSession(sessions, name, {
|
|
707
|
+
await removeSession(sessions, name, { closeWorkspace: false });
|
|
403
708
|
continue;
|
|
404
709
|
}
|
|
405
710
|
console.error(`[dispatcher] failed to poll session "${name}": ${error}`);
|
|
@@ -419,27 +724,27 @@ function renderSessionTable(sessions) {
|
|
|
419
724
|
const maxProjectWidth = 20;
|
|
420
725
|
const rows = entries.map((session) => ({
|
|
421
726
|
project: getDisplayWidth2(session.name) > maxProjectWidth ? truncateToWidth2(session.name, maxProjectWidth) : session.name,
|
|
422
|
-
|
|
727
|
+
workspace: session.workspaceId,
|
|
423
728
|
pane: session.paneId,
|
|
424
729
|
status: session.status,
|
|
425
730
|
uptime: formatUptime(session.startedAt, /* @__PURE__ */ new Date())
|
|
426
731
|
}));
|
|
427
732
|
const colWidths = {
|
|
428
733
|
project: Math.max(7, ...rows.map((r) => getDisplayWidth2(r.project))),
|
|
429
|
-
|
|
734
|
+
workspace: Math.max(9, ...rows.map((r) => r.workspace.length)),
|
|
430
735
|
pane: Math.max(4, ...rows.map((r) => r.pane.length)),
|
|
431
736
|
status: Math.max(6, ...rows.map((r) => r.status.length)),
|
|
432
737
|
uptime: Math.max(6, ...rows.map((r) => r.uptime.length))
|
|
433
738
|
};
|
|
434
|
-
const cols = [colWidths.project, colWidths.
|
|
739
|
+
const cols = [colWidths.project, colWidths.workspace, colWidths.pane, colWidths.status, colWidths.uptime];
|
|
435
740
|
const line = (l, m, r, f) => `${l}${cols.map((w) => f.repeat(w + 2)).join(m)}${r}`;
|
|
436
|
-
const row = (project,
|
|
741
|
+
const row = (project, workspace, pane, status, uptime) => `\u2502 ${padToWidth2(project, colWidths.project)} \u2502 ${workspace.padEnd(colWidths.workspace)} \u2502 ${pane.padEnd(colWidths.pane)} \u2502 ${status.padEnd(colWidths.status)} \u2502 ${uptime.padEnd(colWidths.uptime)} \u2502`;
|
|
437
742
|
const lines = [];
|
|
438
743
|
lines.push(line("\u250C", "\u252C", "\u2510", "\u2500"));
|
|
439
|
-
lines.push(row("Project", "
|
|
744
|
+
lines.push(row("Project", "Workspace", "Pane", "Status", "Uptime"));
|
|
440
745
|
lines.push(line("\u251C", "\u253C", "\u2524", "\u2500"));
|
|
441
746
|
for (const r of rows) {
|
|
442
|
-
lines.push(row(r.project, r.
|
|
747
|
+
lines.push(row(r.project, r.workspace, r.pane, r.status, r.uptime));
|
|
443
748
|
}
|
|
444
749
|
lines.push(line("\u2514", "\u2534", "\u2518", "\u2500"));
|
|
445
750
|
console.clear();
|
|
@@ -449,8 +754,8 @@ function monitorSessions(sessions, herdr, options) {
|
|
|
449
754
|
const pollIntervalMs = options?.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
450
755
|
const renderIntervalMs = options?.renderIntervalMs ?? 1e3;
|
|
451
756
|
let resolveDone;
|
|
452
|
-
const done = new Promise((
|
|
453
|
-
resolveDone =
|
|
757
|
+
const done = new Promise((resolve3) => {
|
|
758
|
+
resolveDone = resolve3;
|
|
454
759
|
});
|
|
455
760
|
let settled = false;
|
|
456
761
|
const finish = () => {
|
|
@@ -492,24 +797,24 @@ async function waitUntilSessionsEmpty(sessions, herdr, pollIntervalMs, timeoutMs
|
|
|
492
797
|
await pollOnce(sessions, herdr);
|
|
493
798
|
if (sessions.size === 0) return true;
|
|
494
799
|
if (Date.now() >= deadline) return false;
|
|
495
|
-
await new Promise((
|
|
800
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
|
|
496
801
|
}
|
|
497
802
|
return true;
|
|
498
803
|
}
|
|
499
|
-
async function
|
|
804
|
+
async function closeRemainingWorkspaces(sessions, herdr, timeoutMs) {
|
|
500
805
|
const results = await Promise.all(
|
|
501
806
|
[...sessions.values()].map(async (session) => {
|
|
502
807
|
try {
|
|
503
808
|
await Promise.race([
|
|
504
|
-
herdr.
|
|
809
|
+
herdr.workspaceClose(session.workspaceId),
|
|
505
810
|
new Promise((_, reject) => {
|
|
506
|
-
setTimeout(() => reject(new Error("
|
|
811
|
+
setTimeout(() => reject(new Error("workspaceClose timed out")), timeoutMs).unref();
|
|
507
812
|
})
|
|
508
813
|
]);
|
|
509
814
|
return true;
|
|
510
815
|
} catch (error) {
|
|
511
816
|
console.error(
|
|
512
|
-
`[dispatcher] failed to close
|
|
817
|
+
`[dispatcher] failed to close workspace "${session.workspaceId}" for session "${session.name}" during shutdown: ${error}`
|
|
513
818
|
);
|
|
514
819
|
return false;
|
|
515
820
|
}
|
|
@@ -517,29 +822,29 @@ async function closeRemainingTabs(sessions, herdr, timeoutMs) {
|
|
|
517
822
|
);
|
|
518
823
|
return results.every((closed) => closed);
|
|
519
824
|
}
|
|
520
|
-
async function forceKillAllSessions(sessions, herdr,
|
|
825
|
+
async function forceKillAllSessions(sessions, herdr, workspaceCloseTimeoutMs) {
|
|
521
826
|
console.log(`[dispatcher] force-kill requested, sending ctrl-c and closing ${sessions.size} session(s) immediately`);
|
|
522
827
|
await sendCtrlCToAllSessions(sessions, herdr);
|
|
523
|
-
await
|
|
828
|
+
await closeRemainingWorkspaces(sessions, herdr, workspaceCloseTimeoutMs);
|
|
524
829
|
process.exit(1);
|
|
525
830
|
}
|
|
526
831
|
async function shutdownDispatcher(sessions, monitorHandle, options) {
|
|
527
832
|
if (shutdownPromise) {
|
|
528
833
|
if (options?.forceKill) {
|
|
529
|
-
const herdr = options.herdr ?? await
|
|
530
|
-
const
|
|
531
|
-
await forceKillAllSessions(sessions, herdr,
|
|
834
|
+
const herdr = options.herdr ?? await loadHerdr2();
|
|
835
|
+
const workspaceCloseTimeoutMs = options.workspaceCloseTimeoutMs ?? 5e3;
|
|
836
|
+
await forceKillAllSessions(sessions, herdr, workspaceCloseTimeoutMs);
|
|
532
837
|
return;
|
|
533
838
|
}
|
|
534
839
|
console.log("[dispatcher] shutdown already in progress, ignoring duplicate request");
|
|
535
840
|
return shutdownPromise;
|
|
536
841
|
}
|
|
537
842
|
shutdownPromise = (async () => {
|
|
538
|
-
const herdr = options?.herdr ?? await
|
|
843
|
+
const herdr = options?.herdr ?? await loadHerdr2();
|
|
539
844
|
const pollIntervalMs = options?.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
540
845
|
const shutdownTimeoutMs = options?.shutdownTimeoutMs ?? SHUTDOWN_TIMEOUT_MS;
|
|
541
846
|
const retryTimeoutMs = options?.retryTimeoutMs ?? SHUTDOWN_RETRY_TIMEOUT_MS;
|
|
542
|
-
const
|
|
847
|
+
const workspaceCloseTimeoutMs = options?.workspaceCloseTimeoutMs ?? 5e3;
|
|
543
848
|
if (monitorHandle) {
|
|
544
849
|
monitorHandle.stop();
|
|
545
850
|
await monitorHandle.done;
|
|
@@ -554,8 +859,8 @@ async function shutdownDispatcher(sessions, monitorHandle, options) {
|
|
|
554
859
|
await sendCtrlCToAllSessions(sessions, herdr);
|
|
555
860
|
finishedInTime = await waitUntilSessionsEmpty(sessions, herdr, pollIntervalMs, retryTimeoutMs);
|
|
556
861
|
}
|
|
557
|
-
const
|
|
558
|
-
process.exit(finishedInTime &&
|
|
862
|
+
const allWorkspacesClosed = await closeRemainingWorkspaces(sessions, herdr, workspaceCloseTimeoutMs);
|
|
863
|
+
process.exit(finishedInTime && allWorkspacesClosed ? 0 : 1);
|
|
559
864
|
})();
|
|
560
865
|
try {
|
|
561
866
|
await shutdownPromise;
|
|
@@ -590,7 +895,7 @@ function createDispatcherShutdownHandler(shutdown2) {
|
|
|
590
895
|
};
|
|
591
896
|
return { handle, isShuttingDown: () => shuttingDown2 };
|
|
592
897
|
}
|
|
593
|
-
var getDisplayWidth2, truncateToWidth2, padToWidth2, POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS, PANE_READY_TIMEOUT_MS, PANE_READY_POLL_INTERVAL_MS, WORKER_STARTUP_TIMEOUT_MS, WORKER_STARTUP_POLL_INTERVAL_MS, SEND_MAX_ATTEMPTS,
|
|
898
|
+
var getDisplayWidth2, truncateToWidth2, padToWidth2, POLL_INTERVAL_MS, SHUTDOWN_TIMEOUT_MS, PANE_READY_TIMEOUT_MS, PANE_READY_POLL_INTERVAL_MS, WORKER_STARTUP_TIMEOUT_MS, WORKER_STARTUP_POLL_INTERVAL_MS, SEND_MAX_ATTEMPTS, LABEL_PREFIX, SHELL_NAME_PATTERN, SHUTDOWN_RETRY_TIMEOUT_MS, CTRL_C_KEY, shutdownPromise;
|
|
594
899
|
var init_dispatcher = __esm({
|
|
595
900
|
async "src/dispatcher.ts"() {
|
|
596
901
|
"use strict";
|
|
@@ -602,7 +907,7 @@ var init_dispatcher = __esm({
|
|
|
602
907
|
WORKER_STARTUP_TIMEOUT_MS = 30 * 1e3;
|
|
603
908
|
WORKER_STARTUP_POLL_INTERVAL_MS = 500;
|
|
604
909
|
SEND_MAX_ATTEMPTS = 3;
|
|
605
|
-
|
|
910
|
+
LABEL_PREFIX = "ctw:";
|
|
606
911
|
SHELL_NAME_PATTERN = /^-?(zsh|bash|sh)$/;
|
|
607
912
|
SHUTDOWN_RETRY_TIMEOUT_MS = 90 * 1e3;
|
|
608
913
|
CTRL_C_KEY = "ctrl+c";
|
|
@@ -613,29 +918,29 @@ var init_dispatcher = __esm({
|
|
|
613
918
|
import { createRequire } from "node:module";
|
|
614
919
|
var childProcess = createRequire(import.meta.url)("node:child_process");
|
|
615
920
|
function execGh(args) {
|
|
616
|
-
return new Promise((
|
|
921
|
+
return new Promise((resolve3, reject) => {
|
|
617
922
|
childProcess.execFile("gh", args, (error, stdout, stderr) => {
|
|
618
923
|
if (error) {
|
|
619
924
|
reject(new Error(`gh ${args.join(" ")} failed: ${stderr || error.message}`));
|
|
620
925
|
return;
|
|
621
926
|
}
|
|
622
|
-
|
|
927
|
+
resolve3(stdout.trim());
|
|
623
928
|
});
|
|
624
929
|
});
|
|
625
930
|
}
|
|
626
931
|
function execGhAllowExit(args, allowedCodes) {
|
|
627
|
-
return new Promise((
|
|
932
|
+
return new Promise((resolve3, reject) => {
|
|
628
933
|
childProcess.execFile("gh", args, (error, stdout, stderr) => {
|
|
629
934
|
if (error) {
|
|
630
935
|
const code = error.code;
|
|
631
936
|
if (typeof code === "number" && allowedCodes.includes(code)) {
|
|
632
|
-
|
|
937
|
+
resolve3(stdout.trim());
|
|
633
938
|
return;
|
|
634
939
|
}
|
|
635
940
|
reject(new Error(`gh ${args.join(" ")} failed: ${stderr || error.message}`));
|
|
636
941
|
return;
|
|
637
942
|
}
|
|
638
|
-
|
|
943
|
+
resolve3(stdout.trim());
|
|
639
944
|
});
|
|
640
945
|
});
|
|
641
946
|
}
|
|
@@ -820,7 +1125,7 @@ async function withRetry(fn, maxRetries = 5, baseDelayMs = 1e3) {
|
|
|
820
1125
|
if (attempt === maxRetries) throw err;
|
|
821
1126
|
const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
|
|
822
1127
|
console.error(`[gh] Attempt ${attempt}/${maxRetries} failed, retrying in ${delayMs}ms...`);
|
|
823
|
-
await new Promise((
|
|
1128
|
+
await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
824
1129
|
}
|
|
825
1130
|
}
|
|
826
1131
|
throw new Error("unreachable");
|
|
@@ -899,13 +1204,38 @@ var CLAUDE_SPAWN_ENV = {
|
|
|
899
1204
|
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: "1",
|
|
900
1205
|
CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS: "0"
|
|
901
1206
|
};
|
|
902
|
-
var SYSTEM_PROMPT = `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F \`claude-task-worker\` \u306E\u30EF\u30FC\u30AB\u30FC\u304B\u3089
|
|
903
|
-
var SUBAGENT_SYSTEM_PROMPT = `\u3042\u306A\u305F\u306F claude-task-worker \u306E\u30EF\u30FC\u30AB\u30FC\u304C \`claude -p\`\uFF08\u975E\u5BFE\u8A71 print \u30E2\u30FC\u30C9\uFF09\u3067\u81EA\u52D5\u8D77\u52D5\u3057\u305F\u30BB\u30C3\u30B7\u30E7\u30F3\u5185\u306E\u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3067\u3059\u3002\u4EE5\u4E0B\u306E\u81EA\u5F8B\u5B9F\u884C\u539F\u5247\u3092\u5FC5\u305A\u9075\u5B88\u3057\u3066\u304F\u3060\u3055\u3044\u3002
|
|
1207
|
+
var SYSTEM_PROMPT = `\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F \`claude-task-worker\` \u306E\u30EF\u30FC\u30AB\u30FC\u304B\u3089\u81EA\u52D5\u8D77\u52D5\u3055\u308C\u3066\u3044\u308B\uFF08\u5FDC\u7B54\u3067\u304D\u308B\u30E6\u30FC\u30B6\u30FC\u306F\u5E38\u99D0\u3057\u3066\u3044\u306A\u3044\uFF09\u3002\u4EE5\u4E0B\u306E\u81EA\u5F8B\u5B9F\u884C\u539F\u5247\u3092\u5FC5\u305A\u9075\u5B88\u3059\u308B\u3053\u3068\u3002
|
|
904
1208
|
|
|
905
|
-
- \u30E6\u30FC\u30B6\u30FC\u3078\u306E\u78BA\u8A8D\u30FB\u8CEA\u554F\u306F\u884C\u308F\
|
|
1209
|
+
- \u30E6\u30FC\u30B6\u30FC\u3078\u306E\u78BA\u8A8D\u30FB\u8CEA\u554F\u306F\u884C\u308F\u305A\u3001\u8D77\u52D5\u3055\u308C\u305F\u30B9\u30AD\u30EB\u306E\u30EB\u30FC\u30EB\u306B\u5F93\u3063\u3066\u81EA\u5F8B\u7684\u306B\u5224\u65AD\u3059\u308B
|
|
906
1210
|
- \u66D6\u6627\u306A\u5834\u5408\u306F\u300C\u3088\u308A\u5B89\u5168\u306A\u5074\uFF08\u7834\u58CA\u7684\u3067\u306A\u3044\u5074\uFF09\u300D\u3092\u9078\u629E\u3057\u3001\u305D\u306E\u5224\u65AD\u3068\u6839\u62E0\u3092\u6700\u7D42\u5831\u544A\u306B\u660E\u8A18\u3059\u308B
|
|
907
|
-
- \
|
|
908
|
-
- \
|
|
1211
|
+
- \u5168\u30B9\u30C6\u30C3\u30D7\u3092\u5B8C\u9042\u3057\u3066\u304B\u3089\u7D42\u4E86\u3059\u308B\uFF08\u30B9\u30AD\u30EB\u306B\u5B9A\u7FA9\u3055\u308C\u305F\u4E2D\u65AD\u6761\u4EF6\u306B\u8A72\u5F53\u3057\u305F\u5834\u5408\u306E\u307F\u3001\u7406\u7531\u3092\u51FA\u529B\u3057\u3066\u7D42\u4E86\u3059\u308B\uFF09
|
|
1212
|
+
- \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3078\u4F5C\u696D\u3092\u59D4\u8B72\u3059\u308B\u5834\u5408\u306F\u3001\u4E0A\u8A18\u306E\u539F\u5247\u3092\u59D4\u8B72\u30D7\u30ED\u30F3\u30D7\u30C8\u306B\u3082\u660E\u8A18\u3057\u3066\u4F1D\u3048\u308B
|
|
1213
|
+
- \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306E\u5B8C\u4E86\u5831\u544A\u306F\u9D5C\u5451\u307F\u306B\u3057\u306A\u3044\u3002\`git diff\` \u7B49\u3067\u5B9F\u969B\u306E\u6210\u679C\u7269\u3092\u691C\u8A3C\u3057\u3066\u304B\u3089\u5B8C\u4E86\u6271\u3044\u306B\u3059\u308B`;
|
|
1214
|
+
function buildClaudeArgs({ mode, prompt, model, effort }) {
|
|
1215
|
+
return [
|
|
1216
|
+
...mode === "herdr" ? [] : ["-p"],
|
|
1217
|
+
prompt,
|
|
1218
|
+
"--dangerously-skip-permissions",
|
|
1219
|
+
"--chrome",
|
|
1220
|
+
"--disallowedTools",
|
|
1221
|
+
DISALLOWED_TOOLS_ARG,
|
|
1222
|
+
"--append-system-prompt",
|
|
1223
|
+
SYSTEM_PROMPT,
|
|
1224
|
+
"--model",
|
|
1225
|
+
model,
|
|
1226
|
+
"--effort",
|
|
1227
|
+
effort
|
|
1228
|
+
];
|
|
1229
|
+
}
|
|
1230
|
+
function buildClaudeEnv(mode) {
|
|
1231
|
+
if (mode === "herdr") {
|
|
1232
|
+
return {
|
|
1233
|
+
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: CLAUDE_SPAWN_ENV.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS,
|
|
1234
|
+
HERDR_DISABLE_SOUND: "1"
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
return { ...CLAUDE_SPAWN_ENV };
|
|
1238
|
+
}
|
|
909
1239
|
|
|
910
1240
|
// src/config.ts
|
|
911
1241
|
import { readFileSync } from "node:fs";
|
|
@@ -1150,6 +1480,7 @@ async function assertRemoteTrackingExists(epicBranch) {
|
|
|
1150
1480
|
|
|
1151
1481
|
// src/process-manager.ts
|
|
1152
1482
|
import { spawn } from "node:child_process";
|
|
1483
|
+
import { basename } from "node:path";
|
|
1153
1484
|
init_table();
|
|
1154
1485
|
|
|
1155
1486
|
// src/task-result.ts
|
|
@@ -1171,8 +1502,200 @@ function buildTaskResult(code, stdout, stderrTail) {
|
|
|
1171
1502
|
return { status: completed ? "completed" : "failed", output };
|
|
1172
1503
|
}
|
|
1173
1504
|
|
|
1505
|
+
// src/user-config.ts
|
|
1506
|
+
import { readFileSync as readFileSync2, statSync } from "node:fs";
|
|
1507
|
+
import { homedir } from "node:os";
|
|
1508
|
+
import { isAbsolute, join as join2, resolve } from "node:path";
|
|
1509
|
+
var RESERVED_ALL = "all";
|
|
1510
|
+
var DEFAULT_RUN_MODE = "default";
|
|
1511
|
+
var UserConfigError = class extends Error {
|
|
1512
|
+
constructor(message) {
|
|
1513
|
+
super(message);
|
|
1514
|
+
this.name = "UserConfigError";
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
function getConfigDir() {
|
|
1518
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
1519
|
+
const configHome = xdg && xdg.length > 0 ? xdg : join2(homedir(), ".config");
|
|
1520
|
+
return join2(configHome, "claude-task-worker");
|
|
1521
|
+
}
|
|
1522
|
+
function getUserConfigPath() {
|
|
1523
|
+
return join2(getConfigDir(), "config.json");
|
|
1524
|
+
}
|
|
1525
|
+
function isDirectory(path) {
|
|
1526
|
+
try {
|
|
1527
|
+
return statSync(path).isDirectory();
|
|
1528
|
+
} catch {
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
function parseConfigFile(path) {
|
|
1533
|
+
let content;
|
|
1534
|
+
try {
|
|
1535
|
+
content = readFileSync2(path, "utf-8");
|
|
1536
|
+
} catch (err) {
|
|
1537
|
+
if (err.code === "ENOENT") return void 0;
|
|
1538
|
+
throw err;
|
|
1539
|
+
}
|
|
1540
|
+
try {
|
|
1541
|
+
return JSON.parse(content);
|
|
1542
|
+
} catch (err) {
|
|
1543
|
+
if (err instanceof SyntaxError) {
|
|
1544
|
+
throw new UserConfigError(`config file contains invalid JSON: ${path}: ${err.message}`);
|
|
1545
|
+
}
|
|
1546
|
+
throw err;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
function readRawConfig() {
|
|
1550
|
+
return parseConfigFile(getUserConfigPath());
|
|
1551
|
+
}
|
|
1552
|
+
function parseMode(raw, path) {
|
|
1553
|
+
if (!("mode" in raw)) return DEFAULT_RUN_MODE;
|
|
1554
|
+
const value = raw["mode"];
|
|
1555
|
+
if (value === "default" || value === "herdr") return value;
|
|
1556
|
+
console.warn(`[config] invalid mode: ${JSON.stringify(value)} in ${path}, using "${DEFAULT_RUN_MODE}"`);
|
|
1557
|
+
return DEFAULT_RUN_MODE;
|
|
1558
|
+
}
|
|
1559
|
+
function loadUserConfig() {
|
|
1560
|
+
const path = getUserConfigPath();
|
|
1561
|
+
const raw = readRawConfig();
|
|
1562
|
+
if (raw === void 0) {
|
|
1563
|
+
throw new UserConfigError(`config.json not found: ${path}`);
|
|
1564
|
+
}
|
|
1565
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1566
|
+
throw new UserConfigError(`config.json must contain a JSON object: ${path}`);
|
|
1567
|
+
}
|
|
1568
|
+
if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
|
|
1569
|
+
throw new UserConfigError(`config.json must contain a "projects" section as an object: ${path}`);
|
|
1570
|
+
}
|
|
1571
|
+
if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
|
|
1572
|
+
throw new UserConfigError(`config.json "projectGroups" must be an object: ${path}`);
|
|
1573
|
+
}
|
|
1574
|
+
const mode = parseMode(raw, path);
|
|
1575
|
+
const rawProjects = raw["projects"];
|
|
1576
|
+
const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
|
|
1577
|
+
const projectKeys = Object.keys(rawProjects);
|
|
1578
|
+
const groupKeys = Object.keys(rawProjectGroups);
|
|
1579
|
+
if (projectKeys.includes(RESERVED_ALL) || groupKeys.includes(RESERVED_ALL)) {
|
|
1580
|
+
throw new UserConfigError(
|
|
1581
|
+
`"${RESERVED_ALL}" is a reserved word and cannot be used as a key in "projects" or "projectGroups"`
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
const groupKeySet = new Set(groupKeys);
|
|
1585
|
+
const duplicateKeys = projectKeys.filter((key) => groupKeySet.has(key));
|
|
1586
|
+
if (duplicateKeys.length > 0) {
|
|
1587
|
+
throw new UserConfigError(
|
|
1588
|
+
`"projects" and "projectGroups" share the same key namespace; duplicate keys are not allowed: ${duplicateKeys.join(", ")}`
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
const projects = {};
|
|
1592
|
+
for (const [name, value] of Object.entries(rawProjects)) {
|
|
1593
|
+
if (name === "__proto__") {
|
|
1594
|
+
throw new UserConfigError(`"__proto__" cannot be used as a key in "projects": ${path}`);
|
|
1595
|
+
}
|
|
1596
|
+
if (typeof value !== "string" || !isAbsolute(value)) {
|
|
1597
|
+
console.warn(`[config] invalid projects.${name}: expected an absolute path, skipping`);
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
if (!isDirectory(value)) {
|
|
1601
|
+
console.warn(`[config] projects.${name} does not exist as a directory: ${value}, skipping`);
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
projects[name] = value;
|
|
1605
|
+
}
|
|
1606
|
+
const projectGroups = {};
|
|
1607
|
+
for (const [groupName, value] of Object.entries(rawProjectGroups)) {
|
|
1608
|
+
if (groupName === "__proto__") {
|
|
1609
|
+
throw new UserConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${path}`);
|
|
1610
|
+
}
|
|
1611
|
+
if (!Array.isArray(value)) {
|
|
1612
|
+
console.warn(`[config] invalid projectGroups.${groupName}: expected an array, skipping`);
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
const members = [];
|
|
1616
|
+
for (const member of value) {
|
|
1617
|
+
if (typeof member !== "string" || !Object.prototype.hasOwnProperty.call(projects, member)) {
|
|
1618
|
+
console.warn(`[config] projectGroups.${groupName} references unknown project "${String(member)}", skipping`);
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
members.push(member);
|
|
1622
|
+
}
|
|
1623
|
+
projectGroups[groupName] = members;
|
|
1624
|
+
}
|
|
1625
|
+
return { mode, projects, projectGroups };
|
|
1626
|
+
}
|
|
1627
|
+
var cachedRunMode;
|
|
1628
|
+
function getRunMode() {
|
|
1629
|
+
if (cachedRunMode === void 0) {
|
|
1630
|
+
cachedRunMode = readRunMode();
|
|
1631
|
+
}
|
|
1632
|
+
return cachedRunMode;
|
|
1633
|
+
}
|
|
1634
|
+
function readRunMode() {
|
|
1635
|
+
let raw;
|
|
1636
|
+
try {
|
|
1637
|
+
raw = readRawConfig();
|
|
1638
|
+
} catch (err) {
|
|
1639
|
+
console.warn(`[config] failed to read config file, using "${DEFAULT_RUN_MODE}" mode: ${err}`);
|
|
1640
|
+
return DEFAULT_RUN_MODE;
|
|
1641
|
+
}
|
|
1642
|
+
if (raw === void 0) return DEFAULT_RUN_MODE;
|
|
1643
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
1644
|
+
return DEFAULT_RUN_MODE;
|
|
1645
|
+
}
|
|
1646
|
+
return parseMode(raw, getUserConfigPath());
|
|
1647
|
+
}
|
|
1648
|
+
function findProjectNameByPath(path) {
|
|
1649
|
+
let config;
|
|
1650
|
+
try {
|
|
1651
|
+
config = loadUserConfig();
|
|
1652
|
+
} catch {
|
|
1653
|
+
return void 0;
|
|
1654
|
+
}
|
|
1655
|
+
const target = resolve(path);
|
|
1656
|
+
for (const [name, projectPath] of Object.entries(config.projects)) {
|
|
1657
|
+
if (resolve(projectPath) === target) return name;
|
|
1658
|
+
}
|
|
1659
|
+
return void 0;
|
|
1660
|
+
}
|
|
1661
|
+
function resolveTargetProjects(requested, config) {
|
|
1662
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1663
|
+
for (const name of requested) {
|
|
1664
|
+
if (name === RESERVED_ALL) {
|
|
1665
|
+
for (const [projectName, projectPath] of Object.entries(config.projects)) {
|
|
1666
|
+
resolved.set(projectName, { name: projectName, path: projectPath });
|
|
1667
|
+
}
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1670
|
+
if (Object.prototype.hasOwnProperty.call(config.projects, name)) {
|
|
1671
|
+
resolved.set(name, { name, path: config.projects[name] });
|
|
1672
|
+
continue;
|
|
1673
|
+
}
|
|
1674
|
+
if (Object.prototype.hasOwnProperty.call(config.projectGroups, name)) {
|
|
1675
|
+
for (const projectName of config.projectGroups[name]) {
|
|
1676
|
+
const projectPath = config.projects[projectName];
|
|
1677
|
+
if (projectPath === void 0) continue;
|
|
1678
|
+
resolved.set(projectName, { name: projectName, path: projectPath });
|
|
1679
|
+
}
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
const availableProjects = Object.keys(config.projects).join(", ") || "(none)";
|
|
1683
|
+
const availableGroups = Object.keys(config.projectGroups).join(", ") || "(none)";
|
|
1684
|
+
throw new UserConfigError(
|
|
1685
|
+
`Unknown project or group: "${name}". Available projects: ${availableProjects}. Available groups: ${availableGroups}.`
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
const resolvedProjects = Array.from(resolved.values());
|
|
1689
|
+
if (resolvedProjects.length === 0) {
|
|
1690
|
+
throw new UserConfigError(`No projects resolved from requested targets: ${requested.join(", ")}`);
|
|
1691
|
+
}
|
|
1692
|
+
return resolvedProjects;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1174
1695
|
// src/process-manager.ts
|
|
1175
1696
|
var childProcesses = /* @__PURE__ */ new Map();
|
|
1697
|
+
var herdrTasks = /* @__PURE__ */ new Map();
|
|
1698
|
+
var herdrAbortSignal = { aborted: false };
|
|
1176
1699
|
var tasks = /* @__PURE__ */ new Map();
|
|
1177
1700
|
var shuttingDown = false;
|
|
1178
1701
|
function setShuttingDown() {
|
|
@@ -1228,7 +1751,9 @@ function renderTable() {
|
|
|
1228
1751
|
title: getDisplayWidth(t.title) > maxTitleWidth ? truncateToWidth(t.title, maxTitleWidth) : t.title,
|
|
1229
1752
|
worker: t.workerName,
|
|
1230
1753
|
path: t.path ? t.path.length > maxPathWidth ? truncateToWidth(t.path, maxPathWidth) : t.path : "",
|
|
1231
|
-
|
|
1754
|
+
// herdr モードでは agent ステータス(working / blocked 等)を併記し、
|
|
1755
|
+
// 人の介入が必要な blocked にも気づけるようにする。
|
|
1756
|
+
status: t.agentStatus ? `${t.status}:${t.agentStatus}` : t.status,
|
|
1232
1757
|
time: formatTime(t.startedAt),
|
|
1233
1758
|
duration: formatDuration(t.startedAt)
|
|
1234
1759
|
})),
|
|
@@ -1287,6 +1812,65 @@ function ensureRenderInterval() {
|
|
|
1287
1812
|
renderInterval = setInterval(renderTable, 1e3);
|
|
1288
1813
|
renderInterval.unref();
|
|
1289
1814
|
}
|
|
1815
|
+
async function finishTask(id, result, onComplete) {
|
|
1816
|
+
try {
|
|
1817
|
+
await Promise.race([
|
|
1818
|
+
onComplete?.(result.status, result.output) ?? Promise.resolve(),
|
|
1819
|
+
new Promise(
|
|
1820
|
+
(_, reject) => setTimeout(() => reject(new Error("onComplete timed out after 120s")), 12e4).unref()
|
|
1821
|
+
)
|
|
1822
|
+
]);
|
|
1823
|
+
} catch (err) {
|
|
1824
|
+
console.error(`[worker] onComplete error for #${id}: ${err}`);
|
|
1825
|
+
}
|
|
1826
|
+
const task = tasks.get(id);
|
|
1827
|
+
if (task) {
|
|
1828
|
+
task.status = result.status;
|
|
1829
|
+
task.finishedAt = /* @__PURE__ */ new Date();
|
|
1830
|
+
task.agentStatus = void 0;
|
|
1831
|
+
}
|
|
1832
|
+
renderTable();
|
|
1833
|
+
}
|
|
1834
|
+
function resolveProjectName(cwd = process.cwd()) {
|
|
1835
|
+
const injected = process.env.CTW_PROJECT_NAME;
|
|
1836
|
+
if (injected && injected.length > 0) return injected;
|
|
1837
|
+
return findProjectNameByPath(cwd) ?? basename(cwd);
|
|
1838
|
+
}
|
|
1839
|
+
async function runViaHerdr(command, args, id, onComplete, cwd, env) {
|
|
1840
|
+
const { startHerdrTask: startHerdrTask2, stopHerdrTask: stopHerdrTask2, taskTabLabel: taskTabLabel2, waitForHerdrTask: waitForHerdrTask2 } = await Promise.resolve().then(() => (init_herdr_runner(), herdr_runner_exports));
|
|
1841
|
+
const { getCurrentWorkspaceId: getCurrentWorkspaceId2 } = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
|
|
1842
|
+
const label = taskTabLabel2(resolveProjectName(), id);
|
|
1843
|
+
let task;
|
|
1844
|
+
let result;
|
|
1845
|
+
herdrTasks.set(id, { paneId: "", tabId: "" });
|
|
1846
|
+
try {
|
|
1847
|
+
task = await startHerdrTask2({
|
|
1848
|
+
label,
|
|
1849
|
+
cwd: cwd ?? process.cwd(),
|
|
1850
|
+
argv: [command, ...args],
|
|
1851
|
+
env,
|
|
1852
|
+
workspaceId: getCurrentWorkspaceId2()
|
|
1853
|
+
});
|
|
1854
|
+
herdrTasks.set(id, task);
|
|
1855
|
+
result = await waitForHerdrTask2(task.paneId, {
|
|
1856
|
+
signal: herdrAbortSignal,
|
|
1857
|
+
onBlocked: () => console.warn(`[worker] #${id} is blocked and waiting for input in herdr tab "${label}"`),
|
|
1858
|
+
onStatus: (status) => {
|
|
1859
|
+
const task2 = tasks.get(id);
|
|
1860
|
+
if (task2 && task2.status === "running") task2.agentStatus = status;
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
} catch (err) {
|
|
1864
|
+
console.error(`[worker] failed to run #${id} via herdr: ${err}`);
|
|
1865
|
+
result = { status: "failed", output: `[worker] failed to run the task via herdr: ${err}` };
|
|
1866
|
+
} finally {
|
|
1867
|
+
if (task) {
|
|
1868
|
+
await stopHerdrTask2(task);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
await finishTask(id, result, onComplete);
|
|
1872
|
+
herdrTasks.delete(id);
|
|
1873
|
+
}
|
|
1290
1874
|
function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
|
|
1291
1875
|
tasks.set(id, {
|
|
1292
1876
|
id,
|
|
@@ -1298,6 +1882,10 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
|
|
|
1298
1882
|
});
|
|
1299
1883
|
ensureRenderInterval();
|
|
1300
1884
|
renderTable();
|
|
1885
|
+
if (getRunMode() === "herdr") {
|
|
1886
|
+
void runViaHerdr(command, args, id, onComplete, cwd, env);
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1301
1889
|
const child = spawn(command, args, {
|
|
1302
1890
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1303
1891
|
detached: true,
|
|
@@ -1320,55 +1908,25 @@ function run(command, args, id, title, workerName, path, onComplete, cwd, env) {
|
|
|
1320
1908
|
}
|
|
1321
1909
|
});
|
|
1322
1910
|
child.on("close", async (code) => {
|
|
1323
|
-
const
|
|
1911
|
+
const result = buildTaskResult(
|
|
1324
1912
|
code,
|
|
1325
1913
|
Buffer.concat(outputChunks).toString("utf-8"),
|
|
1326
1914
|
Buffer.concat(stderrChunks).toString("utf-8").slice(-STDERR_TAIL_LIMIT)
|
|
1327
1915
|
);
|
|
1328
|
-
|
|
1329
|
-
await Promise.race([
|
|
1330
|
-
onComplete?.(finalStatus, output) ?? Promise.resolve(),
|
|
1331
|
-
new Promise(
|
|
1332
|
-
(_, reject) => setTimeout(() => reject(new Error("onComplete timed out after 120s")), 12e4).unref()
|
|
1333
|
-
)
|
|
1334
|
-
]);
|
|
1335
|
-
} catch (err) {
|
|
1336
|
-
console.error(`[worker] onComplete error for #${id}: ${err}`);
|
|
1337
|
-
}
|
|
1338
|
-
const task = tasks.get(id);
|
|
1339
|
-
if (task) {
|
|
1340
|
-
task.status = finalStatus;
|
|
1341
|
-
task.finishedAt = /* @__PURE__ */ new Date();
|
|
1342
|
-
}
|
|
1916
|
+
await finishTask(id, result, onComplete);
|
|
1343
1917
|
childProcesses.delete(id);
|
|
1344
|
-
renderTable();
|
|
1345
1918
|
});
|
|
1346
1919
|
child.on("error", async (err) => {
|
|
1347
1920
|
console.error(`[worker] failed to spawn process for #${id}: ${err.message}`);
|
|
1348
|
-
|
|
1349
|
-
await Promise.race([
|
|
1350
|
-
onComplete?.("failed", err.message) ?? Promise.resolve(),
|
|
1351
|
-
new Promise(
|
|
1352
|
-
(_, reject) => setTimeout(() => reject(new Error("onComplete timed out after 120s")), 12e4).unref()
|
|
1353
|
-
)
|
|
1354
|
-
]);
|
|
1355
|
-
} catch (callbackErr) {
|
|
1356
|
-
console.error(`[worker] onComplete error for #${id}: ${callbackErr}`);
|
|
1357
|
-
}
|
|
1358
|
-
const task = tasks.get(id);
|
|
1359
|
-
if (task) {
|
|
1360
|
-
task.status = "failed";
|
|
1361
|
-
task.finishedAt = /* @__PURE__ */ new Date();
|
|
1362
|
-
}
|
|
1921
|
+
await finishTask(id, { status: "failed", output: err.message }, onComplete);
|
|
1363
1922
|
childProcesses.delete(id);
|
|
1364
|
-
renderTable();
|
|
1365
1923
|
});
|
|
1366
1924
|
}
|
|
1367
1925
|
function waitForAllProcesses() {
|
|
1368
|
-
return new Promise((
|
|
1926
|
+
return new Promise((resolve3) => {
|
|
1369
1927
|
const check = () => {
|
|
1370
|
-
if (childProcesses.size === 0) {
|
|
1371
|
-
|
|
1928
|
+
if (childProcesses.size === 0 && herdrTasks.size === 0) {
|
|
1929
|
+
resolve3();
|
|
1372
1930
|
} else {
|
|
1373
1931
|
setTimeout(check, 500);
|
|
1374
1932
|
}
|
|
@@ -1388,6 +1946,9 @@ function shutdown(signal = "SIGTERM") {
|
|
|
1388
1946
|
}
|
|
1389
1947
|
}
|
|
1390
1948
|
}
|
|
1949
|
+
if (herdrTasks.size > 0) {
|
|
1950
|
+
herdrAbortSignal.aborted = true;
|
|
1951
|
+
}
|
|
1391
1952
|
}
|
|
1392
1953
|
|
|
1393
1954
|
// src/random-name.ts
|
|
@@ -1639,16 +2200,16 @@ function isGeneratedWorktreeName(name) {
|
|
|
1639
2200
|
|
|
1640
2201
|
// src/slack.ts
|
|
1641
2202
|
import { exec } from "node:child_process";
|
|
1642
|
-
import { readFileSync as
|
|
1643
|
-
import { homedir as
|
|
1644
|
-
import { join as
|
|
2203
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
2204
|
+
import { homedir as homedir3 } from "node:os";
|
|
2205
|
+
import { join as join4 } from "node:path";
|
|
1645
2206
|
import { promisify as promisify2 } from "node:util";
|
|
1646
2207
|
|
|
1647
2208
|
// src/runcat.ts
|
|
1648
2209
|
import { mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
1649
|
-
import { homedir } from "node:os";
|
|
1650
|
-
import { dirname, join as
|
|
1651
|
-
var RUNCAT_OUT_FILE = process.env.RUNCAT_OUT_FILE ??
|
|
2210
|
+
import { homedir as homedir2 } from "node:os";
|
|
2211
|
+
import { dirname, join as join3 } from "node:path";
|
|
2212
|
+
var RUNCAT_OUT_FILE = process.env.RUNCAT_OUT_FILE ?? join3(process.env.HOME ?? homedir2(), ".claude", "runcat-usage.json");
|
|
1652
2213
|
var JST = "Asia/Tokyo";
|
|
1653
2214
|
function formatG(value) {
|
|
1654
2215
|
return String(Number(value.toPrecision(6)));
|
|
@@ -1712,7 +2273,7 @@ function buildRuncatSnapshot(usage, now = /* @__PURE__ */ new Date()) {
|
|
|
1712
2273
|
function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
|
|
1713
2274
|
const snapshot = buildRuncatSnapshot(usage);
|
|
1714
2275
|
const dir = dirname(outFile);
|
|
1715
|
-
const tmp =
|
|
2276
|
+
const tmp = join3(dir, `.runcat-${process.pid}-${Date.now()}.json`);
|
|
1716
2277
|
try {
|
|
1717
2278
|
mkdirSync(dir, { recursive: true });
|
|
1718
2279
|
writeFileSync(tmp, JSON.stringify(snapshot), "utf-8");
|
|
@@ -1749,13 +2310,13 @@ async function getOAuthToken() {
|
|
|
1749
2310
|
const { stdout } = await execAsync('security find-generic-password -s "Claude Code-credentials" -w');
|
|
1750
2311
|
return extractToken(JSON.parse(stdout.trim()));
|
|
1751
2312
|
} catch {
|
|
1752
|
-
const raw =
|
|
2313
|
+
const raw = readFileSync3(join4(homedir3(), ".claude", ".credentials.json"), "utf-8");
|
|
1753
2314
|
return extractToken(JSON.parse(raw));
|
|
1754
2315
|
}
|
|
1755
2316
|
}
|
|
1756
2317
|
function readUsageCache() {
|
|
1757
2318
|
try {
|
|
1758
|
-
const raw =
|
|
2319
|
+
const raw = readFileSync3(USAGE_CACHE_PATH, "utf-8");
|
|
1759
2320
|
const cached = JSON.parse(raw);
|
|
1760
2321
|
if (Date.now() - cached.timestamp < USAGE_CACHE_TTL_SECONDS * 1e3) {
|
|
1761
2322
|
return cached.data;
|
|
@@ -1857,11 +2418,11 @@ async function notifyError(workerName, repoName, error) {
|
|
|
1857
2418
|
import { execFile as execFile2 } from "node:child_process";
|
|
1858
2419
|
import { promisify as promisify3 } from "node:util";
|
|
1859
2420
|
import { readdir, rm, stat } from "node:fs/promises";
|
|
1860
|
-
import { basename, resolve, sep } from "node:path";
|
|
2421
|
+
import { basename as basename2, resolve as resolve2, sep } from "node:path";
|
|
1861
2422
|
var execFileAsync2 = promisify3(execFile2);
|
|
1862
2423
|
var WORKTREES_DIR = ".claude/worktrees";
|
|
1863
2424
|
function isManagedWorktreePath(path) {
|
|
1864
|
-
return
|
|
2425
|
+
return resolve2(path).startsWith(resolve2(WORKTREES_DIR) + sep);
|
|
1865
2426
|
}
|
|
1866
2427
|
async function pathExists(path) {
|
|
1867
2428
|
try {
|
|
@@ -1948,7 +2509,7 @@ async function removeWorktree(worktreeId) {
|
|
|
1948
2509
|
const worktreePath = getWorktreePath(worktreeId);
|
|
1949
2510
|
let entry;
|
|
1950
2511
|
try {
|
|
1951
|
-
entry = (await listWorktreeEntries()).find((e) =>
|
|
2512
|
+
entry = (await listWorktreeEntries()).find((e) => resolve2(e.path) === resolve2(worktreePath));
|
|
1952
2513
|
} catch (error) {
|
|
1953
2514
|
console.error(`[worktree] Failed to list worktrees:`, error);
|
|
1954
2515
|
}
|
|
@@ -1972,7 +2533,7 @@ async function removeWorktreeByBranch(branchName) {
|
|
|
1972
2533
|
console.log(`[worktree] Skipping unmanaged worktree for branch ${branchName}: ${entry.path}`);
|
|
1973
2534
|
continue;
|
|
1974
2535
|
}
|
|
1975
|
-
const worktreeId =
|
|
2536
|
+
const worktreeId = basename2(entry.path);
|
|
1976
2537
|
if (isWorktreeInUse(worktreeId)) {
|
|
1977
2538
|
console.log(`[worktree] Skipping worktree in use by a running task for branch ${branchName}: ${entry.path}`);
|
|
1978
2539
|
continue;
|
|
@@ -1999,7 +2560,7 @@ async function removeStaleWorktrees() {
|
|
|
1999
2560
|
try {
|
|
2000
2561
|
for (const entry of await listWorktreeEntries()) {
|
|
2001
2562
|
if (!isManagedWorktreePath(entry.path)) continue;
|
|
2002
|
-
const worktreeId =
|
|
2563
|
+
const worktreeId = basename2(entry.path);
|
|
2003
2564
|
if (isGeneratedWorktreeName(worktreeId)) worktreeIds.add(worktreeId);
|
|
2004
2565
|
}
|
|
2005
2566
|
} catch (error) {
|
|
@@ -2058,22 +2619,8 @@ function createIssuePollingWorker(config) {
|
|
|
2058
2619
|
const { model, effort, skill } = getWorkerConfig(config.name);
|
|
2059
2620
|
const command = skill || config.command;
|
|
2060
2621
|
const parentNumber = issue.parent?.number;
|
|
2061
|
-
const
|
|
2062
|
-
|
|
2063
|
-
`${command} ${issue.number}`,
|
|
2064
|
-
"--dangerously-skip-permissions",
|
|
2065
|
-
"--chrome",
|
|
2066
|
-
"--disallowedTools",
|
|
2067
|
-
DISALLOWED_TOOLS_ARG,
|
|
2068
|
-
"--append-system-prompt",
|
|
2069
|
-
SYSTEM_PROMPT,
|
|
2070
|
-
"--append-subagent-system-prompt",
|
|
2071
|
-
SUBAGENT_SYSTEM_PROMPT,
|
|
2072
|
-
"--model",
|
|
2073
|
-
model,
|
|
2074
|
-
"--effort",
|
|
2075
|
-
effort
|
|
2076
|
-
];
|
|
2622
|
+
const mode = getRunMode();
|
|
2623
|
+
const claudeArgs = buildClaudeArgs({ mode, prompt: `${command} ${issue.number}`, model, effort });
|
|
2077
2624
|
let baseBranch = defaultBranch;
|
|
2078
2625
|
if (parentNumber !== void 0) {
|
|
2079
2626
|
baseBranch = `cc-epic-${parentNumber}`;
|
|
@@ -2129,7 +2676,7 @@ function createIssuePollingWorker(config) {
|
|
|
2129
2676
|
}
|
|
2130
2677
|
},
|
|
2131
2678
|
cwd,
|
|
2132
|
-
|
|
2679
|
+
buildClaudeEnv(mode)
|
|
2133
2680
|
);
|
|
2134
2681
|
} catch (err) {
|
|
2135
2682
|
console.error(`[${config.name}] setup error for #${issue.number}: ${err}`);
|
|
@@ -2238,24 +2785,10 @@ function createPrPollingWorker(config) {
|
|
|
2238
2785
|
const cwd = getWorktreePath(worktreeId);
|
|
2239
2786
|
const { model, effort, skill } = getWorkerConfig(config.name);
|
|
2240
2787
|
const command = skill || config.command;
|
|
2788
|
+
const mode = getRunMode();
|
|
2241
2789
|
run(
|
|
2242
2790
|
"claude",
|
|
2243
|
-
|
|
2244
|
-
"-p",
|
|
2245
|
-
`${command} ${pr.number}`,
|
|
2246
|
-
"--dangerously-skip-permissions",
|
|
2247
|
-
"--chrome",
|
|
2248
|
-
"--disallowedTools",
|
|
2249
|
-
DISALLOWED_TOOLS_ARG,
|
|
2250
|
-
"--append-system-prompt",
|
|
2251
|
-
SYSTEM_PROMPT,
|
|
2252
|
-
"--append-subagent-system-prompt",
|
|
2253
|
-
SUBAGENT_SYSTEM_PROMPT,
|
|
2254
|
-
"--model",
|
|
2255
|
-
model,
|
|
2256
|
-
"--effort",
|
|
2257
|
-
effort
|
|
2258
|
-
],
|
|
2791
|
+
buildClaudeArgs({ mode, prompt: `${command} ${pr.number}`, model, effort }),
|
|
2259
2792
|
pr.number,
|
|
2260
2793
|
`PR #${pr.number} (${pr.headRefName})`,
|
|
2261
2794
|
config.name,
|
|
@@ -2298,7 +2831,7 @@ function createPrPollingWorker(config) {
|
|
|
2298
2831
|
}
|
|
2299
2832
|
},
|
|
2300
2833
|
cwd,
|
|
2301
|
-
|
|
2834
|
+
buildClaudeEnv(mode)
|
|
2302
2835
|
);
|
|
2303
2836
|
} catch (err) {
|
|
2304
2837
|
console.error(`[${config.name}] setup error for PR #${pr.number}: ${err}`);
|
|
@@ -2559,7 +3092,7 @@ async function init(options = {}) {
|
|
|
2559
3092
|
// src/commands/run-command.ts
|
|
2560
3093
|
import { spawn as spawn2 } from "node:child_process";
|
|
2561
3094
|
function runCommand(command, args) {
|
|
2562
|
-
return new Promise((
|
|
3095
|
+
return new Promise((resolve3, reject) => {
|
|
2563
3096
|
const child = spawn2(command, args, {
|
|
2564
3097
|
stdio: "inherit",
|
|
2565
3098
|
shell: process.platform === "win32"
|
|
@@ -2582,7 +3115,7 @@ function runCommand(command, args) {
|
|
|
2582
3115
|
child.on("close", (code) => {
|
|
2583
3116
|
cleanup();
|
|
2584
3117
|
if (code === 0) {
|
|
2585
|
-
|
|
3118
|
+
resolve3();
|
|
2586
3119
|
} else {
|
|
2587
3120
|
reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`));
|
|
2588
3121
|
}
|
|
@@ -2686,14 +3219,14 @@ async function update() {
|
|
|
2686
3219
|
}
|
|
2687
3220
|
|
|
2688
3221
|
// src/commands/version.ts
|
|
2689
|
-
import { readFileSync as
|
|
2690
|
-
import { dirname as dirname2, join as
|
|
3222
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
3223
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
2691
3224
|
import { fileURLToPath } from "node:url";
|
|
2692
3225
|
function version() {
|
|
2693
3226
|
const here = dirname2(fileURLToPath(import.meta.url));
|
|
2694
|
-
const pkgPath =
|
|
3227
|
+
const pkgPath = join5(here, "..", "package.json");
|
|
2695
3228
|
try {
|
|
2696
|
-
const pkg = JSON.parse(
|
|
3229
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
2697
3230
|
console.log(pkg.version ?? "unknown");
|
|
2698
3231
|
} catch (err) {
|
|
2699
3232
|
console.error(`[version] Failed to read version: ${err.message}`);
|
|
@@ -2744,141 +3277,6 @@ function buildForwardedCommand(argv) {
|
|
|
2744
3277
|
return ["claude-task-worker", ...tokens.map(shellQuote)].join(" ");
|
|
2745
3278
|
}
|
|
2746
3279
|
|
|
2747
|
-
// src/projects-config.ts
|
|
2748
|
-
import { readFileSync as readFileSync4, statSync } from "node:fs";
|
|
2749
|
-
import { homedir as homedir3 } from "node:os";
|
|
2750
|
-
import { isAbsolute, join as join5 } from "node:path";
|
|
2751
|
-
var RESERVED_ALL = "all";
|
|
2752
|
-
var ProjectsConfigError = class extends Error {
|
|
2753
|
-
constructor(message) {
|
|
2754
|
-
super(message);
|
|
2755
|
-
this.name = "ProjectsConfigError";
|
|
2756
|
-
}
|
|
2757
|
-
};
|
|
2758
|
-
function getProjectsConfigPath() {
|
|
2759
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
2760
|
-
const configHome = xdg && xdg.length > 0 ? xdg : join5(homedir3(), ".config");
|
|
2761
|
-
return join5(configHome, "claude-task-worker", "projects.json");
|
|
2762
|
-
}
|
|
2763
|
-
var PROJECTS_CONFIG_PATH = getProjectsConfigPath();
|
|
2764
|
-
function isDirectory(path) {
|
|
2765
|
-
try {
|
|
2766
|
-
return statSync(path).isDirectory();
|
|
2767
|
-
} catch {
|
|
2768
|
-
return false;
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
function loadProjectsConfig() {
|
|
2772
|
-
const configPath = PROJECTS_CONFIG_PATH;
|
|
2773
|
-
let raw;
|
|
2774
|
-
try {
|
|
2775
|
-
raw = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
2776
|
-
} catch (err) {
|
|
2777
|
-
if (err.code === "ENOENT") {
|
|
2778
|
-
throw new ProjectsConfigError(`projects.json not found: ${configPath}`);
|
|
2779
|
-
}
|
|
2780
|
-
if (err instanceof SyntaxError) {
|
|
2781
|
-
throw new ProjectsConfigError(`projects.json contains invalid JSON: ${configPath}: ${err.message}`);
|
|
2782
|
-
}
|
|
2783
|
-
throw err;
|
|
2784
|
-
}
|
|
2785
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2786
|
-
throw new ProjectsConfigError(`projects.json must contain a JSON object: ${configPath}`);
|
|
2787
|
-
}
|
|
2788
|
-
if (!("projects" in raw) || typeof raw["projects"] !== "object" || raw["projects"] === null || Array.isArray(raw["projects"])) {
|
|
2789
|
-
throw new ProjectsConfigError(`projects.json must contain a "projects" section as an object: ${configPath}`);
|
|
2790
|
-
}
|
|
2791
|
-
if ("projectGroups" in raw && (typeof raw["projectGroups"] !== "object" || raw["projectGroups"] === null || Array.isArray(raw["projectGroups"]))) {
|
|
2792
|
-
throw new ProjectsConfigError(`projects.json "projectGroups" must be an object: ${configPath}`);
|
|
2793
|
-
}
|
|
2794
|
-
const rawProjects = raw["projects"];
|
|
2795
|
-
const rawProjectGroups = "projectGroups" in raw ? raw["projectGroups"] : {};
|
|
2796
|
-
const projectKeys = Object.keys(rawProjects);
|
|
2797
|
-
const groupKeys = Object.keys(rawProjectGroups);
|
|
2798
|
-
if (projectKeys.includes(RESERVED_ALL) || groupKeys.includes(RESERVED_ALL)) {
|
|
2799
|
-
throw new ProjectsConfigError(
|
|
2800
|
-
`"${RESERVED_ALL}" is a reserved word and cannot be used as a key in "projects" or "projectGroups"`
|
|
2801
|
-
);
|
|
2802
|
-
}
|
|
2803
|
-
const groupKeySet = new Set(groupKeys);
|
|
2804
|
-
const duplicateKeys = projectKeys.filter((key) => groupKeySet.has(key));
|
|
2805
|
-
if (duplicateKeys.length > 0) {
|
|
2806
|
-
throw new ProjectsConfigError(
|
|
2807
|
-
`"projects" and "projectGroups" share the same key namespace; duplicate keys are not allowed: ${duplicateKeys.join(", ")}`
|
|
2808
|
-
);
|
|
2809
|
-
}
|
|
2810
|
-
const projects = {};
|
|
2811
|
-
for (const [name, value] of Object.entries(rawProjects)) {
|
|
2812
|
-
if (name === "__proto__") {
|
|
2813
|
-
throw new ProjectsConfigError(`"__proto__" cannot be used as a key in "projects": ${configPath}`);
|
|
2814
|
-
}
|
|
2815
|
-
if (typeof value !== "string" || !isAbsolute(value)) {
|
|
2816
|
-
console.warn(`[projects-config] invalid projects.${name}: expected an absolute path, skipping`);
|
|
2817
|
-
continue;
|
|
2818
|
-
}
|
|
2819
|
-
if (!isDirectory(value)) {
|
|
2820
|
-
console.warn(`[projects-config] projects.${name} does not exist as a directory: ${value}, skipping`);
|
|
2821
|
-
continue;
|
|
2822
|
-
}
|
|
2823
|
-
projects[name] = value;
|
|
2824
|
-
}
|
|
2825
|
-
const projectGroups = {};
|
|
2826
|
-
for (const [groupName, value] of Object.entries(rawProjectGroups)) {
|
|
2827
|
-
if (groupName === "__proto__") {
|
|
2828
|
-
throw new ProjectsConfigError(`"__proto__" cannot be used as a key in "projectGroups": ${configPath}`);
|
|
2829
|
-
}
|
|
2830
|
-
if (!Array.isArray(value)) {
|
|
2831
|
-
console.warn(`[projects-config] invalid projectGroups.${groupName}: expected an array, skipping`);
|
|
2832
|
-
continue;
|
|
2833
|
-
}
|
|
2834
|
-
const members = [];
|
|
2835
|
-
for (const member of value) {
|
|
2836
|
-
if (typeof member !== "string" || !Object.prototype.hasOwnProperty.call(projects, member)) {
|
|
2837
|
-
console.warn(
|
|
2838
|
-
`[projects-config] projectGroups.${groupName} references unknown project "${String(member)}", skipping`
|
|
2839
|
-
);
|
|
2840
|
-
continue;
|
|
2841
|
-
}
|
|
2842
|
-
members.push(member);
|
|
2843
|
-
}
|
|
2844
|
-
projectGroups[groupName] = members;
|
|
2845
|
-
}
|
|
2846
|
-
return { projects, projectGroups };
|
|
2847
|
-
}
|
|
2848
|
-
function resolveTargetProjects(requested, config) {
|
|
2849
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
2850
|
-
for (const name of requested) {
|
|
2851
|
-
if (name === RESERVED_ALL) {
|
|
2852
|
-
for (const [projectName, projectPath] of Object.entries(config.projects)) {
|
|
2853
|
-
resolved.set(projectName, { name: projectName, path: projectPath });
|
|
2854
|
-
}
|
|
2855
|
-
continue;
|
|
2856
|
-
}
|
|
2857
|
-
if (Object.prototype.hasOwnProperty.call(config.projects, name)) {
|
|
2858
|
-
resolved.set(name, { name, path: config.projects[name] });
|
|
2859
|
-
continue;
|
|
2860
|
-
}
|
|
2861
|
-
if (Object.prototype.hasOwnProperty.call(config.projectGroups, name)) {
|
|
2862
|
-
for (const projectName of config.projectGroups[name]) {
|
|
2863
|
-
const projectPath = config.projects[projectName];
|
|
2864
|
-
if (projectPath === void 0) continue;
|
|
2865
|
-
resolved.set(projectName, { name: projectName, path: projectPath });
|
|
2866
|
-
}
|
|
2867
|
-
continue;
|
|
2868
|
-
}
|
|
2869
|
-
const availableProjects = Object.keys(config.projects).join(", ") || "(none)";
|
|
2870
|
-
const availableGroups = Object.keys(config.projectGroups).join(", ") || "(none)";
|
|
2871
|
-
throw new ProjectsConfigError(
|
|
2872
|
-
`Unknown project or group: "${name}". Available projects: ${availableProjects}. Available groups: ${availableGroups}.`
|
|
2873
|
-
);
|
|
2874
|
-
}
|
|
2875
|
-
const resolvedProjects = Array.from(resolved.values());
|
|
2876
|
-
if (resolvedProjects.length === 0) {
|
|
2877
|
-
throw new ProjectsConfigError(`No projects resolved from requested targets: ${requested.join(", ")}`);
|
|
2878
|
-
}
|
|
2879
|
-
return resolvedProjects;
|
|
2880
|
-
}
|
|
2881
|
-
|
|
2882
3280
|
// src/index.ts
|
|
2883
3281
|
var WORKERS = {
|
|
2884
3282
|
"exec-issue": execIssueWorker,
|
|
@@ -2981,6 +3379,18 @@ process.on("unhandledRejection", (err) => {
|
|
|
2981
3379
|
console.error("[worker] unhandled rejection:", err);
|
|
2982
3380
|
process.exit(1);
|
|
2983
3381
|
});
|
|
3382
|
+
async function assertRunModeAvailable() {
|
|
3383
|
+
if (getRunMode() !== "herdr") return;
|
|
3384
|
+
const herdr = await Promise.resolve().then(() => (init_herdr(), herdr_exports));
|
|
3385
|
+
try {
|
|
3386
|
+
await herdr.checkHerdrAvailable();
|
|
3387
|
+
} catch (err) {
|
|
3388
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3389
|
+
console.error(`[worker] config.json has mode "herdr" but herdr is unavailable: ${message}`);
|
|
3390
|
+
process.exit(1);
|
|
3391
|
+
}
|
|
3392
|
+
console.log("[worker] run mode: herdr (each task runs as a TUI session in its own herdr tab)");
|
|
3393
|
+
}
|
|
2984
3394
|
if (!hasProjectFilter()) {
|
|
2985
3395
|
process.on("SIGTERM", async () => {
|
|
2986
3396
|
if (isShuttingDown()) return;
|
|
@@ -2998,7 +3408,7 @@ if (!hasProjectFilter()) {
|
|
|
2998
3408
|
forceKilling = true;
|
|
2999
3409
|
console.log("\n[worker] Force killing running tasks... (cleaning up labels and worktrees)");
|
|
3000
3410
|
shutdown("SIGKILL");
|
|
3001
|
-
const cleanupTimeout = new Promise((
|
|
3411
|
+
const cleanupTimeout = new Promise((resolve3) => setTimeout(resolve3, 6e4).unref());
|
|
3002
3412
|
await Promise.race([waitForAllProcesses(), cleanupTimeout]);
|
|
3003
3413
|
process.exit(1);
|
|
3004
3414
|
}
|
|
@@ -3022,7 +3432,7 @@ if (hasProjectFilter()) {
|
|
|
3022
3432
|
process.on("SIGTERM", shutdownController.handle);
|
|
3023
3433
|
process.on("SIGINT", shutdownController.handle);
|
|
3024
3434
|
try {
|
|
3025
|
-
const config =
|
|
3435
|
+
const config = loadUserConfig();
|
|
3026
3436
|
const projects = resolveTargetProjects(parseProjectFilters(), config);
|
|
3027
3437
|
const forwardedCommand = buildForwardedCommand(process.argv.slice(2));
|
|
3028
3438
|
sessions = await dispatcher.runDispatcher(projects, forwardedCommand);
|
|
@@ -3037,7 +3447,7 @@ if (hasProjectFilter()) {
|
|
|
3037
3447
|
process.exit(0);
|
|
3038
3448
|
}
|
|
3039
3449
|
} catch (err) {
|
|
3040
|
-
if (err instanceof
|
|
3450
|
+
if (err instanceof UserConfigError || err instanceof herdr.HerdrUnavailableError) {
|
|
3041
3451
|
console.error(`[dispatcher] ${err.message}`);
|
|
3042
3452
|
process.exit(1);
|
|
3043
3453
|
}
|
|
@@ -3069,6 +3479,7 @@ if (hasProjectFilter()) {
|
|
|
3069
3479
|
const epicFilters = parseEpicFilters();
|
|
3070
3480
|
const labelFilters = parseLabelFilters();
|
|
3071
3481
|
(async () => {
|
|
3482
|
+
await assertRunModeAvailable();
|
|
3072
3483
|
await removeStaleWorktrees();
|
|
3073
3484
|
await Promise.all([
|
|
3074
3485
|
execIssueWorker({ epicFilters, labelFilters }),
|
|
@@ -3084,6 +3495,7 @@ if (hasProjectFilter()) {
|
|
|
3084
3495
|
const epicFilters = parseEpicFilters();
|
|
3085
3496
|
const labelFilters = parseLabelFilters();
|
|
3086
3497
|
(async () => {
|
|
3498
|
+
await assertRunModeAvailable();
|
|
3087
3499
|
await removeStaleWorktrees();
|
|
3088
3500
|
await Promise.all([
|
|
3089
3501
|
execIssueWorker({ epicFilters, labelFilters }),
|
|
@@ -3102,6 +3514,7 @@ if (hasProjectFilter()) {
|
|
|
3102
3514
|
const epicFilters = parseEpicFilters();
|
|
3103
3515
|
const labelFilters = parseLabelFilters();
|
|
3104
3516
|
(async () => {
|
|
3517
|
+
await assertRunModeAvailable();
|
|
3105
3518
|
await removeStaleWorktrees();
|
|
3106
3519
|
await WORKERS[workerType]({ epicFilters, labelFilters });
|
|
3107
3520
|
})();
|