@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.
Files changed (3) hide show
  1. package/dist/cli.js +160 -36
  2. package/dist/index.js +156 -35
  3. 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 lines = [];
14958
- lines.push("");
14959
- lines.push(titleRule(`Harmony Agent Daemon v${version}`));
14960
- lines.push("");
14961
- for (const row of configRows(config, projectName, gitProvider, httpPort, model)) {
14962
- lines.push(` ${dim(row.label.padEnd(9))} ${row.value}`);
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
- lines.push("");
14965
- for (const row of checks) {
14966
- lines.push(row.kind === "warn" ? ` ${yellow("⚠")} ${row.message}` : ` ${cyan("✓")} ${row.message}`);
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 dim(s) {
15052
- return `${ANSI.dim}${s}${ANSI.reset}`;
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 = 70, ANSI;
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
- cyan: "\x1B[36m",
15069
- yellow: "\x1B[33m"
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.info(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
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.check(sweepBannerLine(config.agent));
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.check(`Recovery: ${outcomes.length} orphan(s) handled${errored > 0 ? `, ${errored} with errors` : ""}`);
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.check("Realtime: connecting…");
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 lines = [];
14957
- lines.push("");
14958
- lines.push(titleRule(`Harmony Agent Daemon v${version}`));
14959
- lines.push("");
14960
- for (const row of configRows(config, projectName, gitProvider, httpPort, model)) {
14961
- lines.push(` ${dim(row.label.padEnd(9))} ${row.value}`);
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
- lines.push("");
14964
- for (const row of checks) {
14965
- lines.push(row.kind === "warn" ? ` ${yellow("⚠")} ${row.message}` : ` ${cyan("✓")} ${row.message}`);
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 dim(s) {
15051
- return `${ANSI.dim}${s}${ANSI.reset}`;
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 = 70, ANSI;
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
- cyan: "\x1B[36m",
15068
- yellow: "\x1B[33m"
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.info(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
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.check(sweepBannerLine(config.agent));
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.check(`Recovery: ${outcomes.length} orphan(s) handled${errored > 0 ? `, ${errored} with errors` : ""}`);
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.check("Realtime: connecting…");
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.7",
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",