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