@gethmy/agent 1.10.3 → 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 +414 -228
- package/dist/index.js +414 -228
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -396,9 +396,14 @@ 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
|
+
tiers: {
|
|
403
|
+
simple: "claude-haiku-4-5",
|
|
404
|
+
advanced: "claude-sonnet-4-6",
|
|
405
|
+
research: "claude-opus-4-8"
|
|
406
|
+
},
|
|
402
407
|
reviewModel: "sonnet",
|
|
403
408
|
maxTurns: 80,
|
|
404
409
|
reviewMaxTurns: 60,
|
|
@@ -2076,8 +2081,127 @@ var init_git_diff_stat = __esm(() => {
|
|
|
2076
2081
|
init_log();
|
|
2077
2082
|
});
|
|
2078
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
|
+
|
|
2079
2203
|
// src/verification.ts
|
|
2080
|
-
import { execFileSync as
|
|
2204
|
+
import { execFileSync as execFileSync7, spawn } from "node:child_process";
|
|
2081
2205
|
async function runVerification(worktreePath, config, workerId) {
|
|
2082
2206
|
const result = {
|
|
2083
2207
|
passed: true,
|
|
@@ -2086,39 +2210,43 @@ async function runVerification(worktreePath, config, workerId) {
|
|
|
2086
2210
|
reviewFindings: []
|
|
2087
2211
|
};
|
|
2088
2212
|
if (config.verification.build) {
|
|
2089
|
-
log.info(
|
|
2213
|
+
log.info(TAG12, `[worker:${workerId}] Running build...`);
|
|
2090
2214
|
result.buildErrors = runBuild(worktreePath, config.verification.timeout);
|
|
2091
2215
|
if (result.buildErrors.length > 0) {
|
|
2092
|
-
log.warn(
|
|
2216
|
+
log.warn(TAG12, `[worker:${workerId}] Build failed with ${result.buildErrors.length} error(s)`);
|
|
2093
2217
|
result.passed = false;
|
|
2094
2218
|
} else {
|
|
2095
|
-
log.info(
|
|
2219
|
+
log.info(TAG12, `[worker:${workerId}] Build passed`);
|
|
2096
2220
|
}
|
|
2097
2221
|
}
|
|
2098
2222
|
if (config.verification.lint) {
|
|
2099
|
-
log.info(
|
|
2223
|
+
log.info(TAG12, `[worker:${workerId}] Running lint...`);
|
|
2100
2224
|
result.lintWarnings = runLint(worktreePath, config.verification.timeout);
|
|
2101
2225
|
if (result.lintWarnings.length > 0) {
|
|
2102
|
-
log.warn(
|
|
2226
|
+
log.warn(TAG12, `[worker:${workerId}] Lint found ${result.lintWarnings.length} issue(s)`);
|
|
2103
2227
|
} else {
|
|
2104
|
-
log.info(
|
|
2228
|
+
log.info(TAG12, `[worker:${workerId}] Lint passed`);
|
|
2105
2229
|
}
|
|
2106
2230
|
}
|
|
2107
2231
|
if (config.verification.deepReview) {
|
|
2108
|
-
log.info(
|
|
2232
|
+
log.info(TAG12, `[worker:${workerId}] Running deep review...`);
|
|
2109
2233
|
result.reviewFindings = await runDeepReview(worktreePath, config, workerId);
|
|
2110
2234
|
if (result.reviewFindings.length > 0) {
|
|
2111
|
-
log.warn(
|
|
2235
|
+
log.warn(TAG12, `[worker:${workerId}] Deep review found ${result.reviewFindings.length} finding(s)`);
|
|
2112
2236
|
} else {
|
|
2113
|
-
log.info(
|
|
2237
|
+
log.info(TAG12, `[worker:${workerId}] Deep review passed`);
|
|
2114
2238
|
}
|
|
2115
2239
|
}
|
|
2116
2240
|
return result;
|
|
2117
2241
|
}
|
|
2118
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
|
+
}
|
|
2119
2248
|
try {
|
|
2120
|
-
|
|
2121
|
-
execFileSync6(cmd, args, {
|
|
2249
|
+
execFileSync7(command.cmd, command.args, {
|
|
2122
2250
|
cwd: worktreePath,
|
|
2123
2251
|
timeout,
|
|
2124
2252
|
stdio: "pipe"
|
|
@@ -2129,9 +2257,13 @@ function runBuild(worktreePath, timeout) {
|
|
|
2129
2257
|
}
|
|
2130
2258
|
}
|
|
2131
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
|
+
}
|
|
2132
2265
|
try {
|
|
2133
|
-
|
|
2134
|
-
execFileSync6(cmd, args, {
|
|
2266
|
+
execFileSync7(command.cmd, command.args, {
|
|
2135
2267
|
cwd: worktreePath,
|
|
2136
2268
|
timeout,
|
|
2137
2269
|
stdio: "pipe"
|
|
@@ -2142,6 +2274,10 @@ function runLint(worktreePath, timeout) {
|
|
|
2142
2274
|
}
|
|
2143
2275
|
}
|
|
2144
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
|
+
}
|
|
2145
2281
|
const port = config.verification.devServerBasePort + workerId;
|
|
2146
2282
|
let devServer = null;
|
|
2147
2283
|
try {
|
|
@@ -2154,12 +2290,12 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
2154
2290
|
await waitForDevServer(devServer, 30000);
|
|
2155
2291
|
await probeDevServer(port);
|
|
2156
2292
|
} catch (err) {
|
|
2157
|
-
log.error(
|
|
2293
|
+
log.error(TAG12, `Dev server did not become ready: ${err instanceof Error ? err.message : err}`);
|
|
2158
2294
|
return [];
|
|
2159
2295
|
}
|
|
2160
2296
|
let diff = "";
|
|
2161
2297
|
try {
|
|
2162
|
-
diff =
|
|
2298
|
+
diff = execFileSync7("git", ["diff", `origin/${config.worktree.baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
|
|
2163
2299
|
} catch {
|
|
2164
2300
|
diff = "(unable to retrieve diff)";
|
|
2165
2301
|
}
|
|
@@ -2176,7 +2312,7 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
2176
2312
|
].join(`
|
|
2177
2313
|
`);
|
|
2178
2314
|
const leanSources = config.claude.leanSettingSources;
|
|
2179
|
-
const output =
|
|
2315
|
+
const output = execFileSync7("claude", [
|
|
2180
2316
|
"--print",
|
|
2181
2317
|
"--model",
|
|
2182
2318
|
"sonnet",
|
|
@@ -2193,7 +2329,7 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
2193
2329
|
});
|
|
2194
2330
|
return parseReviewFindings(output);
|
|
2195
2331
|
} catch (err) {
|
|
2196
|
-
log.error(
|
|
2332
|
+
log.error(TAG12, `Deep review failed: ${err instanceof Error ? err.message : err}`);
|
|
2197
2333
|
return [];
|
|
2198
2334
|
} finally {
|
|
2199
2335
|
if (devServer && !devServer.killed) {
|
|
@@ -2229,8 +2365,8 @@ function attemptAutoFix(worktreePath, config, errors) {
|
|
|
2229
2365
|
"--",
|
|
2230
2366
|
fixPrompt
|
|
2231
2367
|
];
|
|
2232
|
-
log.info(
|
|
2233
|
-
|
|
2368
|
+
log.info(TAG12, "Spawning Claude for auto-fix...");
|
|
2369
|
+
execFileSync7("claude", args, {
|
|
2234
2370
|
cwd: worktreePath,
|
|
2235
2371
|
timeout: config.verification.timeout,
|
|
2236
2372
|
stdio: "pipe"
|
|
@@ -2260,7 +2396,7 @@ async function reportFindings(client, cardId, result, recovery) {
|
|
|
2260
2396
|
try {
|
|
2261
2397
|
await client.createSubtask(cardId, title);
|
|
2262
2398
|
} catch (err) {
|
|
2263
|
-
log.error(
|
|
2399
|
+
log.error(TAG12, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
2264
2400
|
}
|
|
2265
2401
|
}));
|
|
2266
2402
|
if (overflow > 0) {
|
|
@@ -2268,7 +2404,7 @@ async function reportFindings(client, cardId, result, recovery) {
|
|
|
2268
2404
|
await client.createSubtask(cardId, `...and ${overflow} more issues`);
|
|
2269
2405
|
} catch {}
|
|
2270
2406
|
}
|
|
2271
|
-
log.info(
|
|
2407
|
+
log.info(TAG12, `Reported ${Math.min(items.length, maxSubtasks)} finding(s) as subtasks on card ${cardId}`);
|
|
2272
2408
|
}
|
|
2273
2409
|
function parseErrorOutput(err) {
|
|
2274
2410
|
const stderr = err?.stderr?.toString() ?? "";
|
|
@@ -2352,10 +2488,11 @@ async function probeDevServer(port, timeoutMs = 5000) {
|
|
|
2352
2488
|
clearTimeout(timer);
|
|
2353
2489
|
}
|
|
2354
2490
|
}
|
|
2355
|
-
var
|
|
2491
|
+
var TAG12 = "verification", DevServerReadinessError;
|
|
2356
2492
|
var init_verification = __esm(() => {
|
|
2357
2493
|
init_log();
|
|
2358
2494
|
init_pm();
|
|
2495
|
+
init_project_type();
|
|
2359
2496
|
DevServerReadinessError = class DevServerReadinessError extends Error {
|
|
2360
2497
|
constructor(message) {
|
|
2361
2498
|
super(message);
|
|
@@ -2365,7 +2502,7 @@ var init_verification = __esm(() => {
|
|
|
2365
2502
|
});
|
|
2366
2503
|
|
|
2367
2504
|
// src/completion.ts
|
|
2368
|
-
import { execFileSync as
|
|
2505
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
2369
2506
|
function formatTokenCount(tokens) {
|
|
2370
2507
|
if (tokens >= 1e6)
|
|
2371
2508
|
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
@@ -2395,7 +2532,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2395
2532
|
};
|
|
2396
2533
|
const hasCommits = checkHasCommits(worktreePath, config.worktree.baseBranch);
|
|
2397
2534
|
if (!hasCommits) {
|
|
2398
|
-
log.warn(
|
|
2535
|
+
log.warn(TAG13, `No commits on branch ${branchName} — skipping completion`);
|
|
2399
2536
|
await client.endAgentSession(card.id, {
|
|
2400
2537
|
status: "completed",
|
|
2401
2538
|
progressPercent: 100,
|
|
@@ -2404,13 +2541,13 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2404
2541
|
cleanupWorktree(worktreePath, branchName);
|
|
2405
2542
|
return true;
|
|
2406
2543
|
}
|
|
2407
|
-
log.info(
|
|
2544
|
+
log.info(TAG13, `Pushing branch ${branchName} (pre-verify)...`);
|
|
2408
2545
|
let lastPushedSha = null;
|
|
2409
2546
|
try {
|
|
2410
2547
|
pushBranch(branchName, worktreePath);
|
|
2411
2548
|
lastPushedSha = readHeadSha(worktreePath);
|
|
2412
2549
|
} catch (err) {
|
|
2413
|
-
log.error(
|
|
2550
|
+
log.error(TAG13, `pre-verify push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
2414
2551
|
}
|
|
2415
2552
|
const recoveryUrl = lastPushedSha ? getBranchWebUrl(branchName, worktreePath) : null;
|
|
2416
2553
|
if (config.verification.enabled) {
|
|
@@ -2425,7 +2562,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2425
2562
|
let autoFixAttempts = 0;
|
|
2426
2563
|
if (!result.passed && config.verification.autoFix) {
|
|
2427
2564
|
for (let attempt = 0;attempt < config.verification.maxFixAttempts; attempt++) {
|
|
2428
|
-
log.info(
|
|
2565
|
+
log.info(TAG13, `Auto-fix attempt ${attempt + 1}/${config.verification.maxFixAttempts}`);
|
|
2429
2566
|
await client.updateAgentProgress(card.id, {
|
|
2430
2567
|
agentIdentifier: agentIdentifier(workerId),
|
|
2431
2568
|
agentName: AGENT_NAME,
|
|
@@ -2438,14 +2575,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2438
2575
|
result = await runVerification(worktreePath, config, workerId);
|
|
2439
2576
|
autoFixAttempts = attempt + 1;
|
|
2440
2577
|
if (result.passed) {
|
|
2441
|
-
log.info(
|
|
2578
|
+
log.info(TAG13, `Auto-fix succeeded on attempt ${attempt + 1}`);
|
|
2442
2579
|
const sha = readHeadSha(worktreePath);
|
|
2443
2580
|
if (sha && sha !== lastPushedSha) {
|
|
2444
2581
|
try {
|
|
2445
2582
|
pushBranch(branchName, worktreePath);
|
|
2446
2583
|
lastPushedSha = sha;
|
|
2447
2584
|
} catch (err) {
|
|
2448
|
-
log.warn(
|
|
2585
|
+
log.warn(TAG13, `post-fix push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
2449
2586
|
}
|
|
2450
2587
|
}
|
|
2451
2588
|
break;
|
|
@@ -2454,14 +2591,14 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2454
2591
|
}
|
|
2455
2592
|
verificationResult = result;
|
|
2456
2593
|
if (!result.passed) {
|
|
2457
|
-
log.warn(
|
|
2594
|
+
log.warn(TAG13, `Verification failed for #${card.short_id} — reporting findings`);
|
|
2458
2595
|
const failSha = readHeadSha(worktreePath);
|
|
2459
2596
|
if (failSha && failSha !== lastPushedSha) {
|
|
2460
2597
|
try {
|
|
2461
2598
|
pushBranch(branchName, worktreePath);
|
|
2462
2599
|
lastPushedSha = failSha;
|
|
2463
2600
|
} catch (err) {
|
|
2464
|
-
log.warn(
|
|
2601
|
+
log.warn(TAG13, `post-fail push failed for ${branchName}: ${err instanceof Error ? err.message : err}`);
|
|
2465
2602
|
}
|
|
2466
2603
|
}
|
|
2467
2604
|
const failureSummary = buildVerificationFailureSummary(result, autoFixAttempts);
|
|
@@ -2472,7 +2609,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2472
2609
|
recoveryBranch: branchName
|
|
2473
2610
|
});
|
|
2474
2611
|
} catch (err) {
|
|
2475
|
-
log.debug(
|
|
2612
|
+
log.debug(TAG13, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
2476
2613
|
}
|
|
2477
2614
|
await reportFindings(client, card.id, result, lastPushedSha ? { branchName, branchUrl: recoveryUrl } : null);
|
|
2478
2615
|
await moveCardToColumn(client, card, config.verification.failColumn);
|
|
@@ -2486,7 +2623,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2486
2623
|
cleanupWorktree(worktreePath, branchName);
|
|
2487
2624
|
return false;
|
|
2488
2625
|
}
|
|
2489
|
-
log.info(
|
|
2626
|
+
log.info(TAG13, `Verification passed for #${card.short_id}`);
|
|
2490
2627
|
}
|
|
2491
2628
|
let prUrl = null;
|
|
2492
2629
|
if (config.completion.createPR) {
|
|
@@ -2499,7 +2636,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2499
2636
|
try {
|
|
2500
2637
|
await onMovedToCompletion(card);
|
|
2501
2638
|
} catch (err) {
|
|
2502
|
-
log.warn(
|
|
2639
|
+
log.warn(TAG13, `successor promotion failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
2503
2640
|
}
|
|
2504
2641
|
}
|
|
2505
2642
|
}
|
|
@@ -2533,7 +2670,7 @@ async function runCompletion(client, card, branchName, worktreePath, config, wor
|
|
|
2533
2670
|
});
|
|
2534
2671
|
}
|
|
2535
2672
|
cleanupWorktree(worktreePath, branchName);
|
|
2536
|
-
log.info(
|
|
2673
|
+
log.info(TAG13, `Completion done for #${card.short_id}${prUrl ? ` — PR: ${prUrl}` : ""}`);
|
|
2537
2674
|
return true;
|
|
2538
2675
|
}
|
|
2539
2676
|
function buildVerificationFailureSummary(result, autoFixAttempts) {
|
|
@@ -2553,7 +2690,7 @@ function buildVerificationFailureSummary(result, autoFixAttempts) {
|
|
|
2553
2690
|
}
|
|
2554
2691
|
function readHeadSha(worktreePath) {
|
|
2555
2692
|
try {
|
|
2556
|
-
return
|
|
2693
|
+
return execFileSync8("git", ["rev-parse", "HEAD"], {
|
|
2557
2694
|
cwd: worktreePath,
|
|
2558
2695
|
encoding: "utf-8"
|
|
2559
2696
|
}).trim();
|
|
@@ -2563,7 +2700,7 @@ function readHeadSha(worktreePath) {
|
|
|
2563
2700
|
}
|
|
2564
2701
|
function checkHasCommits(worktreePath, baseBranch) {
|
|
2565
2702
|
try {
|
|
2566
|
-
const count =
|
|
2703
|
+
const count = execFileSync8("git", ["rev-list", "--count", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
|
|
2567
2704
|
return parseInt(count, 10) > 0;
|
|
2568
2705
|
} catch {
|
|
2569
2706
|
return false;
|
|
@@ -2572,7 +2709,7 @@ function checkHasCommits(worktreePath, baseBranch) {
|
|
|
2572
2709
|
async function postSummary(client, card, branchName, worktreePath, prUrl, baseBranch, sessionStats) {
|
|
2573
2710
|
let commitLog = "";
|
|
2574
2711
|
try {
|
|
2575
|
-
commitLog =
|
|
2712
|
+
commitLog = execFileSync8("git", ["log", "--oneline", `origin/${baseBranch}..HEAD`], { cwd: worktreePath, encoding: "utf-8" }).trim();
|
|
2576
2713
|
} catch {}
|
|
2577
2714
|
const SUMMARY_MARKER = `---
|
|
2578
2715
|
**Agent completed**`;
|
|
@@ -2617,12 +2754,12 @@ ${commitLog}
|
|
|
2617
2754
|
description: baseDesc + parts.join(`
|
|
2618
2755
|
`)
|
|
2619
2756
|
});
|
|
2620
|
-
log.info(
|
|
2757
|
+
log.info(TAG13, `Posted completion summary to #${card.short_id}`);
|
|
2621
2758
|
} catch (err) {
|
|
2622
|
-
log.error(
|
|
2759
|
+
log.error(TAG13, `Failed to post summary: ${err instanceof Error ? err.message : err}`);
|
|
2623
2760
|
}
|
|
2624
2761
|
}
|
|
2625
|
-
var
|
|
2762
|
+
var TAG13 = "completion";
|
|
2626
2763
|
var init_completion = __esm(() => {
|
|
2627
2764
|
init_board_helpers();
|
|
2628
2765
|
init_episode_writer();
|
|
@@ -2642,7 +2779,12 @@ function spawnInGroup(command, args, options = {}) {
|
|
|
2642
2779
|
return spawn2(command, args, {
|
|
2643
2780
|
...options,
|
|
2644
2781
|
detached: true,
|
|
2645
|
-
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
|
+
}
|
|
2646
2788
|
});
|
|
2647
2789
|
}
|
|
2648
2790
|
function signalGroup(proc, signal) {
|
|
@@ -2657,7 +2799,7 @@ function signalGroup(proc, signal) {
|
|
|
2657
2799
|
} catch (err) {
|
|
2658
2800
|
const code = err.code;
|
|
2659
2801
|
if (code !== "ESRCH") {
|
|
2660
|
-
log.warn(
|
|
2802
|
+
log.warn(TAG14, `signal ${signal} to pgid ${proc.pid} failed: ${err instanceof Error ? err.message : err}`);
|
|
2661
2803
|
}
|
|
2662
2804
|
}
|
|
2663
2805
|
}
|
|
@@ -2682,7 +2824,7 @@ async function terminateGroup(proc, opts) {
|
|
|
2682
2824
|
return;
|
|
2683
2825
|
signalGroup(proc, "SIGKILL");
|
|
2684
2826
|
}
|
|
2685
|
-
var
|
|
2827
|
+
var TAG14 = "pgroup";
|
|
2686
2828
|
var init_process_group = __esm(() => {
|
|
2687
2829
|
init_log();
|
|
2688
2830
|
});
|
|
@@ -2785,7 +2927,7 @@ class ProgressTracker {
|
|
|
2785
2927
|
}
|
|
2786
2928
|
onToolStart(name, input) {
|
|
2787
2929
|
this.toolCallCount++;
|
|
2788
|
-
log.debug(
|
|
2930
|
+
log.debug(TAG15, `Tool: ${name} (count: ${this.toolCallCount}, phase: ${this.phase})`);
|
|
2789
2931
|
const filePath = this.extractString(input, "file_path");
|
|
2790
2932
|
if (filePath) {
|
|
2791
2933
|
if (EDIT_TOOLS.has(name)) {
|
|
@@ -2856,7 +2998,7 @@ class ProgressTracker {
|
|
|
2856
2998
|
transitionTo(newPhase) {
|
|
2857
2999
|
if (PHASE_ORDER[newPhase] <= PHASE_ORDER[this.phase])
|
|
2858
3000
|
return;
|
|
2859
|
-
log.info(
|
|
3001
|
+
log.info(TAG15, `Phase: ${this.phase} → ${newPhase}`);
|
|
2860
3002
|
this.phase = newPhase;
|
|
2861
3003
|
this.progress = Math.max(this.progress, PHASES[newPhase].min);
|
|
2862
3004
|
this.lastAction = "";
|
|
@@ -2955,7 +3097,7 @@ class ProgressTracker {
|
|
|
2955
3097
|
}
|
|
2956
3098
|
sendUpdate(currentTask) {
|
|
2957
3099
|
this.lastUpdateAt = Date.now();
|
|
2958
|
-
log.debug(
|
|
3100
|
+
log.debug(TAG15, `Progress: ${this.progress}% — ${currentTask}`);
|
|
2959
3101
|
this.client.updateAgentProgress(this.cardId, {
|
|
2960
3102
|
agentIdentifier: agentIdentifier(this.workerId),
|
|
2961
3103
|
agentName: AGENT_NAME,
|
|
@@ -2972,7 +3114,7 @@ class ProgressTracker {
|
|
|
2972
3114
|
modelName: this.lastCost?.modelName,
|
|
2973
3115
|
numTurns: this.lastCost?.numTurns ?? 0
|
|
2974
3116
|
}).catch((err) => {
|
|
2975
|
-
log.warn(
|
|
3117
|
+
log.warn(TAG15, `Failed to send progress update: ${err}`);
|
|
2976
3118
|
});
|
|
2977
3119
|
this.flushActivityLog();
|
|
2978
3120
|
}
|
|
@@ -3013,7 +3155,7 @@ class ProgressTracker {
|
|
|
3013
3155
|
toolName: e.toolName ?? undefined
|
|
3014
3156
|
}))
|
|
3015
3157
|
}).catch((err) => {
|
|
3016
|
-
log.warn(
|
|
3158
|
+
log.warn(TAG15, `Failed to flush activity log: ${err}`);
|
|
3017
3159
|
this.logBuffer.unshift(...raw);
|
|
3018
3160
|
if (this.logBuffer.length > MAX_LOG_BUFFER) {
|
|
3019
3161
|
this.logBuffer.length = MAX_LOG_BUFFER;
|
|
@@ -3044,7 +3186,7 @@ class ProgressTracker {
|
|
|
3044
3186
|
return null;
|
|
3045
3187
|
}
|
|
3046
3188
|
}
|
|
3047
|
-
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;
|
|
3048
3190
|
var init_progress_tracker = __esm(() => {
|
|
3049
3191
|
init_log();
|
|
3050
3192
|
init_types();
|
|
@@ -3119,7 +3261,7 @@ function parseReviewOutput(stdout) {
|
|
|
3119
3261
|
try {
|
|
3120
3262
|
const parsed = JSON.parse(raw);
|
|
3121
3263
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
3122
|
-
log.debug(
|
|
3264
|
+
log.debug(TAG16, "Parsed review output from fenced JSON block");
|
|
3123
3265
|
return extractResult(parsed);
|
|
3124
3266
|
}
|
|
3125
3267
|
} catch {}
|
|
@@ -3145,21 +3287,21 @@ function parseReviewOutput(stdout) {
|
|
|
3145
3287
|
try {
|
|
3146
3288
|
const parsed = JSON.parse(candidates[i]);
|
|
3147
3289
|
if (parsed && typeof parsed === "object" && "verdict" in parsed) {
|
|
3148
|
-
log.debug(
|
|
3290
|
+
log.debug(TAG16, "Parsed review output from raw JSON object");
|
|
3149
3291
|
return extractResult(parsed);
|
|
3150
3292
|
}
|
|
3151
3293
|
} catch {}
|
|
3152
3294
|
}
|
|
3153
3295
|
const verdictMatch = stdout.match(/"verdict"\s*:\s*"(approved|rejected)"/i);
|
|
3154
3296
|
if (verdictMatch) {
|
|
3155
|
-
log.warn(
|
|
3297
|
+
log.warn(TAG16, `Parsed verdict via regex fallback — findings lost (${verdictMatch[1]})`);
|
|
3156
3298
|
return {
|
|
3157
3299
|
verdict: verdictMatch[1].toLowerCase(),
|
|
3158
3300
|
summary: "Parsed via regex fallback — original JSON was malformed. Check run log.",
|
|
3159
3301
|
findings: []
|
|
3160
3302
|
};
|
|
3161
3303
|
}
|
|
3162
|
-
log.warn(
|
|
3304
|
+
log.warn(TAG16, "Failed to parse review JSON output — returning error verdict (card stays in Review)");
|
|
3163
3305
|
return {
|
|
3164
3306
|
verdict: "error",
|
|
3165
3307
|
summary: stdout.slice(0, 500),
|
|
@@ -3192,7 +3334,7 @@ async function postReviewComment(client, card, commentType, body) {
|
|
|
3192
3334
|
try {
|
|
3193
3335
|
await client.addComment(card.id, body, { commentType });
|
|
3194
3336
|
} catch (err) {
|
|
3195
|
-
log.error(
|
|
3337
|
+
log.error(TAG16, `Failed to post review comment to #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
3196
3338
|
}
|
|
3197
3339
|
}
|
|
3198
3340
|
async function runReviewCompletion(client, card, result, config, worktreePath, branchName, sessionStats, runLogPath, workspaceId, agentSessionId, stateStore) {
|
|
@@ -3206,11 +3348,11 @@ async function runReviewCompletion(client, card, result, config, worktreePath, b
|
|
|
3206
3348
|
const currentCycle = getReviewCycle(freshDesc) + 1;
|
|
3207
3349
|
const maxCycles = config.review.maxReviewCycles;
|
|
3208
3350
|
if (result.verdict === "error") {
|
|
3209
|
-
log.warn(
|
|
3351
|
+
log.warn(TAG16, `#${card.short_id} review output unparseable — labelling "${NEED_REVIEW_LABEL}" for manual inspection`);
|
|
3210
3352
|
try {
|
|
3211
3353
|
await addLabelByName(client, card, NEED_REVIEW_LABEL, NEED_REVIEW_LABEL_COLOR);
|
|
3212
3354
|
} catch (err) {
|
|
3213
|
-
log.warn(
|
|
3355
|
+
log.warn(TAG16, `Failed to add "${NEED_REVIEW_LABEL}" label: ${err instanceof Error ? err.message : err}`);
|
|
3214
3356
|
}
|
|
3215
3357
|
if (config.review.postFindings) {
|
|
3216
3358
|
const rawTail = runLogPath ? tailRunLog(runLogPath) : null;
|
|
@@ -3253,7 +3395,7 @@ ${runLogTail}
|
|
|
3253
3395
|
renameRemoteBranch(branchName, newRef, worktreePath);
|
|
3254
3396
|
approvedBranch = newRef;
|
|
3255
3397
|
} catch (err) {
|
|
3256
|
-
log.warn(
|
|
3398
|
+
log.warn(TAG16, `Branch rename failed (continuing on ${branchName}): ${err instanceof Error ? err.message : err}`);
|
|
3257
3399
|
}
|
|
3258
3400
|
}
|
|
3259
3401
|
if (config.review.createPR && approvedBranch) {
|
|
@@ -3280,14 +3422,14 @@ ${runLogTail}
|
|
|
3280
3422
|
progressPercent: 100,
|
|
3281
3423
|
...buildTokenPayload(sessionStats)
|
|
3282
3424
|
});
|
|
3283
|
-
log.info(
|
|
3425
|
+
log.info(TAG16, `#${card.short_id} approved${prUrl ? ` — PR: ${prUrl}` : ""} — labeled "${config.review.approvedLabel}"`);
|
|
3284
3426
|
} else {
|
|
3285
3427
|
const criticalFindings = result.findings.filter((f) => f.severity === "critical").slice(0, MAX_FINDINGS);
|
|
3286
3428
|
const majorFindings = result.findings.filter((f) => f.severity === "major").slice(0, MAX_FINDINGS);
|
|
3287
3429
|
const linkedFindings = [...criticalFindings, ...majorFindings];
|
|
3288
3430
|
const minorFindings = result.findings.filter((f) => f.severity === "minor").slice(0, MAX_FINDINGS);
|
|
3289
3431
|
if (currentCycle >= maxCycles) {
|
|
3290
|
-
log.warn(
|
|
3432
|
+
log.warn(TAG16, `#${card.short_id} reached max review cycles (${maxCycles}), moving to Done with note`);
|
|
3291
3433
|
await moveCardToColumn(client, card, config.review.moveToColumn);
|
|
3292
3434
|
const body = [
|
|
3293
3435
|
"**Review — needs human review.**",
|
|
@@ -3338,7 +3480,7 @@ ${finding.description}${locationLine}`
|
|
|
3338
3480
|
await client.addLinkToCard(card.id, newCardId, "relates_to");
|
|
3339
3481
|
}
|
|
3340
3482
|
} catch (err) {
|
|
3341
|
-
log.error(
|
|
3483
|
+
log.error(TAG16, `Failed to create finding card: ${err instanceof Error ? err.message : err}`);
|
|
3342
3484
|
}
|
|
3343
3485
|
}));
|
|
3344
3486
|
await Promise.all(minorFindings.map(async (finding) => {
|
|
@@ -3346,7 +3488,7 @@ ${finding.description}${locationLine}`
|
|
|
3346
3488
|
const title = finding.title.length > 120 ? `${finding.title.slice(0, 117)}...` : finding.title;
|
|
3347
3489
|
await client.createSubtask(card.id, title);
|
|
3348
3490
|
} catch (err) {
|
|
3349
|
-
log.error(
|
|
3491
|
+
log.error(TAG16, `Failed to create subtask: ${err instanceof Error ? err.message : err}`);
|
|
3350
3492
|
}
|
|
3351
3493
|
}));
|
|
3352
3494
|
const baseDesc = stripReviewSummary(freshDesc);
|
|
@@ -3354,7 +3496,7 @@ ${finding.description}${locationLine}`
|
|
|
3354
3496
|
try {
|
|
3355
3497
|
await client.updateCard(card.id, { description: updatedDesc });
|
|
3356
3498
|
} catch (err) {
|
|
3357
|
-
log.error(
|
|
3499
|
+
log.error(TAG16, `Failed to update review cycle marker: ${err instanceof Error ? err.message : err}`);
|
|
3358
3500
|
}
|
|
3359
3501
|
const scopeLine = result.scopeCheck ? `Scope: ${result.scopeCheck.status}${result.scopeCheck.notes ? ` — ${result.scopeCheck.notes}` : ""}` : "";
|
|
3360
3502
|
const body = [
|
|
@@ -3370,9 +3512,9 @@ ${finding.description}${locationLine}`
|
|
|
3370
3512
|
if (config.planning.enabled && card.plan_id) {
|
|
3371
3513
|
try {
|
|
3372
3514
|
await client.updateCard(card.id, { needsPlanRefresh: true });
|
|
3373
|
-
log.info(
|
|
3515
|
+
log.info(TAG16, `#${card.short_id} flagged needs_plan_refresh after rejected review`);
|
|
3374
3516
|
} catch (err) {
|
|
3375
|
-
log.warn(
|
|
3517
|
+
log.warn(TAG16, `Failed to flag needs_plan_refresh for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
3376
3518
|
}
|
|
3377
3519
|
}
|
|
3378
3520
|
await moveCardToColumn(client, card, config.review.failColumn);
|
|
@@ -3386,10 +3528,10 @@ ${finding.description}${locationLine}`
|
|
|
3386
3528
|
recoveryBranch
|
|
3387
3529
|
});
|
|
3388
3530
|
} catch (err) {
|
|
3389
|
-
log.debug(
|
|
3531
|
+
log.debug(TAG16, `recordFailureSummary failed: ${err instanceof Error ? err.message : err}`);
|
|
3390
3532
|
}
|
|
3391
3533
|
if (recoveryBranch) {
|
|
3392
|
-
log.info(
|
|
3534
|
+
log.info(TAG16, `#${card.short_id} recovery branch ${recoveryBranch}${recoveryUrl ? ` (${recoveryUrl})` : ""}`);
|
|
3393
3535
|
}
|
|
3394
3536
|
await client.endAgentSession(card.id, {
|
|
3395
3537
|
status: "failed",
|
|
@@ -3398,7 +3540,7 @@ ${finding.description}${locationLine}`
|
|
|
3398
3540
|
recoveryBranch,
|
|
3399
3541
|
...buildTokenPayload(sessionStats)
|
|
3400
3542
|
});
|
|
3401
|
-
log.info(
|
|
3543
|
+
log.info(TAG16, `#${card.short_id} rejected (cycle ${currentCycle}/${maxCycles}) — moved to "${config.review.failColumn}"`);
|
|
3402
3544
|
}
|
|
3403
3545
|
if (workspaceId && (result.verdict === "approved" || result.verdict === "rejected")) {
|
|
3404
3546
|
const originalEpisodeId = await findLatestImplementEpisode(client, workspaceId, card.project_id, card.short_id);
|
|
@@ -3420,7 +3562,7 @@ ${finding.description}${locationLine}`
|
|
|
3420
3562
|
cleanupWorktree(worktreePath, branchName);
|
|
3421
3563
|
}
|
|
3422
3564
|
}
|
|
3423
|
-
var
|
|
3565
|
+
var TAG16 = "review-completion", MAX_FINDINGS = 10, REVIEW_MARKER = `---
|
|
3424
3566
|
**Review:`, RUN_LOG_TAIL_BYTES = 2048;
|
|
3425
3567
|
var init_review_completion = __esm(() => {
|
|
3426
3568
|
init_board_helpers();
|
|
@@ -3434,6 +3576,19 @@ var init_review_completion = __esm(() => {
|
|
|
3434
3576
|
|
|
3435
3577
|
// ../harmony-shared/dist/cardLinks.js
|
|
3436
3578
|
var init_cardLinks = () => {};
|
|
3579
|
+
// ../harmony-shared/dist/classification.js
|
|
3580
|
+
function escalateTier(tier) {
|
|
3581
|
+
const i = MODEL_TIERS.indexOf(tier);
|
|
3582
|
+
return MODEL_TIERS[Math.min(i + 1, MODEL_TIERS.length - 1)];
|
|
3583
|
+
}
|
|
3584
|
+
function isModelTier(v) {
|
|
3585
|
+
return typeof v === "string" && MODEL_TIERS.includes(v);
|
|
3586
|
+
}
|
|
3587
|
+
var MODEL_TIERS;
|
|
3588
|
+
var init_classification = __esm(() => {
|
|
3589
|
+
MODEL_TIERS = ["simple", "advanced", "research"];
|
|
3590
|
+
});
|
|
3591
|
+
|
|
3437
3592
|
// ../harmony-shared/dist/commentSerializer.js
|
|
3438
3593
|
function sanitizeHeaderField(value) {
|
|
3439
3594
|
return value.replace(/[\]\r\n|<>]/g, " ").trim() || "—";
|
|
@@ -3645,6 +3800,7 @@ var init_types2 = () => {};
|
|
|
3645
3800
|
// ../harmony-shared/dist/index.js
|
|
3646
3801
|
var init_dist = __esm(() => {
|
|
3647
3802
|
init_cardLinks();
|
|
3803
|
+
init_classification();
|
|
3648
3804
|
init_commentSerializer();
|
|
3649
3805
|
init_constants();
|
|
3650
3806
|
init_logger();
|
|
@@ -3766,7 +3922,7 @@ __export(exports_state_store, {
|
|
|
3766
3922
|
StateStore: () => StateStore
|
|
3767
3923
|
});
|
|
3768
3924
|
import {
|
|
3769
|
-
existsSync as
|
|
3925
|
+
existsSync as existsSync5,
|
|
3770
3926
|
mkdirSync as mkdirSync2,
|
|
3771
3927
|
readFileSync as readFileSync3,
|
|
3772
3928
|
renameSync,
|
|
@@ -3808,18 +3964,18 @@ class StateStore {
|
|
|
3808
3964
|
static open(path) {
|
|
3809
3965
|
const resolved = path ?? defaultStatePath();
|
|
3810
3966
|
const dir = dirname(resolved);
|
|
3811
|
-
if (!
|
|
3967
|
+
if (!existsSync5(dir))
|
|
3812
3968
|
mkdirSync2(dir, { recursive: true });
|
|
3813
3969
|
return new StateStore(resolved);
|
|
3814
3970
|
}
|
|
3815
3971
|
load() {
|
|
3816
|
-
if (!
|
|
3972
|
+
if (!existsSync5(this.path))
|
|
3817
3973
|
return emptyState();
|
|
3818
3974
|
try {
|
|
3819
3975
|
const raw = readFileSync3(this.path, "utf-8");
|
|
3820
3976
|
const parsed = JSON.parse(raw);
|
|
3821
3977
|
if (parsed?.version !== SCHEMA_VERSION) {
|
|
3822
|
-
log.warn(
|
|
3978
|
+
log.warn(TAG17, `state file has version ${parsed?.version}, expected ${SCHEMA_VERSION} — starting fresh`);
|
|
3823
3979
|
return emptyState();
|
|
3824
3980
|
}
|
|
3825
3981
|
return {
|
|
@@ -3832,7 +3988,7 @@ class StateStore {
|
|
|
3832
3988
|
daily: parsed.daily ?? []
|
|
3833
3989
|
};
|
|
3834
3990
|
} catch (err) {
|
|
3835
|
-
log.error(
|
|
3991
|
+
log.error(TAG17, `failed to read state file: ${err instanceof Error ? err.message : err}`);
|
|
3836
3992
|
return emptyState();
|
|
3837
3993
|
}
|
|
3838
3994
|
}
|
|
@@ -3988,7 +4144,7 @@ class StateStore {
|
|
|
3988
4144
|
return this.state.daily.find((d) => d.date === key)?.costCents ?? 0;
|
|
3989
4145
|
}
|
|
3990
4146
|
}
|
|
3991
|
-
var
|
|
4147
|
+
var TAG17 = "state-store", SCHEMA_VERSION = 1;
|
|
3992
4148
|
var init_state_store = __esm(() => {
|
|
3993
4149
|
init_log();
|
|
3994
4150
|
});
|
|
@@ -4015,7 +4171,7 @@ function normalizeToolResultContent(raw) {
|
|
|
4015
4171
|
return String(raw);
|
|
4016
4172
|
}
|
|
4017
4173
|
}
|
|
4018
|
-
var
|
|
4174
|
+
var TAG18 = "stream-parser", StreamParser;
|
|
4019
4175
|
var init_stream_parser = __esm(() => {
|
|
4020
4176
|
init_log();
|
|
4021
4177
|
StreamParser = class StreamParser extends EventEmitter {
|
|
@@ -4059,14 +4215,14 @@ var init_stream_parser = __esm(() => {
|
|
|
4059
4215
|
try {
|
|
4060
4216
|
msg = JSON.parse(line);
|
|
4061
4217
|
} catch {
|
|
4062
|
-
log.debug(
|
|
4218
|
+
log.debug(TAG18, `Non-JSON line: ${line.slice(0, 100)}`);
|
|
4063
4219
|
return;
|
|
4064
4220
|
}
|
|
4065
4221
|
try {
|
|
4066
4222
|
this.handleMessage(msg);
|
|
4067
4223
|
} catch (err) {
|
|
4068
4224
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4069
|
-
log.warn(
|
|
4225
|
+
log.warn(TAG18, `Error handling stream event: ${errMsg}`);
|
|
4070
4226
|
this.emit("parse_error", errMsg);
|
|
4071
4227
|
}
|
|
4072
4228
|
}
|
|
@@ -4148,7 +4304,7 @@ async function withRetry(step, cardShortId, op, attempts, backoffMs) {
|
|
|
4148
4304
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
4149
4305
|
if (i < attempts - 1) {
|
|
4150
4306
|
const wait = backoffMs * 2 ** i;
|
|
4151
|
-
log.warn(
|
|
4307
|
+
log.warn(TAG19, `${step} failed for #${cardShortId} (attempt ${i + 1}/${attempts}): ${msg2} — retrying in ${wait}ms`);
|
|
4152
4308
|
await new Promise((r) => setTimeout(r, wait));
|
|
4153
4309
|
}
|
|
4154
4310
|
}
|
|
@@ -4170,10 +4326,10 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
4170
4326
|
if (opts.strictColumn) {
|
|
4171
4327
|
throw new TransitionError("move", 1, msg);
|
|
4172
4328
|
}
|
|
4173
|
-
log.warn(
|
|
4329
|
+
log.warn(TAG19, `#${shortId}: ${msg} — skipping move`);
|
|
4174
4330
|
} else if (card.column_id !== target.id) {
|
|
4175
4331
|
await withRetry("move", shortId, () => client.moveCard(card.id, target.id), attempts, backoffMs);
|
|
4176
|
-
log.info(
|
|
4332
|
+
log.info(TAG19, `#${shortId} → "${target.name}"`);
|
|
4177
4333
|
card.column_id = target.id;
|
|
4178
4334
|
}
|
|
4179
4335
|
}
|
|
@@ -4186,7 +4342,7 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
4186
4342
|
continue;
|
|
4187
4343
|
await withRetry("addLabel", shortId, () => client.addLabelToCard(card.id, labelId), attempts, backoffMs);
|
|
4188
4344
|
existing.add(labelId);
|
|
4189
|
-
log.info(
|
|
4345
|
+
log.info(TAG19, `#${shortId} +label "${name}"`);
|
|
4190
4346
|
}
|
|
4191
4347
|
card.labelIds = Array.from(existing);
|
|
4192
4348
|
}
|
|
@@ -4198,17 +4354,17 @@ async function runTransition(client, card, plan, opts = {}) {
|
|
|
4198
4354
|
continue;
|
|
4199
4355
|
await withRetry("removeLabel", shortId, () => client.removeLabelFromCard(card.id, match.id), attempts, backoffMs);
|
|
4200
4356
|
existing.delete(match.id);
|
|
4201
|
-
log.info(
|
|
4357
|
+
log.info(TAG19, `#${shortId} -label "${name}"`);
|
|
4202
4358
|
}
|
|
4203
4359
|
card.labelIds = Array.from(existing);
|
|
4204
4360
|
}
|
|
4205
4361
|
if (plan.updateCard) {
|
|
4206
4362
|
await withRetry("updateCard", shortId, () => client.updateCard(card.id, plan.updateCard), attempts, backoffMs);
|
|
4207
|
-
log.info(
|
|
4363
|
+
log.info(TAG19, `#${shortId} updated`);
|
|
4208
4364
|
}
|
|
4209
4365
|
if (plan.endSession) {
|
|
4210
4366
|
await withRetry("endSession", shortId, () => client.endAgentSession(card.id, plan.endSession), attempts, backoffMs);
|
|
4211
|
-
log.info(
|
|
4367
|
+
log.info(TAG19, `#${shortId} session ended (${plan.endSession.status})`);
|
|
4212
4368
|
}
|
|
4213
4369
|
if (opts.store && opts.runId) {
|
|
4214
4370
|
try {
|
|
@@ -4221,11 +4377,11 @@ async function ensureLabel(client, projectId, name, color, attempts, backoffMs)
|
|
|
4221
4377
|
const result = await withRetry("addLabel", 0, () => client.createLabel(projectId, { name, color: color ?? "#8b5cf6" }), attempts, backoffMs);
|
|
4222
4378
|
return result?.label?.id ?? null;
|
|
4223
4379
|
} catch (err) {
|
|
4224
|
-
log.warn(
|
|
4380
|
+
log.warn(TAG19, `ensureLabel "${name}" failed: ${err instanceof Error ? err.message : err}`);
|
|
4225
4381
|
return null;
|
|
4226
4382
|
}
|
|
4227
4383
|
}
|
|
4228
|
-
var
|
|
4384
|
+
var TAG19 = "transition", TransitionError;
|
|
4229
4385
|
var init_transitions = __esm(() => {
|
|
4230
4386
|
init_log();
|
|
4231
4387
|
TransitionError = class TransitionError extends Error {
|
|
@@ -4243,7 +4399,7 @@ var init_transitions = __esm(() => {
|
|
|
4243
4399
|
});
|
|
4244
4400
|
|
|
4245
4401
|
// src/review-worker.ts
|
|
4246
|
-
import { execFileSync as
|
|
4402
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
4247
4403
|
|
|
4248
4404
|
class ReviewWorker {
|
|
4249
4405
|
config;
|
|
@@ -4309,7 +4465,7 @@ class ReviewWorker {
|
|
|
4309
4465
|
}
|
|
4310
4466
|
}
|
|
4311
4467
|
get tag() {
|
|
4312
|
-
return `${
|
|
4468
|
+
return `${TAG20}:${this.id}`;
|
|
4313
4469
|
}
|
|
4314
4470
|
get isIdle() {
|
|
4315
4471
|
return this.state === "idle";
|
|
@@ -4366,7 +4522,7 @@ class ReviewWorker {
|
|
|
4366
4522
|
let localDiff = null;
|
|
4367
4523
|
if (localMode) {
|
|
4368
4524
|
log.info(this.tag, `No branch found for #${card.short_id}, attempting local review`);
|
|
4369
|
-
this.worktreePath =
|
|
4525
|
+
this.worktreePath = execFileSync9("git", ["rev-parse", "--show-toplevel"], {
|
|
4370
4526
|
encoding: "utf-8",
|
|
4371
4527
|
timeout: 5000
|
|
4372
4528
|
}).trim();
|
|
@@ -4433,7 +4589,7 @@ class ReviewWorker {
|
|
|
4433
4589
|
if (localMode) {
|
|
4434
4590
|
diff = localDiff ?? "";
|
|
4435
4591
|
} else {
|
|
4436
|
-
diff =
|
|
4592
|
+
diff = execFileSync9("git", ["diff", `origin/${this.config.worktree.baseBranch}..HEAD`], { cwd, encoding: "utf-8", timeout: 30000 });
|
|
4437
4593
|
}
|
|
4438
4594
|
} catch {
|
|
4439
4595
|
diff = "(unable to retrieve diff)";
|
|
@@ -4713,7 +4869,7 @@ class ReviewWorker {
|
|
|
4713
4869
|
}
|
|
4714
4870
|
resolveLocalChanges(repoRoot, shortId) {
|
|
4715
4871
|
try {
|
|
4716
|
-
const localChanges =
|
|
4872
|
+
const localChanges = execFileSync9("git", ["diff", "HEAD"], {
|
|
4717
4873
|
cwd: repoRoot,
|
|
4718
4874
|
encoding: "utf-8",
|
|
4719
4875
|
timeout: 5000
|
|
@@ -4725,7 +4881,7 @@ class ReviewWorker {
|
|
|
4725
4881
|
log.warn(this.tag, "Failed to check uncommitted changes");
|
|
4726
4882
|
}
|
|
4727
4883
|
try {
|
|
4728
|
-
const matchingCommits =
|
|
4884
|
+
const matchingCommits = execFileSync9("git", ["log", "--format=%H", "-20", `--grep=#${shortId}`], { cwd: repoRoot, encoding: "utf-8", timeout: 1e4 }).trim();
|
|
4729
4885
|
if (matchingCommits) {
|
|
4730
4886
|
const hashes = matchingCommits.split(`
|
|
4731
4887
|
`).filter((h) => /^[0-9a-f]{4,40}$/i.test(h));
|
|
@@ -4735,7 +4891,7 @@ class ReviewWorker {
|
|
|
4735
4891
|
const diffs = [];
|
|
4736
4892
|
for (const hash of hashes) {
|
|
4737
4893
|
try {
|
|
4738
|
-
const commitDiff =
|
|
4894
|
+
const commitDiff = execFileSync9("git", ["diff", `${hash}~1..${hash}`], { cwd: repoRoot, encoding: "utf-8", timeout: 30000 });
|
|
4739
4895
|
if (commitDiff)
|
|
4740
4896
|
diffs.push(commitDiff);
|
|
4741
4897
|
} catch {
|
|
@@ -4778,7 +4934,7 @@ class ReviewWorker {
|
|
|
4778
4934
|
this.lastSessionStats = null;
|
|
4779
4935
|
}
|
|
4780
4936
|
}
|
|
4781
|
-
var
|
|
4937
|
+
var TAG20 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEOUT = 1e4;
|
|
4782
4938
|
var init_review_worker = __esm(() => {
|
|
4783
4939
|
init_board_helpers();
|
|
4784
4940
|
init_completion();
|
|
@@ -4827,7 +4983,7 @@ class SleepGuard {
|
|
|
4827
4983
|
if (!this.child.killed)
|
|
4828
4984
|
this.child.kill("SIGTERM");
|
|
4829
4985
|
this.child = null;
|
|
4830
|
-
log.info(
|
|
4986
|
+
log.info(TAG21, "sleep assertion released");
|
|
4831
4987
|
}
|
|
4832
4988
|
}
|
|
4833
4989
|
start() {
|
|
@@ -4842,7 +4998,7 @@ class SleepGuard {
|
|
|
4842
4998
|
spawned = true;
|
|
4843
4999
|
});
|
|
4844
5000
|
child.on("error", (err) => {
|
|
4845
|
-
log.warn(
|
|
5001
|
+
log.warn(TAG21, `caffeinate unavailable: ${err.message}`);
|
|
4846
5002
|
if (this.child === child)
|
|
4847
5003
|
this.child = null;
|
|
4848
5004
|
});
|
|
@@ -4855,13 +5011,13 @@ class SleepGuard {
|
|
|
4855
5011
|
});
|
|
4856
5012
|
child.unref();
|
|
4857
5013
|
this.child = child;
|
|
4858
|
-
log.info(
|
|
5014
|
+
log.info(TAG21, "sleep assertion acquired (caffeinate -i)");
|
|
4859
5015
|
} catch (err) {
|
|
4860
|
-
log.warn(
|
|
5016
|
+
log.warn(TAG21, `failed to spawn caffeinate: ${err instanceof Error ? err.message : err}`);
|
|
4861
5017
|
}
|
|
4862
5018
|
}
|
|
4863
5019
|
}
|
|
4864
|
-
var
|
|
5020
|
+
var TAG21 = "sleep-guard";
|
|
4865
5021
|
var init_sleep_guard = __esm(() => {
|
|
4866
5022
|
init_log();
|
|
4867
5023
|
});
|
|
@@ -4872,7 +5028,7 @@ async function fetchBlocksLinks(client, cardId) {
|
|
|
4872
5028
|
const { links } = await client.getCardLinks(cardId);
|
|
4873
5029
|
return links.filter((l) => l.link_type === "blocks");
|
|
4874
5030
|
} catch (err) {
|
|
4875
|
-
log.warn(
|
|
5031
|
+
log.warn(TAG22, `link fetch failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
4876
5032
|
return null;
|
|
4877
5033
|
}
|
|
4878
5034
|
}
|
|
@@ -4904,37 +5060,66 @@ async function promoteUnblockedSuccessors(completedCard, deps) {
|
|
|
4904
5060
|
const successors = links.filter((l) => l.direction === "outgoing" && !l.target_card.done);
|
|
4905
5061
|
if (successors.length === 0)
|
|
4906
5062
|
return;
|
|
4907
|
-
log.info(
|
|
5063
|
+
log.info(TAG22, `#${completedCard.short_id} completed — checking ${successors.length} chained successor(s)`);
|
|
4908
5064
|
for (const link of successors) {
|
|
4909
5065
|
const successorId = link.target_card.id;
|
|
4910
5066
|
try {
|
|
4911
5067
|
const { card } = await deps.client.getCard(successorId);
|
|
4912
5068
|
if (card.assigned_agent_id === deps.agentId) {} else if (card.assigned_agent_id === null && !card.assignee_id) {
|
|
4913
|
-
log.info(
|
|
5069
|
+
log.info(TAG22, `successor #${card.short_id} unassigned — auto-assigning to continue chain`);
|
|
4914
5070
|
await deps.client.updateCard(successorId, {
|
|
4915
5071
|
assignedAgentId: deps.agentId
|
|
4916
5072
|
});
|
|
4917
5073
|
} else {
|
|
4918
|
-
log.debug(
|
|
5074
|
+
log.debug(TAG22, `successor #${card.short_id} assigned to different entity — skipping`);
|
|
4919
5075
|
continue;
|
|
4920
5076
|
}
|
|
4921
5077
|
await deps.enqueue(successorId);
|
|
4922
5078
|
} catch (err) {
|
|
4923
|
-
log.warn(
|
|
5079
|
+
log.warn(TAG22, `promotion failed for successor ${successorId}: ${err instanceof Error ? err.message : err}`);
|
|
4924
5080
|
}
|
|
4925
5081
|
}
|
|
4926
5082
|
}
|
|
4927
|
-
var
|
|
5083
|
+
var TAG22 = "unblock";
|
|
4928
5084
|
var init_unblock = __esm(() => {
|
|
4929
5085
|
init_log();
|
|
4930
5086
|
});
|
|
4931
5087
|
|
|
4932
5088
|
// src/model-tier.ts
|
|
4933
|
-
function
|
|
4934
|
-
|
|
5089
|
+
function clampWithdrawn(model) {
|
|
5090
|
+
return WITHDRAWN_MODEL.test(model) ? MAX_IMPLEMENT_MODEL : model;
|
|
5091
|
+
}
|
|
5092
|
+
function chooseImplementModel(claude, card, attempts) {
|
|
5093
|
+
if (card.model_override) {
|
|
5094
|
+
return {
|
|
5095
|
+
model: clampWithdrawn(card.model_override),
|
|
5096
|
+
escalated: false,
|
|
5097
|
+
source: "override"
|
|
5098
|
+
};
|
|
5099
|
+
}
|
|
5100
|
+
if (isModelTier(card.model_tier)) {
|
|
5101
|
+
const retry = attempts >= claude.escalateAfterAttempts;
|
|
5102
|
+
const tier = retry ? escalateTier(card.model_tier) : card.model_tier;
|
|
5103
|
+
const mapped = claude.tiers?.[tier];
|
|
5104
|
+
return {
|
|
5105
|
+
model: clampWithdrawn(mapped && mapped.length > 0 ? mapped : claude.model),
|
|
5106
|
+
escalated: retry,
|
|
5107
|
+
source: "tier"
|
|
5108
|
+
};
|
|
5109
|
+
}
|
|
5110
|
+
const highPriority = card.priority === "high" || card.priority === "urgent";
|
|
4935
5111
|
const escalated = highPriority || attempts >= claude.escalateAfterAttempts;
|
|
4936
|
-
return {
|
|
5112
|
+
return {
|
|
5113
|
+
model: clampWithdrawn(escalated ? claude.escalateModel : claude.model),
|
|
5114
|
+
escalated,
|
|
5115
|
+
source: "policy"
|
|
5116
|
+
};
|
|
4937
5117
|
}
|
|
5118
|
+
var MAX_IMPLEMENT_MODEL = "claude-opus-4-8", WITHDRAWN_MODEL;
|
|
5119
|
+
var init_model_tier = __esm(() => {
|
|
5120
|
+
init_dist();
|
|
5121
|
+
WITHDRAWN_MODEL = /fable/i;
|
|
5122
|
+
});
|
|
4938
5123
|
|
|
4939
5124
|
// src/prompt.ts
|
|
4940
5125
|
async function buildPrompt(enriched, branchName, worktreePath, client, workspaceId, projectId) {
|
|
@@ -4950,11 +5135,11 @@ async function buildPrompt(enriched, branchName, worktreePath, client, workspace
|
|
|
4950
5135
|
Do NOT push to main. All your work stays on \`${branchName}\`.
|
|
4951
5136
|
When finished, call harmony_end_agent_session with status="completed".`
|
|
4952
5137
|
});
|
|
4953
|
-
log.info(
|
|
5138
|
+
log.info(TAG23, `Generated prompt for #${card.short_id} — ${result.contextSummary.memoryCount} memories, ${result.tokenEstimate} tokens`);
|
|
4954
5139
|
return result.prompt + pastEpisodesSection;
|
|
4955
5140
|
} catch (err) {
|
|
4956
5141
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4957
|
-
log.warn(
|
|
5142
|
+
log.warn(TAG23, `Failed to generate prompt via API, using fallback: ${msg}`);
|
|
4958
5143
|
const commentsSection = await renderCommentsSection(client, card.id);
|
|
4959
5144
|
return buildFallbackPrompt(enriched, branchName, worktreePath) + commentsSection + pastEpisodesSection;
|
|
4960
5145
|
}
|
|
@@ -4972,7 +5157,7 @@ async function renderCommentsSection(client, cardId) {
|
|
|
4972
5157
|
|
|
4973
5158
|
${section}` : "";
|
|
4974
5159
|
} catch (err) {
|
|
4975
|
-
log.warn(
|
|
5160
|
+
log.warn(TAG23, "comment-thread fetch failed", {
|
|
4976
5161
|
event: "comment_fetch_failed",
|
|
4977
5162
|
error: err instanceof Error ? err.message : String(err)
|
|
4978
5163
|
});
|
|
@@ -5022,7 +5207,7 @@ ${description}`.trim();
|
|
|
5022
5207
|
## Similar past tasks
|
|
5023
5208
|
${bullets}`;
|
|
5024
5209
|
} catch (err) {
|
|
5025
|
-
log.warn(
|
|
5210
|
+
log.warn(TAG23, "past-episodes recall failed", {
|
|
5026
5211
|
event: "episode_recall_failed",
|
|
5027
5212
|
error: err instanceof Error ? err.message : String(err)
|
|
5028
5213
|
});
|
|
@@ -5063,7 +5248,7 @@ ${subtaskStr}
|
|
|
5063
5248
|
You are working in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.
|
|
5064
5249
|
Do NOT push to main. All your work stays on \`${branchName}\`.`;
|
|
5065
5250
|
}
|
|
5066
|
-
var
|
|
5251
|
+
var TAG23 = "prompt";
|
|
5067
5252
|
var init_prompt = __esm(() => {
|
|
5068
5253
|
init_dist();
|
|
5069
5254
|
init_log();
|
|
@@ -5142,7 +5327,7 @@ class Worker {
|
|
|
5142
5327
|
}
|
|
5143
5328
|
}
|
|
5144
5329
|
get tag() {
|
|
5145
|
-
return `${
|
|
5330
|
+
return `${TAG24}:${this.id}`;
|
|
5146
5331
|
}
|
|
5147
5332
|
get isIdle() {
|
|
5148
5333
|
return this.state === "idle";
|
|
@@ -5201,7 +5386,7 @@ class Worker {
|
|
|
5201
5386
|
});
|
|
5202
5387
|
const sid = session && typeof session === "object" && "id" in session ? session.id : null;
|
|
5203
5388
|
if (!sid) {
|
|
5204
|
-
log.warn(
|
|
5389
|
+
log.warn(TAG24, "startAgentSession returned no session id");
|
|
5205
5390
|
}
|
|
5206
5391
|
this.sessionId = sid;
|
|
5207
5392
|
await this.recordPhase("preparing");
|
|
@@ -5377,9 +5562,9 @@ class Worker {
|
|
|
5377
5562
|
}
|
|
5378
5563
|
selectImplementModel(card) {
|
|
5379
5564
|
const attempts = this.stateStore.getCard(card.id)?.attempts ?? 1;
|
|
5380
|
-
const { model, escalated } = chooseImplementModel(this.config.claude, card
|
|
5381
|
-
if (escalated) {
|
|
5382
|
-
log.info(this.tag, `
|
|
5565
|
+
const { model, escalated, source } = chooseImplementModel(this.config.claude, card, attempts);
|
|
5566
|
+
if (source !== "policy" || escalated) {
|
|
5567
|
+
log.info(this.tag, `Implement model "${model}" (source=${source}, escalated=${escalated}, attempts=${attempts}, priority=${card.priority ?? "none"}, tier=${card.model_tier ?? "none"})`);
|
|
5383
5568
|
}
|
|
5384
5569
|
return model;
|
|
5385
5570
|
}
|
|
@@ -5709,12 +5894,13 @@ class Worker {
|
|
|
5709
5894
|
this.runTurns = 0;
|
|
5710
5895
|
}
|
|
5711
5896
|
}
|
|
5712
|
-
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;
|
|
5713
5898
|
var init_worker = __esm(() => {
|
|
5714
5899
|
init_board_helpers();
|
|
5715
5900
|
init_completion();
|
|
5716
5901
|
init_error_classifier();
|
|
5717
5902
|
init_log();
|
|
5903
|
+
init_model_tier();
|
|
5718
5904
|
init_plan_phase();
|
|
5719
5905
|
init_process_group();
|
|
5720
5906
|
init_progress_tracker();
|
|
@@ -5778,39 +5964,39 @@ class Pool {
|
|
|
5778
5964
|
}
|
|
5779
5965
|
async enqueue(card, column, labels, subtasks, mode = "implement") {
|
|
5780
5966
|
if (this.implQueue.has(card.id) || this.reviewQueue.has(card.id) || this.isCardActive(card.id)) {
|
|
5781
|
-
log.debug(
|
|
5967
|
+
log.debug(TAG25, `Card ${card.id} already queued or active, skipping`);
|
|
5782
5968
|
return;
|
|
5783
5969
|
}
|
|
5784
5970
|
if (mode === "implement") {
|
|
5785
5971
|
if (this.authPaused) {
|
|
5786
|
-
log.debug(
|
|
5972
|
+
log.debug(TAG25, `#${card.short_id} held — agent paused (auth error)`);
|
|
5787
5973
|
await this.emitWaiting(card.id, "Agent paused — Anthropic auth error, check API credentials");
|
|
5788
5974
|
return;
|
|
5789
5975
|
}
|
|
5790
5976
|
const cooldownMs = this.apiCooldownRemainingMs();
|
|
5791
5977
|
if (cooldownMs > 0) {
|
|
5792
|
-
log.debug(
|
|
5978
|
+
log.debug(TAG25, `#${card.short_id} held — API cooldown ${Math.round(cooldownMs / 1000)}s remaining`);
|
|
5793
5979
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
5794
5980
|
return;
|
|
5795
5981
|
}
|
|
5796
5982
|
const decision = this.budget.check(card.id);
|
|
5797
5983
|
if (!decision.allow) {
|
|
5798
5984
|
if (decision.reason === "daily_budget") {
|
|
5799
|
-
log.warn(
|
|
5985
|
+
log.warn(TAG25, `#${card.short_id} skipped (daily_budget): ${decision.detail}`);
|
|
5800
5986
|
await this.emitWaiting(card.id, `Daily budget reached — waiting for reset (${decision.detail})`);
|
|
5801
5987
|
} else {
|
|
5802
|
-
log.debug(
|
|
5988
|
+
log.debug(TAG25, `#${card.short_id} gave up: ${decision.detail}`);
|
|
5803
5989
|
}
|
|
5804
5990
|
return;
|
|
5805
5991
|
}
|
|
5806
5992
|
const blockers = await getUnresolvedBlockers(this.client, card, this.projectId);
|
|
5807
5993
|
if (blockers === null) {
|
|
5808
|
-
log.warn(
|
|
5994
|
+
log.warn(TAG25, `#${card.short_id} blocker check failed — deferring to next tick`);
|
|
5809
5995
|
return;
|
|
5810
5996
|
}
|
|
5811
5997
|
if (blockers.length > 0) {
|
|
5812
5998
|
const list = blockers.map((b) => `#${b.shortId}`).join(", ");
|
|
5813
|
-
log.info(
|
|
5999
|
+
log.info(TAG25, `#${card.short_id} blocked by ${list} — waiting`);
|
|
5814
6000
|
await this.emitWaiting(card.id, `Blocked by ${list} — waiting for chain`);
|
|
5815
6001
|
return;
|
|
5816
6002
|
}
|
|
@@ -5839,7 +6025,7 @@ class Pool {
|
|
|
5839
6025
|
});
|
|
5840
6026
|
this.lastWaitingEmit.set(cardId, currentTask);
|
|
5841
6027
|
} catch (err) {
|
|
5842
|
-
log.debug(
|
|
6028
|
+
log.debug(TAG25, `waiting emit failed for ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
5843
6029
|
}
|
|
5844
6030
|
}
|
|
5845
6031
|
noteApiError(err) {
|
|
@@ -5847,7 +6033,7 @@ class Pool {
|
|
|
5847
6033
|
return;
|
|
5848
6034
|
if (err.kind === "auth") {
|
|
5849
6035
|
if (!this.authPaused) {
|
|
5850
|
-
log.error(
|
|
6036
|
+
log.error(TAG25, "Auth error from Claude CLI — pausing implement pickups until the daemon is restarted with valid credentials");
|
|
5851
6037
|
}
|
|
5852
6038
|
this.authPaused = true;
|
|
5853
6039
|
return;
|
|
@@ -5856,7 +6042,7 @@ class Pool {
|
|
|
5856
6042
|
const until = Date.now() + cooldownMs;
|
|
5857
6043
|
if (until > this.apiCooldownUntil) {
|
|
5858
6044
|
this.apiCooldownUntil = until;
|
|
5859
|
-
log.warn(
|
|
6045
|
+
log.warn(TAG25, `${describeApiError(err.kind)} — pausing implement pickups for ${Math.round(cooldownMs / 1000)}s`);
|
|
5860
6046
|
}
|
|
5861
6047
|
}
|
|
5862
6048
|
apiCooldownRemainingMs() {
|
|
@@ -5869,13 +6055,13 @@ class Pool {
|
|
|
5869
6055
|
const removed = queue.remove(cardId);
|
|
5870
6056
|
if (removed) {
|
|
5871
6057
|
this.cardDataCache.delete(cardId);
|
|
5872
|
-
log.info(
|
|
6058
|
+
log.info(TAG25, `Removed #${removed.shortId} from ${removed.mode} queue`);
|
|
5873
6059
|
return;
|
|
5874
6060
|
}
|
|
5875
6061
|
}
|
|
5876
6062
|
const worker = this.implWorkers.find((w) => w.cardId === cardId) ?? this.reviewWorkers.find((w) => w.cardId === cardId);
|
|
5877
6063
|
if (worker) {
|
|
5878
|
-
log.info(
|
|
6064
|
+
log.info(TAG25, `Cancelling worker ${worker.id} for card ${cardId}`);
|
|
5879
6065
|
await worker.cancel();
|
|
5880
6066
|
}
|
|
5881
6067
|
}
|
|
@@ -5903,10 +6089,10 @@ class Pool {
|
|
|
5903
6089
|
async handleAgentCommand(cardId, command) {
|
|
5904
6090
|
const worker = this.implWorkers.find((w) => w.cardId === cardId && w.isActive) ?? this.reviewWorkers.find((w) => w.cardId === cardId && w.isActive);
|
|
5905
6091
|
if (!worker) {
|
|
5906
|
-
log.debug(
|
|
6092
|
+
log.debug(TAG25, `No active worker for card ${cardId}, ignoring ${command}`);
|
|
5907
6093
|
return;
|
|
5908
6094
|
}
|
|
5909
|
-
log.info(
|
|
6095
|
+
log.info(TAG25, `Agent command: ${command} → worker ${worker.id} (card ${cardId})`);
|
|
5910
6096
|
switch (command) {
|
|
5911
6097
|
case "pause":
|
|
5912
6098
|
await worker.pause();
|
|
@@ -5954,7 +6140,7 @@ class Pool {
|
|
|
5954
6140
|
};
|
|
5955
6141
|
}
|
|
5956
6142
|
async shutdown() {
|
|
5957
|
-
log.info(
|
|
6143
|
+
log.info(TAG25, "Shutting down pool...");
|
|
5958
6144
|
this.shuttingDown = true;
|
|
5959
6145
|
const active = [
|
|
5960
6146
|
...this.implWorkers.filter((w) => w.isActive),
|
|
@@ -5962,7 +6148,7 @@ class Pool {
|
|
|
5962
6148
|
];
|
|
5963
6149
|
await Promise.all(active.map((w) => w.cancel()));
|
|
5964
6150
|
this.sleepGuard.stop();
|
|
5965
|
-
log.info(
|
|
6151
|
+
log.info(TAG25, "Pool shutdown complete");
|
|
5966
6152
|
}
|
|
5967
6153
|
cardDataCache = new Map;
|
|
5968
6154
|
tryDispatchFor(workers, queue, label) {
|
|
@@ -5970,7 +6156,7 @@ class Pool {
|
|
|
5970
6156
|
return false;
|
|
5971
6157
|
const idle = workers.find((w) => w.isIdle);
|
|
5972
6158
|
if (!idle) {
|
|
5973
|
-
log.debug(
|
|
6159
|
+
log.debug(TAG25, `No idle ${label} workers (queue: ${queue.length})`);
|
|
5974
6160
|
return false;
|
|
5975
6161
|
}
|
|
5976
6162
|
const next = queue.dequeue();
|
|
@@ -5978,18 +6164,18 @@ class Pool {
|
|
|
5978
6164
|
return false;
|
|
5979
6165
|
const data = this.cardDataCache.get(next.cardId);
|
|
5980
6166
|
if (!data) {
|
|
5981
|
-
log.warn(
|
|
6167
|
+
log.warn(TAG25, `No cached data for card ${next.cardId}, skipping`);
|
|
5982
6168
|
return false;
|
|
5983
6169
|
}
|
|
5984
6170
|
this.cardDataCache.delete(next.cardId);
|
|
5985
6171
|
this.lastWaitingEmit.delete(next.cardId);
|
|
5986
|
-
log.info(
|
|
6172
|
+
log.info(TAG25, `Dispatching #${next.shortId} to ${label} worker ${idle.id}`);
|
|
5987
6173
|
this.sleepGuard.acquire();
|
|
5988
6174
|
idle.run(data.card, data.column, data.labels, data.subtasks);
|
|
5989
6175
|
return true;
|
|
5990
6176
|
}
|
|
5991
6177
|
}
|
|
5992
|
-
var
|
|
6178
|
+
var TAG25 = "pool";
|
|
5993
6179
|
var init_pool = __esm(() => {
|
|
5994
6180
|
init_error_classifier();
|
|
5995
6181
|
init_log();
|
|
@@ -6010,7 +6196,7 @@ __export(exports_port_registry, {
|
|
|
6010
6196
|
clearDaemonPort: () => clearDaemonPort
|
|
6011
6197
|
});
|
|
6012
6198
|
import {
|
|
6013
|
-
existsSync as
|
|
6199
|
+
existsSync as existsSync6,
|
|
6014
6200
|
mkdirSync as mkdirSync3,
|
|
6015
6201
|
readFileSync as readFileSync4,
|
|
6016
6202
|
renameSync as renameSync2,
|
|
@@ -6022,7 +6208,7 @@ function defaultRegistryPath() {
|
|
|
6022
6208
|
return join4(homedir4(), ".harmony-mcp", "agent-ports.json");
|
|
6023
6209
|
}
|
|
6024
6210
|
function load(path) {
|
|
6025
|
-
if (!
|
|
6211
|
+
if (!existsSync6(path))
|
|
6026
6212
|
return {};
|
|
6027
6213
|
try {
|
|
6028
6214
|
const raw = readFileSync4(path, "utf-8");
|
|
@@ -6031,13 +6217,13 @@ function load(path) {
|
|
|
6031
6217
|
return parsed;
|
|
6032
6218
|
return {};
|
|
6033
6219
|
} catch (err) {
|
|
6034
|
-
log.warn(
|
|
6220
|
+
log.warn(TAG26, `failed to read ${path}: ${err instanceof Error ? err.message : err}`);
|
|
6035
6221
|
return {};
|
|
6036
6222
|
}
|
|
6037
6223
|
}
|
|
6038
6224
|
function save(path, registry) {
|
|
6039
6225
|
const dir = dirname2(path);
|
|
6040
|
-
if (!
|
|
6226
|
+
if (!existsSync6(dir))
|
|
6041
6227
|
mkdirSync3(dir, { recursive: true });
|
|
6042
6228
|
const tmp = `${path}.tmp`;
|
|
6043
6229
|
writeFileSync2(tmp, JSON.stringify(registry, null, 2), "utf-8");
|
|
@@ -6049,7 +6235,7 @@ function recordDaemonPort(projectId, entry, path = defaultRegistryPath()) {
|
|
|
6049
6235
|
registry[projectId] = { ...entry, updatedAt: Date.now() };
|
|
6050
6236
|
save(path, registry);
|
|
6051
6237
|
} catch (err) {
|
|
6052
|
-
log.warn(
|
|
6238
|
+
log.warn(TAG26, `failed to record port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
6053
6239
|
}
|
|
6054
6240
|
}
|
|
6055
6241
|
function lookupDaemonPort(projectId, path = defaultRegistryPath()) {
|
|
@@ -6065,10 +6251,10 @@ function clearDaemonPort(projectId, pid, path = defaultRegistryPath()) {
|
|
|
6065
6251
|
delete registry[projectId];
|
|
6066
6252
|
save(path, registry);
|
|
6067
6253
|
} catch (err) {
|
|
6068
|
-
log.warn(
|
|
6254
|
+
log.warn(TAG26, `failed to clear port for ${projectId}: ${err instanceof Error ? err.message : err}`);
|
|
6069
6255
|
}
|
|
6070
6256
|
}
|
|
6071
|
-
var
|
|
6257
|
+
var TAG26 = "port-registry";
|
|
6072
6258
|
var init_port_registry = __esm(() => {
|
|
6073
6259
|
init_log();
|
|
6074
6260
|
});
|
|
@@ -6089,7 +6275,7 @@ async function fetchCardSafely(client, cardId) {
|
|
|
6089
6275
|
const { card } = await client.getCard(cardId);
|
|
6090
6276
|
return card;
|
|
6091
6277
|
} catch (err) {
|
|
6092
|
-
log.warn(
|
|
6278
|
+
log.warn(TAG27, `cannot fetch card ${cardId}: ${err instanceof Error ? err.message : err}`);
|
|
6093
6279
|
return null;
|
|
6094
6280
|
}
|
|
6095
6281
|
}
|
|
@@ -6099,7 +6285,7 @@ async function recoverOrphans(store, client, config) {
|
|
|
6099
6285
|
return [];
|
|
6100
6286
|
}
|
|
6101
6287
|
const outcomes = [];
|
|
6102
|
-
log.info(
|
|
6288
|
+
log.info(TAG27, `recovering ${active.length} orphan run(s) from prior daemon`);
|
|
6103
6289
|
for (const run of active) {
|
|
6104
6290
|
const outcome = {
|
|
6105
6291
|
runId: run.runId,
|
|
@@ -6111,11 +6297,11 @@ async function recoverOrphans(store, client, config) {
|
|
|
6111
6297
|
};
|
|
6112
6298
|
outcomes.push(outcome);
|
|
6113
6299
|
if (isProcessAlive(run.daemonPid, process.pid)) {
|
|
6114
|
-
log.warn(
|
|
6300
|
+
log.warn(TAG27, `run ${run.runId} claims live daemon pid ${run.daemonPid} — skipping`);
|
|
6115
6301
|
outcome.actions.push("skipped: daemon pid still alive");
|
|
6116
6302
|
continue;
|
|
6117
6303
|
}
|
|
6118
|
-
log.info(
|
|
6304
|
+
log.info(TAG27, `recovering ${run.pipeline} run ${run.runId} for card #${run.cardShortId}`);
|
|
6119
6305
|
await recoverRun(run, store, client, config, outcome);
|
|
6120
6306
|
}
|
|
6121
6307
|
return outcomes;
|
|
@@ -6133,7 +6319,7 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
6133
6319
|
} catch (err) {
|
|
6134
6320
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6135
6321
|
outcome.errors.push(`endAgentSession: ${msg}`);
|
|
6136
|
-
log.warn(
|
|
6322
|
+
log.warn(TAG27, `endAgentSession failed for ${run.cardId}: ${msg}`);
|
|
6137
6323
|
}
|
|
6138
6324
|
const card = await fetchCardSafely(client, run.cardId);
|
|
6139
6325
|
if (card) {
|
|
@@ -6176,9 +6362,9 @@ async function recoverRun(run, store, client, config, outcome) {
|
|
|
6176
6362
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6177
6363
|
outcome.errors.push(`endRun: ${msg}`);
|
|
6178
6364
|
}
|
|
6179
|
-
log.info(
|
|
6365
|
+
log.info(TAG27, `recovered run ${run.runId} (card #${run.cardShortId}): ${outcome.actions.join(", ")}${outcome.errors.length ? ` | errors: ${outcome.errors.join("; ")}` : ""}`);
|
|
6180
6366
|
}
|
|
6181
|
-
var
|
|
6367
|
+
var TAG27 = "recovery", RECOVERED_LABEL = "agent-recovered", RECOVERED_LABEL_COLOR = "#f59e0b";
|
|
6182
6368
|
var init_recovery = __esm(() => {
|
|
6183
6369
|
init_board_helpers();
|
|
6184
6370
|
init_log();
|
|
@@ -6226,7 +6412,7 @@ class Reconciler {
|
|
|
6226
6412
|
clearInterval(this.timer);
|
|
6227
6413
|
this.timer = null;
|
|
6228
6414
|
}
|
|
6229
|
-
log.info(
|
|
6415
|
+
log.info(TAG28, "Heartbeat stopped");
|
|
6230
6416
|
}
|
|
6231
6417
|
async recoverStaleRuns() {
|
|
6232
6418
|
if (!this.stateStore || !this.agentConfig)
|
|
@@ -6243,7 +6429,7 @@ class Reconciler {
|
|
|
6243
6429
|
if (!daemonDead && !(heartbeatStale && ourZombie))
|
|
6244
6430
|
continue;
|
|
6245
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`;
|
|
6246
|
-
log.warn(
|
|
6432
|
+
log.warn(TAG28, `zombie run ${run.runId} (#${run.cardShortId}): ${reason} — recovering`);
|
|
6247
6433
|
await recoverRun(run, this.stateStore, this.client, this.agentConfig, {
|
|
6248
6434
|
runId: run.runId,
|
|
6249
6435
|
cardId: run.cardId,
|
|
@@ -6270,11 +6456,11 @@ class Reconciler {
|
|
|
6270
6456
|
const stalledAt = Date.parse(card.updated_at ?? "");
|
|
6271
6457
|
if (!Number.isFinite(stalledAt) || now - stalledAt < graceMs)
|
|
6272
6458
|
continue;
|
|
6273
|
-
log.warn(
|
|
6459
|
+
log.warn(TAG28, `#${card.short_id} stranded in "${inProgressCol.name}" (no live run) — requeueing to "${pickupCol.name}"`);
|
|
6274
6460
|
try {
|
|
6275
6461
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
6276
6462
|
} catch (err) {
|
|
6277
|
-
log.error(
|
|
6463
|
+
log.error(TAG28, `stranded requeue failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6278
6464
|
}
|
|
6279
6465
|
}
|
|
6280
6466
|
}
|
|
@@ -6297,11 +6483,11 @@ class Reconciler {
|
|
|
6297
6483
|
const parkedAt = Date.parse(card.updated_at ?? "");
|
|
6298
6484
|
if (!Number.isFinite(parkedAt) || now - parkedAt < ttlMs)
|
|
6299
6485
|
continue;
|
|
6300
|
-
log.warn(
|
|
6486
|
+
log.warn(TAG28, `#${card.short_id} parked for approval > ${planning.approvalTtlHours}h — auto-releasing to "${pickupCol.name}"`);
|
|
6301
6487
|
try {
|
|
6302
6488
|
await this.client.moveCard(card.id, pickupCol.id);
|
|
6303
6489
|
} catch (err) {
|
|
6304
|
-
log.error(
|
|
6490
|
+
log.error(TAG28, `auto-release failed for #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
6305
6491
|
}
|
|
6306
6492
|
}
|
|
6307
6493
|
}
|
|
@@ -6330,18 +6516,18 @@ class Reconciler {
|
|
|
6330
6516
|
const subtasks = card.subtasks ?? [];
|
|
6331
6517
|
const mode = reviewColumnIds.has(card.column_id) ? "review" : "implement";
|
|
6332
6518
|
if (mode === "review" && this.approvedLabel && hasLabel(cardLabels, this.approvedLabel)) {
|
|
6333
|
-
log.debug(
|
|
6519
|
+
log.debug(TAG28, `Skipping #${card.short_id} — already has "${this.approvedLabel}" label`);
|
|
6334
6520
|
continue;
|
|
6335
6521
|
}
|
|
6336
6522
|
if (mode === "review" && hasLabel(cardLabels, NEED_REVIEW_LABEL)) {
|
|
6337
|
-
log.debug(
|
|
6523
|
+
log.debug(TAG28, `Skipping #${card.short_id} — has "${NEED_REVIEW_LABEL}" label (needs human)`);
|
|
6338
6524
|
continue;
|
|
6339
6525
|
}
|
|
6340
6526
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
6341
|
-
log.debug(
|
|
6527
|
+
log.debug(TAG28, `Skipping #${card.short_id} — no branch reference (not qualified for auto-review)`);
|
|
6342
6528
|
continue;
|
|
6343
6529
|
}
|
|
6344
|
-
log.info(
|
|
6530
|
+
log.info(TAG28, `Missed assignment: #${card.short_id} "${card.title}" (${mode}) — enqueueing`);
|
|
6345
6531
|
await this.pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
6346
6532
|
}
|
|
6347
6533
|
}
|
|
@@ -6351,18 +6537,18 @@ class Reconciler {
|
|
|
6351
6537
|
await this.recoverStrandedInProgress(cards, columns, knownCardIds);
|
|
6352
6538
|
for (const knownId of knownCardIds) {
|
|
6353
6539
|
if (!allAgentCardIds.has(knownId)) {
|
|
6354
|
-
log.info(
|
|
6540
|
+
log.info(TAG28, `Missed unassign: ${knownId} — removing`);
|
|
6355
6541
|
await this.pool.removeCard(knownId);
|
|
6356
6542
|
}
|
|
6357
6543
|
}
|
|
6358
6544
|
await this.releaseStalledApprovals(cards, columns, knownCardIds);
|
|
6359
|
-
log.debug(
|
|
6545
|
+
log.debug(TAG28, `Reconciled: ${assignedCards.length} assigned, ${knownCardIds.size} known`);
|
|
6360
6546
|
} catch (err) {
|
|
6361
|
-
log.error(
|
|
6547
|
+
log.error(TAG28, `Heartbeat failed: ${err instanceof Error ? err.message : err}`);
|
|
6362
6548
|
}
|
|
6363
6549
|
}
|
|
6364
6550
|
}
|
|
6365
|
-
var
|
|
6551
|
+
var TAG28 = "reconcile";
|
|
6366
6552
|
var init_reconcile = __esm(() => {
|
|
6367
6553
|
init_board_helpers();
|
|
6368
6554
|
init_log();
|
|
@@ -6400,7 +6586,7 @@ function prettyBanner(config, version) {
|
|
|
6400
6586
|
checks.push({ kind: "ok", message });
|
|
6401
6587
|
},
|
|
6402
6588
|
warn(message) {
|
|
6403
|
-
log.warn(
|
|
6589
|
+
log.warn(TAG29, message);
|
|
6404
6590
|
checks.push({ kind: "warn", message: message.split(`
|
|
6405
6591
|
`, 1)[0] });
|
|
6406
6592
|
},
|
|
@@ -6425,25 +6611,25 @@ function prettyBanner(config, version) {
|
|
|
6425
6611
|
};
|
|
6426
6612
|
}
|
|
6427
6613
|
function jsonBanner(config, version) {
|
|
6428
|
-
log.info(
|
|
6429
|
-
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(", ")}`);
|
|
6430
6616
|
if (config.agent.review.enabled) {
|
|
6431
|
-
log.info(
|
|
6617
|
+
log.info(TAG29, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
6432
6618
|
}
|
|
6433
6619
|
let failed = false;
|
|
6434
6620
|
return {
|
|
6435
6621
|
setProjectName(_name) {},
|
|
6436
6622
|
setGitProvider(provider) {
|
|
6437
|
-
log.info(
|
|
6623
|
+
log.info(TAG29, `Git provider: ${provider}`);
|
|
6438
6624
|
},
|
|
6439
6625
|
setHttpPort(port) {
|
|
6440
|
-
log.info(
|
|
6626
|
+
log.info(TAG29, `HTTP server on port ${port}`);
|
|
6441
6627
|
},
|
|
6442
6628
|
check(message) {
|
|
6443
|
-
log.info(
|
|
6629
|
+
log.info(TAG29, message);
|
|
6444
6630
|
},
|
|
6445
6631
|
warn(message) {
|
|
6446
|
-
log.warn(
|
|
6632
|
+
log.warn(TAG29, message);
|
|
6447
6633
|
},
|
|
6448
6634
|
fail() {
|
|
6449
6635
|
failed = true;
|
|
@@ -6451,7 +6637,7 @@ function jsonBanner(config, version) {
|
|
|
6451
6637
|
async ready(message) {
|
|
6452
6638
|
if (failed)
|
|
6453
6639
|
return;
|
|
6454
|
-
log.info(
|
|
6640
|
+
log.info(TAG29, message);
|
|
6455
6641
|
}
|
|
6456
6642
|
};
|
|
6457
6643
|
}
|
|
@@ -6528,7 +6714,7 @@ function cyan(s) {
|
|
|
6528
6714
|
function yellow(s) {
|
|
6529
6715
|
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
6530
6716
|
}
|
|
6531
|
-
var
|
|
6717
|
+
var TAG29 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
6532
6718
|
var init_startup_banner = __esm(() => {
|
|
6533
6719
|
init_log();
|
|
6534
6720
|
ANSI = {
|
|
@@ -6675,18 +6861,18 @@ class Watcher {
|
|
|
6675
6861
|
}
|
|
6676
6862
|
async start() {
|
|
6677
6863
|
if (!isPretty()) {
|
|
6678
|
-
log.info(
|
|
6864
|
+
log.info(TAG30, "Connecting to Supabase realtime (broadcast)...");
|
|
6679
6865
|
}
|
|
6680
6866
|
this.supabase = createClient(this.credentials.supabaseUrl, this.credentials.supabaseAnonKey);
|
|
6681
6867
|
const presenceChannel = this.supabase.channel(`board-presence-${this.projectId}`);
|
|
6682
6868
|
const channel = this.supabase.channel(`board-${this.projectId}`).on("broadcast", { event: "card_update" }, (msg) => {
|
|
6683
|
-
log.debug(
|
|
6869
|
+
log.debug(TAG30, `Broadcast: card_update ${JSON.stringify(msg.payload)}`);
|
|
6684
6870
|
this.onCardBroadcast({
|
|
6685
6871
|
event: "card_update",
|
|
6686
6872
|
payload: msg.payload ?? {}
|
|
6687
6873
|
});
|
|
6688
6874
|
}).on("broadcast", { event: "card_created" }, (msg) => {
|
|
6689
|
-
log.debug(
|
|
6875
|
+
log.debug(TAG30, `Broadcast: card_created ${JSON.stringify(msg.payload)}`);
|
|
6690
6876
|
this.onCardBroadcast({
|
|
6691
6877
|
event: "card_created",
|
|
6692
6878
|
payload: msg.payload ?? {}
|
|
@@ -6696,29 +6882,29 @@ class Watcher {
|
|
|
6696
6882
|
const cardId = payload.card_id;
|
|
6697
6883
|
const command = payload.command;
|
|
6698
6884
|
if (cardId && command) {
|
|
6699
|
-
log.info(
|
|
6885
|
+
log.info(TAG30, `Broadcast: agent_command ${command} for ${cardId}`);
|
|
6700
6886
|
this.onAgentCommand?.({ cardId, command });
|
|
6701
6887
|
}
|
|
6702
6888
|
}).subscribe((status) => {
|
|
6703
6889
|
if (status === "SUBSCRIBED") {
|
|
6704
6890
|
this.connected = true;
|
|
6705
6891
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
6706
|
-
log.info(
|
|
6892
|
+
log.info(TAG30, "Broadcast subscription active");
|
|
6707
6893
|
}
|
|
6708
6894
|
this.maybeResolveReady();
|
|
6709
6895
|
} else if (status === "CHANNEL_ERROR") {
|
|
6710
6896
|
this.connected = false;
|
|
6711
|
-
log.error(
|
|
6897
|
+
log.error(TAG30, "Broadcast channel error — will rely on reconciliation");
|
|
6712
6898
|
} else if (status === "TIMED_OUT") {
|
|
6713
6899
|
this.connected = false;
|
|
6714
|
-
log.warn(
|
|
6900
|
+
log.warn(TAG30, "Broadcast subscription timed out — retrying...");
|
|
6715
6901
|
} else if (status === "CLOSED") {
|
|
6716
6902
|
this.connected = false;
|
|
6717
6903
|
}
|
|
6718
6904
|
});
|
|
6719
6905
|
this.channel = channel;
|
|
6720
6906
|
presenceChannel.on("presence", { event: "sync" }, () => {
|
|
6721
|
-
log.debug(
|
|
6907
|
+
log.debug(TAG30, "Presence sync");
|
|
6722
6908
|
}).subscribe(async (status) => {
|
|
6723
6909
|
if (status === "SUBSCRIBED") {
|
|
6724
6910
|
await presenceChannel.track({
|
|
@@ -6731,7 +6917,7 @@ class Watcher {
|
|
|
6731
6917
|
agentName: this.identity.agentName
|
|
6732
6918
|
});
|
|
6733
6919
|
if (!isPretty() || !this.suppressStartupLogs) {
|
|
6734
|
-
log.info(
|
|
6920
|
+
log.info(TAG30, "Presence tracked on board-presence channel");
|
|
6735
6921
|
}
|
|
6736
6922
|
this.presenceTracked = true;
|
|
6737
6923
|
this.maybeResolveReady();
|
|
@@ -6753,10 +6939,10 @@ class Watcher {
|
|
|
6753
6939
|
this.supabase = null;
|
|
6754
6940
|
}
|
|
6755
6941
|
this.connected = false;
|
|
6756
|
-
log.info(
|
|
6942
|
+
log.info(TAG30, "Broadcast subscription stopped");
|
|
6757
6943
|
}
|
|
6758
6944
|
}
|
|
6759
|
-
var
|
|
6945
|
+
var TAG30 = "watcher";
|
|
6760
6946
|
var init_watcher = __esm(() => {
|
|
6761
6947
|
init_log();
|
|
6762
6948
|
});
|
|
@@ -6769,8 +6955,8 @@ __export(exports_worktree_gc, {
|
|
|
6769
6955
|
isTransientGitNetworkError: () => isTransientGitNetworkError,
|
|
6770
6956
|
WorktreeGc: () => WorktreeGc
|
|
6771
6957
|
});
|
|
6772
|
-
import { execFileSync as
|
|
6773
|
-
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";
|
|
6774
6960
|
import { resolve as resolve3 } from "node:path";
|
|
6775
6961
|
function isTransientGitNetworkError(message) {
|
|
6776
6962
|
return TRANSIENT_GIT_NETWORK_ERROR.test(message);
|
|
@@ -6795,7 +6981,7 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
6795
6981
|
const baseAbs = resolve3(repoRoot, basePath);
|
|
6796
6982
|
let entries;
|
|
6797
6983
|
try {
|
|
6798
|
-
entries =
|
|
6984
|
+
entries = readdirSync2(baseAbs);
|
|
6799
6985
|
} catch {
|
|
6800
6986
|
return result;
|
|
6801
6987
|
}
|
|
@@ -6837,16 +7023,16 @@ function runWorktreeGc(basePath, store, opts = {}) {
|
|
|
6837
7023
|
}
|
|
6838
7024
|
}
|
|
6839
7025
|
try {
|
|
6840
|
-
|
|
7026
|
+
execFileSync10("git", ["worktree", "prune", "--expire=now"], {
|
|
6841
7027
|
cwd: repoRoot,
|
|
6842
7028
|
stdio: "pipe"
|
|
6843
7029
|
});
|
|
6844
7030
|
} catch {}
|
|
6845
7031
|
if (result.removed.length > 0) {
|
|
6846
|
-
log.info(
|
|
7032
|
+
log.info(TAG31, `GC removed ${result.removed.length} orphan worktree(s): ${result.removed.map((p) => p.split("/").pop()).join(", ")}`);
|
|
6847
7033
|
}
|
|
6848
7034
|
if (result.errors.length > 0) {
|
|
6849
|
-
log.warn(
|
|
7035
|
+
log.warn(TAG31, `GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.path}: ${e.error}`).join("; ")}`);
|
|
6850
7036
|
}
|
|
6851
7037
|
return result;
|
|
6852
7038
|
}
|
|
@@ -6868,7 +7054,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6868
7054
|
return result;
|
|
6869
7055
|
}
|
|
6870
7056
|
try {
|
|
6871
|
-
|
|
7057
|
+
execFileSync10("git", ["fetch", "--prune", "origin"], {
|
|
6872
7058
|
cwd: repoRoot,
|
|
6873
7059
|
stdio: "pipe",
|
|
6874
7060
|
...GIT_NETWORK_EXEC
|
|
@@ -6876,7 +7062,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6876
7062
|
} catch (err) {
|
|
6877
7063
|
const detail = gitErrorDetail(err);
|
|
6878
7064
|
if (isTransientGitNetworkError(detail)) {
|
|
6879
|
-
log.debug(
|
|
7065
|
+
log.debug(TAG31, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
6880
7066
|
return result;
|
|
6881
7067
|
}
|
|
6882
7068
|
result.errors.push({ ref: "fetch", error: detail });
|
|
@@ -6884,7 +7070,7 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6884
7070
|
const refPattern = `refs/remotes/origin/${opts.prefix}*`;
|
|
6885
7071
|
let listing = "";
|
|
6886
7072
|
try {
|
|
6887
|
-
listing =
|
|
7073
|
+
listing = execFileSync10("git", [
|
|
6888
7074
|
"for-each-ref",
|
|
6889
7075
|
"--format=%(refname:strip=3) %(committerdate:unix)",
|
|
6890
7076
|
refPattern
|
|
@@ -6915,11 +7101,11 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6915
7101
|
continue;
|
|
6916
7102
|
}
|
|
6917
7103
|
if (clock() > sweepDeadline) {
|
|
6918
|
-
log.debug(
|
|
7104
|
+
log.debug(TAG31, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
6919
7105
|
break;
|
|
6920
7106
|
}
|
|
6921
7107
|
try {
|
|
6922
|
-
|
|
7108
|
+
execFileSync10("git", ["push", "origin", `:refs/heads/${ref}`], {
|
|
6923
7109
|
cwd: repoRoot,
|
|
6924
7110
|
stdio: "pipe",
|
|
6925
7111
|
...GIT_NETWORK_EXEC
|
|
@@ -6928,17 +7114,17 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6928
7114
|
} catch (err) {
|
|
6929
7115
|
const detail = gitErrorDetail(err);
|
|
6930
7116
|
if (isTransientGitNetworkError(detail)) {
|
|
6931
|
-
log.debug(
|
|
7117
|
+
log.debug(TAG31, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
6932
7118
|
break;
|
|
6933
7119
|
}
|
|
6934
7120
|
result.errors.push({ ref, error: detail });
|
|
6935
7121
|
}
|
|
6936
7122
|
}
|
|
6937
7123
|
if (result.removed.length > 0) {
|
|
6938
|
-
log.info(
|
|
7124
|
+
log.info(TAG31, `Pruned ${result.removed.length} stale remote branch(es) under ${opts.prefix}: ${result.removed.join(", ")}`);
|
|
6939
7125
|
}
|
|
6940
7126
|
if (result.errors.length > 0) {
|
|
6941
|
-
log.warn(
|
|
7127
|
+
log.warn(TAG31, `Remote branch GC had ${result.errors.length} error(s): ${result.errors.map((e) => `${e.ref}: ${e.error}`).join("; ")}`);
|
|
6942
7128
|
}
|
|
6943
7129
|
return result;
|
|
6944
7130
|
}
|
|
@@ -6969,27 +7155,27 @@ class WorktreeGc {
|
|
|
6969
7155
|
try {
|
|
6970
7156
|
runWorktreeGc(this.basePath, this.store);
|
|
6971
7157
|
} catch (err) {
|
|
6972
|
-
log.warn(
|
|
7158
|
+
log.warn(TAG31, `GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
6973
7159
|
}
|
|
6974
7160
|
if (this.remoteOpts) {
|
|
6975
7161
|
try {
|
|
6976
7162
|
pruneFailedRemoteBranches(this.remoteOpts);
|
|
6977
7163
|
} catch (err) {
|
|
6978
|
-
log.warn(
|
|
7164
|
+
log.warn(TAG31, `Remote GC tick failed: ${err instanceof Error ? err.message : err}`);
|
|
6979
7165
|
}
|
|
6980
7166
|
}
|
|
6981
7167
|
}
|
|
6982
7168
|
}
|
|
6983
7169
|
function getRepoRoot2() {
|
|
6984
7170
|
try {
|
|
6985
|
-
return
|
|
7171
|
+
return execFileSync10("git", ["rev-parse", "--show-toplevel"], {
|
|
6986
7172
|
encoding: "utf-8"
|
|
6987
7173
|
}).trim();
|
|
6988
7174
|
} catch {
|
|
6989
7175
|
return null;
|
|
6990
7176
|
}
|
|
6991
7177
|
}
|
|
6992
|
-
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;
|
|
6993
7179
|
var init_worktree_gc = __esm(() => {
|
|
6994
7180
|
init_log();
|
|
6995
7181
|
init_worktree();
|
|
@@ -7023,12 +7209,12 @@ __export(exports_src, {
|
|
|
7023
7209
|
validatePrerequisites: () => validatePrerequisites,
|
|
7024
7210
|
main: () => main
|
|
7025
7211
|
});
|
|
7026
|
-
import { execFileSync as
|
|
7212
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
7027
7213
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7028
7214
|
import { createRequire as createRequire2 } from "node:module";
|
|
7029
7215
|
async function validatePrerequisites(config, banner) {
|
|
7030
7216
|
try {
|
|
7031
|
-
const ver =
|
|
7217
|
+
const ver = execFileSync11("claude", ["--version"], {
|
|
7032
7218
|
encoding: "utf-8"
|
|
7033
7219
|
}).trim();
|
|
7034
7220
|
banner.check(`Claude CLI ${ver}`);
|
|
@@ -7043,14 +7229,14 @@ async function validatePrerequisites(config, banner) {
|
|
|
7043
7229
|
validateGitProviderCli(provider);
|
|
7044
7230
|
}
|
|
7045
7231
|
try {
|
|
7046
|
-
const status =
|
|
7232
|
+
const status = execFileSync11("git", ["status", "--porcelain"], {
|
|
7047
7233
|
encoding: "utf-8"
|
|
7048
7234
|
}).trim();
|
|
7049
7235
|
if (status) {
|
|
7050
7236
|
banner.warn(`Working directory has uncommitted changes:
|
|
7051
7237
|
${status}`);
|
|
7052
7238
|
}
|
|
7053
|
-
|
|
7239
|
+
execFileSync11("git", ["rev-parse", "--verify", `origin/${config.agent.worktree.baseBranch}`], {
|
|
7054
7240
|
encoding: "utf-8",
|
|
7055
7241
|
stdio: "pipe"
|
|
7056
7242
|
});
|
|
@@ -7093,7 +7279,7 @@ async function main() {
|
|
|
7093
7279
|
} catch (err) {
|
|
7094
7280
|
if (err instanceof ConfigValidationError) {
|
|
7095
7281
|
banner.fail();
|
|
7096
|
-
log.error(
|
|
7282
|
+
log.error(TAG32, err.message);
|
|
7097
7283
|
process.exit(1);
|
|
7098
7284
|
}
|
|
7099
7285
|
throw err;
|
|
@@ -7202,7 +7388,7 @@ async function main() {
|
|
|
7202
7388
|
if (shuttingDown)
|
|
7203
7389
|
return;
|
|
7204
7390
|
shuttingDown = true;
|
|
7205
|
-
log.info(
|
|
7391
|
+
log.info(TAG32, `Received ${signal}, shutting down gracefully...`);
|
|
7206
7392
|
reconciler.stop();
|
|
7207
7393
|
mergeMonitor?.stop();
|
|
7208
7394
|
worktreeGc.stop();
|
|
@@ -7212,18 +7398,18 @@ async function main() {
|
|
|
7212
7398
|
}
|
|
7213
7399
|
await watcher.stop();
|
|
7214
7400
|
await pool.shutdown();
|
|
7215
|
-
log.info(
|
|
7401
|
+
log.info(TAG32, "Daemon stopped.");
|
|
7216
7402
|
process.exit(exitCode);
|
|
7217
7403
|
};
|
|
7218
7404
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
7219
7405
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
7220
7406
|
process.on("uncaughtException", (err) => {
|
|
7221
|
-
log.error(
|
|
7407
|
+
log.error(TAG32, `Uncaught exception: ${err.message}`);
|
|
7222
7408
|
exitCode = 1;
|
|
7223
7409
|
shutdown("uncaughtException");
|
|
7224
7410
|
});
|
|
7225
7411
|
process.on("unhandledRejection", (reason) => {
|
|
7226
|
-
log.error(
|
|
7412
|
+
log.error(TAG32, `Unhandled rejection: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
7227
7413
|
exitCode = 1;
|
|
7228
7414
|
shutdown("unhandledRejection");
|
|
7229
7415
|
});
|
|
@@ -7276,34 +7462,34 @@ async function handleBroadcast(event, client, pool, config, agentId) {
|
|
|
7276
7462
|
if (assignedAgentId === undefined)
|
|
7277
7463
|
return;
|
|
7278
7464
|
if (assignedAgentId === agentId) {
|
|
7279
|
-
log.info(
|
|
7465
|
+
log.info(TAG32, `Broadcast: card ${cardId} assigned to agent`);
|
|
7280
7466
|
try {
|
|
7281
7467
|
await tryEnqueueCard(cardId, client, pool, config, agentId);
|
|
7282
7468
|
} catch (err) {
|
|
7283
|
-
log.error(
|
|
7469
|
+
log.error(TAG32, `Failed to process assignment: ${err instanceof Error ? err.message : err}`);
|
|
7284
7470
|
}
|
|
7285
7471
|
} else if (pool.isCardKnown(cardId)) {
|
|
7286
|
-
log.info(
|
|
7472
|
+
log.info(TAG32, `Broadcast: card ${cardId} unassigned from agent`);
|
|
7287
7473
|
await pool.removeCard(cardId);
|
|
7288
7474
|
}
|
|
7289
7475
|
}
|
|
7290
7476
|
async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
7291
7477
|
const { card } = await client.getCard(cardId);
|
|
7292
7478
|
if (card.assigned_agent_id !== agentId) {
|
|
7293
|
-
log.debug(
|
|
7479
|
+
log.debug(TAG32, `Card ${cardId} no longer assigned to agent — skipping`);
|
|
7294
7480
|
return;
|
|
7295
7481
|
}
|
|
7296
7482
|
const board = await client.getBoard(config.projectId, { summary: true });
|
|
7297
7483
|
const columns = board.columns;
|
|
7298
7484
|
const column = columns.find((c) => c.id === card.column_id);
|
|
7299
7485
|
if (!column) {
|
|
7300
|
-
log.warn(
|
|
7486
|
+
log.warn(TAG32, `Column not found for card ${cardId}`);
|
|
7301
7487
|
return;
|
|
7302
7488
|
}
|
|
7303
7489
|
const isPickupColumn = config.agent.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
7304
7490
|
const isReviewColumn = config.agent.review.enabled && config.agent.review.pickupColumns.some((name) => name.toLowerCase() === column.name.toLowerCase());
|
|
7305
7491
|
if (!isPickupColumn && !isReviewColumn) {
|
|
7306
|
-
log.info(
|
|
7492
|
+
log.info(TAG32, `Card #${card.short_id} is in "${column.name}", not a pickup/review column — skipping`);
|
|
7307
7493
|
return;
|
|
7308
7494
|
}
|
|
7309
7495
|
const mode = isReviewColumn ? "review" : "implement";
|
|
@@ -7311,16 +7497,16 @@ async function tryEnqueueCard(cardId, client, pool, config, agentId) {
|
|
|
7311
7497
|
const cardLabels = resolveCardLabels(card, labelMap);
|
|
7312
7498
|
const subtasks = card.subtasks ?? [];
|
|
7313
7499
|
if (mode === "review" && config.agent.review.approvedLabel && hasLabel(cardLabels, config.agent.review.approvedLabel)) {
|
|
7314
|
-
log.debug(
|
|
7500
|
+
log.debug(TAG32, `Card #${card.short_id} already has "${config.agent.review.approvedLabel}" — skipping review`);
|
|
7315
7501
|
return;
|
|
7316
7502
|
}
|
|
7317
7503
|
if (mode === "review" && !extractBranchFromDescription(card.description)) {
|
|
7318
|
-
log.info(
|
|
7504
|
+
log.info(TAG32, `Card #${card.short_id} has no branch reference — skipping auto-review`);
|
|
7319
7505
|
return;
|
|
7320
7506
|
}
|
|
7321
7507
|
await pool.enqueue(card, column, cardLabels, subtasks, mode);
|
|
7322
7508
|
}
|
|
7323
|
-
var
|
|
7509
|
+
var TAG32 = "daemon", PKG_VERSION;
|
|
7324
7510
|
var init_src = __esm(() => {
|
|
7325
7511
|
init_board_helpers();
|
|
7326
7512
|
init_config();
|