@gethmy/agent 1.10.4 → 1.10.6
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 +1 -1
- package/dist/cli.js +371 -225
- package/dist/index.js +371 -225
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -396,8 +396,8 @@ var init_types = __esm(() => {
|
|
|
396
396
|
postSummary: true
|
|
397
397
|
},
|
|
398
398
|
claude: {
|
|
399
|
-
model: "opus",
|
|
400
|
-
escalateModel: "claude-
|
|
399
|
+
model: "claude-opus-4-8",
|
|
400
|
+
escalateModel: "claude-opus-4-8",
|
|
401
401
|
escalateAfterAttempts: 2,
|
|
402
402
|
tiers: {
|
|
403
403
|
simple: "claude-haiku-4-5",
|
|
@@ -2081,8 +2081,127 @@ var init_git_diff_stat = __esm(() => {
|
|
|
2081
2081
|
init_log();
|
|
2082
2082
|
});
|
|
2083
2083
|
|
|
2084
|
+
// src/project-type.ts
|
|
2085
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
2086
|
+
import { existsSync as existsSync4, readdirSync } from "node:fs";
|
|
2087
|
+
function detect(dir) {
|
|
2088
|
+
const cached2 = _cache.get(dir);
|
|
2089
|
+
if (cached2)
|
|
2090
|
+
return cached2;
|
|
2091
|
+
const result = detectUncached(dir);
|
|
2092
|
+
_cache.set(dir, result);
|
|
2093
|
+
log.info(TAG11, `Detected project type in ${dir}: ${result.kind}`);
|
|
2094
|
+
return result;
|
|
2095
|
+
}
|
|
2096
|
+
function detectUncached(dir) {
|
|
2097
|
+
if (existsSync4(`${dir}/package.json`))
|
|
2098
|
+
return { kind: "node" };
|
|
2099
|
+
if (existsSync4(`${dir}/Package.swift`))
|
|
2100
|
+
return { kind: "swift-spm" };
|
|
2101
|
+
const entries = safeReaddir(dir);
|
|
2102
|
+
const workspace = entries.find((e) => e.endsWith(".xcworkspace"));
|
|
2103
|
+
if (workspace) {
|
|
2104
|
+
return {
|
|
2105
|
+
kind: "swift-xcode",
|
|
2106
|
+
xcodeContainer: `${dir}/${workspace}`,
|
|
2107
|
+
xcodeIsWorkspace: true
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
const project = entries.find((e) => e.endsWith(".xcodeproj"));
|
|
2111
|
+
if (project) {
|
|
2112
|
+
return {
|
|
2113
|
+
kind: "swift-xcode",
|
|
2114
|
+
xcodeContainer: `${dir}/${project}`,
|
|
2115
|
+
xcodeIsWorkspace: false
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
return { kind: "unknown" };
|
|
2119
|
+
}
|
|
2120
|
+
function safeReaddir(dir) {
|
|
2121
|
+
try {
|
|
2122
|
+
return readdirSync(dir);
|
|
2123
|
+
} catch {
|
|
2124
|
+
return [];
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
function buildCommand(dir) {
|
|
2128
|
+
const pt = detect(dir);
|
|
2129
|
+
switch (pt.kind) {
|
|
2130
|
+
case "node": {
|
|
2131
|
+
const [cmd, args] = spawnRunArgs("build");
|
|
2132
|
+
return { cmd, args };
|
|
2133
|
+
}
|
|
2134
|
+
case "swift-spm":
|
|
2135
|
+
return { cmd: "swift", args: ["build"] };
|
|
2136
|
+
case "swift-xcode":
|
|
2137
|
+
return xcodeBuildCommand(pt);
|
|
2138
|
+
case "unknown":
|
|
2139
|
+
return null;
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
function lintCommand(dir) {
|
|
2143
|
+
const pt = detect(dir);
|
|
2144
|
+
switch (pt.kind) {
|
|
2145
|
+
case "node": {
|
|
2146
|
+
const [cmd, args] = spawnRunArgs("lint");
|
|
2147
|
+
return { cmd, args };
|
|
2148
|
+
}
|
|
2149
|
+
case "swift-spm":
|
|
2150
|
+
case "swift-xcode":
|
|
2151
|
+
case "unknown":
|
|
2152
|
+
return null;
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
function supportsDevServer(dir) {
|
|
2156
|
+
return detect(dir).kind === "node";
|
|
2157
|
+
}
|
|
2158
|
+
function xcodeBuildCommand(pt) {
|
|
2159
|
+
const container = pt.xcodeContainer;
|
|
2160
|
+
if (!container)
|
|
2161
|
+
return null;
|
|
2162
|
+
const scheme = resolveXcodeScheme(pt);
|
|
2163
|
+
if (!scheme) {
|
|
2164
|
+
log.warn(TAG11, "Could not resolve an Xcode scheme — skipping build (best-effort)");
|
|
2165
|
+
return null;
|
|
2166
|
+
}
|
|
2167
|
+
const containerFlag = pt.xcodeIsWorkspace ? "-workspace" : "-project";
|
|
2168
|
+
return {
|
|
2169
|
+
cmd: "xcodebuild",
|
|
2170
|
+
args: [
|
|
2171
|
+
containerFlag,
|
|
2172
|
+
container,
|
|
2173
|
+
"-scheme",
|
|
2174
|
+
scheme,
|
|
2175
|
+
"-destination",
|
|
2176
|
+
"generic/platform=iOS",
|
|
2177
|
+
"CODE_SIGNING_ALLOWED=NO",
|
|
2178
|
+
"build"
|
|
2179
|
+
]
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
function resolveXcodeScheme(pt) {
|
|
2183
|
+
if (!pt.xcodeContainer)
|
|
2184
|
+
return null;
|
|
2185
|
+
const flag = pt.xcodeIsWorkspace ? "-workspace" : "-project";
|
|
2186
|
+
try {
|
|
2187
|
+
const out = execFileSync6("xcodebuild", ["-list", "-json", flag, pt.xcodeContainer], { encoding: "utf-8", timeout: 30000, stdio: "pipe" });
|
|
2188
|
+
const parsed = JSON.parse(out);
|
|
2189
|
+
const schemes = pt.xcodeIsWorkspace ? parsed.workspace?.schemes ?? [] : parsed.project?.schemes ?? [];
|
|
2190
|
+
return schemes[0] ?? null;
|
|
2191
|
+
} catch (err) {
|
|
2192
|
+
log.warn(TAG11, `xcodebuild -list failed: ${err instanceof Error ? err.message : err}`);
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
var TAG11 = "project-type", _cache;
|
|
2197
|
+
var init_project_type = __esm(() => {
|
|
2198
|
+
init_log();
|
|
2199
|
+
init_pm();
|
|
2200
|
+
_cache = new Map;
|
|
2201
|
+
});
|
|
2202
|
+
|
|
2084
2203
|
// src/verification.ts
|
|
2085
|
-
import { execFileSync as
|
|
2204
|
+
import { execFileSync as execFileSync7, spawn } from "node:child_process";
|
|
2086
2205
|
async function runVerification(worktreePath, config, workerId) {
|
|
2087
2206
|
const result = {
|
|
2088
2207
|
passed: true,
|
|
@@ -2091,39 +2210,43 @@ async function runVerification(worktreePath, config, workerId) {
|
|
|
2091
2210
|
reviewFindings: []
|
|
2092
2211
|
};
|
|
2093
2212
|
if (config.verification.build) {
|
|
2094
|
-
log.info(
|
|
2213
|
+
log.info(TAG12, `[worker:${workerId}] Running build...`);
|
|
2095
2214
|
result.buildErrors = runBuild(worktreePath, config.verification.timeout);
|
|
2096
2215
|
if (result.buildErrors.length > 0) {
|
|
2097
|
-
log.warn(
|
|
2216
|
+
log.warn(TAG12, `[worker:${workerId}] Build failed with ${result.buildErrors.length} error(s)`);
|
|
2098
2217
|
result.passed = false;
|
|
2099
2218
|
} else {
|
|
2100
|
-
log.info(
|
|
2219
|
+
log.info(TAG12, `[worker:${workerId}] Build passed`);
|
|
2101
2220
|
}
|
|
2102
2221
|
}
|
|
2103
2222
|
if (config.verification.lint) {
|
|
2104
|
-
log.info(
|
|
2223
|
+
log.info(TAG12, `[worker:${workerId}] Running lint...`);
|
|
2105
2224
|
result.lintWarnings = runLint(worktreePath, config.verification.timeout);
|
|
2106
2225
|
if (result.lintWarnings.length > 0) {
|
|
2107
|
-
log.warn(
|
|
2226
|
+
log.warn(TAG12, `[worker:${workerId}] Lint found ${result.lintWarnings.length} issue(s)`);
|
|
2108
2227
|
} else {
|
|
2109
|
-
log.info(
|
|
2228
|
+
log.info(TAG12, `[worker:${workerId}] Lint passed`);
|
|
2110
2229
|
}
|
|
2111
2230
|
}
|
|
2112
2231
|
if (config.verification.deepReview) {
|
|
2113
|
-
log.info(
|
|
2232
|
+
log.info(TAG12, `[worker:${workerId}] Running deep review...`);
|
|
2114
2233
|
result.reviewFindings = await runDeepReview(worktreePath, config, workerId);
|
|
2115
2234
|
if (result.reviewFindings.length > 0) {
|
|
2116
|
-
log.warn(
|
|
2235
|
+
log.warn(TAG12, `[worker:${workerId}] Deep review found ${result.reviewFindings.length} finding(s)`);
|
|
2117
2236
|
} else {
|
|
2118
|
-
log.info(
|
|
2237
|
+
log.info(TAG12, `[worker:${workerId}] Deep review passed`);
|
|
2119
2238
|
}
|
|
2120
2239
|
}
|
|
2121
2240
|
return result;
|
|
2122
2241
|
}
|
|
2123
2242
|
function runBuild(worktreePath, timeout) {
|
|
2243
|
+
const command = buildCommand(worktreePath);
|
|
2244
|
+
if (!command) {
|
|
2245
|
+
log.warn(TAG12, `No known build toolchain for ${worktreePath} — skipping build`);
|
|
2246
|
+
return [];
|
|
2247
|
+
}
|
|
2124
2248
|
try {
|
|
2125
|
-
|
|
2126
|
-
execFileSync6(cmd, args, {
|
|
2249
|
+
execFileSync7(command.cmd, command.args, {
|
|
2127
2250
|
cwd: worktreePath,
|
|
2128
2251
|
timeout,
|
|
2129
2252
|
stdio: "pipe"
|
|
@@ -2134,9 +2257,13 @@ function runBuild(worktreePath, timeout) {
|
|
|
2134
2257
|
}
|
|
2135
2258
|
}
|
|
2136
2259
|
function runLint(worktreePath, timeout) {
|
|
2260
|
+
const command = lintCommand(worktreePath);
|
|
2261
|
+
if (!command) {
|
|
2262
|
+
log.info(TAG12, `No lint step for detected toolchain in ${worktreePath} — skipping lint`);
|
|
2263
|
+
return [];
|
|
2264
|
+
}
|
|
2137
2265
|
try {
|
|
2138
|
-
|
|
2139
|
-
execFileSync6(cmd, args, {
|
|
2266
|
+
execFileSync7(command.cmd, command.args, {
|
|
2140
2267
|
cwd: worktreePath,
|
|
2141
2268
|
timeout,
|
|
2142
2269
|
stdio: "pipe"
|
|
@@ -2147,6 +2274,10 @@ function runLint(worktreePath, timeout) {
|
|
|
2147
2274
|
}
|
|
2148
2275
|
}
|
|
2149
2276
|
async function runDeepReview(worktreePath, config, workerId) {
|
|
2277
|
+
if (!supportsDevServer(worktreePath)) {
|
|
2278
|
+
log.info(TAG12, `[worker:${workerId}] Detected non-web toolchain — skipping deep review`);
|
|
2279
|
+
return [];
|
|
2280
|
+
}
|
|
2150
2281
|
const port = config.verification.devServerBasePort + workerId;
|
|
2151
2282
|
let devServer = null;
|
|
2152
2283
|
try {
|
|
@@ -2159,12 +2290,12 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
2159
2290
|
await waitForDevServer(devServer, 30000);
|
|
2160
2291
|
await probeDevServer(port);
|
|
2161
2292
|
} catch (err) {
|
|
2162
|
-
log.error(
|
|
2293
|
+
log.error(TAG12, `Dev server did not become ready: ${err instanceof Error ? err.message : err}`);
|
|
2163
2294
|
return [];
|
|
2164
2295
|
}
|
|
2165
2296
|
let diff = "";
|
|
2166
2297
|
try {
|
|
2167
|
-
diff =
|
|
2298
|
+
diff = execFileSync7("git", ["diff", `origin/${config.worktree.baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
|
|
2168
2299
|
} catch {
|
|
2169
2300
|
diff = "(unable to retrieve diff)";
|
|
2170
2301
|
}
|
|
@@ -2181,7 +2312,7 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
2181
2312
|
].join(`
|
|
2182
2313
|
`);
|
|
2183
2314
|
const leanSources = config.claude.leanSettingSources;
|
|
2184
|
-
const output =
|
|
2315
|
+
const output = execFileSync7("claude", [
|
|
2185
2316
|
"--print",
|
|
2186
2317
|
"--model",
|
|
2187
2318
|
"sonnet",
|
|
@@ -2198,7 +2329,7 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
2198
2329
|
});
|
|
2199
2330
|
return parseReviewFindings(output);
|
|
2200
2331
|
} catch (err) {
|
|
2201
|
-
log.error(
|
|
2332
|
+
log.error(TAG12, `Deep review failed: ${err instanceof Error ? err.message : err}`);
|
|
2202
2333
|
return [];
|
|
2203
2334
|
} finally {
|
|
2204
2335
|
if (devServer && !devServer.killed) {
|
|
@@ -2234,8 +2365,8 @@ function attemptAutoFix(worktreePath, config, errors) {
|
|
|
2234
2365
|
"--",
|
|
2235
2366
|
fixPrompt
|
|
2236
2367
|
];
|
|
2237
|
-
log.info(
|
|
2238
|
-
|
|
2368
|
+
log.info(TAG12, "Spawning Claude for auto-fix...");
|
|
2369
|
+
execFileSync7("claude", args, {
|
|
2239
2370
|
cwd: worktreePath,
|
|
2240
2371
|
timeout: config.verification.timeout,
|
|
2241
2372
|
stdio: "pipe"
|
|
@@ -2265,7 +2396,7 @@ async function reportFindings(client, cardId, result, recovery) {
|
|
|
2265
2396
|
try {
|
|
2266
2397
|
await client.createSubtask(cardId, title);
|
|
2267
2398
|
} catch (err) {
|
|
2268
|
-
log.error(
|
|
2399
|
+
log.error(TAG12, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
2269
2400
|
}
|
|
2270
2401
|
}));
|
|
2271
2402
|
if (overflow > 0) {
|
|
@@ -2273,7 +2404,7 @@ async function reportFindings(client, cardId, result, recovery) {
|
|
|
2273
2404
|
await client.createSubtask(cardId, `...and ${overflow} more issues`);
|
|
2274
2405
|
} catch {}
|
|
2275
2406
|
}
|
|
2276
|
-
log.info(
|
|
2407
|
+
log.info(TAG12, `Reported ${Math.min(items.length, maxSubtasks)} finding(s) as subtasks on card ${cardId}`);
|
|
2277
2408
|
}
|
|
2278
2409
|
function parseErrorOutput(err) {
|
|
2279
2410
|
const stderr = err?.stderr?.toString() ?? "";
|
|
@@ -2357,10 +2488,11 @@ async function probeDevServer(port, timeoutMs = 5000) {
|
|
|
2357
2488
|
clearTimeout(timer);
|
|
2358
2489
|
}
|
|
2359
2490
|
}
|
|
2360
|
-
var
|
|
2491
|
+
var TAG12 = "verification", DevServerReadinessError;
|
|
2361
2492
|
var init_verification = __esm(() => {
|
|
2362
2493
|
init_log();
|
|
2363
2494
|
init_pm();
|
|
2495
|
+
init_project_type();
|
|
2364
2496
|
DevServerReadinessError = class DevServerReadinessError extends Error {
|
|
2365
2497
|
constructor(message) {
|
|
2366
2498
|
super(message);
|
|
@@ -2370,7 +2502,7 @@ var init_verification = __esm(() => {
|
|
|
2370
2502
|
});
|
|
2371
2503
|
|
|
2372
2504
|
// src/completion.ts
|
|
2373
|
-
import { execFileSync as
|
|
2505
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
2374
2506
|
function formatTokenCount(tokens) {
|
|
2375
2507
|
if (tokens >= 1e6)
|
|
2376
2508
|
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
@@ -2400,7 +2532,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2400
2532
|
};
|
|
2401
2533
|
const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
|
|
2402
2534
|
if (!hasCommits) {
|
|
2403
|
-
log.warn(
|
|
2535
|
+
log.warn(TAG13, `No commits on branch ${branchName} — skipping completion`);
|
|
2404
2536
|
await client.endAgentSession(card.id, {
|
|
2405
2537
|
status: "completed",
|
|
2406
2538
|
progressPercent: 100,
|
|
@@ -2409,13 +2541,13 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2409
2541
|
cleanupWorktree(worktreePath, branchName);
|
|
2410
2542
|
return true;
|
|
2411
2543
|
}
|
|
2412
|
-
log.info(
|
|
2544
|
+
log.info(TAG13, `Pushing branch ${branchName} (pre-verify)...`);
|
|
2413
2545
|
let lastPushedSha = null;
|
|
2414
2546
|
try {
|
|
2415
2547
|
pushBranch(branchName, worktreePath);
|
|
2416
2548
|
lastPushedSha = readHeadSha(worktreePath);
|
|
2417
2549
|
} catch (err) {
|
|
2418
|
-
log.error(
|
|
2550
|
+
log.error(TAG13, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
2419
2551
|
}
|
|
2420
2552
|
const recoveryUrl = lastPushedSha ? getBranchWebUrl(branchName, worktreePath) : null;
|
|
2421
2553
|
if (config.verification.enabled) {
|
|
@@ -2430,7 +2562,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2430
2562
|
let autoFixAttempts = 0;
|
|
2431
2563
|
if (!result.passed && config.verification.autoFix) {
|
|
2432
2564
|
for (let attempt = 0;attempt < config.verification.maxFixAttempts; attempt++) {
|
|
2433
|
-
log.info(
|
|
2565
|
+
log.info(TAG13, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
|
|
2434
2566
|
await client.updateAgentProgress(card.id, {
|
|
2435
2567
|
agentIdentifier: agentIdentifier(workerId),
|
|
2436
2568
|
agentName: AGENT_NAME,
|
|
@@ -2443,14 +2575,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2443
2575
|
result = await runVerification(worktreePath, config, workerId);
|
|
2444
2576
|
autoFixAttempts = attempt + 1;
|
|
2445
2577
|
if (result.passed) {
|
|
2446
|
-
log.info(
|
|
2578
|
+
log.info(TAG13, `Auto-fix succeeded on attempt ${attempt + 1}`);
|
|
2447
2579
|
const sha = readHeadSha(worktreePath);
|
|
2448
2580
|
if (sha && sha !== lastPushedSha) {
|
|
2449
2581
|
try {
|
|
2450
2582
|
pushBranch(branchName, worktreePath);
|
|
2451
2583
|
lastPushedSha = sha;
|
|
2452
2584
|
} catch (err) {
|
|
2453
|
-
log.warn(
|
|
2585
|
+
log.warn(TAG13, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
2454
2586
|
}
|
|
2455
2587
|
}
|
|
2456
2588
|
break;
|
|
@@ -2459,14 +2591,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2459
2591
|
}
|
|
2460
2592
|
verificationResult = result;
|
|
2461
2593
|
if (!result.passed) {
|
|
2462
|
-
log.warn(
|
|
2594
|
+
log.warn(TAG13, `Verification failed for #${card.short_id} — reporting findings`);
|
|
2463
2595
|
const failSha = readHeadSha(worktreePath);
|
|
2464
2596
|
if (failSha && failSha !== lastPushedSha) {
|
|
2465
2597
|
try {
|
|
2466
2598
|
pushBranch(branchName, worktreePath);
|
|
2467
2599
|
lastPushedSha = failSha;
|
|
2468
2600
|
} catch (err) {
|
|
2469
|
-
log.warn(
|
|
2601
|
+
log.warn(TAG13, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
2470
2602
|
}
|
|
2471
2603
|
}
|
|
2472
2604
|
const failureSummary = buildVerificationFailureSummary(result, autoFixAttempts);
|
|
@@ -2477,7 +2609,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2477
2609
|
recoveryBranch: branchName
|
|
2478
2610
|
});
|
|
2479
2611
|
} catch (err) {
|
|
2480
|
-
log.debug(
|
|
2612
|
+
log.debug(TAG13, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
2481
2613
|
}
|
|
2482
2614
|
await reportFindings(client, card.id, result, lastPushedSha ? { branchName, branchUrl: recoveryUrl } : null);
|
|
2483
2615
|
await moveCardToColumn(client, card, config.verification.failColumn);
|
|
@@ -2491,7 +2623,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2491
2623
|
cleanupWorktree(worktreePath, branchName);
|
|
2492
2624
|
return false;
|
|
2493
2625
|
}
|
|
2494
|
-
log.info(
|
|
2626
|
+
log.info(TAG13, `Verification passed for #${card.short_id}`);
|
|
2495
2627
|
}
|
|
2496
2628
|
let prUrl = null;
|
|
2497
2629
|
if (config.completion.createPR) {
|
|
@@ -2504,7 +2636,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2504
2636
|
try {
|
|
2505
2637
|
await onMovedToCompletion(card);
|
|
2506
2638
|
} catch (err) {
|
|
2507
|
-
log.warn(
|
|
2639
|
+
log.warn(TAG13, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
2508
2640
|
}
|
|
2509
2641
|
}
|
|
2510
2642
|
}
|
|
@@ -2538,7 +2670,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2538
2670
|
});
|
|
2539
2671
|
}
|
|
2540
2672
|
cleanupWorktree(worktreePath, branchName);
|
|
2541
|
-
log.info(
|
|
2673
|
+
log.info(TAG13, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
|
|
2542
2674
|
return true;
|
|
2543
2675
|
}
|
|
2544
2676
|
function buildVerificationFailureSummary(result, autoFixAttempts) {
|
|
@@ -2558,7 +2690,7 @@ function buildVerificationFailureSummary(result, autoFixAttempts) {
|
|
|
2558
2690
|
}
|
|
2559
2691
|
function readHeadSha(worktreePath) {
|
|
2560
2692
|
try {
|
|
2561
|
-
return
|
|
2693
|
+
return execFileSync8("git", ["rev-parse", "HEAD"], {
|
|
2562
2694
|
cwd: worktreePath,
|
|
2563
2695
|
encoding: "utf-8"
|
|
2564
2696
|
}).trim();
|
|
@@ -2568,7 +2700,7 @@ function readHeadSha(worktreePath) {
|
|
|
2568
2700
|
}
|
|
2569
2701
|
function checkHasCommits(worktreePath, baseBranch) {
|
|
2570
2702
|
try {
|
|
2571
|
-
const count =
|
|
2703
|
+
const count = execFileSync8("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
|
|
2572
2704
|
return parseInt(count, 10) > 0;
|
|
2573
2705
|
} catch {
|
|
2574
2706
|
return false;
|
|
@@ -2577,7 +2709,7 @@ function checkHasCommits(worktreePath, baseBranch) {
|
|
|
2577
2709
|
async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
|
|
2578
2710
|
let commitLog = "";
|
|
2579
2711
|
try {
|
|
2580
|
-
commitLog =
|
|
2712
|
+
commitLog = execFileSync8("git", ["log", "--oneline", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
|
|
2581
2713
|
} catch {}
|
|
2582
2714
|
const SUMMARY_MARKER = `---
|
|
2583
2715
|
**Agent completed**`;
|
|
@@ -2622,12 +2754,12 @@ ${commitLog}
|
|
|
2622
2754
|
description: baseDesc + parts.join(`
|
|
2623
2755
|
`)
|
|
2624
2756
|
});
|
|
2625
|
-
log.info(
|
|
2757
|
+
log.info(TAG13, `Posted completion summary to #${card.short_id}`);
|
|
2626
2758
|
} catch (err) {
|
|
2627
|
-
log.error(
|
|
2759
|
+
log.error(TAG13, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
|
|
2628
2760
|
}
|
|
2629
2761
|
}
|
|
2630
|
-
var
|
|
2762
|
+
var TAG13 = "completion";
|
|
2631
2763
|
var init_completion = __esm(() => {
|
|
2632
2764
|
init_board_helpers();
|
|
2633
2765
|
init_episode_writer();
|
|
@@ -2647,7 +2779,12 @@ function spawnInGroup(command, args, options = {}) {
|
|
|
2647
2779
|
return spawn2(command, args, {
|
|
2648
2780
|
...options,
|
|
2649
2781
|
detached: true,
|
|
2650
|
-
stdio: options.stdio ?? ["ignore", "pipe", "pipe"]
|
|
2782
|
+
stdio: options.stdio ?? ["ignore", "pipe", "pipe"],
|
|
2783
|
+
env: {
|
|
2784
|
+
...process.env,
|
|
2785
|
+
...options.env,
|
|
2786
|
+
CMUX_CLAUDE_HOOKS_DISABLED: "1"
|
|
2787
|
+
}
|
|
2651
2788
|
});
|
|
2652
2789
|
}
|
|
2653
2790
|
function signalGroup(proc, signal) {
|
|
@@ -2662,7 +2799,7 @@ function signalGroup(proc, signal) {
|
|
|
2662
2799
|
} catch (err) {
|
|
2663
2800
|
const code = err.code;
|
|
2664
2801
|
if (code !== "ESRCH") {
|
|
2665
|
-
log.warn(
|
|
2802
|
+
log.warn(TAG14, `signal ${signal} to pgid ${proc.pid} failed: ${err instanceof Error ? err.message : err}`);
|
|
2666
2803
|
}
|
|
2667
2804
|
}
|
|
2668
2805
|
}
|
|
@@ -2687,7 +2824,7 @@ async function terminateGroup(proc, opts) {
|
|
|
2687
2824
|
return;
|
|
2688
2825
|
signalGroup(proc, "SIGKILL");
|
|
2689
2826
|
}
|
|
2690
|
-
var
|
|
2827
|
+
var TAG14 = "pgroup";
|
|
2691
2828
|
var init_process_group = __esm(() => {
|
|
2692
2829
|
init_log();
|
|
2693
2830
|
});
|
|
@@ -2790,7 +2927,7 @@ class ProgressTracker {
|
|
|
2790
2927
|
}
|
|
2791
2928
|
onToolStart(name, input) {
|
|
2792
2929
|
this.toolCallCount++;
|
|
2793
|
-
log.debug(
|
|
2930
|
+
log.debug(TAG15, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
|
|
2794
2931
|
const filePath = this.extractString(input, "file_path");
|
|
2795
2932
|
if (filePath) {
|
|
2796
2933
|
if (EDIT_TOOLS.has(name)) {
|
|
@@ -2861,7 +2998,7 @@ class ProgressTracker {
|
|
|
2861
2998
|
transitionTo(newPhase) {
|
|
2862
2999
|
if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
|
|
2863
3000
|
return;
|
|
2864
|
-
log.info(
|
|
3001
|
+
log.info(TAG15, `Phase: ${this.phase} → ${newPhase}`);
|
|
2865
3002
|
this.phase = newPhase;
|
|
2866
3003
|
this.progress = Math.max(this.progress, PHASES[newPhase].min);
|
|
2867
3004
|
this.lastAction = "";
|
|
@@ -2960,7 +3097,7 @@ class ProgressTracker {
|
|
|
2960
3097
|
}
|
|
2961
3098
|
sendUpdate(currentTask) {
|
|
2962
3099
|
this.lastUpdateAt = Date.now();
|
|
2963
|
-
log.debug(
|
|
3100
|
+
log.debug(TAG15, `Progress: ${this.progress}% — ${currentTask}`);
|
|
2964
3101
|
this.client.updateAgentProgress(this.cardId, {
|
|
2965
3102
|
agentIdentifier: agentIdentifier(this.workerId),
|
|
2966
3103
|
agentName: AGENT_NAME,
|
|
@@ -2977,7 +3114,7 @@ class ProgressTracker {
|
|
|
2977
3114
|
modelName: this.lastCost?.modelName,
|
|
2978
3115
|
numTurns: this.lastCost?.numTurns ?? 0
|
|
2979
3116
|
}).catch((err) => {
|
|
2980
|
-
log.warn(
|
|
3117
|
+
log.warn(TAG15, `Failed to send progress update: ${err}`);
|
|
2981
3118
|
});
|
|
2982
3119
|
this.flushActivityLog();
|
|
2983
3120
|
}
|
|
@@ -3018,7 +3155,7 @@ class ProgressTracker {
|
|
|
3018
3155
|
toolName: e.toolName ?? undefined
|
|
3019
3156
|
}))
|
|
3020
3157
|
}).catch((err) => {
|
|
3021
|
-
log.warn(
|
|
3158
|
+
log.warn(TAG15, `Failed to flush activity log: ${err}`);
|
|
3022
3159
|
this.logBuffer.unshift(...raw);
|
|
3023
3160
|
if (this.logBuffer.length > MAX_LOG_BUFFER) {
|
|
3024
3161
|
this.logBuffer.length = MAX_LOG_BUFFER;
|
|
@@ -3049,7 +3186,7 @@ class ProgressTracker {
|
|
|
3049
3186
|
return null;
|
|
3050
3187
|
}
|
|
3051
3188
|
}
|
|
3052
|
-
var
|
|
3189
|
+
var TAG15 = "progress-tracker", THROTTLE_MS = 5000, HEARTBEAT_MS = 60000, MAX_TASK_LENGTH = 120, MAX_LOG_BUFFER = 500, MAX_TEXT_BLOCKS = 40, SENTENCE_SPLIT, ACTION_PREFIX, GIT_COMMIT_RE, BUILD_CMD_RE, PHASES, PHASE_ORDER, EDIT_TOOLS, FILE_TOOL_VERBS;
|
|
3053
3190
|
var init_progress_tracker = __esm(() => {
|
|
3054
3191
|
init_log();
|
|
3055
3192
|
init_types();
|
|
@@ -3124,7 +3261,7 @@ function parseReviewOutput(stdout) {
|
|
|
3124
3261
|
try {
|
|
3125
3262
|
const parsed = JSON.parse(raw);
|
|
3126
3263
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
3127
|
-
log.debug(
|
|
3264
|
+
log.debug(TAG16, "Parsed review output from fenced JSON block");
|
|
3128
3265
|
return extractResult(parsed);
|
|
3129
3266
|
}
|
|
3130
3267
|
} catch {}
|
|
@@ -3150,21 +3287,21 @@ function parseReviewOutput(stdout) {
|
|
|
3150
3287
|
try {
|
|
3151
3288
|
const parsed = JSON.parse(candidates[i]);
|
|
3152
3289
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
3153
|
-
log.debug(
|
|
3290
|
+
log.debug(TAG16, "Parsed review output from raw JSON object");
|
|
3154
3291
|
return extractResult(parsed);
|
|
3155
3292
|
}
|
|
3156
3293
|
} catch {}
|
|
3157
3294
|
}
|
|
3158
3295
|
const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
|
|
3159
3296
|
if (verdictMatch) {
|
|
3160
|
-
log.warn(
|
|
3297
|
+
log.warn(TAG16, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
|
|
3161
3298
|
return {
|
|
3162
3299
|
verdict: verdictMatch[1].toLowerCase(),
|
|
3163
3300
|
summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
|
|
3164
3301
|
findings: []
|
|
3165
3302
|
};
|
|
3166
3303
|
}
|
|
3167
|
-
log.warn(
|
|
3304
|
+
log.warn(TAG16, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
|
|
3168
3305
|
return {
|
|
3169
3306
|
verdict: "error",
|
|
3170
3307
|
summary: stdout.slice(0, 500),
|
|
@@ -3197,7 +3334,7 @@ async function postReviewComment(client, card, commentType, body) {
|
|
|
3197
3334
|
try {
|
|
3198
3335
|
await client.addComment(card.id, body, { commentType });
|
|
3199
3336
|
} catch (err) {
|
|
3200
|
-
log.error(
|
|
3337
|
+
log.error(TAG16, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
3201
3338
|
}
|
|
3202
3339
|
}
|
|
3203
3340
|
async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore) {
|
|
@@ -3211,11 +3348,11 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
3211
3348
|
const currentCycle = getReviewCycle(freshDesc) + 1;
|
|
3212
3349
|
const maxCycles = config.review.maxReviewCycles;
|
|
3213
3350
|
if (result.verdict === "error") {
|
|
3214
|
-
log.warn(
|
|
3351
|
+
log.warn(TAG16, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
|
|
3215
3352
|
try {
|
|
3216
3353
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
3217
3354
|
} catch (err) {
|
|
3218
|
-
log.warn(
|
|
3355
|
+
log.warn(TAG16, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
3219
3356
|
}
|
|
3220
3357
|
if (config.review.postFindings) {
|
|
3221
3358
|
const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
|
|
@@ -3258,7 +3395,7 @@ ${runLogTail}
|
|
|
3258
3395
|
renameRemoteBranch(branchName, newRef, worktreePath);
|
|
3259
3396
|
approvedBranch = newRef;
|
|
3260
3397
|
} catch (err) {
|
|
3261
|
-
log.warn(
|
|
3398
|
+
log.warn(TAG16, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
|
|
3262
3399
|
}
|
|
3263
3400
|
}
|
|
3264
3401
|
if (config.review.createPR && approvedBranch) {
|
|
@@ -3285,14 +3422,14 @@ ${runLogTail}
|
|
|
3285
3422
|
progressPercent: 100,
|
|
3286
3423
|
...buildTokenPayload(sessionStats)
|
|
3287
3424
|
});
|
|
3288
|
-
log.info(
|
|
3425
|
+
log.info(TAG16, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
|
|
3289
3426
|
} else {
|
|
3290
3427
|
const criticalFindings = result.findings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
|
|
3291
3428
|
const majorFindings = result.findings.filter((f) => f.severity === "major").slice(0, MAX_FINDINGS);
|
|
3292
3429
|
const linkedFindings = [...criticalFindings, ...majorFindings];
|
|
3293
3430
|
const minorFindings = result.findings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
|
|
3294
3431
|
if (currentCycle >= maxCycles) {
|
|
3295
|
-
log.warn(
|
|
3432
|
+
log.warn(TAG16, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
|
|
3296
3433
|
await moveCardToColumn(client, card, config.review.moveToColumn);
|
|
3297
3434
|
const body = [
|
|
3298
3435
|
"**Review — needs human review.**",
|
|
@@ -3343,7 +3480,7 @@ ${finding.description}${locationLine}`
|
|
|
3343
3480
|
await client.addLinkToCard(card.id, newCardId, "relates_to");
|
|
3344
3481
|
}
|
|
3345
3482
|
} catch (err) {
|
|
3346
|
-
log.error(
|
|
3483
|
+
log.error(TAG16, `Failed to create finding card: ${err instanceof Error ? err.message : err}`);
|
|
3347
3484
|
}
|
|
3348
3485
|
}));
|
|
3349
3486
|
await Promise.all(minorFindings.map(async (finding) => {
|
|
@@ -3351,7 +3488,7 @@ ${finding.description}${locationLine}`
|
|
|
3351
3488
|
const title = finding.title.length > 120 ? `${finding.title.slice(0, 117)}...` : finding.title;
|
|
3352
3489
|
await client.createSubtask(card.id, title);
|
|
3353
3490
|
} catch (err) {
|
|
3354
|
-
log.error(
|
|
3491
|
+
log.error(TAG16, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
3355
3492
|
}
|
|
3356
3493
|
}));
|
|
3357
3494
|
const baseDesc = stripReviewSummary(freshDesc);
|
|
@@ -3359,7 +3496,7 @@ ${finding.description}${locationLine}`
|
|
|
3359
3496
|
try {
|
|
3360
3497
|
await client.updateCard(card.id, { description: updatedDesc });
|
|
3361
3498
|
} catch (err) {
|
|
3362
|
-
log.error(
|
|
3499
|
+
log.error(TAG16, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
|
|
3363
3500
|
}
|
|
3364
3501
|
const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
|
|
3365
3502
|
const body = [
|
|
@@ -3375,9 +3512,9 @@ ${finding.description}${locationLine}`
|
|
|
3375
3512
|
if (config.planning.enabled && card.plan_id) {
|
|
3376
3513
|
try {
|
|
3377
3514
|
await client.updateCard(card.id, { needsPlanRefresh: true });
|
|
3378
|
-
log.info(
|
|
3515
|
+
log.info(TAG16, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
|
|
3379
3516
|
} catch (err) {
|
|
3380
|
-
log.warn(
|
|
3517
|
+
log.warn(TAG16, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
3381
3518
|
}
|
|
3382
3519
|
}
|
|
3383
3520
|
await moveCardToColumn(client, card, config.review.failColumn);
|
|
@@ -3391,10 +3528,10 @@ ${finding.description}${locationLine}`
|
|
|
3391
3528
|
recoveryBranch
|
|
3392
3529
|
});
|
|
3393
3530
|
} catch (err) {
|
|
3394
|
-
log.debug(
|
|
3531
|
+
log.debug(TAG16, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
3395
3532
|
}
|
|
3396
3533
|
if (recoveryBranch) {
|
|
3397
|
-
log.info(
|
|
3534
|
+
log.info(TAG16, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
|
|
3398
3535
|
}
|
|
3399
3536
|
await client.endAgentSession(card.id, {
|
|
3400
3537
|
status: "failed",
|
|
@@ -3403,7 +3540,7 @@ ${finding.description}${locationLine}`
|
|
|
3403
3540
|
recoveryBranch,
|
|
3404
3541
|
...buildTokenPayload(sessionStats)
|
|
3405
3542
|
});
|
|
3406
|
-
log.info(
|
|
3543
|
+
log.info(TAG16, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
|
|
3407
3544
|
}
|
|
3408
3545
|
if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
|
|
3409
3546
|
const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
|
|
@@ -3425,7 +3562,7 @@ ${finding.description}${locationLine}`
|
|
|
3425
3562
|
cleanupWorktree(worktreePath, branchName);
|
|
3426
3563
|
}
|
|
3427
3564
|
}
|
|
3428
|
-
var
|
|
3565
|
+
var TAG16 = "review-completion", MAX_FINDINGS = 10, REVIEW_MARKER = `---
|
|
3429
3566
|
**Review:`, RUN_LOG_TAIL_BYTES = 2048;
|
|
3430
3567
|
var init_review_completion = __esm(() => {
|
|
3431
3568
|
init_board_helpers();
|
|
@@ -3785,7 +3922,7 @@ __export(exports_state_store, {
|
|
|
3785
3922
|
StateStore: () => StateStore
|
|
3786
3923
|
});
|
|
3787
3924
|
import {
|
|
3788
|
-
existsSync as
|
|
3925
|
+
existsSync as existsSync5,
|
|
3789
3926
|
mkdirSync as mkdirSync2,
|
|
3790
3927
|
readFileSync as readFileSync3,
|
|
3791
3928
|
renameSync,
|
|
@@ -3827,18 +3964,18 @@ class StateStore {
|
|
|
3827
3964
|
static open(path) {
|
|
3828
3965
|
const resolved = path ?? defaultStatePath();
|
|
3829
3966
|
const dir = dirname(resolved);
|
|
3830
|
-
if (!
|
|
3967
|
+
if (!existsSync5(dir))
|
|
3831
3968
|
mkdirSync2(dir, { recursive: true });
|
|
3832
3969
|
return new StateStore(resolved);
|
|
3833
3970
|
}
|
|
3834
3971
|
load() {
|
|
3835
|
-
if (!
|
|
3972
|
+
if (!existsSync5(this.path))
|
|
3836
3973
|
return emptyState();
|
|
3837
3974
|
try {
|
|
3838
3975
|
const raw = readFileSync3(this.path, "utf-8");
|
|
3839
3976
|
const parsed = JSON.parse(raw);
|
|
3840
3977
|
if (parsed?.version !== SCHEMA_VERSION) {
|
|
3841
|
-
log.warn(
|
|
3978
|
+
log.warn(TAG17, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — starting fresh`);
|
|
3842
3979
|
return emptyState();
|
|
3843
3980
|
}
|
|
3844
3981
|
return {
|
|
@@ -3851,7 +3988,7 @@ class StateStore {
|
|
|
3851
3988
|
daily: parsed.daily ?? []
|
|
3852
3989
|
};
|
|
3853
3990
|
} catch (err) {
|
|
3854
|
-
log.error(
|
|
3991
|
+
log.error(TAG17, `failed to read state file: ${err instanceof Error ? err.message : err}`);
|
|
3855
3992
|
return emptyState();
|
|
3856
3993
|
}
|
|
3857
3994
|
}
|
|
@@ -4007,7 +4144,7 @@ class StateStore {
|
|
|
4007
4144
|
return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
|
|
4008
4145
|
}
|
|
4009
4146
|
}
|
|
4010
|
-
var
|
|
4147
|
+
var TAG17 = "state-store", SCHEMA_VERSION = 1;
|
|
4011
4148
|
var init_state_store = __esm(() => {
|
|
4012
4149
|
init_log();
|
|
4013
4150
|
});
|
|
@@ -4034,7 +4171,7 @@ function normalizeToolResultContent(raw) {
|
|
|
4034
4171
|
return String(raw);
|
|
4035
4172
|
}
|
|
4036
4173
|
}
|
|
4037
|
-
var
|
|
4174
|
+
var TAG18 = "stream-parser", StreamParser;
|
|
4038
4175
|
var init_stream_parser = __esm(() => {
|
|
4039
4176
|
init_log();
|
|
4040
4177
|
StreamParser = class StreamParser extends EventEmitter {
|
|
@@ -4078,14 +4215,14 @@ var init_stream_parser = __esm(() => {
|
|
|
4078
4215
|
try {
|
|
4079
4216
|
msg = JSON.parse(line);
|
|
4080
4217
|
} catch {
|
|
4081
|
-
log.debug(
|
|
4218
|
+
log.debug(TAG18, `Non-JSON line: ${line.slice(0, 100)}`);
|
|
4082
4219
|
return;
|
|
4083
4220
|
}
|
|
4084
4221
|
try {
|
|
4085
4222
|
this.handleMessage(msg);
|
|
4086
4223
|
} catch (err) {
|
|
4087
4224
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4088
|
-
log.warn(
|
|
4225
|
+
log.warn(TAG18, `Error handling stream event: ${errMsg}`);
|
|
4089
4226
|
this.emit("parse_error", errMsg);
|
|
4090
4227
|
}
|
|
4091
4228
|
}
|
|
@@ -4167,7 +4304,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
|
4167
4304
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
4168
4305
|
if (i < attempts - 1) {
|
|
4169
4306
|
const wait = backoffMs * 2 ** i;
|
|
4170
|
-
log.warn(
|
|
4307
|
+
log.warn(TAG19, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
|
|
4171
4308
|
await new Promise((r) => setTimeout(r, wait));
|
|
4172
4309
|
}
|
|
4173
4310
|
}
|
|
@@ -4189,10 +4326,10 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
4189
4326
|
if (opts.strictColumn) {
|
|
4190
4327
|
throw new TransitionError("move", 1, msg);
|
|
4191
4328
|
}
|
|
4192
|
-
log.warn(
|
|
4329
|
+
log.warn(TAG19, `#${shortId}: ${msg} — skipping move`);
|
|
4193
4330
|
} else if (card.column_id !== target.id) {
|
|
4194
4331
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
4195
|
-
log.info(
|
|
4332
|
+
log.info(TAG19, `#${shortId} → "${target.name}"`);
|
|
4196
4333
|
card.column_id = target.id;
|
|
4197
4334
|
}
|
|
4198
4335
|
}
|
|
@@ -4205,7 +4342,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
4205
4342
|
continue;
|
|
4206
4343
|
await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
|
|
4207
4344
|
existing.add(labelId);
|
|
4208
|
-
log.info(
|
|
4345
|
+
log.info(TAG19, `#${shortId} +label "${name}"`);
|
|
4209
4346
|
}
|
|
4210
4347
|
card.labelIds = Array.from(existing);
|
|
4211
4348
|
}
|
|
@@ -4217,17 +4354,17 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
4217
4354
|
continue;
|
|
4218
4355
|
await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
|
|
4219
4356
|
existing.delete(match.id);
|
|
4220
|
-
log.info(
|
|
4357
|
+
log.info(TAG19, `#${shortId} -label "${name}"`);
|
|
4221
4358
|
}
|
|
4222
4359
|
card.labelIds = Array.from(existing);
|
|
4223
4360
|
}
|
|
4224
4361
|
if (plan.updateCard) {
|
|
4225
4362
|
await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
|
|
4226
|
-
log.info(
|
|
4363
|
+
log.info(TAG19, `#${shortId} updated`);
|
|
4227
4364
|
}
|
|
4228
4365
|
if (plan.endSession) {
|
|
4229
4366
|
await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
|
|
4230
|
-
log.info(
|
|
4367
|
+
log.info(TAG19, `#${shortId} session ended (${plan.endSession.status})`);
|
|
4231
4368
|
}
|
|
4232
4369
|
if (opts.store && opts.runId) {
|
|
4233
4370
|
try {
|
|
@@ -4240,11 +4377,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
|
|
|
4240
4377
|
const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
|
|
4241
4378
|
return result?.label?.id ?? null;
|
|
4242
4379
|
} catch (err) {
|
|
4243
|
-
log.warn(
|
|
4380
|
+
log.warn(TAG19, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
|
|
4244
4381
|
return null;
|
|
4245
4382
|
}
|
|
4246
4383
|
}
|
|
4247
|
-
var
|
|
4384
|
+
var TAG19 = "transition", TransitionError;
|
|
4248
4385
|
var init_transitions = __esm(() => {
|
|
4249
4386
|
init_log();
|
|
4250
4387
|
TransitionError = class TransitionError extends Error {
|
|
@@ -4262,7 +4399,7 @@ var init_transitions = __esm(() => {
|
|
|
4262
4399
|
});
|
|
4263
4400
|
|
|
4264
4401
|
// src/review-worker.ts
|
|
4265
|
-
import { execFileSync as
|
|
4402
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
4266
4403
|
|
|
4267
4404
|
class ReviewWorker {
|
|
4268
4405
|
config;
|
|
@@ -4328,7 +4465,7 @@ class ReviewWorker {
|
|
|
4328
4465
|
}
|
|
4329
4466
|
}
|
|
4330
4467
|
get tag() {
|
|
4331
|
-
return `${
|
|
4468
|
+
return `${TAG20}:${this.id}`;
|
|
4332
4469
|
}
|
|
4333
4470
|
get isIdle() {
|
|
4334
4471
|
return this.state === "idle";
|
|
@@ -4385,7 +4522,7 @@ class ReviewWorker {
|
|
|
4385
4522
|
let localDiff = null;
|
|
4386
4523
|
if (localMode) {
|
|
4387
4524
|
log.info(this.tag, `No branch found for #${card.short_id}, attempting local review`);
|
|
4388
|
-
this.worktreePath =
|
|
4525
|
+
this.worktreePath = execFileSync9("git", ["rev-parse", "--show-toplevel"], {
|
|
4389
4526
|
encoding: "utf-8",
|
|
4390
4527
|
timeout: 5000
|
|
4391
4528
|
}).trim();
|
|
@@ -4452,7 +4589,7 @@ class ReviewWorker {
|
|
|
4452
4589
|
if (localMode) {
|
|
4453
4590
|
diff = localDiff ?? "";
|
|
4454
4591
|
} else {
|
|
4455
|
-
diff =
|
|
4592
|
+
diff = execFileSync9("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
|
|
4456
4593
|
}
|
|
4457
4594
|
} catch {
|
|
4458
4595
|
diff = "(unable to retrieve diff)";
|
|
@@ -4732,7 +4869,7 @@ class ReviewWorker {
|
|
|
4732
4869
|
}
|
|
4733
4870
|
resolveLocalChanges(repoRoot, shortId) {
|
|
4734
4871
|
try {
|
|
4735
|
-
const localChanges =
|
|
4872
|
+
const localChanges = execFileSync9("git", ["diff", "HEAD"], {
|
|
4736
4873
|
cwd: repoRoot,
|
|
4737
4874
|
encoding: "utf-8",
|
|
4738
4875
|
timeout: 5000
|
|
@@ -4744,7 +4881,7 @@ class ReviewWorker {
|
|
|
4744
4881
|
log.warn(this.tag, "Failed to check uncommitted changes");
|
|
4745
4882
|
}
|
|
4746
4883
|
try {
|
|
4747
|
-
const matchingCommits =
|
|
4884
|
+
const matchingCommits = execFileSync9("git", ["log", "--format=%H", "-20", `--grep=#${shortId}`], { cwd: repoRoot, encoding: "utf-8", timeout: 1e4 }).trim();
|
|
4748
4885
|
if (matchingCommits) {
|
|
4749
4886
|
const hashes = matchingCommits.split(`
|
|
4750
4887
|
`).filter((h) => /^[0-9a-f]{4,40}$/i.test(h));
|
|
@@ -4754,7 +4891,7 @@ class ReviewWorker {
|
|
|
4754
4891
|
const diffs = [];
|
|
4755
4892
|
for (const hash of hashes) {
|
|
4756
4893
|
try {
|
|
4757
|
-
const commitDiff =
|
|
4894
|
+
const commitDiff = execFileSync9("git", ["diff", `${hash}~1..${hash}`], { cwd: repoRoot, encoding: "utf-8", timeout: 30000 });
|
|
4758
4895
|
if (commitDiff)
|
|
4759
4896
|
diffs.push(commitDiff);
|
|
4760
4897
|
} catch {
|
|
@@ -4797,7 +4934,7 @@ class ReviewWorker {
|
|
|
4797
4934
|
this.lastSessionStats = null;
|
|
4798
4935
|
}
|
|
4799
4936
|
}
|
|
4800
|
-
var
|
|
4937
|
+
var TAG20 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
|
|
4801
4938
|
var init_review_worker = __esm(() => {
|
|
4802
4939
|
init_board_helpers();
|
|
4803
4940
|
init_completion();
|
|
@@ -4846,7 +4983,7 @@ class SleepGuard {
|
|
|
4846
4983
|
if (!this.child.killed)
|
|
4847
4984
|
this.child.kill("SIGTERM");
|
|
4848
4985
|
this.child = null;
|
|
4849
|
-
log.info(
|
|
4986
|
+
log.info(TAG21, "sleep assertion released");
|
|
4850
4987
|
}
|
|
4851
4988
|
}
|
|
4852
4989
|
start() {
|
|
@@ -4861,7 +4998,7 @@ class SleepGuard {
|
|
|
4861
4998
|
spawned = true;
|
|
4862
4999
|
});
|
|
4863
5000
|
child.on("error", (err) => {
|
|
4864
|
-
log.warn(
|
|
5001
|
+
log.warn(TAG21, `caffeinate unavailable: ${err.message}`);
|
|
4865
5002
|
if (this.child === child)
|
|
4866
5003
|
this.child = null;
|
|
4867
5004
|
});
|
|
@@ -4874,13 +5011,13 @@ class SleepGuard {
|
|
|
4874
5011
|
});
|
|
4875
5012
|
child.unref();
|
|
4876
5013
|
this.child = child;
|
|
4877
|
-
log.info(
|
|
5014
|
+
log.info(TAG21, "sleep assertion acquired (caffeinate -i)");
|
|
4878
5015
|
} catch (err) {
|
|
4879
|
-
log.warn(
|
|
5016
|
+
log.warn(TAG21, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
|
|
4880
5017
|
}
|
|
4881
5018
|
}
|
|
4882
5019
|
}
|
|
4883
|
-
var
|
|
5020
|
+
var TAG21 = "sleep-guard";
|
|
4884
5021
|
var init_sleep_guard = __esm(() => {
|
|
4885
5022
|
init_log();
|
|
4886
5023
|
});
|
|
@@ -4891,7 +5028,7 @@ async function fetchBlocksLinks(client, cardId) {
|
|
|
4891
5028
|
const { links } = await client.getCardLinks(cardId);
|
|
4892
5029
|
return links.filter((l) => l.link_type === "blocks");
|
|
4893
5030
|
} catch (err) {
|
|
4894
|
-
log.warn(
|
|
5031
|
+
log.warn(TAG22, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
4895
5032
|
return null;
|
|
4896
5033
|
}
|
|
4897
5034
|
}
|
|
@@ -4923,42 +5060,49 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
|
4923
5060
|
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
4924
5061
|
if (successors.length === 0)
|
|
4925
5062
|
return;
|
|
4926
|
-
log.info(
|
|
5063
|
+
log.info(TAG22, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
4927
5064
|
for (const link of successors) {
|
|
4928
5065
|
const successorId = link.target_card.id;
|
|
4929
5066
|
try {
|
|
4930
5067
|
const { card } = await deps.client.getCard(successorId);
|
|
4931
5068
|
if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
|
|
4932
|
-
log.info(
|
|
5069
|
+
log.info(TAG22, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
|
|
4933
5070
|
await deps.client.updateCard(successorId, {
|
|
4934
5071
|
assignedAgentId: deps.agentId
|
|
4935
5072
|
});
|
|
4936
5073
|
} else {
|
|
4937
|
-
log.debug(
|
|
5074
|
+
log.debug(TAG22, `successor #${card.short_id} assigned to different entity — skipping`);
|
|
4938
5075
|
continue;
|
|
4939
5076
|
}
|
|
4940
5077
|
await deps.enqueue(successorId);
|
|
4941
5078
|
} catch (err) {
|
|
4942
|
-
log.warn(
|
|
5079
|
+
log.warn(TAG22, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
4943
5080
|
}
|
|
4944
5081
|
}
|
|
4945
5082
|
}
|
|
4946
|
-
var
|
|
5083
|
+
var TAG22 = "unblock";
|
|
4947
5084
|
var init_unblock = __esm(() => {
|
|
4948
5085
|
init_log();
|
|
4949
5086
|
});
|
|
4950
5087
|
|
|
4951
5088
|
// src/model-tier.ts
|
|
5089
|
+
function clampWithdrawn(model) {
|
|
5090
|
+
return WITHDRAWN_MODEL.test(model) ? MAX_IMPLEMENT_MODEL : model;
|
|
5091
|
+
}
|
|
4952
5092
|
function chooseImplementModel(claude, card, attempts) {
|
|
4953
5093
|
if (card.model_override) {
|
|
4954
|
-
return {
|
|
5094
|
+
return {
|
|
5095
|
+
model: clampWithdrawn(card.model_override),
|
|
5096
|
+
escalated: false,
|
|
5097
|
+
source: "override"
|
|
5098
|
+
};
|
|
4955
5099
|
}
|
|
4956
5100
|
if (isModelTier(card.model_tier)) {
|
|
4957
5101
|
const retry = attempts >= claude.escalateAfterAttempts;
|
|
4958
5102
|
const tier = retry ? escalateTier(card.model_tier) : card.model_tier;
|
|
4959
5103
|
const mapped = claude.tiers?.[tier];
|
|
4960
5104
|
return {
|
|
4961
|
-
model: mapped && mapped.length > 0 ? mapped : claude.model,
|
|
5105
|
+
model: clampWithdrawn(mapped && mapped.length > 0 ? mapped : claude.model),
|
|
4962
5106
|
escalated: retry,
|
|
4963
5107
|
source: "tier"
|
|
4964
5108
|
};
|
|
@@ -4966,13 +5110,15 @@ function chooseImplementModel(claude, card, attempts) {
|
|
|
4966
5110
|
const highPriority = card.priority === "high" || card.priority === "urgent";
|
|
4967
5111
|
const escalated = highPriority || attempts >= claude.escalateAfterAttempts;
|
|
4968
5112
|
return {
|
|
4969
|
-
model: escalated ? claude.escalateModel : claude.model,
|
|
5113
|
+
model: clampWithdrawn(escalated ? claude.escalateModel : claude.model),
|
|
4970
5114
|
escalated,
|
|
4971
5115
|
source: "policy"
|
|
4972
5116
|
};
|
|
4973
5117
|
}
|
|
5118
|
+
var MAX_IMPLEMENT_MODEL = "claude-opus-4-8", WITHDRAWN_MODEL;
|
|
4974
5119
|
var init_model_tier = __esm(() => {
|
|
4975
5120
|
init_dist();
|
|
5121
|
+
WITHDRAWN_MODEL = /fable/i;
|
|
4976
5122
|
});
|
|
4977
5123
|
|
|
4978
5124
|
// src/prompt.ts
|
|
@@ -4989,11 +5135,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
4989
5135
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
4990
5136
|
When finished, call harmony_end_agent_session with status="completed".`
|
|
4991
5137
|
});
|
|
4992
|
-
log.info(
|
|
5138
|
+
log.info(TAG23, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
4993
5139
|
return result.prompt + pastEpisodesSection;
|
|
4994
5140
|
} catch (err) {
|
|
4995
5141
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4996
|
-
log.warn(
|
|
5142
|
+
log.warn(TAG23, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
4997
5143
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
4998
5144
|
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
|
|
4999
5145
|
}
|
|
@@ -5011,7 +5157,7 @@ async function renderCommentsSection(client, cardId) {
|
|
|
5011
5157
|
|
|
5012
5158
|
${section}` : "";
|
|
5013
5159
|
} catch (err) {
|
|
5014
|
-
log.warn(
|
|
5160
|
+
log.warn(TAG23, "comment-thread fetch failed", {
|
|
5015
5161
|
event: "comment_fetch_failed",
|
|
5016
5162
|
error: err instanceof Error ? err.message : String(err)
|
|
5017
5163
|
});
|
|
@@ -5061,7 +5207,7 @@ ${description}`.trim();
|
|
|
5061
5207
|
## Similar past tasks
|
|
5062
5208
|
${bullets}`;
|
|
5063
5209
|
} catch (err) {
|
|
5064
|
-
log.warn(
|
|
5210
|
+
log.warn(TAG23, "past-episodes recall failed", {
|
|
5065
5211
|
event: "episode_recall_failed",
|
|
5066
5212
|
error: err instanceof Error ? err.message : String(err)
|
|
5067
5213
|
});
|
|
@@ -5102,7 +5248,7 @@ ${subtaskStr}
|
|
|
5102
5248
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
5103
5249
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
5104
5250
|
}
|
|
5105
|
-
var
|
|
5251
|
+
var TAG23 = "prompt";
|
|
5106
5252
|
var init_prompt = __esm(() => {
|
|
5107
5253
|
init_dist();
|
|
5108
5254
|
init_log();
|
|
@@ -5181,7 +5327,7 @@ class Worker {
|
|
|
5181
5327
|
}
|
|
5182
5328
|
}
|
|
5183
5329
|
get tag() {
|
|
5184
|
-
return `${
|
|
5330
|
+
return `${TAG24}:${this.id}`;
|
|
5185
5331
|
}
|
|
5186
5332
|
get isIdle() {
|
|
5187
5333
|
return this.state === "idle";
|
|
@@ -5240,7 +5386,7 @@ class Worker {
|
|
|
5240
5386
|
});
|
|
5241
5387
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
5242
5388
|
if (!sid) {
|
|
5243
|
-
log.warn(
|
|
5389
|
+
log.warn(TAG24, "startAgentSession returned no session id");
|
|
5244
5390
|
}
|
|
5245
5391
|
this.sessionId = sid;
|
|
5246
5392
|
await this.recordPhase("preparing");
|
|
@@ -5748,7 +5894,7 @@ class Worker {
|
|
|
5748
5894
|
this.runTurns = 0;
|
|
5749
5895
|
}
|
|
5750
5896
|
}
|
|
5751
|
-
var
|
|
5897
|
+
var TAG24 = "worker", CANCEL_SIGINT_TIMEOUT2 = 30000, CANCEL_SIGTERM_TIMEOUT2 = 1e4, PLAN_ALLOWED_TOOLS = "Read,Grep,Glob,mcp__harmony__*", IMPLEMENT_ALLOWED_TOOLS = "Bash,Read,Write,Edit,Glob,Grep,Agent,mcp__harmony__*", PLAN_PHASE_TIMEOUT;
|
|
5752
5898
|
var init_worker = __esm(() => {
|
|
5753
5899
|
init_board_helpers();
|
|
5754
5900
|
init_completion();
|
|
@@ -5818,39 +5964,39 @@ class Pool {
|
|
|
5818
5964
|
}
|
|
5819
5965
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
5820
5966
|
if (this.implQueue.has(card.id) || this.reviewQueue.has(card.id) || this.isCardActive(card.id)) {
|
|
5821
|
-
log.debug(
|
|
5967
|
+
log.debug(TAG25, `Card ${card.id} already queued or active, skipping`);
|
|
5822
5968
|
return;
|
|
5823
5969
|
}
|
|
5824
5970
|
if (mode === "implement") {
|
|
5825
5971
|
if (this.authPaused) {
|
|
5826
|
-
log.debug(
|
|
5972
|
+
log.debug(TAG25, `#${card.short_id} held — agent paused (auth error)`);
|
|
5827
5973
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
5828
5974
|
return;
|
|
5829
5975
|
}
|
|
5830
5976
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
5831
5977
|
if (cooldownMs > 0) {
|
|
5832
|
-
log.debug(
|
|
5978
|
+
log.debug(TAG25, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
5833
5979
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
5834
5980
|
return;
|
|
5835
5981
|
}
|
|
5836
5982
|
const decision = this.budget.check(card.id);
|
|
5837
5983
|
if (!decision.allow) {
|
|
5838
5984
|
if (decision.reason === "daily_budget") {
|
|
5839
|
-
log.warn(
|
|
5985
|
+
log.warn(TAG25, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
|
|
5840
5986
|
await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
|
|
5841
5987
|
} else {
|
|
5842
|
-
log.debug(
|
|
5988
|
+
log.debug(TAG25, `#${card.short_id} gave up: ${decision.detail}`);
|
|
5843
5989
|
}
|
|
5844
5990
|
return;
|
|
5845
5991
|
}
|
|
5846
5992
|
const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
|
|
5847
5993
|
if (blockers === null) {
|
|
5848
|
-
log.warn(
|
|
5994
|
+
log.warn(TAG25, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
5849
5995
|
return;
|
|
5850
5996
|
}
|
|
5851
5997
|
if (blockers.length > 0) {
|
|
5852
5998
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
5853
|
-
log.info(
|
|
5999
|
+
log.info(TAG25, `#${card.short_id} blocked by ${list} — waiting`);
|
|
5854
6000
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
5855
6001
|
return;
|
|
5856
6002
|
}
|
|
@@ -5879,7 +6025,7 @@ class Pool {
|
|
|
5879
6025
|
});
|
|
5880
6026
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
5881
6027
|
} catch (err) {
|
|
5882
|
-
log.debug(
|
|
6028
|
+
log.debug(TAG25, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
5883
6029
|
}
|
|
5884
6030
|
}
|
|
5885
6031
|
noteApiError(err) {
|
|
@@ -5887,7 +6033,7 @@ class Pool {
|
|
|
5887
6033
|
return;
|
|
5888
6034
|
if (err.kind === "auth") {
|
|
5889
6035
|
if (!this.authPaused) {
|
|
5890
|
-
log.error(
|
|
6036
|
+
log.error(TAG25, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
5891
6037
|
}
|
|
5892
6038
|
this.authPaused = true;
|
|
5893
6039
|
return;
|
|
@@ -5896,7 +6042,7 @@ class Pool {
|
|
|
5896
6042
|
const until = Date.now() + cooldownMs;
|
|
5897
6043
|
if (until > this.apiCooldownUntil) {
|
|
5898
6044
|
this.apiCooldownUntil = until;
|
|
5899
|
-
log.warn(
|
|
6045
|
+
log.warn(TAG25, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
5900
6046
|
}
|
|
5901
6047
|
}
|
|
5902
6048
|
apiCooldownRemainingMs() {
|
|
@@ -5909,13 +6055,13 @@ class Pool {
|
|
|
5909
6055
|
const removed = queue.remove(cardId);
|
|
5910
6056
|
if (removed) {
|
|
5911
6057
|
this.cardDataCache.delete(cardId);
|
|
5912
|
-
log.info(
|
|
6058
|
+
log.info(TAG25, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
5913
6059
|
return;
|
|
5914
6060
|
}
|
|
5915
6061
|
}
|
|
5916
6062
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
5917
6063
|
if (worker) {
|
|
5918
|
-
log.info(
|
|
6064
|
+
log.info(TAG25, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
5919
6065
|
await worker.cancel();
|
|
5920
6066
|
}
|
|
5921
6067
|
}
|
|
@@ -5943,10 +6089,10 @@ class Pool {
|
|
|
5943
6089
|
async handleAgentCommand(cardId, command) {
|
|
5944
6090
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
5945
6091
|
if (!worker) {
|
|
5946
|
-
log.debug(
|
|
6092
|
+
log.debug(TAG25, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
5947
6093
|
return;
|
|
5948
6094
|
}
|
|
5949
|
-
log.info(
|
|
6095
|
+
log.info(TAG25, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
5950
6096
|
switch (command) {
|
|
5951
6097
|
case "pause":
|
|
5952
6098
|
await worker.pause();
|
|
@@ -5994,7 +6140,7 @@ class Pool {
|
|
|
5994
6140
|
};
|
|
5995
6141
|
}
|
|
5996
6142
|
async shutdown() {
|
|
5997
|
-
log.info(
|
|
6143
|
+
log.info(TAG25, "Shutting down pool...");
|
|
5998
6144
|
this.shuttingDown = true;
|
|
5999
6145
|
const active = [
|
|
6000
6146
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -6002,7 +6148,7 @@ class Pool {
|
|
|
6002
6148
|
];
|
|
6003
6149
|
await Promise.all(active.map((w) => w.cancel()));
|
|
6004
6150
|
this.sleepGuard.stop();
|
|
6005
|
-
log.info(
|
|
6151
|
+
log.info(TAG25, "Pool shutdown complete");
|
|
6006
6152
|
}
|
|
6007
6153
|
cardDataCache = new Map;
|
|
6008
6154
|
tryDispatchFor(workers, queue, label) {
|
|
@@ -6010,7 +6156,7 @@ class Pool {
|
|
|
6010
6156
|
return false;
|
|
6011
6157
|
const idle = workers.find((w) => w.isIdle);
|
|
6012
6158
|
if (!idle) {
|
|
6013
|
-
log.debug(
|
|
6159
|
+
log.debug(TAG25, `No idle ${label} workers (queue: ${queue.length})`);
|
|
6014
6160
|
return false;
|
|
6015
6161
|
}
|
|
6016
6162
|
const next = queue.dequeue();
|
|
@@ -6018,18 +6164,18 @@ class Pool {
|
|
|
6018
6164
|
return false;
|
|
6019
6165
|
const data = this.cardDataCache.get(next.cardId);
|
|
6020
6166
|
if (!data) {
|
|
6021
|
-
log.warn(
|
|
6167
|
+
log.warn(TAG25, `No cached data for card ${next.cardId}, skipping`);
|
|
6022
6168
|
return false;
|
|
6023
6169
|
}
|
|
6024
6170
|
this.cardDataCache.delete(next.cardId);
|
|
6025
6171
|
this.lastWaitingEmit.delete(next.cardId);
|
|
6026
|
-
log.info(
|
|
6172
|
+
log.info(TAG25, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
6027
6173
|
this.sleepGuard.acquire();
|
|
6028
6174
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
6029
6175
|
return true;
|
|
6030
6176
|
}
|
|
6031
6177
|
}
|
|
6032
|
-
var
|
|
6178
|
+
var TAG25 = "pool";
|
|
6033
6179
|
var init_pool = __esm(() => {
|
|
6034
6180
|
init_error_classifier();
|
|
6035
6181
|
init_log();
|
|
@@ -6050,7 +6196,7 @@ __export(exports_port_registry, {
|
|
|
6050
6196
|
clearDaemonPort: () => clearDaemonPort
|
|
6051
6197
|
});
|
|
6052
6198
|
import {
|
|
6053
|
-
existsSync as
|
|
6199
|
+
existsSync as existsSync6,
|
|
6054
6200
|
mkdirSync as mkdirSync3,
|
|
6055
6201
|
readFileSync as readFileSync4,
|
|
6056
6202
|
renameSync as renameSync2,
|
|
@@ -6062,7 +6208,7 @@ function defaultRegistryPath() {
|
|
|
6062
6208
|
return join4(homedir4(), ".harmony-mcp", "agent-ports.json");
|
|
6063
6209
|
}
|
|
6064
6210
|
function load(path) {
|
|
6065
|
-
if (!
|
|
6211
|
+
if (!existsSync6(path))
|
|
6066
6212
|
return {};
|
|
6067
6213
|
try {
|
|
6068
6214
|
const raw = readFileSync4(path, "utf-8");
|
|
@@ -6071,13 +6217,13 @@ function load(path) {
|
|
|
6071
6217
|
return parsed;
|
|
6072
6218
|
return {};
|
|
6073
6219
|
} catch (err) {
|
|
6074
|
-
log.warn(
|
|
6220
|
+
log.warn(TAG26, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
6075
6221
|
return {};
|
|
6076
6222
|
}
|
|
6077
6223
|
}
|
|
6078
6224
|
function save(path, registry) {
|
|
6079
6225
|
const dir = dirname2(path);
|
|
6080
|
-
if (!
|
|
6226
|
+
if (!existsSync6(dir))
|
|
6081
6227
|
mkdirSync3(dir, { recursive: true });
|
|
6082
6228
|
const tmp = `${path}.tmp`;
|
|
6083
6229
|
writeFileSync2(tmp, JSON.stringify(registry, null, 2), "utf-8");
|
|
@@ -6089,7 +6235,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
6089
6235
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
6090
6236
|
save(path, registry);
|
|
6091
6237
|
} catch (err) {
|
|
6092
|
-
log.warn(
|
|
6238
|
+
log.warn(TAG26, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
6093
6239
|
}
|
|
6094
6240
|
}
|
|
6095
6241
|
function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
|
|
@@ -6105,10 +6251,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
6105
6251
|
delete registry[projectId];
|
|
6106
6252
|
save(path, registry);
|
|
6107
6253
|
} catch (err) {
|
|
6108
|
-
log.warn(
|
|
6254
|
+
log.warn(TAG26, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
6109
6255
|
}
|
|
6110
6256
|
}
|
|
6111
|
-
var
|
|
6257
|
+
var TAG26 = "port-registry";
|
|
6112
6258
|
var init_port_registry = __esm(() => {
|
|
6113
6259
|
init_log();
|
|
6114
6260
|
});
|
|
@@ -6129,7 +6275,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
6129
6275
|
const { card } = await client.getCard(cardId);
|
|
6130
6276
|
return card;
|
|
6131
6277
|
} catch (err) {
|
|
6132
|
-
log.warn(
|
|
6278
|
+
log.warn(TAG27, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
6133
6279
|
return null;
|
|
6134
6280
|
}
|
|
6135
6281
|
}
|
|
@@ -6139,7 +6285,7 @@ async function recoverOrphans(store, client, config) {
|
|
|
6139
6285
|
return [];
|
|
6140
6286
|
}
|
|
6141
6287
|
const outcomes = [];
|
|
6142
|
-
log.info(
|
|
6288
|
+
log.info(TAG27, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
6143
6289
|
for (const run of active) {
|
|
6144
6290
|
const outcome = {
|
|
6145
6291
|
runId: run.runId,
|
|
@@ -6151,11 +6297,11 @@ async function recoverOrphans(store, client, config) {
|
|
|
6151
6297
|
};
|
|
6152
6298
|
outcomes.push(outcome);
|
|
6153
6299
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
6154
|
-
log.warn(
|
|
6300
|
+
log.warn(TAG27, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
6155
6301
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
6156
6302
|
continue;
|
|
6157
6303
|
}
|
|
6158
|
-
log.info(
|
|
6304
|
+
log.info(TAG27, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
6159
6305
|
await recoverRun(run, store, client, config, outcome);
|
|
6160
6306
|
}
|
|
6161
6307
|
return outcomes;
|
|
@@ -6173,7 +6319,7 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
6173
6319
|
} catch (err) {
|
|
6174
6320
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6175
6321
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
6176
|
-
log.warn(
|
|
6322
|
+
log.warn(TAG27, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
6177
6323
|
}
|
|
6178
6324
|
const card = await fetchCardSafely(client, run.cardId);
|
|
6179
6325
|
if (card) {
|
|
@@ -6216,9 +6362,9 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
6216
6362
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6217
6363
|
outcome.errors.push(`endRun: ${msg}`);
|
|
6218
6364
|
}
|
|
6219
|
-
log.info(
|
|
6365
|
+
log.info(TAG27, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
6220
6366
|
}
|
|
6221
|
-
var
|
|
6367
|
+
var TAG27 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
6222
6368
|
var init_recovery = __esm(() => {
|
|
6223
6369
|
init_board_helpers();
|
|
6224
6370
|
init_log();
|
|
@@ -6266,7 +6412,7 @@ class Reconciler {
|
|
|
6266
6412
|
clearInterval(this.timer);
|
|
6267
6413
|
this.timer = null;
|
|
6268
6414
|
}
|
|
6269
|
-
log.info(
|
|
6415
|
+
log.info(TAG28, "Heartbeat stopped");
|
|
6270
6416
|
}
|
|
6271
6417
|
async recoverStaleRuns() {
|
|
6272
6418
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -6283,7 +6429,7 @@ class Reconciler {
|
|
|
6283
6429
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
6284
6430
|
continue;
|
|
6285
6431
|
const reason = daemonDead ? `foreign daemon ${run.daemonPid} is dead` : `our worker lost card ${run.cardId} with ${Math.round((now - run.lastHeartbeatAt) / 1000)}s stale heartbeat`;
|
|
6286
|
-
log.warn(
|
|
6432
|
+
log.warn(TAG28, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
6287
6433
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
6288
6434
|
runId: run.runId,
|
|
6289
6435
|
cardId: run.cardId,
|
|
@@ -6310,11 +6456,11 @@ class Reconciler {
|
|
|
6310
6456
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
6311
6457
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
6312
6458
|
continue;
|
|
6313
|
-
log.warn(
|
|
6459
|
+
log.warn(TAG28, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
6314
6460
|
try {
|
|
6315
6461
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
6316
6462
|
} catch (err) {
|
|
6317
|
-
log.error(
|
|
6463
|
+
log.error(TAG28, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6318
6464
|
}
|
|
6319
6465
|
}
|
|
6320
6466
|
}
|
|
@@ -6337,11 +6483,11 @@ class Reconciler {
|
|
|
6337
6483
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
6338
6484
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
6339
6485
|
continue;
|
|
6340
|
-
log.warn(
|
|
6486
|
+
log.warn(TAG28, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
6341
6487
|
try {
|
|
6342
6488
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
6343
6489
|
} catch (err) {
|
|
6344
|
-
log.error(
|
|
6490
|
+
log.error(TAG28, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6345
6491
|
}
|
|
6346
6492
|
}
|
|
6347
6493
|
}
|
|
@@ -6370,18 +6516,18 @@ class Reconciler {
|
|
|
6370
6516
|
const subtasks = card.subtasks ?? [];
|
|
6371
6517
|
const mode = reviewColumnIds.has(card.column_id) ? "review" : "implement";
|
|
6372
6518
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
6373
|
-
log.debug(
|
|
6519
|
+
log.debug(TAG28, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
6374
6520
|
continue;
|
|
6375
6521
|
}
|
|
6376
6522
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
6377
|
-
log.debug(
|
|
6523
|
+
log.debug(TAG28, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
6378
6524
|
continue;
|
|
6379
6525
|
}
|
|
6380
6526
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
6381
|
-
log.debug(
|
|
6527
|
+
log.debug(TAG28, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
|
|
6382
6528
|
continue;
|
|
6383
6529
|
}
|
|
6384
|
-
log.info(
|
|
6530
|
+
log.info(TAG28, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
6385
6531
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
6386
6532
|
}
|
|
6387
6533
|
}
|
|
@@ -6391,18 +6537,18 @@ class Reconciler {
|
|
|
6391
6537
|
await this.recoverStrandedInProgress(cards, columns, knownCardIds);
|
|
6392
6538
|
for (const knownId of knownCardIds) {
|
|
6393
6539
|
if (!allAgentCardIds.has(knownId)) {
|
|
6394
|
-
log.info(
|
|
6540
|
+
log.info(TAG28, `Missed unassign: ${knownId} — removing`);
|
|
6395
6541
|
await this.pool.removeCard(knownId);
|
|
6396
6542
|
}
|
|
6397
6543
|
}
|
|
6398
6544
|
await this.releaseStalledApprovals(cards, columns, knownCardIds);
|
|
6399
|
-
log.debug(
|
|
6545
|
+
log.debug(TAG28, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
6400
6546
|
} catch (err) {
|
|
6401
|
-
log.error(
|
|
6547
|
+
log.error(TAG28, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
6402
6548
|
}
|
|
6403
6549
|
}
|
|
6404
6550
|
}
|
|
6405
|
-
var
|
|
6551
|
+
var TAG28 = "reconcile";
|
|
6406
6552
|
var init_reconcile = __esm(() => {
|
|
6407
6553
|
init_board_helpers();
|
|
6408
6554
|
init_log();
|
|
@@ -6440,7 +6586,7 @@ function prettyBanner(config, version) {
|
|
|
6440
6586
|
checks.push({ kind: "ok", message });
|
|
6441
6587
|
},
|
|
6442
6588
|
warn(message) {
|
|
6443
|
-
log.warn(
|
|
6589
|
+
log.warn(TAG29, message);
|
|
6444
6590
|
checks.push({ kind: "warn", message: message.split(`
|
|
6445
6591
|
`, 1)[0] });
|
|
6446
6592
|
},
|
|
@@ -6465,25 +6611,25 @@ function prettyBanner(config, version) {
|
|
|
6465
6611
|
};
|
|
6466
6612
|
}
|
|
6467
6613
|
function jsonBanner(config, version) {
|
|
6468
|
-
log.info(
|
|
6469
|
-
log.info(
|
|
6614
|
+
log.info(TAG29, `Harmony Agent Daemon v${version} starting...`);
|
|
6615
|
+
log.info(TAG29, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Model: ${config.agent.claude.model} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
6470
6616
|
if (config.agent.review.enabled) {
|
|
6471
|
-
log.info(
|
|
6617
|
+
log.info(TAG29, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
6472
6618
|
}
|
|
6473
6619
|
let failed = false;
|
|
6474
6620
|
return {
|
|
6475
6621
|
setProjectName(_name) {},
|
|
6476
6622
|
setGitProvider(provider) {
|
|
6477
|
-
log.info(
|
|
6623
|
+
log.info(TAG29, `Git provider: ${provider}`);
|
|
6478
6624
|
},
|
|
6479
6625
|
setHttpPort(port) {
|
|
6480
|
-
log.info(
|
|
6626
|
+
log.info(TAG29, `HTTP server on port ${port}`);
|
|
6481
6627
|
},
|
|
6482
6628
|
check(message) {
|
|
6483
|
-
log.info(
|
|
6629
|
+
log.info(TAG29, message);
|
|
6484
6630
|
},
|
|
6485
6631
|
warn(message) {
|
|
6486
|
-
log.warn(
|
|
6632
|
+
log.warn(TAG29, message);
|
|
6487
6633
|
},
|
|
6488
6634
|
fail() {
|
|
6489
6635
|
failed = true;
|
|
@@ -6491,7 +6637,7 @@ function jsonBanner(config, version) {
|
|
|
6491
6637
|
async ready(message) {
|
|
6492
6638
|
if (failed)
|
|
6493
6639
|
return;
|
|
6494
|
-
log.info(
|
|
6640
|
+
log.info(TAG29, message);
|
|
6495
6641
|
}
|
|
6496
6642
|
};
|
|
6497
6643
|
}
|
|
@@ -6568,7 +6714,7 @@ function cyan(s) {
|
|
|
6568
6714
|
function yellow(s) {
|
|
6569
6715
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
6570
6716
|
}
|
|
6571
|
-
var
|
|
6717
|
+
var TAG29 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
6572
6718
|
var init_startup_banner = __esm(() => {
|
|
6573
6719
|
init_log();
|
|
6574
6720
|
ANSI = {
|
|
@@ -6715,18 +6861,18 @@ class Watcher {
|
|
|
6715
6861
|
}
|
|
6716
6862
|
async start() {
|
|
6717
6863
|
if (!isPretty()) {
|
|
6718
|
-
log.info(
|
|
6864
|
+
log.info(TAG30, "Connecting to Supabase realtime (broadcast)...");
|
|
6719
6865
|
}
|
|
6720
6866
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
6721
6867
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
6722
6868
|
const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
6723
|
-
log.debug(
|
|
6869
|
+
log.debug(TAG30, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
6724
6870
|
this.onCardBroadcast({
|
|
6725
6871
|
event: "card_update",
|
|
6726
6872
|
payload: msg.payload ?? {}
|
|
6727
6873
|
});
|
|
6728
6874
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
6729
|
-
log.debug(
|
|
6875
|
+
log.debug(TAG30, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
6730
6876
|
this.onCardBroadcast({
|
|
6731
6877
|
event: "card_created",
|
|
6732
6878
|
payload: msg.payload ?? {}
|
|
@@ -6736,29 +6882,29 @@ class Watcher {
|
|
|
6736
6882
|
const cardId = payload.card_id;
|
|
6737
6883
|
const command = payload.command;
|
|
6738
6884
|
if (cardId && command) {
|
|
6739
|
-
log.info(
|
|
6885
|
+
log.info(TAG30, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
6740
6886
|
this.onAgentCommand?.({ cardId, command });
|
|
6741
6887
|
}
|
|
6742
6888
|
}).subscribe((status) => {
|
|
6743
6889
|
if (status === "SUBSCRIBED") {
|
|
6744
6890
|
this.connected = true;
|
|
6745
6891
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
6746
|
-
log.info(
|
|
6892
|
+
log.info(TAG30, "Broadcast subscription active");
|
|
6747
6893
|
}
|
|
6748
6894
|
this.maybeResolveReady();
|
|
6749
6895
|
} else if (status === "CHANNEL_ERROR") {
|
|
6750
6896
|
this.connected = false;
|
|
6751
|
-
log.error(
|
|
6897
|
+
log.error(TAG30, "Broadcast channel error — will rely on reconciliation");
|
|
6752
6898
|
} else if (status === "TIMED_OUT") {
|
|
6753
6899
|
this.connected = false;
|
|
6754
|
-
log.warn(
|
|
6900
|
+
log.warn(TAG30, "Broadcast subscription timed out — retrying...");
|
|
6755
6901
|
} else if (status === "CLOSED") {
|
|
6756
6902
|
this.connected = false;
|
|
6757
6903
|
}
|
|
6758
6904
|
});
|
|
6759
6905
|
this.channel = channel;
|
|
6760
6906
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
6761
|
-
log.debug(
|
|
6907
|
+
log.debug(TAG30, "Presence sync");
|
|
6762
6908
|
}).subscribe(async (status) => {
|
|
6763
6909
|
if (status === "SUBSCRIBED") {
|
|
6764
6910
|
await presenceChannel.track({
|
|
@@ -6771,7 +6917,7 @@ class Watcher {
|
|
|
6771
6917
|
agentName: this.identity.agentName
|
|
6772
6918
|
});
|
|
6773
6919
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
6774
|
-
log.info(
|
|
6920
|
+
log.info(TAG30, "Presence tracked on board-presence channel");
|
|
6775
6921
|
}
|
|
6776
6922
|
this.presenceTracked = true;
|
|
6777
6923
|
this.maybeResolveReady();
|
|
@@ -6793,10 +6939,10 @@ class Watcher {
|
|
|
6793
6939
|
this.supabase = null;
|
|
6794
6940
|
}
|
|
6795
6941
|
this.connected = false;
|
|
6796
|
-
log.info(
|
|
6942
|
+
log.info(TAG30, "Broadcast subscription stopped");
|
|
6797
6943
|
}
|
|
6798
6944
|
}
|
|
6799
|
-
var
|
|
6945
|
+
var TAG30 = "watcher";
|
|
6800
6946
|
var init_watcher = __esm(() => {
|
|
6801
6947
|
init_log();
|
|
6802
6948
|
});
|
|
@@ -6809,8 +6955,8 @@ __export(exports_worktree_gc, {
|
|
|
6809
6955
|
isTransientGitNetworkError: () => isTransientGitNetworkError,
|
|
6810
6956
|
WorktreeGc: () => WorktreeGc
|
|
6811
6957
|
});
|
|
6812
|
-
import { execFileSync as
|
|
6813
|
-
import { readdirSync, statSync as statSync2 } from "node:fs";
|
|
6958
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
6959
|
+
import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
6814
6960
|
import { resolve as resolve3 } from "node:path";
|
|
6815
6961
|
function isTransientGitNetworkError(message) {
|
|
6816
6962
|
return TRANSIENT_GIT_NETWORK_ERROR.test(message);
|
|
@@ -6835,7 +6981,7 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
6835
6981
|
const baseAbs = resolve3(repoRoot, basePath);
|
|
6836
6982
|
let entries;
|
|
6837
6983
|
try {
|
|
6838
|
-
entries =
|
|
6984
|
+
entries = readdirSync2(baseAbs);
|
|
6839
6985
|
} catch {
|
|
6840
6986
|
return result;
|
|
6841
6987
|
}
|
|
@@ -6877,16 +7023,16 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
6877
7023
|
}
|
|
6878
7024
|
}
|
|
6879
7025
|
try {
|
|
6880
|
-
|
|
7026
|
+
execFileSync10("git", ["worktree", "prune", "--expire=now"], {
|
|
6881
7027
|
cwd: repoRoot,
|
|
6882
7028
|
stdio: "pipe"
|
|
6883
7029
|
});
|
|
6884
7030
|
} catch {}
|
|
6885
7031
|
if (result.removed.length > 0) {
|
|
6886
|
-
log.info(
|
|
7032
|
+
log.info(TAG31, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
6887
7033
|
}
|
|
6888
7034
|
if (result.errors.length > 0) {
|
|
6889
|
-
log.warn(
|
|
7035
|
+
log.warn(TAG31, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
6890
7036
|
}
|
|
6891
7037
|
return result;
|
|
6892
7038
|
}
|
|
@@ -6908,7 +7054,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6908
7054
|
return result;
|
|
6909
7055
|
}
|
|
6910
7056
|
try {
|
|
6911
|
-
|
|
7057
|
+
execFileSync10("git", ["fetch", "--prune", "origin"], {
|
|
6912
7058
|
cwd: repoRoot,
|
|
6913
7059
|
stdio: "pipe",
|
|
6914
7060
|
...GIT_NETWORK_EXEC
|
|
@@ -6916,7 +7062,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6916
7062
|
} catch (err) {
|
|
6917
7063
|
const detail = gitErrorDetail(err);
|
|
6918
7064
|
if (isTransientGitNetworkError(detail)) {
|
|
6919
|
-
log.debug(
|
|
7065
|
+
log.debug(TAG31, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
6920
7066
|
return result;
|
|
6921
7067
|
}
|
|
6922
7068
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -6924,7 +7070,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6924
7070
|
const refPattern = `refs/remotes/origin/${opts.prefix}*`;
|
|
6925
7071
|
let listing = "";
|
|
6926
7072
|
try {
|
|
6927
|
-
listing =
|
|
7073
|
+
listing = execFileSync10("git", [
|
|
6928
7074
|
"for-each-ref",
|
|
6929
7075
|
"--format=%(refname:strip=3) %(committerdate:unix)",
|
|
6930
7076
|
refPattern
|
|
@@ -6955,11 +7101,11 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6955
7101
|
continue;
|
|
6956
7102
|
}
|
|
6957
7103
|
if (clock() > sweepDeadline) {
|
|
6958
|
-
log.debug(
|
|
7104
|
+
log.debug(TAG31, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
6959
7105
|
break;
|
|
6960
7106
|
}
|
|
6961
7107
|
try {
|
|
6962
|
-
|
|
7108
|
+
execFileSync10("git", ["push", "origin", `:refs/heads/${ref}`], {
|
|
6963
7109
|
cwd: repoRoot,
|
|
6964
7110
|
stdio: "pipe",
|
|
6965
7111
|
...GIT_NETWORK_EXEC
|
|
@@ -6968,17 +7114,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6968
7114
|
} catch (err) {
|
|
6969
7115
|
const detail = gitErrorDetail(err);
|
|
6970
7116
|
if (isTransientGitNetworkError(detail)) {
|
|
6971
|
-
log.debug(
|
|
7117
|
+
log.debug(TAG31, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
6972
7118
|
break;
|
|
6973
7119
|
}
|
|
6974
7120
|
result.errors.push({ ref, error: detail });
|
|
6975
7121
|
}
|
|
6976
7122
|
}
|
|
6977
7123
|
if (result.removed.length > 0) {
|
|
6978
|
-
log.info(
|
|
7124
|
+
log.info(TAG31, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
6979
7125
|
}
|
|
6980
7126
|
if (result.errors.length > 0) {
|
|
6981
|
-
log.warn(
|
|
7127
|
+
log.warn(TAG31, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
6982
7128
|
}
|
|
6983
7129
|
return result;
|
|
6984
7130
|
}
|
|
@@ -7009,27 +7155,27 @@ class WorktreeGc {
|
|
|
7009
7155
|
try {
|
|
7010
7156
|
runWorktreeGc(this.basePath, this.store);
|
|
7011
7157
|
} catch (err) {
|
|
7012
|
-
log.warn(
|
|
7158
|
+
log.warn(TAG31, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
7013
7159
|
}
|
|
7014
7160
|
if (this.remoteOpts) {
|
|
7015
7161
|
try {
|
|
7016
7162
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
7017
7163
|
} catch (err) {
|
|
7018
|
-
log.warn(
|
|
7164
|
+
log.warn(TAG31, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
7019
7165
|
}
|
|
7020
7166
|
}
|
|
7021
7167
|
}
|
|
7022
7168
|
}
|
|
7023
7169
|
function getRepoRoot2() {
|
|
7024
7170
|
try {
|
|
7025
|
-
return
|
|
7171
|
+
return execFileSync10("git", ["rev-parse", "--show-toplevel"], {
|
|
7026
7172
|
encoding: "utf-8"
|
|
7027
7173
|
}).trim();
|
|
7028
7174
|
} catch {
|
|
7029
7175
|
return null;
|
|
7030
7176
|
}
|
|
7031
7177
|
}
|
|
7032
|
-
var
|
|
7178
|
+
var TAG31 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
|
|
7033
7179
|
var init_worktree_gc = __esm(() => {
|
|
7034
7180
|
init_log();
|
|
7035
7181
|
init_worktree();
|
|
@@ -7063,12 +7209,12 @@ __export(exports_src, {
|
|
|
7063
7209
|
validatePrerequisites: () => validatePrerequisites,
|
|
7064
7210
|
main: () => main
|
|
7065
7211
|
});
|
|
7066
|
-
import { execFileSync as
|
|
7212
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
7067
7213
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7068
7214
|
import { createRequire as createRequire2 } from "node:module";
|
|
7069
7215
|
async function validatePrerequisites(config, banner) {
|
|
7070
7216
|
try {
|
|
7071
|
-
const ver =
|
|
7217
|
+
const ver = execFileSync11("claude", ["--version"], {
|
|
7072
7218
|
encoding: "utf-8"
|
|
7073
7219
|
}).trim();
|
|
7074
7220
|
banner.check(`Claude CLI ${ver}`);
|
|
@@ -7083,14 +7229,14 @@ async function validatePrerequisites(config, banner) {
|
|
|
7083
7229
|
validateGitProviderCli(provider);
|
|
7084
7230
|
}
|
|
7085
7231
|
try {
|
|
7086
|
-
const status =
|
|
7232
|
+
const status = execFileSync11("git", ["status", "--porcelain"], {
|
|
7087
7233
|
encoding: "utf-8"
|
|
7088
7234
|
}).trim();
|
|
7089
7235
|
if (status) {
|
|
7090
7236
|
banner.warn(`Working directory has uncommitted changes:
|
|
7091
7237
|
${status}`);
|
|
7092
7238
|
}
|
|
7093
|
-
|
|
7239
|
+
execFileSync11("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
|
|
7094
7240
|
encoding: "utf-8",
|
|
7095
7241
|
stdio: "pipe"
|
|
7096
7242
|
});
|
|
@@ -7133,7 +7279,7 @@ async function main() {
|
|
|
7133
7279
|
} catch (err) {
|
|
7134
7280
|
if (err instanceof ConfigValidationError) {
|
|
7135
7281
|
banner.fail();
|
|
7136
|
-
log.error(
|
|
7282
|
+
log.error(TAG32, err.message);
|
|
7137
7283
|
process.exit(1);
|
|
7138
7284
|
}
|
|
7139
7285
|
throw err;
|
|
@@ -7242,7 +7388,7 @@ async function main() {
|
|
|
7242
7388
|
if (shuttingDown)
|
|
7243
7389
|
return;
|
|
7244
7390
|
shuttingDown = true;
|
|
7245
|
-
log.info(
|
|
7391
|
+
log.info(TAG32, `Received ${signal}, shutting down gracefully...`);
|
|
7246
7392
|
reconciler.stop();
|
|
7247
7393
|
mergeMonitor?.stop();
|
|
7248
7394
|
worktreeGc.stop();
|
|
@@ -7252,18 +7398,18 @@ async function main() {
|
|
|
7252
7398
|
}
|
|
7253
7399
|
await watcher.stop();
|
|
7254
7400
|
await pool.shutdown();
|
|
7255
|
-
log.info(
|
|
7401
|
+
log.info(TAG32, "Daemon stopped.");
|
|
7256
7402
|
process.exit(exitCode);
|
|
7257
7403
|
};
|
|
7258
7404
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
7259
7405
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
7260
7406
|
process.on("uncaughtException", (err) => {
|
|
7261
|
-
log.error(
|
|
7407
|
+
log.error(TAG32, `Uncaught exception: ${err.message}`);
|
|
7262
7408
|
exitCode = 1;
|
|
7263
7409
|
shutdown("uncaughtException");
|
|
7264
7410
|
});
|
|
7265
7411
|
process.on("unhandledRejection", (reason) => {
|
|
7266
|
-
log.error(
|
|
7412
|
+
log.error(TAG32, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
7267
7413
|
exitCode = 1;
|
|
7268
7414
|
shutdown("unhandledRejection");
|
|
7269
7415
|
});
|
|
@@ -7316,34 +7462,34 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
7316
7462
|
if (assignedAgentId === undefined)
|
|
7317
7463
|
return;
|
|
7318
7464
|
if (assignedAgentId === agentId) {
|
|
7319
|
-
log.info(
|
|
7465
|
+
log.info(TAG32, `Broadcast: card ${cardId} assigned to agent`);
|
|
7320
7466
|
try {
|
|
7321
7467
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
7322
7468
|
} catch (err) {
|
|
7323
|
-
log.error(
|
|
7469
|
+
log.error(TAG32, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
7324
7470
|
}
|
|
7325
7471
|
} else if (pool.isCardKnown(cardId)) {
|
|
7326
|
-
log.info(
|
|
7472
|
+
log.info(TAG32, `Broadcast: card ${cardId} unassigned from agent`);
|
|
7327
7473
|
await pool.removeCard(cardId);
|
|
7328
7474
|
}
|
|
7329
7475
|
}
|
|
7330
7476
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
7331
7477
|
const { card } = await client.getCard(cardId);
|
|
7332
7478
|
if (card.assigned_agent_id !== agentId) {
|
|
7333
|
-
log.debug(
|
|
7479
|
+
log.debug(TAG32, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
7334
7480
|
return;
|
|
7335
7481
|
}
|
|
7336
7482
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
7337
7483
|
const columns = board.columns;
|
|
7338
7484
|
const column = columns.find((c) => c.id === card.column_id);
|
|
7339
7485
|
if (!column) {
|
|
7340
|
-
log.warn(
|
|
7486
|
+
log.warn(TAG32, `Column not found for card ${cardId}`);
|
|
7341
7487
|
return;
|
|
7342
7488
|
}
|
|
7343
7489
|
const isPickupColumn = config.agent.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
7344
7490
|
const isReviewColumn = config.agent.review.enabled && config.agent.review.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
7345
7491
|
if (!isPickupColumn && !isReviewColumn) {
|
|
7346
|
-
log.info(
|
|
7492
|
+
log.info(TAG32, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
|
|
7347
7493
|
return;
|
|
7348
7494
|
}
|
|
7349
7495
|
const mode = isReviewColumn ? "review" : "implement";
|
|
@@ -7351,16 +7497,16 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
7351
7497
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
7352
7498
|
const subtasks = card.subtasks ?? [];
|
|
7353
7499
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
7354
|
-
log.debug(
|
|
7500
|
+
log.debug(TAG32, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
7355
7501
|
return;
|
|
7356
7502
|
}
|
|
7357
7503
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
7358
|
-
log.info(
|
|
7504
|
+
log.info(TAG32, `Card #${card.short_id} has no branch reference — skipping auto-review`);
|
|
7359
7505
|
return;
|
|
7360
7506
|
}
|
|
7361
7507
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
7362
7508
|
}
|
|
7363
|
-
var
|
|
7509
|
+
var TAG32 = "daemon", PKG_VERSION;
|
|
7364
7510
|
var init_src = __esm(() => {
|
|
7365
7511
|
init_board_helpers();
|
|
7366
7512
|
init_config();
|