@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.
Files changed (3) hide show
  1. package/dist/cli.js +169 -36
  2. package/dist/index.js +165 -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;
@@ -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 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}`);
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
- lines.push("");
14965
- for (const row of checks) {
14966
- lines.push(row.kind === "warn" ? ` ${yellow("⚠")} ${row.message}` : ` ${cyan("✓")} ${row.message}`);
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 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}`;
15180
+ function colorEnabled() {
15181
+ return !process.env.NO_COLOR && process.env.TERM !== "dumb";
15059
15182
  }
15060
- var TAG42 = "daemon", RULE_WIDTH = 70, ANSI;
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
- cyan: "\x1B[36m",
15069
- yellow: "\x1B[33m"
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.info(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
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.check(sweepBannerLine(config.agent));
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.check(`Recovery: ${outcomes.length} orphan(s) handled${errored > 0 ? `, ${errored} with errors` : ""}`);
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.check("Realtime: connecting…");
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 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}`);
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
- lines.push("");
14964
- for (const row of checks) {
14965
- lines.push(row.kind === "warn" ? ` ${yellow("⚠")} ${row.message}` : ` ${cyan("✓")} ${row.message}`);
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 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}`;
15179
+ function colorEnabled() {
15180
+ return !process.env.NO_COLOR && process.env.TERM !== "dumb";
15058
15181
  }
15059
- var TAG42 = "daemon", RULE_WIDTH = 70, ANSI;
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
- cyan: "\x1B[36m",
15068
- yellow: "\x1B[33m"
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.info(TAG45, `Retention sweep: trimmed ${runRecordsPurged} run record(s), removed ${logs.deleted} run log(s) (${Math.round(logs.bytesFreed / 1024)} KB)`);
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.check(sweepBannerLine(config.agent));
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.check(`Recovery: ${outcomes.length} orphan(s) handled${errored > 0 ? `, ${errored} with errors` : ""}`);
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.check("Realtime: connecting…");
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.7",
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",