@gethmy/agent 1.49.7 → 1.49.9
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 +160 -36
- package/dist/index.js +156 -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;
|
|
@@ -14823,6 +14838,15 @@ function ownString(source, key) {
|
|
|
14823
14838
|
const value = own(source, key);
|
|
14824
14839
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
14825
14840
|
}
|
|
14841
|
+
function compactModelsLine(m) {
|
|
14842
|
+
const short = (id) => id.replace(/^claude-/, "");
|
|
14843
|
+
if (m.sizingModel) {
|
|
14844
|
+
const ladder = TIERS.map((tier) => short(m.tiers[tier])).join(" · ");
|
|
14845
|
+
const rule = m.researchRule === "armed" && isJevSizingModel(m.sizingModel) ? " · research rule armed" : "";
|
|
14846
|
+
return `${ladder} sized by ${m.sizingModel}${rule}`;
|
|
14847
|
+
}
|
|
14848
|
+
return m.model === m.escalateModel ? short(m.model) : `${short(m.model)} → ${short(m.escalateModel)} on retry`;
|
|
14849
|
+
}
|
|
14826
14850
|
function startupModelLine(m) {
|
|
14827
14851
|
const origin = m.resolved ? `from ${m.source}` : `from ${m.source}; database model settings unreachable, a pickup may resolve differently`;
|
|
14828
14852
|
return `${implementModelsLine(m)} (${origin})`;
|
|
@@ -14847,7 +14871,8 @@ var exports_startup_banner = {};
|
|
|
14847
14871
|
__export(exports_startup_banner, {
|
|
14848
14872
|
limitsLine: () => limitsLine,
|
|
14849
14873
|
createStartupBanner: () => createStartupBanner,
|
|
14850
|
-
contextSourceLine: () => contextSourceLine
|
|
14874
|
+
contextSourceLine: () => contextSourceLine,
|
|
14875
|
+
budgetLine: () => budgetLine
|
|
14851
14876
|
});
|
|
14852
14877
|
import { isPretty, log as log46 } from "@gethmy/harness";
|
|
14853
14878
|
function createStartupBanner(config, version) {
|
|
@@ -14877,6 +14902,9 @@ function prettyBanner(config, version) {
|
|
|
14877
14902
|
check(message) {
|
|
14878
14903
|
checks.push({ kind: "ok", message });
|
|
14879
14904
|
},
|
|
14905
|
+
notice(message) {
|
|
14906
|
+
checks.push({ kind: "notice", message });
|
|
14907
|
+
},
|
|
14880
14908
|
warn(message) {
|
|
14881
14909
|
log46.warn(TAG42, message);
|
|
14882
14910
|
checks.push({ kind: "warn", message: message.split(`
|
|
@@ -14928,6 +14956,9 @@ function jsonBanner(config, version) {
|
|
|
14928
14956
|
check(message) {
|
|
14929
14957
|
log46.info(TAG42, message);
|
|
14930
14958
|
},
|
|
14959
|
+
notice(message) {
|
|
14960
|
+
log46.info(TAG42, message);
|
|
14961
|
+
},
|
|
14931
14962
|
warn(message) {
|
|
14932
14963
|
log46.warn(TAG42, message);
|
|
14933
14964
|
},
|
|
@@ -14954,24 +14985,119 @@ function renderPretty(input) {
|
|
|
14954
14985
|
checks,
|
|
14955
14986
|
readyMessage
|
|
14956
14987
|
} = input;
|
|
14957
|
-
const
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14988
|
+
const detailed = isVerbose();
|
|
14989
|
+
const columns = process.stderr.columns ?? 80;
|
|
14990
|
+
const width = detailed ? Number.POSITIVE_INFINITY : columns;
|
|
14991
|
+
const rule = dim("─".repeat(Math.min(RULE_WIDTH, columns)));
|
|
14992
|
+
const row = (label, ...value) => clip([[dim, label.padEnd(9)], ...value], width);
|
|
14993
|
+
const reviewEnabled = config.agent.review.enabled;
|
|
14994
|
+
const reviewPips = reviewEnabled ? [
|
|
14995
|
+
[dim, " "],
|
|
14996
|
+
[magenta, pips(config.agent.review.poolSize)],
|
|
14997
|
+
[dim, " review"]
|
|
14998
|
+
] : [];
|
|
14999
|
+
const modelNote = !model ? " (local config)" : model.resolved ? "" : " (database unreachable)";
|
|
15000
|
+
const pickup = reviewEnabled ? `${config.agent.pickupColumns[0]} → ${config.agent.completion.moveToColumn} → ${config.agent.review.moveToColumn}` : config.agent.pickupColumns.join(", ");
|
|
15001
|
+
const lines = [""];
|
|
15002
|
+
lines.push(clip([
|
|
15003
|
+
[magenta, "◆ "],
|
|
15004
|
+
[bold, "Harmony Agent "],
|
|
15005
|
+
[dim, `v${version} · `],
|
|
15006
|
+
[plain, projectName ?? shortenId(config.projectId)],
|
|
15007
|
+
[dim, ` · ${config.userEmail}`]
|
|
15008
|
+
], width));
|
|
15009
|
+
lines.push(rule);
|
|
15010
|
+
lines.push(row("Pickup", [plain, pickup]));
|
|
15011
|
+
lines.push(row("Pool", [magenta, pips(config.agent.poolSize)], [dim, " implement"], ...reviewPips));
|
|
15012
|
+
lines.push(row("Models", [plain, compactModelsLine(model ?? config.agent.claude)], [dim, modelNote]));
|
|
15013
|
+
lines.push(row("Budget", [plain, budgetLine(config)]));
|
|
15014
|
+
lines.push(rule);
|
|
15015
|
+
const warnings = checks.filter((c) => c.kind === "warn");
|
|
15016
|
+
const passed = checks.filter((c) => c.kind === "ok").length;
|
|
15017
|
+
const unreadablePin = config.contextSource.unreadablePin;
|
|
15018
|
+
if (unreadablePin !== null) {
|
|
15019
|
+
warnings.push({
|
|
15020
|
+
kind: "warn",
|
|
15021
|
+
message: `${unreadablePin} is present but unreadable, so its pin was ignored`
|
|
15022
|
+
});
|
|
14963
15023
|
}
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
15024
|
+
const counted = passed + warnings.length;
|
|
15025
|
+
if (detailed) {
|
|
15026
|
+
for (const r of configRows(config, projectName, gitProvider, httpPort, model)) {
|
|
15027
|
+
lines.push(`${dim(r.label.padEnd(9))} ${r.value}`);
|
|
15028
|
+
}
|
|
15029
|
+
lines.push("");
|
|
15030
|
+
for (const c of checks) {
|
|
15031
|
+
if (c.kind === "warn")
|
|
15032
|
+
lines.push(`${yellow("!")} ${c.message}`);
|
|
15033
|
+
else if (c.kind === "notice")
|
|
15034
|
+
lines.push(`› ${c.message}`);
|
|
15035
|
+
else
|
|
15036
|
+
lines.push(`${cyan("✓")} ${c.message}`);
|
|
15037
|
+
}
|
|
15038
|
+
lines.push(rule);
|
|
15039
|
+
} else {
|
|
15040
|
+
for (const c of checks) {
|
|
15041
|
+
if (c.kind === "notice")
|
|
15042
|
+
lines.push(clip([[plain, `› ${c.message}`]], width));
|
|
15043
|
+
}
|
|
15044
|
+
}
|
|
15045
|
+
const verdict = warnings.length > 0 ? [
|
|
15046
|
+
[yellow, "! "],
|
|
15047
|
+
[plain, `${passed}/${counted} checks`]
|
|
15048
|
+
] : [
|
|
15049
|
+
[cyan, "✓ "],
|
|
15050
|
+
[plain, `${passed}/${counted} checks`]
|
|
15051
|
+
];
|
|
15052
|
+
verdict.push([dim, " "], [cyan, "● "], [bold, "ready"]);
|
|
15053
|
+
if (detailed)
|
|
15054
|
+
verdict.push([dim, ` — ${readyMessage}`]);
|
|
15055
|
+
if (config.agent.http.enabled) {
|
|
15056
|
+
const url = daemonBaseUrl(config.agent.http.bindAddr, httpPort ?? config.agent.http.port);
|
|
15057
|
+
verdict.push([dim, " "], [cyan, url]);
|
|
15058
|
+
}
|
|
15059
|
+
lines.push(clip(verdict, width));
|
|
15060
|
+
if (!detailed) {
|
|
15061
|
+
for (const w of warnings) {
|
|
15062
|
+
lines.push(clip([
|
|
15063
|
+
[yellow, "! "],
|
|
15064
|
+
[plain, w.message]
|
|
15065
|
+
], width));
|
|
15066
|
+
}
|
|
14967
15067
|
}
|
|
14968
|
-
lines.push("");
|
|
14969
|
-
lines.push(`${cyan("▶")} ${cyan("Ready")} — ${readyMessage}`);
|
|
14970
|
-
lines.push(dim("─".repeat(RULE_WIDTH)));
|
|
14971
15068
|
lines.push("");
|
|
14972
15069
|
return lines.join(`
|
|
14973
15070
|
`);
|
|
14974
15071
|
}
|
|
15072
|
+
function clip(segs, width) {
|
|
15073
|
+
let room = width;
|
|
15074
|
+
let out = "";
|
|
15075
|
+
for (const [style, text] of segs) {
|
|
15076
|
+
if (room <= 0)
|
|
15077
|
+
break;
|
|
15078
|
+
const chars = [...text];
|
|
15079
|
+
if (chars.length <= room) {
|
|
15080
|
+
out += style(text);
|
|
15081
|
+
room -= chars.length;
|
|
15082
|
+
} else {
|
|
15083
|
+
out += style(`${chars.slice(0, room - 1).join("")}…`);
|
|
15084
|
+
room = 0;
|
|
15085
|
+
}
|
|
15086
|
+
}
|
|
15087
|
+
return out;
|
|
15088
|
+
}
|
|
15089
|
+
function pips(count) {
|
|
15090
|
+
return count <= POOL_PIPS_MAX ? "▰".repeat(count) : `▰×${count}`;
|
|
15091
|
+
}
|
|
15092
|
+
function budgetLine(config) {
|
|
15093
|
+
const { budget } = config.agent;
|
|
15094
|
+
const cap = (value, unit) => value < 0 ? `unlimited ${unit}` : `${value} ${unit}`;
|
|
15095
|
+
const daily = budget.dailyBudgetCents < 0 ? "unlimited/day" : `$${(budget.dailyBudgetCents / 100).toFixed(2).replace(/\.00$/, "")}/day`;
|
|
15096
|
+
return `${daily} · ${cap(budget.maxTurnsPerCard, "turns")} · ${budget.maxAttemptsPerCard} attempts per card`;
|
|
15097
|
+
}
|
|
15098
|
+
function isVerbose() {
|
|
15099
|
+
return process.env.HARMONY_AGENT_VERBOSE === "1" || process.argv.includes("--verbose");
|
|
15100
|
+
}
|
|
14975
15101
|
function configRows(config, projectName, gitProvider, httpPort, model) {
|
|
14976
15102
|
const rows = [];
|
|
14977
15103
|
const projectLabel = projectName ? `${projectName} (${shortenId(config.projectId)})` : shortenId(config.projectId);
|
|
@@ -15034,12 +15160,6 @@ function limitsLine(config) {
|
|
|
15034
15160
|
parts.push(`failed branches ${worktree.failedAttemptRetentionDays > 0 ? `${worktree.failedAttemptRetentionDays}d` : "kept"} (worktree.failedAttemptRetentionDays)`);
|
|
15035
15161
|
return parts.join(" · ");
|
|
15036
15162
|
}
|
|
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
15163
|
function localModelLine(config) {
|
|
15044
15164
|
return `${implementModelsLine(config.agent.claude)} (local config only; database model settings not read)`;
|
|
15045
15165
|
}
|
|
@@ -15048,26 +15168,27 @@ function shortenId(id) {
|
|
|
15048
15168
|
return id;
|
|
15049
15169
|
return `${id.slice(0, 8)}…`;
|
|
15050
15170
|
}
|
|
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}`;
|
|
15171
|
+
function colorEnabled() {
|
|
15172
|
+
return !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
15059
15173
|
}
|
|
15060
|
-
var TAG42 = "daemon", RULE_WIDTH =
|
|
15174
|
+
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
15175
|
var init_startup_banner = __esm(() => {
|
|
15062
15176
|
init_port_registry();
|
|
15063
15177
|
init_runner_registry();
|
|
15064
15178
|
init_startup_model();
|
|
15065
15179
|
ANSI = {
|
|
15066
15180
|
reset: "\x1B[0m",
|
|
15181
|
+
bold: "\x1B[1m",
|
|
15067
15182
|
dim: "\x1B[2m",
|
|
15068
|
-
|
|
15069
|
-
|
|
15183
|
+
yellow: "\x1B[33m",
|
|
15184
|
+
magenta: "\x1B[35m",
|
|
15185
|
+
cyan: "\x1B[36m"
|
|
15070
15186
|
};
|
|
15187
|
+
bold = paint(ANSI.bold);
|
|
15188
|
+
dim = paint(ANSI.dim);
|
|
15189
|
+
cyan = paint(ANSI.cyan);
|
|
15190
|
+
magenta = paint(ANSI.magenta);
|
|
15191
|
+
yellow = paint(ANSI.yellow);
|
|
15071
15192
|
});
|
|
15072
15193
|
|
|
15073
15194
|
// src/state-migration.ts
|
|
@@ -15971,7 +16092,7 @@ async function runRetentionGc(store, opts) {
|
|
|
15971
16092
|
logs = pruneRunLogs(opts.runsDirectory ?? runsDir(), now - opts.runLogDays * DAY_MS2, openRunIds);
|
|
15972
16093
|
}
|
|
15973
16094
|
if (runRecordsPurged > 0 || logs.deleted > 0) {
|
|
15974
|
-
log49.
|
|
16095
|
+
log49.debug(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
|
|
15975
16096
|
}
|
|
15976
16097
|
return {
|
|
15977
16098
|
runRecordsPurged,
|
|
@@ -16436,7 +16557,7 @@ async function main() {
|
|
|
16436
16557
|
throw err;
|
|
16437
16558
|
}
|
|
16438
16559
|
if (config.agent.sweep.enabled) {
|
|
16439
|
-
banner.
|
|
16560
|
+
banner.notice(sweepBannerLine(config.agent));
|
|
16440
16561
|
}
|
|
16441
16562
|
const { agentId, repository, folderPath } = await resolveAgentIdentity(client, config);
|
|
16442
16563
|
banner.check(`Agent registered (${config.agentName}${repository ? ` · ${repository.name}` : ""})`);
|
|
@@ -16472,7 +16593,7 @@ async function main() {
|
|
|
16472
16593
|
banner.check("Recovery: no orphans");
|
|
16473
16594
|
} else {
|
|
16474
16595
|
const errored = outcomes.filter((o) => o.errors.length).length;
|
|
16475
|
-
banner.
|
|
16596
|
+
banner.notice(`Recovery: ${outcomes.length} orphan(s) handled${errored > 0 ? `, ${errored} with errors` : ""}`);
|
|
16476
16597
|
}
|
|
16477
16598
|
const sweepGuard = new SweepGuard(config.agent.sweep, stateStore, () => new BudgetGuard(config.agent.budget, stateStore).checkDailyBudget());
|
|
16478
16599
|
if (config.agent.sweep.enabled) {
|
|
@@ -16683,7 +16804,7 @@ async function main() {
|
|
|
16683
16804
|
if (winner === "ready") {
|
|
16684
16805
|
banner.check("Realtime: broadcast + presence");
|
|
16685
16806
|
} else {
|
|
16686
|
-
banner.
|
|
16807
|
+
banner.notice("Realtime: connecting…");
|
|
16687
16808
|
}
|
|
16688
16809
|
await banner.ready("watching for card assignments");
|
|
16689
16810
|
watcher.allowStartupLogs();
|
|
@@ -17520,6 +17641,9 @@ Flags:
|
|
|
17520
17641
|
(also: HARMONY_AGENT_PRETTY=1)
|
|
17521
17642
|
--json Force structured JSON log output
|
|
17522
17643
|
(also: HARMONY_AGENT_JSON=1)
|
|
17644
|
+
--verbose Print config sources, limits and every
|
|
17645
|
+
startup check in the startup banner
|
|
17646
|
+
(also: HARMONY_AGENT_VERBOSE=1)
|
|
17523
17647
|
|
|
17524
17648
|
By default logs are pretty when stderr is a TTY and JSON when piped.
|
|
17525
17649
|
`.trim();
|
|
@@ -18246,7 +18370,7 @@ what verification rejected (recent):
|
|
|
18246
18370
|
return 0;
|
|
18247
18371
|
}
|
|
18248
18372
|
async function dispatch(argv) {
|
|
18249
|
-
const args = argv.filter((a) => a !== "--pretty" && a !== "--json");
|
|
18373
|
+
const args = argv.filter((a) => a !== "--pretty" && a !== "--json" && a !== "--verbose");
|
|
18250
18374
|
const cmd = args[0];
|
|
18251
18375
|
switch (cmd) {
|
|
18252
18376
|
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;
|
|
@@ -14822,6 +14837,15 @@ function ownString(source, key) {
|
|
|
14822
14837
|
const value = own(source, key);
|
|
14823
14838
|
return typeof value === "string" && value !== "" ? value : undefined;
|
|
14824
14839
|
}
|
|
14840
|
+
function compactModelsLine(m) {
|
|
14841
|
+
const short = (id) => id.replace(/^claude-/, "");
|
|
14842
|
+
if (m.sizingModel) {
|
|
14843
|
+
const ladder = TIERS.map((tier) => short(m.tiers[tier])).join(" · ");
|
|
14844
|
+
const rule = m.researchRule === "armed" && isJevSizingModel(m.sizingModel) ? " · research rule armed" : "";
|
|
14845
|
+
return `${ladder} sized by ${m.sizingModel}${rule}`;
|
|
14846
|
+
}
|
|
14847
|
+
return m.model === m.escalateModel ? short(m.model) : `${short(m.model)} → ${short(m.escalateModel)} on retry`;
|
|
14848
|
+
}
|
|
14825
14849
|
function startupModelLine(m) {
|
|
14826
14850
|
const origin = m.resolved ? `from ${m.source}` : `from ${m.source}; database model settings unreachable, a pickup may resolve differently`;
|
|
14827
14851
|
return `${implementModelsLine(m)} (${origin})`;
|
|
@@ -14846,7 +14870,8 @@ var exports_startup_banner = {};
|
|
|
14846
14870
|
__export(exports_startup_banner, {
|
|
14847
14871
|
limitsLine: () => limitsLine,
|
|
14848
14872
|
createStartupBanner: () => createStartupBanner,
|
|
14849
|
-
contextSourceLine: () => contextSourceLine
|
|
14873
|
+
contextSourceLine: () => contextSourceLine,
|
|
14874
|
+
budgetLine: () => budgetLine
|
|
14850
14875
|
});
|
|
14851
14876
|
import { isPretty, log as log46 } from "@gethmy/harness";
|
|
14852
14877
|
function createStartupBanner(config, version) {
|
|
@@ -14876,6 +14901,9 @@ function prettyBanner(config, version) {
|
|
|
14876
14901
|
check(message) {
|
|
14877
14902
|
checks.push({ kind: "ok", message });
|
|
14878
14903
|
},
|
|
14904
|
+
notice(message) {
|
|
14905
|
+
checks.push({ kind: "notice", message });
|
|
14906
|
+
},
|
|
14879
14907
|
warn(message) {
|
|
14880
14908
|
log46.warn(TAG42, message);
|
|
14881
14909
|
checks.push({ kind: "warn", message: message.split(`
|
|
@@ -14927,6 +14955,9 @@ function jsonBanner(config, version) {
|
|
|
14927
14955
|
check(message) {
|
|
14928
14956
|
log46.info(TAG42, message);
|
|
14929
14957
|
},
|
|
14958
|
+
notice(message) {
|
|
14959
|
+
log46.info(TAG42, message);
|
|
14960
|
+
},
|
|
14930
14961
|
warn(message) {
|
|
14931
14962
|
log46.warn(TAG42, message);
|
|
14932
14963
|
},
|
|
@@ -14953,24 +14984,119 @@ function renderPretty(input) {
|
|
|
14953
14984
|
checks,
|
|
14954
14985
|
readyMessage
|
|
14955
14986
|
} = input;
|
|
14956
|
-
const
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14987
|
+
const detailed = isVerbose();
|
|
14988
|
+
const columns = process.stderr.columns ?? 80;
|
|
14989
|
+
const width = detailed ? Number.POSITIVE_INFINITY : columns;
|
|
14990
|
+
const rule = dim("─".repeat(Math.min(RULE_WIDTH, columns)));
|
|
14991
|
+
const row = (label, ...value) => clip([[dim, label.padEnd(9)], ...value], width);
|
|
14992
|
+
const reviewEnabled = config.agent.review.enabled;
|
|
14993
|
+
const reviewPips = reviewEnabled ? [
|
|
14994
|
+
[dim, " "],
|
|
14995
|
+
[magenta, pips(config.agent.review.poolSize)],
|
|
14996
|
+
[dim, " review"]
|
|
14997
|
+
] : [];
|
|
14998
|
+
const modelNote = !model ? " (local config)" : model.resolved ? "" : " (database unreachable)";
|
|
14999
|
+
const pickup = reviewEnabled ? `${config.agent.pickupColumns[0]} → ${config.agent.completion.moveToColumn} → ${config.agent.review.moveToColumn}` : config.agent.pickupColumns.join(", ");
|
|
15000
|
+
const lines = [""];
|
|
15001
|
+
lines.push(clip([
|
|
15002
|
+
[magenta, "◆ "],
|
|
15003
|
+
[bold, "Harmony Agent "],
|
|
15004
|
+
[dim, `v${version} · `],
|
|
15005
|
+
[plain, projectName ?? shortenId(config.projectId)],
|
|
15006
|
+
[dim, ` · ${config.userEmail}`]
|
|
15007
|
+
], width));
|
|
15008
|
+
lines.push(rule);
|
|
15009
|
+
lines.push(row("Pickup", [plain, pickup]));
|
|
15010
|
+
lines.push(row("Pool", [magenta, pips(config.agent.poolSize)], [dim, " implement"], ...reviewPips));
|
|
15011
|
+
lines.push(row("Models", [plain, compactModelsLine(model ?? config.agent.claude)], [dim, modelNote]));
|
|
15012
|
+
lines.push(row("Budget", [plain, budgetLine(config)]));
|
|
15013
|
+
lines.push(rule);
|
|
15014
|
+
const warnings = checks.filter((c) => c.kind === "warn");
|
|
15015
|
+
const passed = checks.filter((c) => c.kind === "ok").length;
|
|
15016
|
+
const unreadablePin = config.contextSource.unreadablePin;
|
|
15017
|
+
if (unreadablePin !== null) {
|
|
15018
|
+
warnings.push({
|
|
15019
|
+
kind: "warn",
|
|
15020
|
+
message: `${unreadablePin} is present but unreadable, so its pin was ignored`
|
|
15021
|
+
});
|
|
14962
15022
|
}
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
15023
|
+
const counted = passed + warnings.length;
|
|
15024
|
+
if (detailed) {
|
|
15025
|
+
for (const r of configRows(config, projectName, gitProvider, httpPort, model)) {
|
|
15026
|
+
lines.push(`${dim(r.label.padEnd(9))} ${r.value}`);
|
|
15027
|
+
}
|
|
15028
|
+
lines.push("");
|
|
15029
|
+
for (const c of checks) {
|
|
15030
|
+
if (c.kind === "warn")
|
|
15031
|
+
lines.push(`${yellow("!")} ${c.message}`);
|
|
15032
|
+
else if (c.kind === "notice")
|
|
15033
|
+
lines.push(`› ${c.message}`);
|
|
15034
|
+
else
|
|
15035
|
+
lines.push(`${cyan("✓")} ${c.message}`);
|
|
15036
|
+
}
|
|
15037
|
+
lines.push(rule);
|
|
15038
|
+
} else {
|
|
15039
|
+
for (const c of checks) {
|
|
15040
|
+
if (c.kind === "notice")
|
|
15041
|
+
lines.push(clip([[plain, `› ${c.message}`]], width));
|
|
15042
|
+
}
|
|
15043
|
+
}
|
|
15044
|
+
const verdict = warnings.length > 0 ? [
|
|
15045
|
+
[yellow, "! "],
|
|
15046
|
+
[plain, `${passed}/${counted} checks`]
|
|
15047
|
+
] : [
|
|
15048
|
+
[cyan, "✓ "],
|
|
15049
|
+
[plain, `${passed}/${counted} checks`]
|
|
15050
|
+
];
|
|
15051
|
+
verdict.push([dim, " "], [cyan, "● "], [bold, "ready"]);
|
|
15052
|
+
if (detailed)
|
|
15053
|
+
verdict.push([dim, ` — ${readyMessage}`]);
|
|
15054
|
+
if (config.agent.http.enabled) {
|
|
15055
|
+
const url = daemonBaseUrl(config.agent.http.bindAddr, httpPort ?? config.agent.http.port);
|
|
15056
|
+
verdict.push([dim, " "], [cyan, url]);
|
|
15057
|
+
}
|
|
15058
|
+
lines.push(clip(verdict, width));
|
|
15059
|
+
if (!detailed) {
|
|
15060
|
+
for (const w of warnings) {
|
|
15061
|
+
lines.push(clip([
|
|
15062
|
+
[yellow, "! "],
|
|
15063
|
+
[plain, w.message]
|
|
15064
|
+
], width));
|
|
15065
|
+
}
|
|
14966
15066
|
}
|
|
14967
|
-
lines.push("");
|
|
14968
|
-
lines.push(`${cyan("▶")} ${cyan("Ready")} — ${readyMessage}`);
|
|
14969
|
-
lines.push(dim("─".repeat(RULE_WIDTH)));
|
|
14970
15067
|
lines.push("");
|
|
14971
15068
|
return lines.join(`
|
|
14972
15069
|
`);
|
|
14973
15070
|
}
|
|
15071
|
+
function clip(segs, width) {
|
|
15072
|
+
let room = width;
|
|
15073
|
+
let out = "";
|
|
15074
|
+
for (const [style, text] of segs) {
|
|
15075
|
+
if (room <= 0)
|
|
15076
|
+
break;
|
|
15077
|
+
const chars = [...text];
|
|
15078
|
+
if (chars.length <= room) {
|
|
15079
|
+
out += style(text);
|
|
15080
|
+
room -= chars.length;
|
|
15081
|
+
} else {
|
|
15082
|
+
out += style(`${chars.slice(0, room - 1).join("")}…`);
|
|
15083
|
+
room = 0;
|
|
15084
|
+
}
|
|
15085
|
+
}
|
|
15086
|
+
return out;
|
|
15087
|
+
}
|
|
15088
|
+
function pips(count) {
|
|
15089
|
+
return count <= POOL_PIPS_MAX ? "▰".repeat(count) : `▰×${count}`;
|
|
15090
|
+
}
|
|
15091
|
+
function budgetLine(config) {
|
|
15092
|
+
const { budget } = config.agent;
|
|
15093
|
+
const cap = (value, unit) => value < 0 ? `unlimited ${unit}` : `${value} ${unit}`;
|
|
15094
|
+
const daily = budget.dailyBudgetCents < 0 ? "unlimited/day" : `$${(budget.dailyBudgetCents / 100).toFixed(2).replace(/\.00$/, "")}/day`;
|
|
15095
|
+
return `${daily} · ${cap(budget.maxTurnsPerCard, "turns")} · ${budget.maxAttemptsPerCard} attempts per card`;
|
|
15096
|
+
}
|
|
15097
|
+
function isVerbose() {
|
|
15098
|
+
return process.env.HARMONY_AGENT_VERBOSE === "1" || process.argv.includes("--verbose");
|
|
15099
|
+
}
|
|
14974
15100
|
function configRows(config, projectName, gitProvider, httpPort, model) {
|
|
14975
15101
|
const rows = [];
|
|
14976
15102
|
const projectLabel = projectName ? `${projectName} (${shortenId(config.projectId)})` : shortenId(config.projectId);
|
|
@@ -15033,12 +15159,6 @@ function limitsLine(config) {
|
|
|
15033
15159
|
parts.push(`failed branches ${worktree.failedAttemptRetentionDays > 0 ? `${worktree.failedAttemptRetentionDays}d` : "kept"} (worktree.failedAttemptRetentionDays)`);
|
|
15034
15160
|
return parts.join(" · ");
|
|
15035
15161
|
}
|
|
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
15162
|
function localModelLine(config) {
|
|
15043
15163
|
return `${implementModelsLine(config.agent.claude)} (local config only; database model settings not read)`;
|
|
15044
15164
|
}
|
|
@@ -15047,26 +15167,27 @@ function shortenId(id) {
|
|
|
15047
15167
|
return id;
|
|
15048
15168
|
return `${id.slice(0, 8)}…`;
|
|
15049
15169
|
}
|
|
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}`;
|
|
15170
|
+
function colorEnabled() {
|
|
15171
|
+
return !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
15058
15172
|
}
|
|
15059
|
-
var TAG42 = "daemon", RULE_WIDTH =
|
|
15173
|
+
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
15174
|
var init_startup_banner = __esm(() => {
|
|
15061
15175
|
init_port_registry();
|
|
15062
15176
|
init_runner_registry();
|
|
15063
15177
|
init_startup_model();
|
|
15064
15178
|
ANSI = {
|
|
15065
15179
|
reset: "\x1B[0m",
|
|
15180
|
+
bold: "\x1B[1m",
|
|
15066
15181
|
dim: "\x1B[2m",
|
|
15067
|
-
|
|
15068
|
-
|
|
15182
|
+
yellow: "\x1B[33m",
|
|
15183
|
+
magenta: "\x1B[35m",
|
|
15184
|
+
cyan: "\x1B[36m"
|
|
15069
15185
|
};
|
|
15186
|
+
bold = paint(ANSI.bold);
|
|
15187
|
+
dim = paint(ANSI.dim);
|
|
15188
|
+
cyan = paint(ANSI.cyan);
|
|
15189
|
+
magenta = paint(ANSI.magenta);
|
|
15190
|
+
yellow = paint(ANSI.yellow);
|
|
15070
15191
|
});
|
|
15071
15192
|
|
|
15072
15193
|
// src/state-migration.ts
|
|
@@ -15970,7 +16091,7 @@ async function runRetentionGc(store, opts) {
|
|
|
15970
16091
|
logs = pruneRunLogs(opts.runsDirectory ?? runsDir(), now - opts.runLogDays * DAY_MS2, openRunIds);
|
|
15971
16092
|
}
|
|
15972
16093
|
if (runRecordsPurged > 0 || logs.deleted > 0) {
|
|
15973
|
-
log49.
|
|
16094
|
+
log49.debug(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
|
|
15974
16095
|
}
|
|
15975
16096
|
return {
|
|
15976
16097
|
runRecordsPurged,
|
|
@@ -16435,7 +16556,7 @@ async function main() {
|
|
|
16435
16556
|
throw err;
|
|
16436
16557
|
}
|
|
16437
16558
|
if (config.agent.sweep.enabled) {
|
|
16438
|
-
banner.
|
|
16559
|
+
banner.notice(sweepBannerLine(config.agent));
|
|
16439
16560
|
}
|
|
16440
16561
|
const { agentId, repository, folderPath } = await resolveAgentIdentity(client, config);
|
|
16441
16562
|
banner.check(`Agent registered (${config.agentName}${repository ? ` · ${repository.name}` : ""})`);
|
|
@@ -16471,7 +16592,7 @@ async function main() {
|
|
|
16471
16592
|
banner.check("Recovery: no orphans");
|
|
16472
16593
|
} else {
|
|
16473
16594
|
const errored = outcomes.filter((o) => o.errors.length).length;
|
|
16474
|
-
banner.
|
|
16595
|
+
banner.notice(`Recovery: ${outcomes.length} orphan(s) handled${errored > 0 ? `, ${errored} with errors` : ""}`);
|
|
16475
16596
|
}
|
|
16476
16597
|
const sweepGuard = new SweepGuard(config.agent.sweep, stateStore, () => new BudgetGuard(config.agent.budget, stateStore).checkDailyBudget());
|
|
16477
16598
|
if (config.agent.sweep.enabled) {
|
|
@@ -16682,7 +16803,7 @@ async function main() {
|
|
|
16682
16803
|
if (winner === "ready") {
|
|
16683
16804
|
banner.check("Realtime: broadcast + presence");
|
|
16684
16805
|
} else {
|
|
16685
|
-
banner.
|
|
16806
|
+
banner.notice("Realtime: connecting…");
|
|
16686
16807
|
}
|
|
16687
16808
|
await banner.ready("watching for card assignments");
|
|
16688
16809
|
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.9",
|
|
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",
|