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