@matthugh1/conductor-cli 0.2.0 → 0.2.3
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/dist/agent-spawner-BNOGEYDK.js +232 -0
- package/dist/agent.js +227 -89
- package/dist/{chunk-IHARLSA6.js → branch-overview-RRHX3XGY.js} +116 -10
- package/dist/chunk-6AA726KG.js +238 -0
- package/dist/{chunk-FAZ7FCZQ.js → chunk-N2KKNG4C.js} +15 -3
- package/dist/{chunk-MJKFQIYA.js → cli-config-LEERSU5N.js} +23 -25
- package/dist/daemon-ZJDZIP3R.js +607 -0
- package/dist/daemon-client-CTYOJMJP.js +435 -0
- package/dist/{git-hooks-UZJ6AER4.js → git-hooks-RQ6WJQS4.js} +1 -2
- package/dist/{git-wrapper-DVJ46TMA.js → git-wrapper-QRZYTYCZ.js} +1 -2
- package/dist/runner-prompt-MOOPKA5P.js +9 -0
- package/package.json +1 -1
- package/dist/branch-overview-XVHTGFCJ.js +0 -18
- package/dist/chunk-4YEHSYVN.js +0 -17
- package/dist/chunk-JZT526HU.js +0 -536
- package/dist/chunk-PANC6BTV.js +0 -151
- package/dist/chunk-VYINBHPQ.js +0 -22
- package/dist/cli-config-TDSTAXIA.js +0 -17
- package/dist/cli-tasks-NW3BONXC.js +0 -179
- package/dist/db-U6Y3QJDD.js +0 -16
- package/dist/git-snapshots-N3FBS7T3.js +0 -90
- package/dist/health-CTND2ANA.js +0 -147
- package/dist/health-snapshots-6MUVHE3G.js +0 -39
- package/dist/runner-prompt-2B6EXGN6.js +0 -139
- package/dist/work-queue-YE5P4S7R.js +0 -764
- package/dist/worktree-manager-QKRBTPVC.js +0 -30
package/dist/agent.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ensureDatabaseUrl,
|
|
4
|
-
postToServer
|
|
5
|
-
} from "./chunk-MJKFQIYA.js";
|
|
6
2
|
|
|
7
3
|
// ../../src/cli/agent.ts
|
|
8
4
|
import { resolve } from "path";
|
|
@@ -18,6 +14,12 @@ function parseArgs(argv) {
|
|
|
18
14
|
let consecutiveFailures = 3;
|
|
19
15
|
let skipPermissions = false;
|
|
20
16
|
let dryRun = false;
|
|
17
|
+
let checkInterval = 3e4;
|
|
18
|
+
let maxPerDay = 50;
|
|
19
|
+
let maxConcurrent = 1;
|
|
20
|
+
let noWorktree = false;
|
|
21
|
+
let apiUrl;
|
|
22
|
+
let apiKey;
|
|
21
23
|
let command;
|
|
22
24
|
let subcommand;
|
|
23
25
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -36,6 +38,18 @@ function parseArgs(argv) {
|
|
|
36
38
|
skipPermissions = true;
|
|
37
39
|
} else if (arg === "--dry-run") {
|
|
38
40
|
dryRun = true;
|
|
41
|
+
} else if (arg === "--check-interval" && i + 1 < args.length) {
|
|
42
|
+
checkInterval = Number.parseInt(args[++i], 10);
|
|
43
|
+
} else if (arg === "--max-per-day" && i + 1 < args.length) {
|
|
44
|
+
maxPerDay = Number.parseInt(args[++i], 10);
|
|
45
|
+
} else if (arg === "--max-concurrent" && i + 1 < args.length) {
|
|
46
|
+
maxConcurrent = Number.parseInt(args[++i], 10);
|
|
47
|
+
} else if (arg === "--no-worktree") {
|
|
48
|
+
noWorktree = true;
|
|
49
|
+
} else if (arg === "--api-url" && i + 1 < args.length) {
|
|
50
|
+
apiUrl = args[++i];
|
|
51
|
+
} else if (arg === "--api-key" && i + 1 < args.length) {
|
|
52
|
+
apiKey = args[++i];
|
|
39
53
|
} else if (arg === "--json") {
|
|
40
54
|
json = true;
|
|
41
55
|
} else if (arg === "--help" || arg === "-h") {
|
|
@@ -48,7 +62,7 @@ function parseArgs(argv) {
|
|
|
48
62
|
subcommand = arg;
|
|
49
63
|
}
|
|
50
64
|
}
|
|
51
|
-
return { command, subcommand, projectRoot, project, json, help, once, maxDeliverables, timeout, consecutiveFailures, skipPermissions, dryRun };
|
|
65
|
+
return { command, subcommand, projectRoot, project, json, help, once, maxDeliverables, timeout, consecutiveFailures, skipPermissions, dryRun, checkInterval, maxPerDay, maxConcurrent, noWorktree, apiUrl, apiKey };
|
|
52
66
|
}
|
|
53
67
|
var HELP_TEXT = `
|
|
54
68
|
Conductor \u2014 local agent CLI
|
|
@@ -62,10 +76,12 @@ Commands:
|
|
|
62
76
|
hooks Check hook status or install hooks
|
|
63
77
|
watch Poll for cleanup tasks and execute them locally
|
|
64
78
|
run Autonomous runner \u2014 work through deliverable queue
|
|
79
|
+
daemon Long-running daemon \u2014 polls for autonomous work
|
|
80
|
+
daemon cancel Stop a running daemon
|
|
65
81
|
|
|
66
82
|
Options:
|
|
67
83
|
--project-root <path> Project directory (default: cwd)
|
|
68
|
-
--project <name> Project name (for run
|
|
84
|
+
--project <name> Project name (for run/daemon commands)
|
|
69
85
|
--json Output as JSON instead of plain English
|
|
70
86
|
--once Process one task and exit (watch only)
|
|
71
87
|
--help Show this help text
|
|
@@ -77,6 +93,15 @@ Run options:
|
|
|
77
93
|
--skip-permissions Allow Claude to write files without prompting
|
|
78
94
|
--dry-run Show queue without spawning agents
|
|
79
95
|
|
|
96
|
+
Daemon options:
|
|
97
|
+
--check-interval <ms> Poll interval in milliseconds (default: 30000)
|
|
98
|
+
--max-per-day <n> Max tasks per day (default: 50)
|
|
99
|
+
--max-concurrent <n> Max concurrent agent tasks (default: 1)
|
|
100
|
+
--timeout <minutes> Max minutes per deliverable (default: 120)
|
|
101
|
+
--consecutive-failures <n> Stop after N failures in a row (default: 3)
|
|
102
|
+
--no-worktree Skip git worktree isolation
|
|
103
|
+
--skip-permissions Allow Claude to write files without prompting
|
|
104
|
+
|
|
80
105
|
Examples:
|
|
81
106
|
conductor check
|
|
82
107
|
conductor init --project-root /path/to/project
|
|
@@ -85,6 +110,8 @@ Examples:
|
|
|
85
110
|
conductor watch
|
|
86
111
|
conductor run --project Conductor --skip-permissions
|
|
87
112
|
conductor run --project Conductor --dry-run
|
|
113
|
+
conductor daemon --project Conductor
|
|
114
|
+
conductor daemon cancel --project Conductor
|
|
88
115
|
`.trim();
|
|
89
116
|
function writeOut(text) {
|
|
90
117
|
process.stdout.write(text + "\n");
|
|
@@ -93,12 +120,10 @@ function writeErr(text) {
|
|
|
93
120
|
process.stderr.write(text + "\n");
|
|
94
121
|
}
|
|
95
122
|
async function cmdCheck(projectRoot, jsonOutput) {
|
|
96
|
-
const {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
101
|
-
if (projects.length === 0) {
|
|
123
|
+
const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
|
|
124
|
+
const client = createDaemonClient();
|
|
125
|
+
const project = await client.resolveProjectByPath(projectRoot);
|
|
126
|
+
if (!project) {
|
|
102
127
|
if (jsonOutput) {
|
|
103
128
|
writeOut(
|
|
104
129
|
JSON.stringify({
|
|
@@ -113,8 +138,8 @@ async function cmdCheck(projectRoot, jsonOutput) {
|
|
|
113
138
|
}
|
|
114
139
|
return 1;
|
|
115
140
|
}
|
|
116
|
-
const
|
|
117
|
-
const report = await
|
|
141
|
+
const projectId = project.id;
|
|
142
|
+
const report = await runLocalHealthChecks(projectRoot);
|
|
118
143
|
if (jsonOutput) {
|
|
119
144
|
writeOut(JSON.stringify(report, null, 2));
|
|
120
145
|
} else {
|
|
@@ -126,18 +151,15 @@ async function cmdCheck(projectRoot, jsonOutput) {
|
|
|
126
151
|
}
|
|
127
152
|
}
|
|
128
153
|
try {
|
|
129
|
-
|
|
130
|
-
await saveHealthSnapshot(projects[0].id, report);
|
|
154
|
+
await client.saveHealthSnapshot(projectId, report);
|
|
131
155
|
} catch {
|
|
132
|
-
|
|
133
|
-
if (!posted && !jsonOutput) {
|
|
156
|
+
if (!jsonOutput) {
|
|
134
157
|
writeErr("\u26A0 Could not sync results to the dashboard.");
|
|
135
158
|
}
|
|
136
159
|
}
|
|
137
160
|
try {
|
|
138
|
-
const { getGitBranchInfo, formatGitBranchLine, getStatusSummary, getRecentCommits } = await import("./git-wrapper-
|
|
139
|
-
const { getHookStatus } = await import("./git-hooks-
|
|
140
|
-
const { saveGitStatusSnapshot } = await import("./git-snapshots-N3FBS7T3.js");
|
|
161
|
+
const { getGitBranchInfo, formatGitBranchLine, getStatusSummary, getRecentCommits, runGit } = await import("./git-wrapper-QRZYTYCZ.js");
|
|
162
|
+
const { getHookStatus } = await import("./git-hooks-RQ6WJQS4.js");
|
|
141
163
|
const branchInfo = await getGitBranchInfo(projectRoot);
|
|
142
164
|
const branchLine = formatGitBranchLine(branchInfo);
|
|
143
165
|
const status = await getStatusSummary(projectRoot);
|
|
@@ -151,21 +173,20 @@ async function cmdCheck(projectRoot, jsonOutput) {
|
|
|
151
173
|
hookStatus = await getHookStatus(projectRoot);
|
|
152
174
|
} catch {
|
|
153
175
|
}
|
|
154
|
-
const worktreeRows = await dbQuery(
|
|
155
|
-
"SELECT branch_name, worktree_path FROM worktrees WHERE project_id = $1 AND status = 'active'",
|
|
156
|
-
[projects[0].id]
|
|
157
|
-
);
|
|
158
176
|
const worktreeCommits = {};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
177
|
+
try {
|
|
178
|
+
const worktrees = await client.listActiveWorktrees(projectId);
|
|
179
|
+
for (const wt of worktrees) {
|
|
180
|
+
try {
|
|
181
|
+
const out = await runGit(wt.worktreePath, ["log", "-1", "--format=%cI"]);
|
|
182
|
+
const date = out.trim();
|
|
183
|
+
if (date) worktreeCommits[wt.branchName] = date;
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
166
186
|
}
|
|
187
|
+
} catch {
|
|
167
188
|
}
|
|
168
|
-
await saveGitStatusSnapshot(
|
|
189
|
+
await client.saveGitStatusSnapshot(projectId, {
|
|
169
190
|
branch: branchInfo.branchName,
|
|
170
191
|
branchLine,
|
|
171
192
|
status,
|
|
@@ -187,17 +208,16 @@ async function cmdCheck(projectRoot, jsonOutput) {
|
|
|
187
208
|
}
|
|
188
209
|
}
|
|
189
210
|
try {
|
|
190
|
-
const { getGitBranchInfo } = await import("./git-wrapper-
|
|
191
|
-
const { getBranchOverview } = await import("./branch-overview-
|
|
192
|
-
const { saveBranchOverviewSnapshot } = await import("./git-snapshots-N3FBS7T3.js");
|
|
211
|
+
const { getGitBranchInfo } = await import("./git-wrapper-QRZYTYCZ.js");
|
|
212
|
+
const { getBranchOverview } = await import("./branch-overview-RRHX3XGY.js");
|
|
193
213
|
const branchInfo = await getGitBranchInfo(projectRoot);
|
|
194
214
|
let currentBranch = branchInfo.branchName;
|
|
195
215
|
if (currentBranch?.startsWith("detached (")) currentBranch = null;
|
|
196
216
|
const overview = await getBranchOverview(projectRoot, {
|
|
197
|
-
projectId:
|
|
217
|
+
projectId: null,
|
|
198
218
|
currentBranch
|
|
199
219
|
});
|
|
200
|
-
await saveBranchOverviewSnapshot(
|
|
220
|
+
await client.saveBranchOverviewSnapshot(projectId, overview);
|
|
201
221
|
if (!jsonOutput) {
|
|
202
222
|
writeOut(`Branches: ${overview.branches.length} found`);
|
|
203
223
|
}
|
|
@@ -213,14 +233,73 @@ async function cmdCheck(projectRoot, jsonOutput) {
|
|
|
213
233
|
if (hasWarn) return 2;
|
|
214
234
|
return 0;
|
|
215
235
|
}
|
|
236
|
+
async function runLocalHealthChecks(projectRoot) {
|
|
237
|
+
const { existsSync } = await import("fs");
|
|
238
|
+
const { readFile } = await import("fs/promises");
|
|
239
|
+
const { join } = await import("path");
|
|
240
|
+
const { runGit } = await import("./git-wrapper-QRZYTYCZ.js");
|
|
241
|
+
const start = performance.now();
|
|
242
|
+
const checks = [];
|
|
243
|
+
const gitStart = performance.now();
|
|
244
|
+
try {
|
|
245
|
+
const output = await runGit(projectRoot, ["--version"]);
|
|
246
|
+
const version = output.trim().replace("git version ", "");
|
|
247
|
+
checks.push({ name: "Git", status: "pass", message: `Git ${version} found`, durationMs: Math.round(performance.now() - gitStart) });
|
|
248
|
+
} catch {
|
|
249
|
+
checks.push({ name: "Git", status: "fail", message: "Git not found or not working", durationMs: Math.round(performance.now() - gitStart) });
|
|
250
|
+
}
|
|
251
|
+
const hookStart = performance.now();
|
|
252
|
+
const hookPath = join(projectRoot, ".git", "hooks", "post-commit");
|
|
253
|
+
const CONDUCTOR_HOOK_MARKER = "# Conductor narration hook";
|
|
254
|
+
if (!existsSync(hookPath)) {
|
|
255
|
+
checks.push({
|
|
256
|
+
name: "Hooks",
|
|
257
|
+
status: "warn",
|
|
258
|
+
message: "Hooks not installed",
|
|
259
|
+
fixAction: { label: "Install hooks", href: "/git" },
|
|
260
|
+
durationMs: Math.round(performance.now() - hookStart)
|
|
261
|
+
});
|
|
262
|
+
} else {
|
|
263
|
+
try {
|
|
264
|
+
const content = await readFile(hookPath, "utf-8");
|
|
265
|
+
if (content.includes(CONDUCTOR_HOOK_MARKER)) {
|
|
266
|
+
checks.push({ name: "Hooks", status: "pass", message: "Hooks installed", durationMs: Math.round(performance.now() - hookStart) });
|
|
267
|
+
} else {
|
|
268
|
+
checks.push({
|
|
269
|
+
name: "Hooks",
|
|
270
|
+
status: "warn",
|
|
271
|
+
message: "Hook file exists but is not a Conductor hook",
|
|
272
|
+
fixAction: { label: "Install hooks", href: "/git" },
|
|
273
|
+
durationMs: Math.round(performance.now() - hookStart)
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
} catch {
|
|
277
|
+
checks.push({ name: "Hooks", status: "warn", message: "Could not read hook file", durationMs: Math.round(performance.now() - hookStart) });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const apiStart = performance.now();
|
|
281
|
+
try {
|
|
282
|
+
const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
|
|
283
|
+
await createDaemonClient().resolveProjectByPath(projectRoot);
|
|
284
|
+
checks.push({ name: "API", status: "pass", message: "Conductor server reachable", durationMs: Math.round(performance.now() - apiStart) });
|
|
285
|
+
} catch {
|
|
286
|
+
checks.push({ name: "API", status: "warn", message: "Conductor server not reachable", durationMs: Math.round(performance.now() - apiStart) });
|
|
287
|
+
}
|
|
288
|
+
const hasFail = checks.some((c) => c.status === "fail");
|
|
289
|
+
const hasWarn = checks.some((c) => c.status === "warn");
|
|
290
|
+
const overall = hasFail ? "unhealthy" : hasWarn ? "degraded" : "healthy";
|
|
291
|
+
return {
|
|
292
|
+
checks,
|
|
293
|
+
overall,
|
|
294
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
295
|
+
totalDurationMs: Math.round(performance.now() - start)
|
|
296
|
+
};
|
|
297
|
+
}
|
|
216
298
|
async function cmdScan(projectRoot, jsonOutput) {
|
|
217
|
-
const {
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
[projectRoot]
|
|
222
|
-
);
|
|
223
|
-
if (projects.length === 0) {
|
|
299
|
+
const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
|
|
300
|
+
const client = createDaemonClient();
|
|
301
|
+
const project = await client.resolveProjectByPath(projectRoot);
|
|
302
|
+
if (!project) {
|
|
224
303
|
if (jsonOutput) {
|
|
225
304
|
writeOut(
|
|
226
305
|
JSON.stringify({
|
|
@@ -235,8 +314,7 @@ async function cmdScan(projectRoot, jsonOutput) {
|
|
|
235
314
|
}
|
|
236
315
|
return 1;
|
|
237
316
|
}
|
|
238
|
-
const
|
|
239
|
-
const result = await scanWorktrees(projectId, projectRoot);
|
|
317
|
+
const { scan: result } = await client.scanWorktreesViaApi(project.id);
|
|
240
318
|
if (jsonOutput) {
|
|
241
319
|
writeOut(JSON.stringify(result, null, 2));
|
|
242
320
|
} else {
|
|
@@ -262,7 +340,7 @@ async function cmdScan(projectRoot, jsonOutput) {
|
|
|
262
340
|
async function cmdInit(projectRoot, jsonOutput) {
|
|
263
341
|
const { resolve: resolvePath, basename, join } = await import("path");
|
|
264
342
|
const { realpath, mkdir, readFile, writeFile, access } = await import("fs/promises");
|
|
265
|
-
const { getServerBaseUrl } = await import("./cli-config-
|
|
343
|
+
const { getServerBaseUrl } = await import("./cli-config-LEERSU5N.js");
|
|
266
344
|
let root = resolvePath(projectRoot.trim());
|
|
267
345
|
try {
|
|
268
346
|
root = await realpath(root);
|
|
@@ -337,7 +415,7 @@ async function cmdInit(projectRoot, jsonOutput) {
|
|
|
337
415
|
return 0;
|
|
338
416
|
}
|
|
339
417
|
async function cmdHooks(projectRoot, subcommand, jsonOutput) {
|
|
340
|
-
const { getHookStatus, installHooks } = await import("./git-hooks-
|
|
418
|
+
const { getHookStatus, installHooks } = await import("./git-hooks-RQ6WJQS4.js");
|
|
341
419
|
if (subcommand === "install") {
|
|
342
420
|
const result = await installHooks(projectRoot);
|
|
343
421
|
if (jsonOutput) {
|
|
@@ -379,13 +457,11 @@ async function cmdHooks(projectRoot, subcommand, jsonOutput) {
|
|
|
379
457
|
return 2;
|
|
380
458
|
}
|
|
381
459
|
async function cmdWatch(projectRoot, once, jsonOutput) {
|
|
382
|
-
const {
|
|
383
|
-
const {
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
);
|
|
388
|
-
if (projects.length === 0) {
|
|
460
|
+
const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
|
|
461
|
+
const { runGit } = await import("./git-wrapper-QRZYTYCZ.js");
|
|
462
|
+
const client = createDaemonClient();
|
|
463
|
+
const project = await client.resolveProjectByPath(projectRoot);
|
|
464
|
+
if (!project) {
|
|
389
465
|
if (jsonOutput) {
|
|
390
466
|
writeOut(
|
|
391
467
|
JSON.stringify({
|
|
@@ -400,13 +476,56 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
|
|
|
400
476
|
}
|
|
401
477
|
return 1;
|
|
402
478
|
}
|
|
403
|
-
const projectId =
|
|
404
|
-
|
|
479
|
+
const projectId = project.id;
|
|
480
|
+
async function executeTaskViaApi(task) {
|
|
481
|
+
try {
|
|
482
|
+
switch (task.taskType) {
|
|
483
|
+
case "remove_worktree": {
|
|
484
|
+
const worktreeId = task.payload.worktreeId;
|
|
485
|
+
if (!worktreeId) {
|
|
486
|
+
const msg = "Missing worktreeId in task payload.";
|
|
487
|
+
await client.failCliTask(task.id, msg);
|
|
488
|
+
return msg;
|
|
489
|
+
}
|
|
490
|
+
await client.removeWorktreeViaApi(projectId, worktreeId);
|
|
491
|
+
await client.completeCliTask(task.id, { worktreeId });
|
|
492
|
+
return `Removed worktree ${worktreeId}`;
|
|
493
|
+
}
|
|
494
|
+
case "delete_branch": {
|
|
495
|
+
const branchName = task.payload.branchName;
|
|
496
|
+
if (!branchName) {
|
|
497
|
+
const msg = "Missing branchName in task payload.";
|
|
498
|
+
await client.failCliTask(task.id, msg);
|
|
499
|
+
return msg;
|
|
500
|
+
}
|
|
501
|
+
await runGit(projectRoot, ["branch", "-D", branchName]);
|
|
502
|
+
await client.logGitActivityViaApi({
|
|
503
|
+
projectRoot,
|
|
504
|
+
eventType: "branch_delete",
|
|
505
|
+
branch: branchName,
|
|
506
|
+
summary: `Deleted branch "${branchName}"`
|
|
507
|
+
});
|
|
508
|
+
await client.completeCliTask(task.id, { branchName });
|
|
509
|
+
return `Deleted branch "${branchName}"`;
|
|
510
|
+
}
|
|
511
|
+
default: {
|
|
512
|
+
const msg = `Unknown task type: ${task.taskType}`;
|
|
513
|
+
await client.failCliTask(task.id, msg);
|
|
514
|
+
return msg;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
} catch (err) {
|
|
518
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
519
|
+
await client.failCliTask(task.id, msg);
|
|
520
|
+
return `Failed: ${msg}`;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const staleCount = await client.failStaleCliTasks(projectId);
|
|
405
524
|
if (staleCount > 0 && !jsonOutput) {
|
|
406
525
|
writeOut(`Auto-failed ${staleCount} stale task(s).`);
|
|
407
526
|
}
|
|
408
527
|
if (once) {
|
|
409
|
-
const task = await
|
|
528
|
+
const task = await client.claimNextCliTask(projectId);
|
|
410
529
|
if (!task) {
|
|
411
530
|
if (jsonOutput) {
|
|
412
531
|
writeOut(JSON.stringify({ message: "No pending tasks." }));
|
|
@@ -415,7 +534,7 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
|
|
|
415
534
|
}
|
|
416
535
|
return 0;
|
|
417
536
|
}
|
|
418
|
-
const summary = await
|
|
537
|
+
const summary = await executeTaskViaApi(task);
|
|
419
538
|
if (jsonOutput) {
|
|
420
539
|
writeOut(JSON.stringify({ taskId: task.id, taskType: task.taskType, summary }));
|
|
421
540
|
} else {
|
|
@@ -437,9 +556,9 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
|
|
|
437
556
|
process.on("SIGTERM", shutdown);
|
|
438
557
|
while (running) {
|
|
439
558
|
try {
|
|
440
|
-
const task = await
|
|
559
|
+
const task = await client.claimNextCliTask(projectId);
|
|
441
560
|
if (task) {
|
|
442
|
-
const summary = await
|
|
561
|
+
const summary = await executeTaskViaApi(task);
|
|
443
562
|
if (jsonOutput) {
|
|
444
563
|
writeOut(
|
|
445
564
|
JSON.stringify({ taskId: task.id, taskType: task.taskType, summary })
|
|
@@ -448,7 +567,7 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
|
|
|
448
567
|
writeOut(`[${task.taskType}] ${summary}`);
|
|
449
568
|
}
|
|
450
569
|
}
|
|
451
|
-
await
|
|
570
|
+
await client.failStaleCliTasks(projectId);
|
|
452
571
|
} catch (err) {
|
|
453
572
|
const msg = err instanceof Error ? err.message : String(err);
|
|
454
573
|
if (!jsonOutput) {
|
|
@@ -464,24 +583,22 @@ async function cmdWatch(projectRoot, once, jsonOutput) {
|
|
|
464
583
|
async function cmdRun(opts) {
|
|
465
584
|
const { spawn } = await import("child_process");
|
|
466
585
|
const { readFileSync, existsSync } = await import("fs");
|
|
467
|
-
const { assembleAutonomousPrompt } = await import("./runner-prompt-
|
|
468
|
-
const {
|
|
469
|
-
const
|
|
470
|
-
let project
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
project = rows[0].path;
|
|
586
|
+
const { assembleAutonomousPrompt } = await import("./runner-prompt-MOOPKA5P.js");
|
|
587
|
+
const { createDaemonClient } = await import("./daemon-client-CTYOJMJP.js");
|
|
588
|
+
const client = createDaemonClient();
|
|
589
|
+
let project;
|
|
590
|
+
let projectId;
|
|
591
|
+
try {
|
|
592
|
+
const resolved = await client.resolveProject({
|
|
593
|
+
name: opts.projectName,
|
|
594
|
+
path: opts.projectName ? void 0 : opts.projectRoot
|
|
595
|
+
});
|
|
596
|
+
project = resolved.path;
|
|
597
|
+
projectId = resolved.id;
|
|
598
|
+
} catch (err) {
|
|
599
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
600
|
+
writeErr(`Project not found: ${msg}`);
|
|
601
|
+
return 1;
|
|
485
602
|
}
|
|
486
603
|
function ts() {
|
|
487
604
|
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
@@ -525,7 +642,7 @@ async function cmdRun(opts) {
|
|
|
525
642
|
});
|
|
526
643
|
}
|
|
527
644
|
async function getNextItem(skipIds) {
|
|
528
|
-
const queue = await
|
|
645
|
+
const queue = await client.pollWorkQueue(projectId, 50, { autonomous: true });
|
|
529
646
|
for (const item of queue.queue) {
|
|
530
647
|
if (item.type === "deliverable" && (item.tier === "ready" || item.tier === "active") && !skipIds.has(item.entityId)) {
|
|
531
648
|
return item;
|
|
@@ -647,13 +764,6 @@ async function main() {
|
|
|
647
764
|
writeOut(HELP_TEXT);
|
|
648
765
|
process.exit(parsed.help ? 0 : 1);
|
|
649
766
|
}
|
|
650
|
-
const hasDb = await ensureDatabaseUrl();
|
|
651
|
-
if (!hasDb) {
|
|
652
|
-
writeErr(
|
|
653
|
-
"Could not find a database connection. Make sure the MCP server is running (npm run mcp) and try again."
|
|
654
|
-
);
|
|
655
|
-
process.exit(1);
|
|
656
|
-
}
|
|
657
767
|
try {
|
|
658
768
|
let exitCode;
|
|
659
769
|
switch (parsed.command) {
|
|
@@ -692,6 +802,34 @@ async function main() {
|
|
|
692
802
|
jsonOutput: parsed.json
|
|
693
803
|
});
|
|
694
804
|
break;
|
|
805
|
+
case "daemon": {
|
|
806
|
+
const { cmdDaemon, cmdDaemonCancel } = await import("./daemon-ZJDZIP3R.js");
|
|
807
|
+
if (parsed.subcommand === "cancel") {
|
|
808
|
+
exitCode = await cmdDaemonCancel(
|
|
809
|
+
parsed.projectRoot,
|
|
810
|
+
parsed.project,
|
|
811
|
+
parsed.json,
|
|
812
|
+
parsed.apiUrl,
|
|
813
|
+
parsed.apiKey
|
|
814
|
+
);
|
|
815
|
+
} else {
|
|
816
|
+
exitCode = await cmdDaemon({
|
|
817
|
+
projectRoot: parsed.projectRoot,
|
|
818
|
+
projectName: parsed.project,
|
|
819
|
+
apiUrl: parsed.apiUrl,
|
|
820
|
+
apiKey: parsed.apiKey,
|
|
821
|
+
checkInterval: parsed.checkInterval,
|
|
822
|
+
maxPerDay: parsed.maxPerDay,
|
|
823
|
+
maxConsecutiveFailures: parsed.consecutiveFailures,
|
|
824
|
+
timeout: parsed.timeout,
|
|
825
|
+
maxConcurrent: parsed.maxConcurrent,
|
|
826
|
+
noWorktree: parsed.noWorktree,
|
|
827
|
+
skipPermissions: parsed.skipPermissions,
|
|
828
|
+
jsonOutput: parsed.json
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
695
833
|
default:
|
|
696
834
|
writeErr(`Unknown command: ${parsed.command}`);
|
|
697
835
|
writeErr("Run conductor --help for usage.");
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
ConductorError,
|
|
3
4
|
runGit
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
query
|
|
7
|
-
} from "./chunk-PANC6BTV.js";
|
|
5
|
+
} from "./chunk-N2KKNG4C.js";
|
|
8
6
|
|
|
9
7
|
// ../../src/core/branch-overview-match.ts
|
|
10
8
|
function slugifyTitle(title) {
|
|
@@ -71,6 +69,114 @@ function resolveDeliverableLink(branchName, hints, sessionByBranch) {
|
|
|
71
69
|
return null;
|
|
72
70
|
}
|
|
73
71
|
|
|
72
|
+
// ../../src/core/db.ts
|
|
73
|
+
import { Pool } from "pg";
|
|
74
|
+
var pool = null;
|
|
75
|
+
function getDatabaseUrl() {
|
|
76
|
+
const url = process.env.DATABASE_URL;
|
|
77
|
+
if (!url) {
|
|
78
|
+
throw new ConductorError(
|
|
79
|
+
"DB_UNREACHABLE" /* DB_UNREACHABLE */,
|
|
80
|
+
"DATABASE_URL is not set. Set it to your Supabase (or other Postgres) connection string."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return url;
|
|
84
|
+
}
|
|
85
|
+
function getPool() {
|
|
86
|
+
if (pool) {
|
|
87
|
+
return pool;
|
|
88
|
+
}
|
|
89
|
+
const connectionString = getDatabaseUrl();
|
|
90
|
+
const isRemote = connectionString.includes("supabase.co");
|
|
91
|
+
pool = new Pool({
|
|
92
|
+
connectionString,
|
|
93
|
+
max: 10,
|
|
94
|
+
connectionTimeoutMillis: 5e3,
|
|
95
|
+
idleTimeoutMillis: 3e4,
|
|
96
|
+
...isRemote ? { ssl: { rejectUnauthorized: false } } : {}
|
|
97
|
+
});
|
|
98
|
+
pool.on("error", () => {
|
|
99
|
+
console.error(
|
|
100
|
+
"Unexpected database error on an idle connection. Check that Postgres is running."
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
return pool;
|
|
104
|
+
}
|
|
105
|
+
var TRANSIENT_CODES = /* @__PURE__ */ new Set(["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT"]);
|
|
106
|
+
function isTransientConnectionError(err) {
|
|
107
|
+
const code = getErrorCode(err);
|
|
108
|
+
if (code && TRANSIENT_CODES.has(code)) return true;
|
|
109
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
110
|
+
return msg.includes("ECONNRESET") || msg.includes("ECONNREFUSED") || msg.includes("ETIMEDOUT") || msg.includes("connection terminated unexpectedly");
|
|
111
|
+
}
|
|
112
|
+
async function query(sql, params) {
|
|
113
|
+
const p = getPool();
|
|
114
|
+
try {
|
|
115
|
+
const result = await p.query(sql, params);
|
|
116
|
+
return result.rows;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
if (isTransientConnectionError(err)) {
|
|
119
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
120
|
+
try {
|
|
121
|
+
const result = await p.query(sql, params);
|
|
122
|
+
return result.rows;
|
|
123
|
+
} catch (retryErr) {
|
|
124
|
+
throw mapDbError(retryErr);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw mapDbError(err);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function getErrorCode(err) {
|
|
131
|
+
if (err !== null && typeof err === "object" && "code" in err) {
|
|
132
|
+
const code = err.code;
|
|
133
|
+
return typeof code === "string" ? code : void 0;
|
|
134
|
+
}
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
function getBestErrorMessage(err) {
|
|
138
|
+
if (err instanceof AggregateError && err.errors.length > 0) {
|
|
139
|
+
const first = err.errors[0];
|
|
140
|
+
if (first instanceof Error && first.message.trim().length > 0) {
|
|
141
|
+
return first.message;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (err instanceof Error && err.message.trim().length > 0) {
|
|
145
|
+
return err.message;
|
|
146
|
+
}
|
|
147
|
+
return String(err);
|
|
148
|
+
}
|
|
149
|
+
function mapDbError(err) {
|
|
150
|
+
const code = getErrorCode(err);
|
|
151
|
+
const message = getBestErrorMessage(err);
|
|
152
|
+
if (code === "ECONNREFUSED" || code === "ECONNRESET" || code === "ETIMEDOUT" || message.includes("ECONNREFUSED") || message.includes("ECONNRESET") || message.includes("ETIMEDOUT") || message.includes("connect ECONNREFUSED") || message.includes("connection terminated unexpectedly")) {
|
|
153
|
+
return new ConductorError(
|
|
154
|
+
"DB_UNREACHABLE" /* DB_UNREACHABLE */,
|
|
155
|
+
"Could not reach the database. Postgres may not be running on this machine.",
|
|
156
|
+
true
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (message.includes("password") || message.includes("authentication") || message.includes("28P01")) {
|
|
160
|
+
return new ConductorError(
|
|
161
|
+
"DB_QUERY_FAILED" /* DB_QUERY_FAILED */,
|
|
162
|
+
"Could not sign in to the database. Check your username and password."
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (message.includes("does not exist") && message.includes("database")) {
|
|
166
|
+
return new ConductorError(
|
|
167
|
+
"DB_QUERY_FAILED" /* DB_QUERY_FAILED */,
|
|
168
|
+
"That database does not exist yet. Create it first, then try again."
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (message.trim().length > 0) {
|
|
172
|
+
return new ConductorError("DB_QUERY_FAILED" /* DB_QUERY_FAILED */, message);
|
|
173
|
+
}
|
|
174
|
+
return new ConductorError(
|
|
175
|
+
"DB_QUERY_FAILED" /* DB_QUERY_FAILED */,
|
|
176
|
+
"Something went wrong while talking to the database."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
74
180
|
// ../../src/core/branch-overview.ts
|
|
75
181
|
var STALE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
76
182
|
var MAX_COMMITS = 20;
|
|
@@ -339,8 +445,9 @@ async function getConflictBranchesForWorkQueue(projectRoot, options) {
|
|
|
339
445
|
}
|
|
340
446
|
}
|
|
341
447
|
const conflicts = [];
|
|
448
|
+
const SKIP_BRANCHES = /* @__PURE__ */ new Set(["dev", "develop", "staging"]);
|
|
342
449
|
for (const name of namesToScan) {
|
|
343
|
-
if (name === defaultBranch) {
|
|
450
|
+
if (name === defaultBranch || SKIP_BRANCHES.has(name)) {
|
|
344
451
|
continue;
|
|
345
452
|
}
|
|
346
453
|
let lastIso = "";
|
|
@@ -595,11 +702,10 @@ async function getMergeStats(projectRoot, defaultBranch, featureBranch) {
|
|
|
595
702
|
const files = await loadChangedFiles(projectRoot, defaultBranch, featureBranch);
|
|
596
703
|
return { commitCount, fileCount: files.length };
|
|
597
704
|
}
|
|
598
|
-
|
|
599
705
|
export {
|
|
600
|
-
isBranchFullyOnMain,
|
|
601
|
-
getConflictBranchesForWorkQueue,
|
|
602
|
-
getBranchOverview,
|
|
603
706
|
assertCanQueueMerge,
|
|
604
|
-
|
|
707
|
+
getBranchOverview,
|
|
708
|
+
getConflictBranchesForWorkQueue,
|
|
709
|
+
getMergeStats,
|
|
710
|
+
isBranchFullyOnMain
|
|
605
711
|
};
|