@gethmy/agent 1.49.7 → 1.49.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +169 -36
- package/dist/index.js +165 -35
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1298,8 +1298,23 @@ import { basename } from "node:path";
|
|
|
1298
1298
|
import { log as log3 } from "@gethmy/harness";
|
|
1299
1299
|
import {
|
|
1300
1300
|
findMappedFolder,
|
|
1301
|
+
registerProjectFolder,
|
|
1301
1302
|
setMappedFolderRepositoryId
|
|
1302
1303
|
} from "@gethmy/mcp/src/config.js";
|
|
1304
|
+
function mapOwnFolder(config) {
|
|
1305
|
+
try {
|
|
1306
|
+
registerProjectFolder({
|
|
1307
|
+
projectId: config.projectId,
|
|
1308
|
+
workspaceId: config.workspaceId,
|
|
1309
|
+
path: config.repoRoot
|
|
1310
|
+
});
|
|
1311
|
+
} catch (err) {
|
|
1312
|
+
log3.warn(TAG3, `Could not map ${config.repoRoot} to its project, running without a repository: ${err instanceof Error ? err.message : String(err)}`);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
log3.info(TAG3, `Mapped ${config.repoRoot} to its project`);
|
|
1316
|
+
return findMappedFolder(config.repoRoot);
|
|
1317
|
+
}
|
|
1303
1318
|
async function resolveAgentIdentity(client, config) {
|
|
1304
1319
|
const { agent: legacy } = await client.registerWorkspaceAgent(config.workspaceId, {
|
|
1305
1320
|
identifier: config.agentIdentifier,
|
|
@@ -1311,7 +1326,7 @@ async function resolveAgentIdentity(client, config) {
|
|
|
1311
1326
|
repository: null,
|
|
1312
1327
|
folderPath: null
|
|
1313
1328
|
};
|
|
1314
|
-
const folder = findMappedFolder(config.repoRoot);
|
|
1329
|
+
const folder = findMappedFolder(config.repoRoot) ?? mapOwnFolder(config);
|
|
1315
1330
|
if (!folder || folder.projectId !== config.projectId)
|
|
1316
1331
|
return legacyIdentity;
|
|
1317
1332
|
let registered;
|
|
@@ -13089,6 +13104,15 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
13089
13104
|
} catch (err) {
|
|
13090
13105
|
log36.warn(TAG32, `Failed to clear the attempt-cap decision deadline for ${cardId}: ${err}`);
|
|
13091
13106
|
}
|
|
13107
|
+
try {
|
|
13108
|
+
await this.client.endAgentSession(cardId, {
|
|
13109
|
+
status: "failed",
|
|
13110
|
+
failureReason: "budget",
|
|
13111
|
+
failureSummary: "Attempt budget granted by a human decision — a fresh run takes the card."
|
|
13112
|
+
});
|
|
13113
|
+
} catch (err) {
|
|
13114
|
+
log36.warn(TAG32, `Failed to end the attempt-cap session for ${cardId}: ${err}`);
|
|
13115
|
+
}
|
|
13092
13116
|
await this.enqueueCard(cardId, "implement");
|
|
13093
13117
|
}
|
|
13094
13118
|
async stopAttemptCap(cardId, failureSummary = "Stopped by a human decision on the attempt budget.") {
|
|
@@ -14823,6 +14847,15 @@ function ownString(source, key) {
|
|
|
14823
14847
|
const value = own(source, key);
|
|
14824
14848
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
14825
14849
|
}
|
|
14850
|
+
function compactModelsLine(m) {
|
|
14851
|
+
const short = (id) => id.replace(/^claude-/, "");
|
|
14852
|
+
if (m.sizingModel) {
|
|
14853
|
+
const ladder = TIERS.map((tier) => short(m.tiers[tier])).join(" · ");
|
|
14854
|
+
const rule = m.researchRule === "armed" && isJevSizingModel(m.sizingModel) ? " · research rule armed" : "";
|
|
14855
|
+
return `${ladder} sized by ${m.sizingModel}${rule}`;
|
|
14856
|
+
}
|
|
14857
|
+
return m.model === m.escalateModel ? short(m.model) : `${short(m.model)} → ${short(m.escalateModel)} on retry`;
|
|
14858
|
+
}
|
|
14826
14859
|
function startupModelLine(m) {
|
|
14827
14860
|
const origin = m.resolved ? `from ${m.source}` : `from ${m.source}; database model settings unreachable, a pickup may resolve differently`;
|
|
14828
14861
|
return `${implementModelsLine(m)} (${origin})`;
|
|
@@ -14847,7 +14880,8 @@ var exports_startup_banner = {};
|
|
|
14847
14880
|
__export(exports_startup_banner, {
|
|
14848
14881
|
limitsLine: () => limitsLine,
|
|
14849
14882
|
createStartupBanner: () => createStartupBanner,
|
|
14850
|
-
contextSourceLine: () => contextSourceLine
|
|
14883
|
+
contextSourceLine: () => contextSourceLine,
|
|
14884
|
+
budgetLine: () => budgetLine
|
|
14851
14885
|
});
|
|
14852
14886
|
import { isPretty, log as log46 } from "@gethmy/harness";
|
|
14853
14887
|
function createStartupBanner(config, version) {
|
|
@@ -14877,6 +14911,9 @@ function prettyBanner(config, version) {
|
|
|
14877
14911
|
check(message) {
|
|
14878
14912
|
checks.push({ kind: "ok", message });
|
|
14879
14913
|
},
|
|
14914
|
+
notice(message) {
|
|
14915
|
+
checks.push({ kind: "notice", message });
|
|
14916
|
+
},
|
|
14880
14917
|
warn(message) {
|
|
14881
14918
|
log46.warn(TAG42, message);
|
|
14882
14919
|
checks.push({ kind: "warn", message: message.split(`
|
|
@@ -14928,6 +14965,9 @@ function jsonBanner(config, version) {
|
|
|
14928
14965
|
check(message) {
|
|
14929
14966
|
log46.info(TAG42, message);
|
|
14930
14967
|
},
|
|
14968
|
+
notice(message) {
|
|
14969
|
+
log46.info(TAG42, message);
|
|
14970
|
+
},
|
|
14931
14971
|
warn(message) {
|
|
14932
14972
|
log46.warn(TAG42, message);
|
|
14933
14973
|
},
|
|
@@ -14954,24 +14994,119 @@ function renderPretty(input) {
|
|
|
14954
14994
|
checks,
|
|
14955
14995
|
readyMessage
|
|
14956
14996
|
} = input;
|
|
14957
|
-
const
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14997
|
+
const detailed = isVerbose();
|
|
14998
|
+
const columns = process.stderr.columns ?? 80;
|
|
14999
|
+
const width = detailed ? Number.POSITIVE_INFINITY : columns;
|
|
15000
|
+
const rule = dim("─".repeat(Math.min(RULE_WIDTH, columns)));
|
|
15001
|
+
const row = (label, ...value) => clip([[dim, label.padEnd(9)], ...value], width);
|
|
15002
|
+
const reviewEnabled = config.agent.review.enabled;
|
|
15003
|
+
const reviewPips = reviewEnabled ? [
|
|
15004
|
+
[dim, " "],
|
|
15005
|
+
[magenta, pips(config.agent.review.poolSize)],
|
|
15006
|
+
[dim, " review"]
|
|
15007
|
+
] : [];
|
|
15008
|
+
const modelNote = !model ? " (local config)" : model.resolved ? "" : " (database unreachable)";
|
|
15009
|
+
const pickup = reviewEnabled ? `${config.agent.pickupColumns[0]} → ${config.agent.completion.moveToColumn} → ${config.agent.review.moveToColumn}` : config.agent.pickupColumns.join(", ");
|
|
15010
|
+
const lines = [""];
|
|
15011
|
+
lines.push(clip([
|
|
15012
|
+
[magenta, "◆ "],
|
|
15013
|
+
[bold, "Harmony Agent "],
|
|
15014
|
+
[dim, `v${version} · `],
|
|
15015
|
+
[plain, projectName ?? shortenId(config.projectId)],
|
|
15016
|
+
[dim, ` · ${config.userEmail}`]
|
|
15017
|
+
], width));
|
|
15018
|
+
lines.push(rule);
|
|
15019
|
+
lines.push(row("Pickup", [plain, pickup]));
|
|
15020
|
+
lines.push(row("Pool", [magenta, pips(config.agent.poolSize)], [dim, " implement"], ...reviewPips));
|
|
15021
|
+
lines.push(row("Models", [plain, compactModelsLine(model ?? config.agent.claude)], [dim, modelNote]));
|
|
15022
|
+
lines.push(row("Budget", [plain, budgetLine(config)]));
|
|
15023
|
+
lines.push(rule);
|
|
15024
|
+
const warnings = checks.filter((c) => c.kind === "warn");
|
|
15025
|
+
const passed = checks.filter((c) => c.kind === "ok").length;
|
|
15026
|
+
const unreadablePin = config.contextSource.unreadablePin;
|
|
15027
|
+
if (unreadablePin !== null) {
|
|
15028
|
+
warnings.push({
|
|
15029
|
+
kind: "warn",
|
|
15030
|
+
message: `${unreadablePin} is present but unreadable, so its pin was ignored`
|
|
15031
|
+
});
|
|
14963
15032
|
}
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
15033
|
+
const counted = passed + warnings.length;
|
|
15034
|
+
if (detailed) {
|
|
15035
|
+
for (const r of configRows(config, projectName, gitProvider, httpPort, model)) {
|
|
15036
|
+
lines.push(`${dim(r.label.padEnd(9))} ${r.value}`);
|
|
15037
|
+
}
|
|
15038
|
+
lines.push("");
|
|
15039
|
+
for (const c of checks) {
|
|
15040
|
+
if (c.kind === "warn")
|
|
15041
|
+
lines.push(`${yellow("!")} ${c.message}`);
|
|
15042
|
+
else if (c.kind === "notice")
|
|
15043
|
+
lines.push(`› ${c.message}`);
|
|
15044
|
+
else
|
|
15045
|
+
lines.push(`${cyan("✓")} ${c.message}`);
|
|
15046
|
+
}
|
|
15047
|
+
lines.push(rule);
|
|
15048
|
+
} else {
|
|
15049
|
+
for (const c of checks) {
|
|
15050
|
+
if (c.kind === "notice")
|
|
15051
|
+
lines.push(clip([[plain, `› ${c.message}`]], width));
|
|
15052
|
+
}
|
|
15053
|
+
}
|
|
15054
|
+
const verdict = warnings.length > 0 ? [
|
|
15055
|
+
[yellow, "! "],
|
|
15056
|
+
[plain, `${passed}/${counted} checks`]
|
|
15057
|
+
] : [
|
|
15058
|
+
[cyan, "✓ "],
|
|
15059
|
+
[plain, `${passed}/${counted} checks`]
|
|
15060
|
+
];
|
|
15061
|
+
verdict.push([dim, " "], [cyan, "● "], [bold, "ready"]);
|
|
15062
|
+
if (detailed)
|
|
15063
|
+
verdict.push([dim, ` — ${readyMessage}`]);
|
|
15064
|
+
if (config.agent.http.enabled) {
|
|
15065
|
+
const url = daemonBaseUrl(config.agent.http.bindAddr, httpPort ?? config.agent.http.port);
|
|
15066
|
+
verdict.push([dim, " "], [cyan, url]);
|
|
15067
|
+
}
|
|
15068
|
+
lines.push(clip(verdict, width));
|
|
15069
|
+
if (!detailed) {
|
|
15070
|
+
for (const w of warnings) {
|
|
15071
|
+
lines.push(clip([
|
|
15072
|
+
[yellow, "! "],
|
|
15073
|
+
[plain, w.message]
|
|
15074
|
+
], width));
|
|
15075
|
+
}
|
|
14967
15076
|
}
|
|
14968
|
-
lines.push("");
|
|
14969
|
-
lines.push(`${cyan("▶")} ${cyan("Ready")} — ${readyMessage}`);
|
|
14970
|
-
lines.push(dim("─".repeat(RULE_WIDTH)));
|
|
14971
15077
|
lines.push("");
|
|
14972
15078
|
return lines.join(`
|
|
14973
15079
|
`);
|
|
14974
15080
|
}
|
|
15081
|
+
function clip(segs, width) {
|
|
15082
|
+
let room = width;
|
|
15083
|
+
let out = "";
|
|
15084
|
+
for (const [style, text] of segs) {
|
|
15085
|
+
if (room <= 0)
|
|
15086
|
+
break;
|
|
15087
|
+
const chars = [...text];
|
|
15088
|
+
if (chars.length <= room) {
|
|
15089
|
+
out += style(text);
|
|
15090
|
+
room -= chars.length;
|
|
15091
|
+
} else {
|
|
15092
|
+
out += style(`${chars.slice(0, room - 1).join("")}…`);
|
|
15093
|
+
room = 0;
|
|
15094
|
+
}
|
|
15095
|
+
}
|
|
15096
|
+
return out;
|
|
15097
|
+
}
|
|
15098
|
+
function pips(count) {
|
|
15099
|
+
return count <= POOL_PIPS_MAX ? "▰".repeat(count) : `▰×${count}`;
|
|
15100
|
+
}
|
|
15101
|
+
function budgetLine(config) {
|
|
15102
|
+
const { budget } = config.agent;
|
|
15103
|
+
const cap = (value, unit) => value < 0 ? `unlimited ${unit}` : `${value} ${unit}`;
|
|
15104
|
+
const daily = budget.dailyBudgetCents < 0 ? "unlimited/day" : `$${(budget.dailyBudgetCents / 100).toFixed(2).replace(/\.00$/, "")}/day`;
|
|
15105
|
+
return `${daily} · ${cap(budget.maxTurnsPerCard, "turns")} · ${budget.maxAttemptsPerCard} attempts per card`;
|
|
15106
|
+
}
|
|
15107
|
+
function isVerbose() {
|
|
15108
|
+
return process.env.HARMONY_AGENT_VERBOSE === "1" || process.argv.includes("--verbose");
|
|
15109
|
+
}
|
|
14975
15110
|
function configRows(config, projectName, gitProvider, httpPort, model) {
|
|
14976
15111
|
const rows = [];
|
|
14977
15112
|
const projectLabel = projectName ? `${projectName} (${shortenId(config.projectId)})` : shortenId(config.projectId);
|
|
@@ -15034,12 +15169,6 @@ function limitsLine(config) {
|
|
|
15034
15169
|
parts.push(`failed branches ${worktree.failedAttemptRetentionDays > 0 ? `${worktree.failedAttemptRetentionDays}d` : "kept"} (worktree.failedAttemptRetentionDays)`);
|
|
15035
15170
|
return parts.join(" · ");
|
|
15036
15171
|
}
|
|
15037
|
-
function titleRule(title) {
|
|
15038
|
-
const prefix = "─── ";
|
|
15039
|
-
const surround = ` `;
|
|
15040
|
-
const suffix = "─".repeat(Math.max(3, RULE_WIDTH - prefix.length - title.length - surround.length));
|
|
15041
|
-
return dim(`${prefix}${title}${surround}${suffix}`);
|
|
15042
|
-
}
|
|
15043
15172
|
function localModelLine(config) {
|
|
15044
15173
|
return `${implementModelsLine(config.agent.claude)} (local config only; database model settings not read)`;
|
|
15045
15174
|
}
|
|
@@ -15048,26 +15177,27 @@ function shortenId(id) {
|
|
|
15048
15177
|
return id;
|
|
15049
15178
|
return `${id.slice(0, 8)}…`;
|
|
15050
15179
|
}
|
|
15051
|
-
function
|
|
15052
|
-
return
|
|
15053
|
-
}
|
|
15054
|
-
function cyan(s) {
|
|
15055
|
-
return `${ANSI.cyan}${s}${ANSI.reset}`;
|
|
15056
|
-
}
|
|
15057
|
-
function yellow(s) {
|
|
15058
|
-
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
15180
|
+
function colorEnabled() {
|
|
15181
|
+
return !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
15059
15182
|
}
|
|
15060
|
-
var TAG42 = "daemon", RULE_WIDTH =
|
|
15183
|
+
var TAG42 = "daemon", RULE_WIDTH = 62, POOL_PIPS_MAX = 12, ANSI, paint = (code) => (s) => colorEnabled() ? `${code}${s}${ANSI.reset}` : s, plain = (s) => s, bold, dim, cyan, magenta, yellow;
|
|
15061
15184
|
var init_startup_banner = __esm(() => {
|
|
15062
15185
|
init_port_registry();
|
|
15063
15186
|
init_runner_registry();
|
|
15064
15187
|
init_startup_model();
|
|
15065
15188
|
ANSI = {
|
|
15066
15189
|
reset: "\x1B[0m",
|
|
15190
|
+
bold: "\x1B[1m",
|
|
15067
15191
|
dim: "\x1B[2m",
|
|
15068
|
-
|
|
15069
|
-
|
|
15192
|
+
yellow: "\x1B[33m",
|
|
15193
|
+
magenta: "\x1B[35m",
|
|
15194
|
+
cyan: "\x1B[36m"
|
|
15070
15195
|
};
|
|
15196
|
+
bold = paint(ANSI.bold);
|
|
15197
|
+
dim = paint(ANSI.dim);
|
|
15198
|
+
cyan = paint(ANSI.cyan);
|
|
15199
|
+
magenta = paint(ANSI.magenta);
|
|
15200
|
+
yellow = paint(ANSI.yellow);
|
|
15071
15201
|
});
|
|
15072
15202
|
|
|
15073
15203
|
// src/state-migration.ts
|
|
@@ -15971,7 +16101,7 @@ async function runRetentionGc(store, opts) {
|
|
|
15971
16101
|
logs = pruneRunLogs(opts.runsDirectory ?? runsDir(), now - opts.runLogDays * DAY_MS2, openRunIds);
|
|
15972
16102
|
}
|
|
15973
16103
|
if (runRecordsPurged > 0 || logs.deleted > 0) {
|
|
15974
|
-
log49.
|
|
16104
|
+
log49.debug(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
|
|
15975
16105
|
}
|
|
15976
16106
|
return {
|
|
15977
16107
|
runRecordsPurged,
|
|
@@ -16436,7 +16566,7 @@ async function main() {
|
|
|
16436
16566
|
throw err;
|
|
16437
16567
|
}
|
|
16438
16568
|
if (config.agent.sweep.enabled) {
|
|
16439
|
-
banner.
|
|
16569
|
+
banner.notice(sweepBannerLine(config.agent));
|
|
16440
16570
|
}
|
|
16441
16571
|
const { agentId, repository, folderPath } = await resolveAgentIdentity(client, config);
|
|
16442
16572
|
banner.check(`Agent registered (${config.agentName}${repository ? ` · ${repository.name}` : ""})`);
|
|
@@ -16472,7 +16602,7 @@ async function main() {
|
|
|
16472
16602
|
banner.check("Recovery: no orphans");
|
|
16473
16603
|
} else {
|
|
16474
16604
|
const errored = outcomes.filter((o) => o.errors.length).length;
|
|
16475
|
-
banner.
|
|
16605
|
+
banner.notice(`Recovery: ${outcomes.length} orphan(s) handled${errored > 0 ? `, ${errored} with errors` : ""}`);
|
|
16476
16606
|
}
|
|
16477
16607
|
const sweepGuard = new SweepGuard(config.agent.sweep, stateStore, () => new BudgetGuard(config.agent.budget, stateStore).checkDailyBudget());
|
|
16478
16608
|
if (config.agent.sweep.enabled) {
|
|
@@ -16683,7 +16813,7 @@ async function main() {
|
|
|
16683
16813
|
if (winner === "ready") {
|
|
16684
16814
|
banner.check("Realtime: broadcast + presence");
|
|
16685
16815
|
} else {
|
|
16686
|
-
banner.
|
|
16816
|
+
banner.notice("Realtime: connecting…");
|
|
16687
16817
|
}
|
|
16688
16818
|
await banner.ready("watching for card assignments");
|
|
16689
16819
|
watcher.allowStartupLogs();
|
|
@@ -17520,6 +17650,9 @@ Flags:
|
|
|
17520
17650
|
(also: HARMONY_AGENT_PRETTY=1)
|
|
17521
17651
|
--json Force structured JSON log output
|
|
17522
17652
|
(also: HARMONY_AGENT_JSON=1)
|
|
17653
|
+
--verbose Print config sources, limits and every
|
|
17654
|
+
startup check in the startup banner
|
|
17655
|
+
(also: HARMONY_AGENT_VERBOSE=1)
|
|
17523
17656
|
|
|
17524
17657
|
By default logs are pretty when stderr is a TTY and JSON when piped.
|
|
17525
17658
|
`.trim();
|
|
@@ -18246,7 +18379,7 @@ what verification rejected (recent):
|
|
|
18246
18379
|
return 0;
|
|
18247
18380
|
}
|
|
18248
18381
|
async function dispatch(argv) {
|
|
18249
|
-
const args = argv.filter((a) => a !== "--pretty" && a !== "--json");
|
|
18382
|
+
const args = argv.filter((a) => a !== "--pretty" && a !== "--json" && a !== "--verbose");
|
|
18250
18383
|
const cmd = args[0];
|
|
18251
18384
|
switch (cmd) {
|
|
18252
18385
|
case undefined:
|
package/dist/index.js
CHANGED
|
@@ -1297,8 +1297,23 @@ import { basename } from "node:path";
|
|
|
1297
1297
|
import { log as log3 } from "@gethmy/harness";
|
|
1298
1298
|
import {
|
|
1299
1299
|
findMappedFolder,
|
|
1300
|
+
registerProjectFolder,
|
|
1300
1301
|
setMappedFolderRepositoryId
|
|
1301
1302
|
} from "@gethmy/mcp/src/config.js";
|
|
1303
|
+
function mapOwnFolder(config) {
|
|
1304
|
+
try {
|
|
1305
|
+
registerProjectFolder({
|
|
1306
|
+
projectId: config.projectId,
|
|
1307
|
+
workspaceId: config.workspaceId,
|
|
1308
|
+
path: config.repoRoot
|
|
1309
|
+
});
|
|
1310
|
+
} catch (err) {
|
|
1311
|
+
log3.warn(TAG3, `Could not map ${config.repoRoot} to its project, running without a repository: ${err instanceof Error ? err.message : String(err)}`);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
log3.info(TAG3, `Mapped ${config.repoRoot} to its project`);
|
|
1315
|
+
return findMappedFolder(config.repoRoot);
|
|
1316
|
+
}
|
|
1302
1317
|
async function resolveAgentIdentity(client, config) {
|
|
1303
1318
|
const { agent: legacy } = await client.registerWorkspaceAgent(config.workspaceId, {
|
|
1304
1319
|
identifier: config.agentIdentifier,
|
|
@@ -1310,7 +1325,7 @@ async function resolveAgentIdentity(client, config) {
|
|
|
1310
1325
|
repository: null,
|
|
1311
1326
|
folderPath: null
|
|
1312
1327
|
};
|
|
1313
|
-
const folder = findMappedFolder(config.repoRoot);
|
|
1328
|
+
const folder = findMappedFolder(config.repoRoot) ?? mapOwnFolder(config);
|
|
1314
1329
|
if (!folder || folder.projectId !== config.projectId)
|
|
1315
1330
|
return legacyIdentity;
|
|
1316
1331
|
let registered;
|
|
@@ -13088,6 +13103,15 @@ Reassign the card — that clears the run count and starts it fresh — or raise
|
|
|
13088
13103
|
} catch (err) {
|
|
13089
13104
|
log36.warn(TAG32, `Failed to clear the attempt-cap decision deadline for ${cardId}: ${err}`);
|
|
13090
13105
|
}
|
|
13106
|
+
try {
|
|
13107
|
+
await this.client.endAgentSession(cardId, {
|
|
13108
|
+
status: "failed",
|
|
13109
|
+
failureReason: "budget",
|
|
13110
|
+
failureSummary: "Attempt budget granted by a human decision — a fresh run takes the card."
|
|
13111
|
+
});
|
|
13112
|
+
} catch (err) {
|
|
13113
|
+
log36.warn(TAG32, `Failed to end the attempt-cap session for ${cardId}: ${err}`);
|
|
13114
|
+
}
|
|
13091
13115
|
await this.enqueueCard(cardId, "implement");
|
|
13092
13116
|
}
|
|
13093
13117
|
async stopAttemptCap(cardId, failureSummary = "Stopped by a human decision on the attempt budget.") {
|
|
@@ -14822,6 +14846,15 @@ function ownString(source, key) {
|
|
|
14822
14846
|
const value = own(source, key);
|
|
14823
14847
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
14824
14848
|
}
|
|
14849
|
+
function compactModelsLine(m) {
|
|
14850
|
+
const short = (id) => id.replace(/^claude-/, "");
|
|
14851
|
+
if (m.sizingModel) {
|
|
14852
|
+
const ladder = TIERS.map((tier) => short(m.tiers[tier])).join(" · ");
|
|
14853
|
+
const rule = m.researchRule === "armed" && isJevSizingModel(m.sizingModel) ? " · research rule armed" : "";
|
|
14854
|
+
return `${ladder} sized by ${m.sizingModel}${rule}`;
|
|
14855
|
+
}
|
|
14856
|
+
return m.model === m.escalateModel ? short(m.model) : `${short(m.model)} → ${short(m.escalateModel)} on retry`;
|
|
14857
|
+
}
|
|
14825
14858
|
function startupModelLine(m) {
|
|
14826
14859
|
const origin = m.resolved ? `from ${m.source}` : `from ${m.source}; database model settings unreachable, a pickup may resolve differently`;
|
|
14827
14860
|
return `${implementModelsLine(m)} (${origin})`;
|
|
@@ -14846,7 +14879,8 @@ var exports_startup_banner = {};
|
|
|
14846
14879
|
__export(exports_startup_banner, {
|
|
14847
14880
|
limitsLine: () => limitsLine,
|
|
14848
14881
|
createStartupBanner: () => createStartupBanner,
|
|
14849
|
-
contextSourceLine: () => contextSourceLine
|
|
14882
|
+
contextSourceLine: () => contextSourceLine,
|
|
14883
|
+
budgetLine: () => budgetLine
|
|
14850
14884
|
});
|
|
14851
14885
|
import { isPretty, log as log46 } from "@gethmy/harness";
|
|
14852
14886
|
function createStartupBanner(config, version) {
|
|
@@ -14876,6 +14910,9 @@ function prettyBanner(config, version) {
|
|
|
14876
14910
|
check(message) {
|
|
14877
14911
|
checks.push({ kind: "ok", message });
|
|
14878
14912
|
},
|
|
14913
|
+
notice(message) {
|
|
14914
|
+
checks.push({ kind: "notice", message });
|
|
14915
|
+
},
|
|
14879
14916
|
warn(message) {
|
|
14880
14917
|
log46.warn(TAG42, message);
|
|
14881
14918
|
checks.push({ kind: "warn", message: message.split(`
|
|
@@ -14927,6 +14964,9 @@ function jsonBanner(config, version) {
|
|
|
14927
14964
|
check(message) {
|
|
14928
14965
|
log46.info(TAG42, message);
|
|
14929
14966
|
},
|
|
14967
|
+
notice(message) {
|
|
14968
|
+
log46.info(TAG42, message);
|
|
14969
|
+
},
|
|
14930
14970
|
warn(message) {
|
|
14931
14971
|
log46.warn(TAG42, message);
|
|
14932
14972
|
},
|
|
@@ -14953,24 +14993,119 @@ function renderPretty(input) {
|
|
|
14953
14993
|
checks,
|
|
14954
14994
|
readyMessage
|
|
14955
14995
|
} = input;
|
|
14956
|
-
const
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14996
|
+
const detailed = isVerbose();
|
|
14997
|
+
const columns = process.stderr.columns ?? 80;
|
|
14998
|
+
const width = detailed ? Number.POSITIVE_INFINITY : columns;
|
|
14999
|
+
const rule = dim("─".repeat(Math.min(RULE_WIDTH, columns)));
|
|
15000
|
+
const row = (label, ...value) => clip([[dim, label.padEnd(9)], ...value], width);
|
|
15001
|
+
const reviewEnabled = config.agent.review.enabled;
|
|
15002
|
+
const reviewPips = reviewEnabled ? [
|
|
15003
|
+
[dim, " "],
|
|
15004
|
+
[magenta, pips(config.agent.review.poolSize)],
|
|
15005
|
+
[dim, " review"]
|
|
15006
|
+
] : [];
|
|
15007
|
+
const modelNote = !model ? " (local config)" : model.resolved ? "" : " (database unreachable)";
|
|
15008
|
+
const pickup = reviewEnabled ? `${config.agent.pickupColumns[0]} → ${config.agent.completion.moveToColumn} → ${config.agent.review.moveToColumn}` : config.agent.pickupColumns.join(", ");
|
|
15009
|
+
const lines = [""];
|
|
15010
|
+
lines.push(clip([
|
|
15011
|
+
[magenta, "◆ "],
|
|
15012
|
+
[bold, "Harmony Agent "],
|
|
15013
|
+
[dim, `v${version} · `],
|
|
15014
|
+
[plain, projectName ?? shortenId(config.projectId)],
|
|
15015
|
+
[dim, ` · ${config.userEmail}`]
|
|
15016
|
+
], width));
|
|
15017
|
+
lines.push(rule);
|
|
15018
|
+
lines.push(row("Pickup", [plain, pickup]));
|
|
15019
|
+
lines.push(row("Pool", [magenta, pips(config.agent.poolSize)], [dim, " implement"], ...reviewPips));
|
|
15020
|
+
lines.push(row("Models", [plain, compactModelsLine(model ?? config.agent.claude)], [dim, modelNote]));
|
|
15021
|
+
lines.push(row("Budget", [plain, budgetLine(config)]));
|
|
15022
|
+
lines.push(rule);
|
|
15023
|
+
const warnings = checks.filter((c) => c.kind === "warn");
|
|
15024
|
+
const passed = checks.filter((c) => c.kind === "ok").length;
|
|
15025
|
+
const unreadablePin = config.contextSource.unreadablePin;
|
|
15026
|
+
if (unreadablePin !== null) {
|
|
15027
|
+
warnings.push({
|
|
15028
|
+
kind: "warn",
|
|
15029
|
+
message: `${unreadablePin} is present but unreadable, so its pin was ignored`
|
|
15030
|
+
});
|
|
14962
15031
|
}
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
15032
|
+
const counted = passed + warnings.length;
|
|
15033
|
+
if (detailed) {
|
|
15034
|
+
for (const r of configRows(config, projectName, gitProvider, httpPort, model)) {
|
|
15035
|
+
lines.push(`${dim(r.label.padEnd(9))} ${r.value}`);
|
|
15036
|
+
}
|
|
15037
|
+
lines.push("");
|
|
15038
|
+
for (const c of checks) {
|
|
15039
|
+
if (c.kind === "warn")
|
|
15040
|
+
lines.push(`${yellow("!")} ${c.message}`);
|
|
15041
|
+
else if (c.kind === "notice")
|
|
15042
|
+
lines.push(`› ${c.message}`);
|
|
15043
|
+
else
|
|
15044
|
+
lines.push(`${cyan("✓")} ${c.message}`);
|
|
15045
|
+
}
|
|
15046
|
+
lines.push(rule);
|
|
15047
|
+
} else {
|
|
15048
|
+
for (const c of checks) {
|
|
15049
|
+
if (c.kind === "notice")
|
|
15050
|
+
lines.push(clip([[plain, `› ${c.message}`]], width));
|
|
15051
|
+
}
|
|
15052
|
+
}
|
|
15053
|
+
const verdict = warnings.length > 0 ? [
|
|
15054
|
+
[yellow, "! "],
|
|
15055
|
+
[plain, `${passed}/${counted} checks`]
|
|
15056
|
+
] : [
|
|
15057
|
+
[cyan, "✓ "],
|
|
15058
|
+
[plain, `${passed}/${counted} checks`]
|
|
15059
|
+
];
|
|
15060
|
+
verdict.push([dim, " "], [cyan, "● "], [bold, "ready"]);
|
|
15061
|
+
if (detailed)
|
|
15062
|
+
verdict.push([dim, ` — ${readyMessage}`]);
|
|
15063
|
+
if (config.agent.http.enabled) {
|
|
15064
|
+
const url = daemonBaseUrl(config.agent.http.bindAddr, httpPort ?? config.agent.http.port);
|
|
15065
|
+
verdict.push([dim, " "], [cyan, url]);
|
|
15066
|
+
}
|
|
15067
|
+
lines.push(clip(verdict, width));
|
|
15068
|
+
if (!detailed) {
|
|
15069
|
+
for (const w of warnings) {
|
|
15070
|
+
lines.push(clip([
|
|
15071
|
+
[yellow, "! "],
|
|
15072
|
+
[plain, w.message]
|
|
15073
|
+
], width));
|
|
15074
|
+
}
|
|
14966
15075
|
}
|
|
14967
|
-
lines.push("");
|
|
14968
|
-
lines.push(`${cyan("▶")} ${cyan("Ready")} — ${readyMessage}`);
|
|
14969
|
-
lines.push(dim("─".repeat(RULE_WIDTH)));
|
|
14970
15076
|
lines.push("");
|
|
14971
15077
|
return lines.join(`
|
|
14972
15078
|
`);
|
|
14973
15079
|
}
|
|
15080
|
+
function clip(segs, width) {
|
|
15081
|
+
let room = width;
|
|
15082
|
+
let out = "";
|
|
15083
|
+
for (const [style, text] of segs) {
|
|
15084
|
+
if (room <= 0)
|
|
15085
|
+
break;
|
|
15086
|
+
const chars = [...text];
|
|
15087
|
+
if (chars.length <= room) {
|
|
15088
|
+
out += style(text);
|
|
15089
|
+
room -= chars.length;
|
|
15090
|
+
} else {
|
|
15091
|
+
out += style(`${chars.slice(0, room - 1).join("")}…`);
|
|
15092
|
+
room = 0;
|
|
15093
|
+
}
|
|
15094
|
+
}
|
|
15095
|
+
return out;
|
|
15096
|
+
}
|
|
15097
|
+
function pips(count) {
|
|
15098
|
+
return count <= POOL_PIPS_MAX ? "▰".repeat(count) : `▰×${count}`;
|
|
15099
|
+
}
|
|
15100
|
+
function budgetLine(config) {
|
|
15101
|
+
const { budget } = config.agent;
|
|
15102
|
+
const cap = (value, unit) => value < 0 ? `unlimited ${unit}` : `${value} ${unit}`;
|
|
15103
|
+
const daily = budget.dailyBudgetCents < 0 ? "unlimited/day" : `$${(budget.dailyBudgetCents / 100).toFixed(2).replace(/\.00$/, "")}/day`;
|
|
15104
|
+
return `${daily} · ${cap(budget.maxTurnsPerCard, "turns")} · ${budget.maxAttemptsPerCard} attempts per card`;
|
|
15105
|
+
}
|
|
15106
|
+
function isVerbose() {
|
|
15107
|
+
return process.env.HARMONY_AGENT_VERBOSE === "1" || process.argv.includes("--verbose");
|
|
15108
|
+
}
|
|
14974
15109
|
function configRows(config, projectName, gitProvider, httpPort, model) {
|
|
14975
15110
|
const rows = [];
|
|
14976
15111
|
const projectLabel = projectName ? `${projectName} (${shortenId(config.projectId)})` : shortenId(config.projectId);
|
|
@@ -15033,12 +15168,6 @@ function limitsLine(config) {
|
|
|
15033
15168
|
parts.push(`failed branches ${worktree.failedAttemptRetentionDays > 0 ? `${worktree.failedAttemptRetentionDays}d` : "kept"} (worktree.failedAttemptRetentionDays)`);
|
|
15034
15169
|
return parts.join(" · ");
|
|
15035
15170
|
}
|
|
15036
|
-
function titleRule(title) {
|
|
15037
|
-
const prefix = "─── ";
|
|
15038
|
-
const surround = ` `;
|
|
15039
|
-
const suffix = "─".repeat(Math.max(3, RULE_WIDTH - prefix.length - title.length - surround.length));
|
|
15040
|
-
return dim(`${prefix}${title}${surround}${suffix}`);
|
|
15041
|
-
}
|
|
15042
15171
|
function localModelLine(config) {
|
|
15043
15172
|
return `${implementModelsLine(config.agent.claude)} (local config only; database model settings not read)`;
|
|
15044
15173
|
}
|
|
@@ -15047,26 +15176,27 @@ function shortenId(id) {
|
|
|
15047
15176
|
return id;
|
|
15048
15177
|
return `${id.slice(0, 8)}…`;
|
|
15049
15178
|
}
|
|
15050
|
-
function
|
|
15051
|
-
return
|
|
15052
|
-
}
|
|
15053
|
-
function cyan(s) {
|
|
15054
|
-
return `${ANSI.cyan}${s}${ANSI.reset}`;
|
|
15055
|
-
}
|
|
15056
|
-
function yellow(s) {
|
|
15057
|
-
return `${ANSI.yellow}${s}${ANSI.reset}`;
|
|
15179
|
+
function colorEnabled() {
|
|
15180
|
+
return !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
15058
15181
|
}
|
|
15059
|
-
var TAG42 = "daemon", RULE_WIDTH =
|
|
15182
|
+
var TAG42 = "daemon", RULE_WIDTH = 62, POOL_PIPS_MAX = 12, ANSI, paint = (code) => (s) => colorEnabled() ? `${code}${s}${ANSI.reset}` : s, plain = (s) => s, bold, dim, cyan, magenta, yellow;
|
|
15060
15183
|
var init_startup_banner = __esm(() => {
|
|
15061
15184
|
init_port_registry();
|
|
15062
15185
|
init_runner_registry();
|
|
15063
15186
|
init_startup_model();
|
|
15064
15187
|
ANSI = {
|
|
15065
15188
|
reset: "\x1B[0m",
|
|
15189
|
+
bold: "\x1B[1m",
|
|
15066
15190
|
dim: "\x1B[2m",
|
|
15067
|
-
|
|
15068
|
-
|
|
15191
|
+
yellow: "\x1B[33m",
|
|
15192
|
+
magenta: "\x1B[35m",
|
|
15193
|
+
cyan: "\x1B[36m"
|
|
15069
15194
|
};
|
|
15195
|
+
bold = paint(ANSI.bold);
|
|
15196
|
+
dim = paint(ANSI.dim);
|
|
15197
|
+
cyan = paint(ANSI.cyan);
|
|
15198
|
+
magenta = paint(ANSI.magenta);
|
|
15199
|
+
yellow = paint(ANSI.yellow);
|
|
15070
15200
|
});
|
|
15071
15201
|
|
|
15072
15202
|
// src/state-migration.ts
|
|
@@ -15970,7 +16100,7 @@ async function runRetentionGc(store, opts) {
|
|
|
15970
16100
|
logs = pruneRunLogs(opts.runsDirectory ?? runsDir(), now - opts.runLogDays * DAY_MS2, openRunIds);
|
|
15971
16101
|
}
|
|
15972
16102
|
if (runRecordsPurged > 0 || logs.deleted > 0) {
|
|
15973
|
-
log49.
|
|
16103
|
+
log49.debug(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
|
|
15974
16104
|
}
|
|
15975
16105
|
return {
|
|
15976
16106
|
runRecordsPurged,
|
|
@@ -16435,7 +16565,7 @@ async function main() {
|
|
|
16435
16565
|
throw err;
|
|
16436
16566
|
}
|
|
16437
16567
|
if (config.agent.sweep.enabled) {
|
|
16438
|
-
banner.
|
|
16568
|
+
banner.notice(sweepBannerLine(config.agent));
|
|
16439
16569
|
}
|
|
16440
16570
|
const { agentId, repository, folderPath } = await resolveAgentIdentity(client, config);
|
|
16441
16571
|
banner.check(`Agent registered (${config.agentName}${repository ? ` · ${repository.name}` : ""})`);
|
|
@@ -16471,7 +16601,7 @@ async function main() {
|
|
|
16471
16601
|
banner.check("Recovery: no orphans");
|
|
16472
16602
|
} else {
|
|
16473
16603
|
const errored = outcomes.filter((o) => o.errors.length).length;
|
|
16474
|
-
banner.
|
|
16604
|
+
banner.notice(`Recovery: ${outcomes.length} orphan(s) handled${errored > 0 ? `, ${errored} with errors` : ""}`);
|
|
16475
16605
|
}
|
|
16476
16606
|
const sweepGuard = new SweepGuard(config.agent.sweep, stateStore, () => new BudgetGuard(config.agent.budget, stateStore).checkDailyBudget());
|
|
16477
16607
|
if (config.agent.sweep.enabled) {
|
|
@@ -16682,7 +16812,7 @@ async function main() {
|
|
|
16682
16812
|
if (winner === "ready") {
|
|
16683
16813
|
banner.check("Realtime: broadcast + presence");
|
|
16684
16814
|
} else {
|
|
16685
|
-
banner.
|
|
16815
|
+
banner.notice("Realtime: connecting…");
|
|
16686
16816
|
}
|
|
16687
16817
|
await banner.ready("watching for card assignments");
|
|
16688
16818
|
watcher.allowStartupLogs();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gethmy/agent",
|
|
3
|
-
"version": "1.49.
|
|
3
|
+
"version": "1.49.10",
|
|
4
4
|
"description": "Push-based agent daemon for Harmony — picks up assigned cards, builds them in isolated git worktrees, and opens PRs. It runs where you put it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|