@gethmy/agent 1.45.0 → 1.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -10
- package/dist/cli.js +288 -40
- package/dist/index.js +270 -36
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -19,10 +19,6 @@ Built for **failsafe auto mode**: crashed daemons recover on restart, misconfigu
|
|
|
19
19
|
npm i -g @gethmy/cli
|
|
20
20
|
hmy agent
|
|
21
21
|
|
|
22
|
-
# Or the daemon package alone
|
|
23
|
-
npm install -g @gethmy/agent
|
|
24
|
-
harmony-agent
|
|
25
|
-
|
|
26
22
|
# Or nothing installed at all (works with any package manager)
|
|
27
23
|
npx @gethmy/agent@latest
|
|
28
24
|
```
|
|
@@ -30,12 +26,11 @@ npx @gethmy/agent@latest
|
|
|
30
26
|
Install when the machine is yours; use `npx` for a container, a CI step or a
|
|
31
27
|
one-off. Both forms stay supported.
|
|
32
28
|
|
|
33
|
-
>
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
37
|
-
>
|
|
38
|
-
> `rm -rf ~/.npm/_npx` (or install globally to skip npx caching entirely).
|
|
29
|
+
> **`npx` can serve a stale build.** The npx cache keeps serving the version it
|
|
30
|
+
> first installed, `@latest` included — a restart six minutes after 1.38.0
|
|
31
|
+
> published still ran 1.37.0 — so you get stale startup logs and CLI behavior.
|
|
32
|
+
> If you suspect a stale run, clear the cache with `rm -rf ~/.npm/_npx`, or
|
|
33
|
+
> install `@gethmy/cli` and run `hmy agent` to skip npx caching entirely.
|
|
39
34
|
|
|
40
35
|
## Configuration
|
|
41
36
|
|
package/dist/cli.js
CHANGED
|
@@ -1103,9 +1103,14 @@ var init_commentSerializer = __esm(() => {
|
|
|
1103
1103
|
});
|
|
1104
1104
|
|
|
1105
1105
|
// ../harmony-shared/src/connectCommands.ts
|
|
1106
|
-
|
|
1106
|
+
function hmyConnectCommand(projectSlug) {
|
|
1107
|
+
const slug = projectSlug?.trim();
|
|
1108
|
+
return slug ? `${CONNECT_VERB} ${slug}` : CONNECT_VERB;
|
|
1109
|
+
}
|
|
1110
|
+
var HMY_INSTALL_URL = "https://gethmy.com/install.sh", HMY_INSTALL_COMMAND, HMY_AGENT_COMMAND = "hmy agent", CONNECT_VERB = "hmy connect", HOSTED_MCP_URL = "https://mcp.gethmy.com/mcp", HOSTED_MCP_CONFIG;
|
|
1107
1111
|
var init_connectCommands = __esm(() => {
|
|
1108
1112
|
HMY_INSTALL_COMMAND = `curl -fsSL ${HMY_INSTALL_URL} | bash`;
|
|
1113
|
+
HOSTED_MCP_CONFIG = JSON.stringify({ mcpServers: { harmony: { type: "http", url: HOSTED_MCP_URL } } }, null, 2);
|
|
1109
1114
|
});
|
|
1110
1115
|
|
|
1111
1116
|
// ../harmony-shared/src/constants.ts
|
|
@@ -1578,6 +1583,18 @@ function own(source, key) {
|
|
|
1578
1583
|
return Object.hasOwn(source, key) ? source[key] : undefined;
|
|
1579
1584
|
}
|
|
1580
1585
|
|
|
1586
|
+
// ../harmony-shared/src/packageCommands.ts
|
|
1587
|
+
function npxArgs(pkg, args) {
|
|
1588
|
+
return ["-y", `${SCOPE}/${pkg}@latest`, ...args];
|
|
1589
|
+
}
|
|
1590
|
+
function npxCommand(pkg, args = []) {
|
|
1591
|
+
return ["npx", ...npxArgs(pkg, args)].join(" ");
|
|
1592
|
+
}
|
|
1593
|
+
var SCOPE = "@gethmy", HMY_NPM_INSTALL_COMMAND;
|
|
1594
|
+
var init_packageCommands = __esm(() => {
|
|
1595
|
+
HMY_NPM_INSTALL_COMMAND = `npm i -g ${SCOPE}/cli`;
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1581
1598
|
// ../harmony-shared/src/parsedState.ts
|
|
1582
1599
|
function parseJsonWithoutPrototypes(text) {
|
|
1583
1600
|
return JSON.parse(text, (_key, value) => {
|
|
@@ -2575,6 +2592,7 @@ var init_src = __esm(() => {
|
|
|
2575
2592
|
init_gateEvaluate();
|
|
2576
2593
|
init_logger();
|
|
2577
2594
|
init_memoryQuery();
|
|
2595
|
+
init_packageCommands();
|
|
2578
2596
|
init_playbookAutoBind();
|
|
2579
2597
|
init_playbookCatalog();
|
|
2580
2598
|
init_playbookStage();
|
|
@@ -3516,7 +3534,7 @@ function agentIdentifier(workerId) {
|
|
|
3516
3534
|
function endStatusForCancel(reason) {
|
|
3517
3535
|
return reason === "human_stop" ? "cancelled" : "paused";
|
|
3518
3536
|
}
|
|
3519
|
-
var DEFAULT_FINDING_AUTHORS, DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", REVIEW_FAILURE_REASON_MAX = 300, AGENT_NAME = "Harmony Agent", DAEMON_IDENTIFIER_BASE = "harmony-daemon", NOTICE_IDENTIFIER;
|
|
3537
|
+
var DEFAULT_FINDING_AUTHORS, DEFAULT_HUMAN_STAGE_HOLD_MINUTES = 60, DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", REVIEW_FAILURE_REASON_MAX = 300, AGENT_NAME = "Harmony Agent", DAEMON_IDENTIFIER_BASE = "harmony-daemon", NOTICE_IDENTIFIER;
|
|
3520
3538
|
var init_types = __esm(() => {
|
|
3521
3539
|
init_src();
|
|
3522
3540
|
init_board_review();
|
|
@@ -3548,12 +3566,12 @@ var init_types = __esm(() => {
|
|
|
3548
3566
|
postSummary: true
|
|
3549
3567
|
},
|
|
3550
3568
|
claude: {
|
|
3551
|
-
model: "claude-opus-5",
|
|
3569
|
+
model: "claude-opus-5-5",
|
|
3552
3570
|
escalateModel: "claude-fable-5-1",
|
|
3553
3571
|
escalateAfterAttempts: 2,
|
|
3554
3572
|
tiers: {
|
|
3555
3573
|
simple: "claude-sonnet-5",
|
|
3556
|
-
advanced: "claude-opus-5",
|
|
3574
|
+
advanced: "claude-opus-5-5",
|
|
3557
3575
|
research: "claude-fable-5-1"
|
|
3558
3576
|
},
|
|
3559
3577
|
scoreBands: DEFAULT_SCORE_BANDS,
|
|
@@ -3660,7 +3678,12 @@ var init_types = __esm(() => {
|
|
|
3660
3678
|
runLogDays: 30
|
|
3661
3679
|
},
|
|
3662
3680
|
planning: DEFAULT_PLANNING_CONFIG,
|
|
3663
|
-
playbooks: {
|
|
3681
|
+
playbooks: {
|
|
3682
|
+
enabled: true,
|
|
3683
|
+
humanStageColumns: [],
|
|
3684
|
+
humanStageHoldMinutes: DEFAULT_HUMAN_STAGE_HOLD_MINUTES,
|
|
3685
|
+
metrics: {}
|
|
3686
|
+
},
|
|
3664
3687
|
contractFirst: DEFAULT_CONTRACT_CONFIG,
|
|
3665
3688
|
boardReview: DEFAULT_BOARD_REVIEW_CONFIG,
|
|
3666
3689
|
sweep: {
|
|
@@ -4041,13 +4064,13 @@ function loadDaemonConfig() {
|
|
|
4041
4064
|
const projectId = getActiveProjectId(repoRoot);
|
|
4042
4065
|
const userEmail = getUserEmail();
|
|
4043
4066
|
if (!workspaceId) {
|
|
4044
|
-
throw new Error(`No active workspace configured in ${contextSource.path}. Run \`
|
|
4067
|
+
throw new Error(`No active workspace configured in ${contextSource.path}. Run \`${hmyConnectCommand()}\` first (no install: \`${npxCommand("mcp", ["connect"])}\`).`);
|
|
4045
4068
|
}
|
|
4046
4069
|
if (!projectId) {
|
|
4047
|
-
throw new Error(`No active project configured in ${contextSource.path}. Run \`
|
|
4070
|
+
throw new Error(`No active project configured in ${contextSource.path}. Run \`${hmyConnectCommand()}\` first (no install: \`${npxCommand("mcp", ["connect"])}\`).`);
|
|
4048
4071
|
}
|
|
4049
4072
|
if (!userEmail) {
|
|
4050
|
-
throw new Error(`No user email configured in ${contextSource.globalPath}. Run \`
|
|
4073
|
+
throw new Error(`No user email configured in ${contextSource.globalPath}. Run \`${hmyConnectCommand()}\` first (no install: \`${npxCommand("mcp", ["connect"])}\`).`);
|
|
4051
4074
|
}
|
|
4052
4075
|
let agentOverrides = {};
|
|
4053
4076
|
let agentName = "Harmony Agent";
|
|
@@ -4160,6 +4183,7 @@ function createApiClient(config) {
|
|
|
4160
4183
|
}
|
|
4161
4184
|
var TAG5 = "config", NOT_A_REPOSITORY_EXIT = 128, warnedLegacyConfigDir = false;
|
|
4162
4185
|
var init_config = __esm(() => {
|
|
4186
|
+
init_src();
|
|
4163
4187
|
init_config_merge();
|
|
4164
4188
|
init_config_validation();
|
|
4165
4189
|
init_paths();
|
|
@@ -8817,7 +8841,7 @@ class StateStore {
|
|
|
8817
8841
|
const rec = this.getCard(cardId);
|
|
8818
8842
|
if (!rec)
|
|
8819
8843
|
return;
|
|
8820
|
-
if (rec.attempts === 0 && !rec.ciRepairAttempts && rec.ciRepairParkedAt == null && (!clearTurns || !rec.totalTurns && !rec.runStarts)) {
|
|
8844
|
+
if (rec.attempts === 0 && !rec.ciRepairAttempts && rec.ciRepairParkedAt == null && rec.stageHold == null && (!clearTurns || !rec.totalTurns && !rec.runStarts)) {
|
|
8821
8845
|
return;
|
|
8822
8846
|
}
|
|
8823
8847
|
rec.attempts = 0;
|
|
@@ -8827,6 +8851,7 @@ class StateStore {
|
|
|
8827
8851
|
rec.totalTurns = 0;
|
|
8828
8852
|
if (clearTurns)
|
|
8829
8853
|
rec.runStarts = 0;
|
|
8854
|
+
rec.stageHold = null;
|
|
8830
8855
|
await this.persist();
|
|
8831
8856
|
}
|
|
8832
8857
|
getCiRepairAttempts(cardId) {
|
|
@@ -8909,6 +8934,29 @@ class StateStore {
|
|
|
8909
8934
|
getCardRunStarts(cardId) {
|
|
8910
8935
|
return this.getCard(cardId)?.runStarts ?? 0;
|
|
8911
8936
|
}
|
|
8937
|
+
getStageHold(cardId) {
|
|
8938
|
+
return this.getCard(cardId)?.stageHold ?? null;
|
|
8939
|
+
}
|
|
8940
|
+
async recordStageHold(cardId, hold) {
|
|
8941
|
+
const rec = this.ensureCard(cardId);
|
|
8942
|
+
const prior = rec.stageHold ?? null;
|
|
8943
|
+
const announced = prior != null && prior.stageId === hold.stageId && prior.reason === hold.reason;
|
|
8944
|
+
rec.stageHold = cloneWithoutPrototype({
|
|
8945
|
+
stageId: hold.stageId,
|
|
8946
|
+
reason: hold.reason,
|
|
8947
|
+
until: hold.untilMs,
|
|
8948
|
+
announcedAt: announced ? prior.announcedAt : Date.now()
|
|
8949
|
+
});
|
|
8950
|
+
await this.persist();
|
|
8951
|
+
return { announced };
|
|
8952
|
+
}
|
|
8953
|
+
async clearStageHold(cardId) {
|
|
8954
|
+
const rec = this.getCard(cardId);
|
|
8955
|
+
if (!rec || rec.stageHold == null)
|
|
8956
|
+
return;
|
|
8957
|
+
rec.stageHold = null;
|
|
8958
|
+
await this.persist();
|
|
8959
|
+
}
|
|
8912
8960
|
getCardCostCents(cardId) {
|
|
8913
8961
|
return this.getCard(cardId)?.totalCostCents ?? 0;
|
|
8914
8962
|
}
|
|
@@ -10219,6 +10267,27 @@ class SleepGuard {
|
|
|
10219
10267
|
var TAG26 = "sleep-guard";
|
|
10220
10268
|
var init_sleep_guard = () => {};
|
|
10221
10269
|
|
|
10270
|
+
// src/stage-hold.ts
|
|
10271
|
+
function classifyStageHold(card, hold, nowMs) {
|
|
10272
|
+
if (!hold)
|
|
10273
|
+
return { kind: "none" };
|
|
10274
|
+
if (!card.current_stage || card.current_stage !== hold.stageId) {
|
|
10275
|
+
return { kind: "stale" };
|
|
10276
|
+
}
|
|
10277
|
+
if (nowMs >= hold.until)
|
|
10278
|
+
return { kind: "lapsed" };
|
|
10279
|
+
const remainingMin = Math.ceil((hold.until - nowMs) / 60000);
|
|
10280
|
+
return {
|
|
10281
|
+
kind: "holding",
|
|
10282
|
+
detail: `parked on human-owned stage "${hold.stageId}" — holding for ~${remainingMin}m`
|
|
10283
|
+
};
|
|
10284
|
+
}
|
|
10285
|
+
function stageHoldUntil(holdMinutes, nowMs) {
|
|
10286
|
+
if (!Number.isFinite(holdMinutes) || holdMinutes <= 0)
|
|
10287
|
+
return nowMs;
|
|
10288
|
+
return nowMs + holdMinutes * 60000;
|
|
10289
|
+
}
|
|
10290
|
+
|
|
10222
10291
|
// src/unblock.ts
|
|
10223
10292
|
import { log as log30 } from "@gethmy/harness";
|
|
10224
10293
|
async function fetchBlocksLinks(client, cardId) {
|
|
@@ -11455,6 +11524,29 @@ var init_stage_advance = __esm(() => {
|
|
|
11455
11524
|
});
|
|
11456
11525
|
|
|
11457
11526
|
// src/workspace-model-config.ts
|
|
11527
|
+
function layerPlatformDefaults(platform, ws) {
|
|
11528
|
+
if (!platform)
|
|
11529
|
+
return ws ?? null;
|
|
11530
|
+
if (!ws)
|
|
11531
|
+
return platform;
|
|
11532
|
+
const merged = { ...platform };
|
|
11533
|
+
for (const [key, value] of Object.entries(ws)) {
|
|
11534
|
+
if (value !== null && value !== undefined) {
|
|
11535
|
+
merged[key] = value;
|
|
11536
|
+
}
|
|
11537
|
+
}
|
|
11538
|
+
if (platform.tiers || ws.tiers) {
|
|
11539
|
+
const tiers = {};
|
|
11540
|
+
for (const layer of [platform.tiers, ws.tiers]) {
|
|
11541
|
+
for (const [tier, model] of Object.entries(layer ?? {})) {
|
|
11542
|
+
if (model)
|
|
11543
|
+
tiers[tier] = model;
|
|
11544
|
+
}
|
|
11545
|
+
}
|
|
11546
|
+
merged.tiers = tiers;
|
|
11547
|
+
}
|
|
11548
|
+
return merged;
|
|
11549
|
+
}
|
|
11458
11550
|
function workspaceConfigToPartial(ws) {
|
|
11459
11551
|
const claude = {};
|
|
11460
11552
|
if (ws.implement_model)
|
|
@@ -11503,6 +11595,15 @@ function resolveEffectiveAgentConfig(cfg, ws) {
|
|
|
11503
11595
|
const withWorkspace = ws ? mergeAgentConfig(DEFAULT_AGENT_CONFIG, workspaceConfigToPartial(ws)) : DEFAULT_AGENT_CONFIG;
|
|
11504
11596
|
return mergeAgentConfig(mergeAgentConfig(withWorkspace, cfg.agentOverrides), cfg.repoAgentOverrides);
|
|
11505
11597
|
}
|
|
11598
|
+
function withdrawnMapFrom(models) {
|
|
11599
|
+
const withdrawn = {};
|
|
11600
|
+
for (const m of models) {
|
|
11601
|
+
if (m.status === "withdrawn" && m.replaced_by) {
|
|
11602
|
+
withdrawn[m.id] = m.replaced_by;
|
|
11603
|
+
}
|
|
11604
|
+
}
|
|
11605
|
+
return withdrawn;
|
|
11606
|
+
}
|
|
11506
11607
|
var init_workspace_model_config = __esm(() => {
|
|
11507
11608
|
init_config_merge();
|
|
11508
11609
|
init_types();
|
|
@@ -11977,7 +12078,7 @@ class Worker {
|
|
|
11977
12078
|
stageCtx = await this.resolveStageContext(card);
|
|
11978
12079
|
if (stageCtx.kind === "hold") {
|
|
11979
12080
|
this.held = true;
|
|
11980
|
-
await this.holdStageCard(card, stageCtx.reason, stageCtx.wait);
|
|
12081
|
+
await this.holdStageCard(card, stageCtx.reason, stageCtx.wait, stageCtx.wait);
|
|
11981
12082
|
return;
|
|
11982
12083
|
}
|
|
11983
12084
|
if (stageCtx.kind === "fanout") {
|
|
@@ -12535,15 +12636,35 @@ class Worker {
|
|
|
12535
12636
|
}
|
|
12536
12637
|
}
|
|
12537
12638
|
}
|
|
12538
|
-
async holdStageCard(card, reason, wait = false) {
|
|
12639
|
+
async holdStageCard(card, reason, wait = false, armCooldown = false) {
|
|
12539
12640
|
log34.info(this.tag, `Holding #${card.short_id}: ${reason}`);
|
|
12540
12641
|
await this.stateStore.decrementAttempt(card.id);
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12642
|
+
let announced = false;
|
|
12643
|
+
const holdStage = armCooldown ? card.current_stage : null;
|
|
12644
|
+
if (holdStage) {
|
|
12645
|
+
try {
|
|
12646
|
+
const recorded = await this.stateStore.recordStageHold(card.id, {
|
|
12647
|
+
stageId: holdStage,
|
|
12648
|
+
reason,
|
|
12649
|
+
untilMs: stageHoldUntil(this.config.playbooks.humanStageHoldMinutes ?? DEFAULT_HUMAN_STAGE_HOLD_MINUTES, Date.now())
|
|
12650
|
+
});
|
|
12651
|
+
announced = recorded.announced;
|
|
12652
|
+
} catch (err) {
|
|
12653
|
+
log34.warn(this.tag, `stage hold not recorded on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
12654
|
+
}
|
|
12655
|
+
}
|
|
12656
|
+
if (announced) {
|
|
12657
|
+
log34.debug(this.tag, `#${card.short_id} already carries this blocker — not reposting`);
|
|
12658
|
+
} else {
|
|
12659
|
+
try {
|
|
12660
|
+
await this.client.addComment(card.id, reason, {
|
|
12661
|
+
commentType: "blocker",
|
|
12662
|
+
agentSessionId: this.sessionId ?? undefined
|
|
12663
|
+
});
|
|
12664
|
+
} catch {
|
|
12665
|
+
await this.stateStore.clearStageHold(card.id).catch(() => {});
|
|
12666
|
+
}
|
|
12667
|
+
}
|
|
12547
12668
|
try {
|
|
12548
12669
|
await runTransition(this.client, card, {
|
|
12549
12670
|
removeLabels: ["agent"],
|
|
@@ -13970,19 +14091,10 @@ class Pool {
|
|
|
13970
14091
|
this.reviewQueue = new PriorityQueue(config);
|
|
13971
14092
|
this.budget = new BudgetGuard(config.budget, this.stateStore);
|
|
13972
14093
|
const workspaceModels = daemonConfig ? new WorkspaceModelCache(async () => {
|
|
13973
|
-
const { config: wsConfig } = await client.getWorkspaceModelConfig(workspaceId);
|
|
13974
|
-
return wsConfig
|
|
13975
|
-
}) : undefined;
|
|
13976
|
-
const modelCatalog = daemonConfig ? new WorkspaceModelCache(async () => {
|
|
13977
|
-
const { models } = await client.getModelCatalog();
|
|
13978
|
-
const withdrawn = {};
|
|
13979
|
-
for (const m of models) {
|
|
13980
|
-
if (m.status === "withdrawn" && m.replaced_by) {
|
|
13981
|
-
withdrawn[m.id] = m.replaced_by;
|
|
13982
|
-
}
|
|
13983
|
-
}
|
|
13984
|
-
return withdrawn;
|
|
14094
|
+
const { config: wsConfig, platformDefaults } = await client.getWorkspaceModelConfig(workspaceId);
|
|
14095
|
+
return layerPlatformDefaults(platformDefaults, wsConfig);
|
|
13985
14096
|
}) : undefined;
|
|
14097
|
+
const modelCatalog = daemonConfig ? new WorkspaceModelCache(async () => withdrawnMapFrom((await client.getModelCatalog()).models)) : undefined;
|
|
13986
14098
|
const reportedConfig = daemonConfig ? new ReportedConfigCache(client, effectiveModelReport(config)) : undefined;
|
|
13987
14099
|
for (let i = 0;i < config.poolSize; i++) {
|
|
13988
14100
|
this.implWorkers.push(new Worker(i, config, client, this.identity, () => {
|
|
@@ -14038,6 +14150,14 @@ class Pool {
|
|
|
14038
14150
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
14039
14151
|
return;
|
|
14040
14152
|
}
|
|
14153
|
+
const stageHold = classifyStageHold(card, this.stateStore.getStageHold(card.id), Date.now());
|
|
14154
|
+
if (stageHold.kind === "holding") {
|
|
14155
|
+
log36.debug(TAG32, `#${card.short_id} held — ${stageHold.detail}`);
|
|
14156
|
+
return;
|
|
14157
|
+
}
|
|
14158
|
+
if (stageHold.kind === "stale") {
|
|
14159
|
+
await this.stateStore.clearStageHold(card.id);
|
|
14160
|
+
}
|
|
14041
14161
|
const decision = this.budget.check(card.id);
|
|
14042
14162
|
if (!decision.allow) {
|
|
14043
14163
|
if (decision.reason === "daily_budget") {
|
|
@@ -14736,6 +14856,7 @@ var init_pool = __esm(() => {
|
|
|
14736
14856
|
init_unblock();
|
|
14737
14857
|
init_worker();
|
|
14738
14858
|
init_workspace_model_cache();
|
|
14859
|
+
init_workspace_model_config();
|
|
14739
14860
|
});
|
|
14740
14861
|
|
|
14741
14862
|
// src/port-registry.ts
|
|
@@ -14974,6 +15095,8 @@ var init_repo_dirs = __esm(() => {
|
|
|
14974
15095
|
|
|
14975
15096
|
// src/card-trust.ts
|
|
14976
15097
|
function isTrustedCardAuthor(card, trustedAuthors) {
|
|
15098
|
+
if (card.source != null)
|
|
15099
|
+
return false;
|
|
14977
15100
|
return card.created_by != null && trustedAuthors.has(card.created_by);
|
|
14978
15101
|
}
|
|
14979
15102
|
|
|
@@ -15033,7 +15156,7 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
15033
15156
|
continue;
|
|
15034
15157
|
const daemonBranch = daemonBranches.get(card.id);
|
|
15035
15158
|
if (!isTrustedCardAuthor(card, trustedAuthors) && daemonBranch !== branch) {
|
|
15036
|
-
log40.debug(TAG36, `#${card.short_id} - skipped: author ${card.created_by ?? "(none)"} is not trusted, and ${daemonBranch ? `the card records ${branch} while this daemon pushed ${daemonBranch}` : "this daemon has pushed no branch for it"}`);
|
|
15159
|
+
log40.debug(TAG36, `#${card.short_id} - skipped: ${card.source != null ? `card was filed externally (source: ${card.source})` : `author ${card.created_by ?? "(none)"} is not trusted`}, and ${daemonBranch ? `the card records ${branch} while this daemon pushed ${daemonBranch}` : "this daemon has pushed no branch for it"}`);
|
|
15037
15160
|
continue;
|
|
15038
15161
|
}
|
|
15039
15162
|
const labels = resolveCardLabels(card, labelMap);
|
|
@@ -15271,7 +15394,7 @@ function rankSweepCandidates(opts) {
|
|
|
15271
15394
|
continue;
|
|
15272
15395
|
}
|
|
15273
15396
|
if (!isTrustedCardAuthor(card, trustedAuthors)) {
|
|
15274
|
-
skip2(card, "untrusted_author", card.created_by == null ? "card has no recorded author" : `author ${card.created_by} is not in sweep.trustedAuthors`);
|
|
15397
|
+
skip2(card, "untrusted_author", card.source != null ? `card was filed externally (source: ${card.source})` : card.created_by == null ? "card has no recorded author" : `author ${card.created_by} is not in sweep.trustedAuthors`);
|
|
15275
15398
|
continue;
|
|
15276
15399
|
}
|
|
15277
15400
|
if (card.assigned_agent_id != null) {
|
|
@@ -16107,6 +16230,93 @@ var init_reconcile = __esm(() => {
|
|
|
16107
16230
|
init_types();
|
|
16108
16231
|
});
|
|
16109
16232
|
|
|
16233
|
+
// src/startup-model.ts
|
|
16234
|
+
function describeStartupModel(config, layers, withdrawn) {
|
|
16235
|
+
const workspace = layers?.workspace ?? null;
|
|
16236
|
+
const platform = layers?.platform ?? null;
|
|
16237
|
+
const database = layerPlatformDefaults(platform, workspace);
|
|
16238
|
+
const claude = resolveEffectiveAgentConfig(config, database).claude;
|
|
16239
|
+
const pinned = ownObject(config.agentOverrides, "claude");
|
|
16240
|
+
const pinnedTiers = ownObject(pinned, "tiers");
|
|
16241
|
+
const pinnedModel = ownString(pinned, "model");
|
|
16242
|
+
let source = "built-in default";
|
|
16243
|
+
if (pinnedModel)
|
|
16244
|
+
source = "config.json";
|
|
16245
|
+
else if (workspace?.implement_model)
|
|
16246
|
+
source = "workspace";
|
|
16247
|
+
else if (platform?.implement_model)
|
|
16248
|
+
source = "platform";
|
|
16249
|
+
const warnings = [];
|
|
16250
|
+
const pins = [
|
|
16251
|
+
["claude.model", pinnedModel, database?.implement_model],
|
|
16252
|
+
[
|
|
16253
|
+
"claude.escalateModel",
|
|
16254
|
+
ownString(pinned, "escalateModel"),
|
|
16255
|
+
database?.escalate_model
|
|
16256
|
+
],
|
|
16257
|
+
...TIERS.map((tier) => [
|
|
16258
|
+
`claude.tiers.${tier}`,
|
|
16259
|
+
ownString(pinnedTiers, tier),
|
|
16260
|
+
database?.tiers?.[tier]
|
|
16261
|
+
])
|
|
16262
|
+
];
|
|
16263
|
+
for (const [key, local, db] of pins) {
|
|
16264
|
+
if (local && db && local !== db) {
|
|
16265
|
+
warnings.push(`config.json pins ${key} to "${local}", which overrides the database default "${db}" — remove it from config.json to follow the app's model settings`);
|
|
16266
|
+
}
|
|
16267
|
+
}
|
|
16268
|
+
const effective = [
|
|
16269
|
+
["claude.model", claude.model],
|
|
16270
|
+
["claude.escalateModel", claude.escalateModel],
|
|
16271
|
+
...TIERS.map((tier) => [`claude.tiers.${tier}`, claude.tiers[tier]])
|
|
16272
|
+
];
|
|
16273
|
+
for (const [key, id] of effective) {
|
|
16274
|
+
const replacement = withdrawn?.[id];
|
|
16275
|
+
if (replacement) {
|
|
16276
|
+
warnings.push(`${key} resolves to "${id}", which is withdrawn in the model catalog — runs use its replacement "${replacement}"; update the setting to match`);
|
|
16277
|
+
}
|
|
16278
|
+
}
|
|
16279
|
+
return {
|
|
16280
|
+
model: claude.model,
|
|
16281
|
+
escalateModel: claude.escalateModel,
|
|
16282
|
+
reviewModel: config.agent.claude.reviewModel,
|
|
16283
|
+
source,
|
|
16284
|
+
resolved: layers !== null && withdrawn !== null,
|
|
16285
|
+
warnings
|
|
16286
|
+
};
|
|
16287
|
+
}
|
|
16288
|
+
async function resolveStartupModel(config, client) {
|
|
16289
|
+
const [layers, withdrawn] = await Promise.all([
|
|
16290
|
+
client.getWorkspaceModelConfig(config.workspaceId).then(({ config: workspace, platformDefaults }) => ({
|
|
16291
|
+
workspace,
|
|
16292
|
+
platform: platformDefaults ?? null
|
|
16293
|
+
})).catch(() => null),
|
|
16294
|
+
client.getModelCatalog().then(({ models }) => withdrawnMapFrom(models)).catch(() => null)
|
|
16295
|
+
]);
|
|
16296
|
+
return describeStartupModel(config, layers, withdrawn);
|
|
16297
|
+
}
|
|
16298
|
+
function ownObject(source, key) {
|
|
16299
|
+
if (typeof source !== "object" || source === null)
|
|
16300
|
+
return {};
|
|
16301
|
+
const value = own(source, key);
|
|
16302
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
16303
|
+
}
|
|
16304
|
+
function ownString(source, key) {
|
|
16305
|
+
const value = own(source, key);
|
|
16306
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
16307
|
+
}
|
|
16308
|
+
function startupModelLine(m) {
|
|
16309
|
+
const models = m.model === m.escalateModel ? m.model : `${m.model} → ${m.escalateModel} on retry/high · review ${m.reviewModel}`;
|
|
16310
|
+
const origin = m.resolved ? `from ${m.source}` : `from ${m.source}; database model settings unreachable, a pickup may resolve differently`;
|
|
16311
|
+
return `${models} (${origin})`;
|
|
16312
|
+
}
|
|
16313
|
+
var TIERS;
|
|
16314
|
+
var init_startup_model = __esm(() => {
|
|
16315
|
+
init_src();
|
|
16316
|
+
init_workspace_model_config();
|
|
16317
|
+
TIERS = ["simple", "advanced", "research"];
|
|
16318
|
+
});
|
|
16319
|
+
|
|
16110
16320
|
// src/startup-banner.ts
|
|
16111
16321
|
var exports_startup_banner = {};
|
|
16112
16322
|
__export(exports_startup_banner, {
|
|
@@ -16122,6 +16332,7 @@ function prettyBanner(config, version) {
|
|
|
16122
16332
|
let projectName;
|
|
16123
16333
|
let gitProvider;
|
|
16124
16334
|
let httpPort;
|
|
16335
|
+
let model;
|
|
16125
16336
|
let failed = false;
|
|
16126
16337
|
let rendered = false;
|
|
16127
16338
|
return {
|
|
@@ -16134,6 +16345,9 @@ function prettyBanner(config, version) {
|
|
|
16134
16345
|
setHttpPort(port) {
|
|
16135
16346
|
httpPort = port;
|
|
16136
16347
|
},
|
|
16348
|
+
setModel(resolved) {
|
|
16349
|
+
model = resolved;
|
|
16350
|
+
},
|
|
16137
16351
|
check(message) {
|
|
16138
16352
|
checks.push({ kind: "ok", message });
|
|
16139
16353
|
},
|
|
@@ -16155,6 +16369,7 @@ function prettyBanner(config, version) {
|
|
|
16155
16369
|
projectName,
|
|
16156
16370
|
gitProvider,
|
|
16157
16371
|
httpPort,
|
|
16372
|
+
model,
|
|
16158
16373
|
checks,
|
|
16159
16374
|
readyMessage: message
|
|
16160
16375
|
});
|
|
@@ -16164,12 +16379,13 @@ function prettyBanner(config, version) {
|
|
|
16164
16379
|
}
|
|
16165
16380
|
function jsonBanner(config, version) {
|
|
16166
16381
|
log45.info(TAG41, `Harmony Agent Daemon v${version} starting...`);
|
|
16167
|
-
log45.info(TAG41, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} |
|
|
16382
|
+
log45.info(TAG41, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
16168
16383
|
log45.info(TAG41, `Config: ${contextSourceLine(config)}`);
|
|
16169
16384
|
if (config.agent.review.enabled) {
|
|
16170
16385
|
log45.info(TAG41, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
16171
16386
|
}
|
|
16172
16387
|
let failed = false;
|
|
16388
|
+
let modelLogged = false;
|
|
16173
16389
|
return {
|
|
16174
16390
|
setProjectName(_name) {},
|
|
16175
16391
|
setGitProvider(provider) {
|
|
@@ -16178,6 +16394,10 @@ function jsonBanner(config, version) {
|
|
|
16178
16394
|
setHttpPort(port) {
|
|
16179
16395
|
log45.info(TAG41, `HTTP server on port ${port}`);
|
|
16180
16396
|
},
|
|
16397
|
+
setModel(resolved) {
|
|
16398
|
+
modelLogged = true;
|
|
16399
|
+
log45.info(TAG41, `Model: ${startupModelLine(resolved)}`);
|
|
16400
|
+
},
|
|
16181
16401
|
check(message) {
|
|
16182
16402
|
log45.info(TAG41, message);
|
|
16183
16403
|
},
|
|
@@ -16190,6 +16410,8 @@ function jsonBanner(config, version) {
|
|
|
16190
16410
|
async ready(message) {
|
|
16191
16411
|
if (failed)
|
|
16192
16412
|
return;
|
|
16413
|
+
if (!modelLogged)
|
|
16414
|
+
log45.info(TAG41, `Model: ${localModelLine(config)}`);
|
|
16193
16415
|
log45.info(TAG41, message);
|
|
16194
16416
|
}
|
|
16195
16417
|
};
|
|
@@ -16201,6 +16423,7 @@ function renderPretty(input) {
|
|
|
16201
16423
|
projectName,
|
|
16202
16424
|
gitProvider,
|
|
16203
16425
|
httpPort,
|
|
16426
|
+
model,
|
|
16204
16427
|
checks,
|
|
16205
16428
|
readyMessage
|
|
16206
16429
|
} = input;
|
|
@@ -16208,7 +16431,7 @@ function renderPretty(input) {
|
|
|
16208
16431
|
lines.push("");
|
|
16209
16432
|
lines.push(titleRule(`Harmony Agent Daemon v${version}`));
|
|
16210
16433
|
lines.push("");
|
|
16211
|
-
for (const row of configRows(config, projectName, gitProvider, httpPort)) {
|
|
16434
|
+
for (const row of configRows(config, projectName, gitProvider, httpPort, model)) {
|
|
16212
16435
|
lines.push(` ${dim(row.label.padEnd(9))} ${row.value}`);
|
|
16213
16436
|
}
|
|
16214
16437
|
lines.push("");
|
|
@@ -16222,7 +16445,7 @@ function renderPretty(input) {
|
|
|
16222
16445
|
return lines.join(`
|
|
16223
16446
|
`);
|
|
16224
16447
|
}
|
|
16225
|
-
function configRows(config, projectName, gitProvider, httpPort) {
|
|
16448
|
+
function configRows(config, projectName, gitProvider, httpPort, model) {
|
|
16226
16449
|
const rows = [];
|
|
16227
16450
|
const projectLabel = projectName ? `${projectName} (${shortenId(config.projectId)})` : shortenId(config.projectId);
|
|
16228
16451
|
rows.push({ label: "Project", value: projectLabel });
|
|
@@ -16231,8 +16454,7 @@ function configRows(config, projectName, gitProvider, httpPort) {
|
|
|
16231
16454
|
const reviewEnabled = config.agent.review.enabled;
|
|
16232
16455
|
const poolDesc = reviewEnabled ? `Pool ${config.agent.poolSize} impl + ${config.agent.review.poolSize} review` : `Pool ${config.agent.poolSize} impl`;
|
|
16233
16456
|
const flowDesc = reviewEnabled ? `Pickup ${config.agent.pickupColumns[0]} → ${config.agent.completion.moveToColumn} → ${config.agent.review.moveToColumn}` : `Pickup ${config.agent.pickupColumns.join(", ")}`;
|
|
16234
|
-
const
|
|
16235
|
-
const modelDesc = model === escalateModel ? model : `${model} → ${escalateModel} on retry/high · review ${reviewModel}`;
|
|
16457
|
+
const modelDesc = model ? startupModelLine(model) : localModelLine(config);
|
|
16236
16458
|
rows.push({
|
|
16237
16459
|
label: "Model",
|
|
16238
16460
|
value: `${modelDesc} · ${poolDesc} · ${flowDesc}`
|
|
@@ -16266,6 +16488,11 @@ function titleRule(title) {
|
|
|
16266
16488
|
const suffix = "─".repeat(Math.max(3, RULE_WIDTH - prefix.length - title.length - surround.length));
|
|
16267
16489
|
return dim(`${prefix}${title}${surround}${suffix}`);
|
|
16268
16490
|
}
|
|
16491
|
+
function localModelLine(config) {
|
|
16492
|
+
const { model, escalateModel, reviewModel } = config.agent.claude;
|
|
16493
|
+
const models = model === escalateModel ? model : `${model} → ${escalateModel} on retry/high · review ${reviewModel}`;
|
|
16494
|
+
return `${models} (local config only; database model settings not read)`;
|
|
16495
|
+
}
|
|
16269
16496
|
function runnerDesc(runner) {
|
|
16270
16497
|
return runner === "sdk" ? "sdk (Agent SDK)" : "cli (Claude CLI)";
|
|
16271
16498
|
}
|
|
@@ -16286,6 +16513,7 @@ function yellow(s) {
|
|
|
16286
16513
|
var TAG41 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
16287
16514
|
var init_startup_banner = __esm(() => {
|
|
16288
16515
|
init_port_registry();
|
|
16516
|
+
init_startup_model();
|
|
16289
16517
|
ANSI = {
|
|
16290
16518
|
reset: "\x1B[0m",
|
|
16291
16519
|
dim: "\x1B[2m",
|
|
@@ -17643,6 +17871,11 @@ async function main() {
|
|
|
17643
17871
|
} catch (err) {
|
|
17644
17872
|
log50.warn(TAG46, `Could not report model config: ${err instanceof Error ? err.message : String(err)}`);
|
|
17645
17873
|
}
|
|
17874
|
+
const startupModel = await resolveStartupModel(config, client);
|
|
17875
|
+
banner.setModel(startupModel);
|
|
17876
|
+
for (const warning of startupModel.warnings) {
|
|
17877
|
+
banner.warn(warning);
|
|
17878
|
+
}
|
|
17646
17879
|
migrateConfigDir();
|
|
17647
17880
|
try {
|
|
17648
17881
|
migrateLegacyStateFile({
|
|
@@ -17965,6 +18198,7 @@ var init_src2 = __esm(() => {
|
|
|
17965
18198
|
init_recovery();
|
|
17966
18199
|
init_review_worktree();
|
|
17967
18200
|
init_startup_banner();
|
|
18201
|
+
init_startup_model();
|
|
17968
18202
|
init_state_migration();
|
|
17969
18203
|
init_state_store();
|
|
17970
18204
|
init_stream_parser_selftest();
|
|
@@ -18082,7 +18316,7 @@ var init_head_anchor = () => {};
|
|
|
18082
18316
|
|
|
18083
18317
|
// src/merge-cleanup.ts
|
|
18084
18318
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
18085
|
-
import { existsSync as existsSync7, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
|
18319
|
+
import { existsSync as existsSync7, lstatSync as lstatSync3, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
|
18086
18320
|
import { basename, dirname as dirname7, isAbsolute, resolve as resolve6, sep as sep4 } from "node:path";
|
|
18087
18321
|
import { cleanupWorktree as cleanupWorktree6, GIT_NO_HOOKS as GIT_NO_HOOKS12, log as log52 } from "@gethmy/harness";
|
|
18088
18322
|
function canonicalize(p) {
|
|
@@ -18187,8 +18421,20 @@ function classifyWorktreeRegistration(worktreePath, cwd) {
|
|
|
18187
18421
|
function isRegisteredWorktree(worktreePath, cwd) {
|
|
18188
18422
|
return classifyWorktreeRegistration(worktreePath, cwd).kind === "registered";
|
|
18189
18423
|
}
|
|
18424
|
+
function pathAbsent(p) {
|
|
18425
|
+
try {
|
|
18426
|
+
lstatSync3(p);
|
|
18427
|
+
return false;
|
|
18428
|
+
} catch {
|
|
18429
|
+
return true;
|
|
18430
|
+
}
|
|
18431
|
+
}
|
|
18190
18432
|
function cleanupMergedWorktree(args) {
|
|
18191
|
-
|
|
18433
|
+
const registration = classifyWorktreeRegistration(args.worktreePath, args.cwd);
|
|
18434
|
+
if (registration.kind === "unregistered" && pathAbsent(args.worktreePath)) {
|
|
18435
|
+
return { removed: true, alreadyGone: true };
|
|
18436
|
+
}
|
|
18437
|
+
if (registration.kind !== "registered") {
|
|
18192
18438
|
const reason = `refusing to clean up ${args.worktreePath} — git does not list it as a ` + `worktree of the repository at ${args.cwd}, and removing a path git ` + `does not manage would be a recursive delete of an unknown directory. ` + `Pass the worktree path Step B5 created, and re-run from the ` + `repository root.`;
|
|
18193
18439
|
log52.warn(TAG48, reason);
|
|
18194
18440
|
return { removed: false, reason };
|
|
@@ -18461,7 +18707,8 @@ async function finish(deps) {
|
|
|
18461
18707
|
deps.emit({
|
|
18462
18708
|
type: "cleaned",
|
|
18463
18709
|
worktree: worktreePath,
|
|
18464
|
-
branch: branchGone ? branch : null
|
|
18710
|
+
branch: branchGone ? branch : null,
|
|
18711
|
+
...out.alreadyGone ? { alreadyRemoved: true } : {}
|
|
18465
18712
|
});
|
|
18466
18713
|
}
|
|
18467
18714
|
} catch (err) {
|
|
@@ -18632,6 +18879,7 @@ var init_scan_commands = __esm(() => {
|
|
|
18632
18879
|
});
|
|
18633
18880
|
|
|
18634
18881
|
// src/cli.ts
|
|
18882
|
+
init_src();
|
|
18635
18883
|
init_package_version();
|
|
18636
18884
|
import { readFileSync as readFileSync11, realpathSync as realpathSync4 } from "node:fs";
|
|
18637
18885
|
import { dirname as dirname8, join as join10 } from "node:path";
|
|
@@ -18911,7 +19159,7 @@ async function reportBuildFreshness(version) {
|
|
|
18911
19159
|
}
|
|
18912
19160
|
if (published === version)
|
|
18913
19161
|
return;
|
|
18914
|
-
process.stderr.write(`preflight: npm serves ${published}. If that is NEWER than the build above, this one is stale — ` +
|
|
19162
|
+
process.stderr.write(`preflight: npm serves ${published}. If that is NEWER than the build above, this one is stale — ` + `\`${HMY_NPM_INSTALL_COMMAND}\` and run it as \`${HMY_AGENT_COMMAND}\`, or clear the npx cache (\`rm -rf ~/.npm/_npx\`), because \`@latest\` alone does not re-resolve it.
|
|
18915
19163
|
`);
|
|
18916
19164
|
}
|
|
18917
19165
|
async function doctorCommand() {
|
package/dist/index.js
CHANGED
|
@@ -1102,9 +1102,14 @@ var init_commentSerializer = __esm(() => {
|
|
|
1102
1102
|
});
|
|
1103
1103
|
|
|
1104
1104
|
// ../harmony-shared/src/connectCommands.ts
|
|
1105
|
-
|
|
1105
|
+
function hmyConnectCommand(projectSlug) {
|
|
1106
|
+
const slug = projectSlug?.trim();
|
|
1107
|
+
return slug ? `${CONNECT_VERB} ${slug}` : CONNECT_VERB;
|
|
1108
|
+
}
|
|
1109
|
+
var HMY_INSTALL_URL = "https://gethmy.com/install.sh", HMY_INSTALL_COMMAND, HMY_AGENT_COMMAND = "hmy agent", CONNECT_VERB = "hmy connect", HOSTED_MCP_URL = "https://mcp.gethmy.com/mcp", HOSTED_MCP_CONFIG;
|
|
1106
1110
|
var init_connectCommands = __esm(() => {
|
|
1107
1111
|
HMY_INSTALL_COMMAND = `curl -fsSL ${HMY_INSTALL_URL} | bash`;
|
|
1112
|
+
HOSTED_MCP_CONFIG = JSON.stringify({ mcpServers: { harmony: { type: "http", url: HOSTED_MCP_URL } } }, null, 2);
|
|
1108
1113
|
});
|
|
1109
1114
|
|
|
1110
1115
|
// ../harmony-shared/src/constants.ts
|
|
@@ -1577,6 +1582,18 @@ function own(source, key) {
|
|
|
1577
1582
|
return Object.hasOwn(source, key) ? source[key] : undefined;
|
|
1578
1583
|
}
|
|
1579
1584
|
|
|
1585
|
+
// ../harmony-shared/src/packageCommands.ts
|
|
1586
|
+
function npxArgs(pkg, args) {
|
|
1587
|
+
return ["-y", `${SCOPE}/${pkg}@latest`, ...args];
|
|
1588
|
+
}
|
|
1589
|
+
function npxCommand(pkg, args = []) {
|
|
1590
|
+
return ["npx", ...npxArgs(pkg, args)].join(" ");
|
|
1591
|
+
}
|
|
1592
|
+
var SCOPE = "@gethmy", HMY_NPM_INSTALL_COMMAND;
|
|
1593
|
+
var init_packageCommands = __esm(() => {
|
|
1594
|
+
HMY_NPM_INSTALL_COMMAND = `npm i -g ${SCOPE}/cli`;
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1580
1597
|
// ../harmony-shared/src/parsedState.ts
|
|
1581
1598
|
function parseJsonWithoutPrototypes(text) {
|
|
1582
1599
|
return JSON.parse(text, (_key, value) => {
|
|
@@ -2574,6 +2591,7 @@ var init_src = __esm(() => {
|
|
|
2574
2591
|
init_gateEvaluate();
|
|
2575
2592
|
init_logger();
|
|
2576
2593
|
init_memoryQuery();
|
|
2594
|
+
init_packageCommands();
|
|
2577
2595
|
init_playbookAutoBind();
|
|
2578
2596
|
init_playbookCatalog();
|
|
2579
2597
|
init_playbookStage();
|
|
@@ -3515,7 +3533,7 @@ function agentIdentifier(workerId) {
|
|
|
3515
3533
|
function endStatusForCancel(reason) {
|
|
3516
3534
|
return reason === "human_stop" ? "cancelled" : "paused";
|
|
3517
3535
|
}
|
|
3518
|
-
var DEFAULT_FINDING_AUTHORS, DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", REVIEW_FAILURE_REASON_MAX = 300, AGENT_NAME = "Harmony Agent", DAEMON_IDENTIFIER_BASE = "harmony-daemon", NOTICE_IDENTIFIER;
|
|
3536
|
+
var DEFAULT_FINDING_AUTHORS, DEFAULT_HUMAN_STAGE_HOLD_MINUTES = 60, DEFAULT_AGENT_CONFIG, IN_PROGRESS_COLUMN = "In Progress", NEED_REVIEW_LABEL = "Need Review", NEED_REVIEW_LABEL_COLOR = "#f59e0b", REVIEW_FAILURE_REASON_MAX = 300, AGENT_NAME = "Harmony Agent", DAEMON_IDENTIFIER_BASE = "harmony-daemon", NOTICE_IDENTIFIER;
|
|
3519
3537
|
var init_types = __esm(() => {
|
|
3520
3538
|
init_src();
|
|
3521
3539
|
init_board_review();
|
|
@@ -3547,12 +3565,12 @@ var init_types = __esm(() => {
|
|
|
3547
3565
|
postSummary: true
|
|
3548
3566
|
},
|
|
3549
3567
|
claude: {
|
|
3550
|
-
model: "claude-opus-5",
|
|
3568
|
+
model: "claude-opus-5-5",
|
|
3551
3569
|
escalateModel: "claude-fable-5-1",
|
|
3552
3570
|
escalateAfterAttempts: 2,
|
|
3553
3571
|
tiers: {
|
|
3554
3572
|
simple: "claude-sonnet-5",
|
|
3555
|
-
advanced: "claude-opus-5",
|
|
3573
|
+
advanced: "claude-opus-5-5",
|
|
3556
3574
|
research: "claude-fable-5-1"
|
|
3557
3575
|
},
|
|
3558
3576
|
scoreBands: DEFAULT_SCORE_BANDS,
|
|
@@ -3659,7 +3677,12 @@ var init_types = __esm(() => {
|
|
|
3659
3677
|
runLogDays: 30
|
|
3660
3678
|
},
|
|
3661
3679
|
planning: DEFAULT_PLANNING_CONFIG,
|
|
3662
|
-
playbooks: {
|
|
3680
|
+
playbooks: {
|
|
3681
|
+
enabled: true,
|
|
3682
|
+
humanStageColumns: [],
|
|
3683
|
+
humanStageHoldMinutes: DEFAULT_HUMAN_STAGE_HOLD_MINUTES,
|
|
3684
|
+
metrics: {}
|
|
3685
|
+
},
|
|
3663
3686
|
contractFirst: DEFAULT_CONTRACT_CONFIG,
|
|
3664
3687
|
boardReview: DEFAULT_BOARD_REVIEW_CONFIG,
|
|
3665
3688
|
sweep: {
|
|
@@ -4040,13 +4063,13 @@ function loadDaemonConfig() {
|
|
|
4040
4063
|
const projectId = getActiveProjectId(repoRoot);
|
|
4041
4064
|
const userEmail = getUserEmail();
|
|
4042
4065
|
if (!workspaceId) {
|
|
4043
|
-
throw new Error(`No active workspace configured in ${contextSource.path}. Run \`
|
|
4066
|
+
throw new Error(`No active workspace configured in ${contextSource.path}. Run \`${hmyConnectCommand()}\` first (no install: \`${npxCommand("mcp", ["connect"])}\`).`);
|
|
4044
4067
|
}
|
|
4045
4068
|
if (!projectId) {
|
|
4046
|
-
throw new Error(`No active project configured in ${contextSource.path}. Run \`
|
|
4069
|
+
throw new Error(`No active project configured in ${contextSource.path}. Run \`${hmyConnectCommand()}\` first (no install: \`${npxCommand("mcp", ["connect"])}\`).`);
|
|
4047
4070
|
}
|
|
4048
4071
|
if (!userEmail) {
|
|
4049
|
-
throw new Error(`No user email configured in ${contextSource.globalPath}. Run \`
|
|
4072
|
+
throw new Error(`No user email configured in ${contextSource.globalPath}. Run \`${hmyConnectCommand()}\` first (no install: \`${npxCommand("mcp", ["connect"])}\`).`);
|
|
4050
4073
|
}
|
|
4051
4074
|
let agentOverrides = {};
|
|
4052
4075
|
let agentName = "Harmony Agent";
|
|
@@ -4159,6 +4182,7 @@ function createApiClient(config) {
|
|
|
4159
4182
|
}
|
|
4160
4183
|
var TAG5 = "config", NOT_A_REPOSITORY_EXIT = 128, warnedLegacyConfigDir = false;
|
|
4161
4184
|
var init_config = __esm(() => {
|
|
4185
|
+
init_src();
|
|
4162
4186
|
init_config_merge();
|
|
4163
4187
|
init_config_validation();
|
|
4164
4188
|
init_paths();
|
|
@@ -8816,7 +8840,7 @@ class StateStore {
|
|
|
8816
8840
|
const rec = this.getCard(cardId);
|
|
8817
8841
|
if (!rec)
|
|
8818
8842
|
return;
|
|
8819
|
-
if (rec.attempts === 0 && !rec.ciRepairAttempts && rec.ciRepairParkedAt == null && (!clearTurns || !rec.totalTurns && !rec.runStarts)) {
|
|
8843
|
+
if (rec.attempts === 0 && !rec.ciRepairAttempts && rec.ciRepairParkedAt == null && rec.stageHold == null && (!clearTurns || !rec.totalTurns && !rec.runStarts)) {
|
|
8820
8844
|
return;
|
|
8821
8845
|
}
|
|
8822
8846
|
rec.attempts = 0;
|
|
@@ -8826,6 +8850,7 @@ class StateStore {
|
|
|
8826
8850
|
rec.totalTurns = 0;
|
|
8827
8851
|
if (clearTurns)
|
|
8828
8852
|
rec.runStarts = 0;
|
|
8853
|
+
rec.stageHold = null;
|
|
8829
8854
|
await this.persist();
|
|
8830
8855
|
}
|
|
8831
8856
|
getCiRepairAttempts(cardId) {
|
|
@@ -8908,6 +8933,29 @@ class StateStore {
|
|
|
8908
8933
|
getCardRunStarts(cardId) {
|
|
8909
8934
|
return this.getCard(cardId)?.runStarts ?? 0;
|
|
8910
8935
|
}
|
|
8936
|
+
getStageHold(cardId) {
|
|
8937
|
+
return this.getCard(cardId)?.stageHold ?? null;
|
|
8938
|
+
}
|
|
8939
|
+
async recordStageHold(cardId, hold) {
|
|
8940
|
+
const rec = this.ensureCard(cardId);
|
|
8941
|
+
const prior = rec.stageHold ?? null;
|
|
8942
|
+
const announced = prior != null && prior.stageId === hold.stageId && prior.reason === hold.reason;
|
|
8943
|
+
rec.stageHold = cloneWithoutPrototype({
|
|
8944
|
+
stageId: hold.stageId,
|
|
8945
|
+
reason: hold.reason,
|
|
8946
|
+
until: hold.untilMs,
|
|
8947
|
+
announcedAt: announced ? prior.announcedAt : Date.now()
|
|
8948
|
+
});
|
|
8949
|
+
await this.persist();
|
|
8950
|
+
return { announced };
|
|
8951
|
+
}
|
|
8952
|
+
async clearStageHold(cardId) {
|
|
8953
|
+
const rec = this.getCard(cardId);
|
|
8954
|
+
if (!rec || rec.stageHold == null)
|
|
8955
|
+
return;
|
|
8956
|
+
rec.stageHold = null;
|
|
8957
|
+
await this.persist();
|
|
8958
|
+
}
|
|
8911
8959
|
getCardCostCents(cardId) {
|
|
8912
8960
|
return this.getCard(cardId)?.totalCostCents ?? 0;
|
|
8913
8961
|
}
|
|
@@ -10218,6 +10266,27 @@ class SleepGuard {
|
|
|
10218
10266
|
var TAG26 = "sleep-guard";
|
|
10219
10267
|
var init_sleep_guard = () => {};
|
|
10220
10268
|
|
|
10269
|
+
// src/stage-hold.ts
|
|
10270
|
+
function classifyStageHold(card, hold, nowMs) {
|
|
10271
|
+
if (!hold)
|
|
10272
|
+
return { kind: "none" };
|
|
10273
|
+
if (!card.current_stage || card.current_stage !== hold.stageId) {
|
|
10274
|
+
return { kind: "stale" };
|
|
10275
|
+
}
|
|
10276
|
+
if (nowMs >= hold.until)
|
|
10277
|
+
return { kind: "lapsed" };
|
|
10278
|
+
const remainingMin = Math.ceil((hold.until - nowMs) / 60000);
|
|
10279
|
+
return {
|
|
10280
|
+
kind: "holding",
|
|
10281
|
+
detail: `parked on human-owned stage "${hold.stageId}" — holding for ~${remainingMin}m`
|
|
10282
|
+
};
|
|
10283
|
+
}
|
|
10284
|
+
function stageHoldUntil(holdMinutes, nowMs) {
|
|
10285
|
+
if (!Number.isFinite(holdMinutes) || holdMinutes <= 0)
|
|
10286
|
+
return nowMs;
|
|
10287
|
+
return nowMs + holdMinutes * 60000;
|
|
10288
|
+
}
|
|
10289
|
+
|
|
10221
10290
|
// src/unblock.ts
|
|
10222
10291
|
import { log as log30 } from "@gethmy/harness";
|
|
10223
10292
|
async function fetchBlocksLinks(client, cardId) {
|
|
@@ -11454,6 +11523,29 @@ var init_stage_advance = __esm(() => {
|
|
|
11454
11523
|
});
|
|
11455
11524
|
|
|
11456
11525
|
// src/workspace-model-config.ts
|
|
11526
|
+
function layerPlatformDefaults(platform, ws) {
|
|
11527
|
+
if (!platform)
|
|
11528
|
+
return ws ?? null;
|
|
11529
|
+
if (!ws)
|
|
11530
|
+
return platform;
|
|
11531
|
+
const merged = { ...platform };
|
|
11532
|
+
for (const [key, value] of Object.entries(ws)) {
|
|
11533
|
+
if (value !== null && value !== undefined) {
|
|
11534
|
+
merged[key] = value;
|
|
11535
|
+
}
|
|
11536
|
+
}
|
|
11537
|
+
if (platform.tiers || ws.tiers) {
|
|
11538
|
+
const tiers = {};
|
|
11539
|
+
for (const layer of [platform.tiers, ws.tiers]) {
|
|
11540
|
+
for (const [tier, model] of Object.entries(layer ?? {})) {
|
|
11541
|
+
if (model)
|
|
11542
|
+
tiers[tier] = model;
|
|
11543
|
+
}
|
|
11544
|
+
}
|
|
11545
|
+
merged.tiers = tiers;
|
|
11546
|
+
}
|
|
11547
|
+
return merged;
|
|
11548
|
+
}
|
|
11457
11549
|
function workspaceConfigToPartial(ws) {
|
|
11458
11550
|
const claude = {};
|
|
11459
11551
|
if (ws.implement_model)
|
|
@@ -11502,6 +11594,15 @@ function resolveEffectiveAgentConfig(cfg, ws) {
|
|
|
11502
11594
|
const withWorkspace = ws ? mergeAgentConfig(DEFAULT_AGENT_CONFIG, workspaceConfigToPartial(ws)) : DEFAULT_AGENT_CONFIG;
|
|
11503
11595
|
return mergeAgentConfig(mergeAgentConfig(withWorkspace, cfg.agentOverrides), cfg.repoAgentOverrides);
|
|
11504
11596
|
}
|
|
11597
|
+
function withdrawnMapFrom(models) {
|
|
11598
|
+
const withdrawn = {};
|
|
11599
|
+
for (const m of models) {
|
|
11600
|
+
if (m.status === "withdrawn" && m.replaced_by) {
|
|
11601
|
+
withdrawn[m.id] = m.replaced_by;
|
|
11602
|
+
}
|
|
11603
|
+
}
|
|
11604
|
+
return withdrawn;
|
|
11605
|
+
}
|
|
11505
11606
|
var init_workspace_model_config = __esm(() => {
|
|
11506
11607
|
init_config_merge();
|
|
11507
11608
|
init_types();
|
|
@@ -11976,7 +12077,7 @@ class Worker {
|
|
|
11976
12077
|
stageCtx = await this.resolveStageContext(card);
|
|
11977
12078
|
if (stageCtx.kind === "hold") {
|
|
11978
12079
|
this.held = true;
|
|
11979
|
-
await this.holdStageCard(card, stageCtx.reason, stageCtx.wait);
|
|
12080
|
+
await this.holdStageCard(card, stageCtx.reason, stageCtx.wait, stageCtx.wait);
|
|
11980
12081
|
return;
|
|
11981
12082
|
}
|
|
11982
12083
|
if (stageCtx.kind === "fanout") {
|
|
@@ -12534,15 +12635,35 @@ class Worker {
|
|
|
12534
12635
|
}
|
|
12535
12636
|
}
|
|
12536
12637
|
}
|
|
12537
|
-
async holdStageCard(card, reason, wait = false) {
|
|
12638
|
+
async holdStageCard(card, reason, wait = false, armCooldown = false) {
|
|
12538
12639
|
log34.info(this.tag, `Holding #${card.short_id}: ${reason}`);
|
|
12539
12640
|
await this.stateStore.decrementAttempt(card.id);
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12641
|
+
let announced = false;
|
|
12642
|
+
const holdStage = armCooldown ? card.current_stage : null;
|
|
12643
|
+
if (holdStage) {
|
|
12644
|
+
try {
|
|
12645
|
+
const recorded = await this.stateStore.recordStageHold(card.id, {
|
|
12646
|
+
stageId: holdStage,
|
|
12647
|
+
reason,
|
|
12648
|
+
untilMs: stageHoldUntil(this.config.playbooks.humanStageHoldMinutes ?? DEFAULT_HUMAN_STAGE_HOLD_MINUTES, Date.now())
|
|
12649
|
+
});
|
|
12650
|
+
announced = recorded.announced;
|
|
12651
|
+
} catch (err) {
|
|
12652
|
+
log34.warn(this.tag, `stage hold not recorded on #${card.short_id}: ${err instanceof Error ? err.message : err}`);
|
|
12653
|
+
}
|
|
12654
|
+
}
|
|
12655
|
+
if (announced) {
|
|
12656
|
+
log34.debug(this.tag, `#${card.short_id} already carries this blocker — not reposting`);
|
|
12657
|
+
} else {
|
|
12658
|
+
try {
|
|
12659
|
+
await this.client.addComment(card.id, reason, {
|
|
12660
|
+
commentType: "blocker",
|
|
12661
|
+
agentSessionId: this.sessionId ?? undefined
|
|
12662
|
+
});
|
|
12663
|
+
} catch {
|
|
12664
|
+
await this.stateStore.clearStageHold(card.id).catch(() => {});
|
|
12665
|
+
}
|
|
12666
|
+
}
|
|
12546
12667
|
try {
|
|
12547
12668
|
await runTransition(this.client, card, {
|
|
12548
12669
|
removeLabels: ["agent"],
|
|
@@ -13969,19 +14090,10 @@ class Pool {
|
|
|
13969
14090
|
this.reviewQueue = new PriorityQueue(config);
|
|
13970
14091
|
this.budget = new BudgetGuard(config.budget, this.stateStore);
|
|
13971
14092
|
const workspaceModels = daemonConfig ? new WorkspaceModelCache(async () => {
|
|
13972
|
-
const { config: wsConfig } = await client.getWorkspaceModelConfig(workspaceId);
|
|
13973
|
-
return wsConfig
|
|
13974
|
-
}) : undefined;
|
|
13975
|
-
const modelCatalog = daemonConfig ? new WorkspaceModelCache(async () => {
|
|
13976
|
-
const { models } = await client.getModelCatalog();
|
|
13977
|
-
const withdrawn = {};
|
|
13978
|
-
for (const m of models) {
|
|
13979
|
-
if (m.status === "withdrawn" && m.replaced_by) {
|
|
13980
|
-
withdrawn[m.id] = m.replaced_by;
|
|
13981
|
-
}
|
|
13982
|
-
}
|
|
13983
|
-
return withdrawn;
|
|
14093
|
+
const { config: wsConfig, platformDefaults } = await client.getWorkspaceModelConfig(workspaceId);
|
|
14094
|
+
return layerPlatformDefaults(platformDefaults, wsConfig);
|
|
13984
14095
|
}) : undefined;
|
|
14096
|
+
const modelCatalog = daemonConfig ? new WorkspaceModelCache(async () => withdrawnMapFrom((await client.getModelCatalog()).models)) : undefined;
|
|
13985
14097
|
const reportedConfig = daemonConfig ? new ReportedConfigCache(client, effectiveModelReport(config)) : undefined;
|
|
13986
14098
|
for (let i = 0;i < config.poolSize; i++) {
|
|
13987
14099
|
this.implWorkers.push(new Worker(i, config, client, this.identity, () => {
|
|
@@ -14037,6 +14149,14 @@ class Pool {
|
|
|
14037
14149
|
await this.emitWaiting(card.id, `Paused — Anthropic API limit, retrying in ~${Math.round(cooldownMs / 1000)}s`);
|
|
14038
14150
|
return;
|
|
14039
14151
|
}
|
|
14152
|
+
const stageHold = classifyStageHold(card, this.stateStore.getStageHold(card.id), Date.now());
|
|
14153
|
+
if (stageHold.kind === "holding") {
|
|
14154
|
+
log36.debug(TAG32, `#${card.short_id} held — ${stageHold.detail}`);
|
|
14155
|
+
return;
|
|
14156
|
+
}
|
|
14157
|
+
if (stageHold.kind === "stale") {
|
|
14158
|
+
await this.stateStore.clearStageHold(card.id);
|
|
14159
|
+
}
|
|
14040
14160
|
const decision = this.budget.check(card.id);
|
|
14041
14161
|
if (!decision.allow) {
|
|
14042
14162
|
if (decision.reason === "daily_budget") {
|
|
@@ -14735,6 +14855,7 @@ var init_pool = __esm(() => {
|
|
|
14735
14855
|
init_unblock();
|
|
14736
14856
|
init_worker();
|
|
14737
14857
|
init_workspace_model_cache();
|
|
14858
|
+
init_workspace_model_config();
|
|
14738
14859
|
});
|
|
14739
14860
|
|
|
14740
14861
|
// src/port-registry.ts
|
|
@@ -14973,6 +15094,8 @@ var init_repo_dirs = __esm(() => {
|
|
|
14973
15094
|
|
|
14974
15095
|
// src/card-trust.ts
|
|
14975
15096
|
function isTrustedCardAuthor(card, trustedAuthors) {
|
|
15097
|
+
if (card.source != null)
|
|
15098
|
+
return false;
|
|
14976
15099
|
return card.created_by != null && trustedAuthors.has(card.created_by);
|
|
14977
15100
|
}
|
|
14978
15101
|
|
|
@@ -15032,7 +15155,7 @@ async function reclaimPreReviewStrands(opts) {
|
|
|
15032
15155
|
continue;
|
|
15033
15156
|
const daemonBranch = daemonBranches.get(card.id);
|
|
15034
15157
|
if (!isTrustedCardAuthor(card, trustedAuthors) && daemonBranch !== branch) {
|
|
15035
|
-
log40.debug(TAG36, `#${card.short_id} - skipped: author ${card.created_by ?? "(none)"} is not trusted, and ${daemonBranch ? `the card records ${branch} while this daemon pushed ${daemonBranch}` : "this daemon has pushed no branch for it"}`);
|
|
15158
|
+
log40.debug(TAG36, `#${card.short_id} - skipped: ${card.source != null ? `card was filed externally (source: ${card.source})` : `author ${card.created_by ?? "(none)"} is not trusted`}, and ${daemonBranch ? `the card records ${branch} while this daemon pushed ${daemonBranch}` : "this daemon has pushed no branch for it"}`);
|
|
15036
15159
|
continue;
|
|
15037
15160
|
}
|
|
15038
15161
|
const labels = resolveCardLabels(card, labelMap);
|
|
@@ -15270,7 +15393,7 @@ function rankSweepCandidates(opts) {
|
|
|
15270
15393
|
continue;
|
|
15271
15394
|
}
|
|
15272
15395
|
if (!isTrustedCardAuthor(card, trustedAuthors)) {
|
|
15273
|
-
skip2(card, "untrusted_author", card.created_by == null ? "card has no recorded author" : `author ${card.created_by} is not in sweep.trustedAuthors`);
|
|
15396
|
+
skip2(card, "untrusted_author", card.source != null ? `card was filed externally (source: ${card.source})` : card.created_by == null ? "card has no recorded author" : `author ${card.created_by} is not in sweep.trustedAuthors`);
|
|
15274
15397
|
continue;
|
|
15275
15398
|
}
|
|
15276
15399
|
if (card.assigned_agent_id != null) {
|
|
@@ -16106,6 +16229,93 @@ var init_reconcile = __esm(() => {
|
|
|
16106
16229
|
init_types();
|
|
16107
16230
|
});
|
|
16108
16231
|
|
|
16232
|
+
// src/startup-model.ts
|
|
16233
|
+
function describeStartupModel(config, layers, withdrawn) {
|
|
16234
|
+
const workspace = layers?.workspace ?? null;
|
|
16235
|
+
const platform = layers?.platform ?? null;
|
|
16236
|
+
const database = layerPlatformDefaults(platform, workspace);
|
|
16237
|
+
const claude = resolveEffectiveAgentConfig(config, database).claude;
|
|
16238
|
+
const pinned = ownObject(config.agentOverrides, "claude");
|
|
16239
|
+
const pinnedTiers = ownObject(pinned, "tiers");
|
|
16240
|
+
const pinnedModel = ownString(pinned, "model");
|
|
16241
|
+
let source = "built-in default";
|
|
16242
|
+
if (pinnedModel)
|
|
16243
|
+
source = "config.json";
|
|
16244
|
+
else if (workspace?.implement_model)
|
|
16245
|
+
source = "workspace";
|
|
16246
|
+
else if (platform?.implement_model)
|
|
16247
|
+
source = "platform";
|
|
16248
|
+
const warnings = [];
|
|
16249
|
+
const pins = [
|
|
16250
|
+
["claude.model", pinnedModel, database?.implement_model],
|
|
16251
|
+
[
|
|
16252
|
+
"claude.escalateModel",
|
|
16253
|
+
ownString(pinned, "escalateModel"),
|
|
16254
|
+
database?.escalate_model
|
|
16255
|
+
],
|
|
16256
|
+
...TIERS.map((tier) => [
|
|
16257
|
+
`claude.tiers.${tier}`,
|
|
16258
|
+
ownString(pinnedTiers, tier),
|
|
16259
|
+
database?.tiers?.[tier]
|
|
16260
|
+
])
|
|
16261
|
+
];
|
|
16262
|
+
for (const [key, local, db] of pins) {
|
|
16263
|
+
if (local && db && local !== db) {
|
|
16264
|
+
warnings.push(`config.json pins ${key} to "${local}", which overrides the database default "${db}" — remove it from config.json to follow the app's model settings`);
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
16267
|
+
const effective = [
|
|
16268
|
+
["claude.model", claude.model],
|
|
16269
|
+
["claude.escalateModel", claude.escalateModel],
|
|
16270
|
+
...TIERS.map((tier) => [`claude.tiers.${tier}`, claude.tiers[tier]])
|
|
16271
|
+
];
|
|
16272
|
+
for (const [key, id] of effective) {
|
|
16273
|
+
const replacement = withdrawn?.[id];
|
|
16274
|
+
if (replacement) {
|
|
16275
|
+
warnings.push(`${key} resolves to "${id}", which is withdrawn in the model catalog — runs use its replacement "${replacement}"; update the setting to match`);
|
|
16276
|
+
}
|
|
16277
|
+
}
|
|
16278
|
+
return {
|
|
16279
|
+
model: claude.model,
|
|
16280
|
+
escalateModel: claude.escalateModel,
|
|
16281
|
+
reviewModel: config.agent.claude.reviewModel,
|
|
16282
|
+
source,
|
|
16283
|
+
resolved: layers !== null && withdrawn !== null,
|
|
16284
|
+
warnings
|
|
16285
|
+
};
|
|
16286
|
+
}
|
|
16287
|
+
async function resolveStartupModel(config, client) {
|
|
16288
|
+
const [layers, withdrawn] = await Promise.all([
|
|
16289
|
+
client.getWorkspaceModelConfig(config.workspaceId).then(({ config: workspace, platformDefaults }) => ({
|
|
16290
|
+
workspace,
|
|
16291
|
+
platform: platformDefaults ?? null
|
|
16292
|
+
})).catch(() => null),
|
|
16293
|
+
client.getModelCatalog().then(({ models }) => withdrawnMapFrom(models)).catch(() => null)
|
|
16294
|
+
]);
|
|
16295
|
+
return describeStartupModel(config, layers, withdrawn);
|
|
16296
|
+
}
|
|
16297
|
+
function ownObject(source, key) {
|
|
16298
|
+
if (typeof source !== "object" || source === null)
|
|
16299
|
+
return {};
|
|
16300
|
+
const value = own(source, key);
|
|
16301
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
16302
|
+
}
|
|
16303
|
+
function ownString(source, key) {
|
|
16304
|
+
const value = own(source, key);
|
|
16305
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
16306
|
+
}
|
|
16307
|
+
function startupModelLine(m) {
|
|
16308
|
+
const models = m.model === m.escalateModel ? m.model : `${m.model} → ${m.escalateModel} on retry/high · review ${m.reviewModel}`;
|
|
16309
|
+
const origin = m.resolved ? `from ${m.source}` : `from ${m.source}; database model settings unreachable, a pickup may resolve differently`;
|
|
16310
|
+
return `${models} (${origin})`;
|
|
16311
|
+
}
|
|
16312
|
+
var TIERS;
|
|
16313
|
+
var init_startup_model = __esm(() => {
|
|
16314
|
+
init_src();
|
|
16315
|
+
init_workspace_model_config();
|
|
16316
|
+
TIERS = ["simple", "advanced", "research"];
|
|
16317
|
+
});
|
|
16318
|
+
|
|
16109
16319
|
// src/startup-banner.ts
|
|
16110
16320
|
var exports_startup_banner = {};
|
|
16111
16321
|
__export(exports_startup_banner, {
|
|
@@ -16121,6 +16331,7 @@ function prettyBanner(config, version) {
|
|
|
16121
16331
|
let projectName;
|
|
16122
16332
|
let gitProvider;
|
|
16123
16333
|
let httpPort;
|
|
16334
|
+
let model;
|
|
16124
16335
|
let failed = false;
|
|
16125
16336
|
let rendered = false;
|
|
16126
16337
|
return {
|
|
@@ -16133,6 +16344,9 @@ function prettyBanner(config, version) {
|
|
|
16133
16344
|
setHttpPort(port) {
|
|
16134
16345
|
httpPort = port;
|
|
16135
16346
|
},
|
|
16347
|
+
setModel(resolved) {
|
|
16348
|
+
model = resolved;
|
|
16349
|
+
},
|
|
16136
16350
|
check(message) {
|
|
16137
16351
|
checks.push({ kind: "ok", message });
|
|
16138
16352
|
},
|
|
@@ -16154,6 +16368,7 @@ function prettyBanner(config, version) {
|
|
|
16154
16368
|
projectName,
|
|
16155
16369
|
gitProvider,
|
|
16156
16370
|
httpPort,
|
|
16371
|
+
model,
|
|
16157
16372
|
checks,
|
|
16158
16373
|
readyMessage: message
|
|
16159
16374
|
});
|
|
@@ -16163,12 +16378,13 @@ function prettyBanner(config, version) {
|
|
|
16163
16378
|
}
|
|
16164
16379
|
function jsonBanner(config, version) {
|
|
16165
16380
|
log45.info(TAG41, `Harmony Agent Daemon v${version} starting...`);
|
|
16166
|
-
log45.info(TAG41, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} |
|
|
16381
|
+
log45.info(TAG41, `Project: ${config.projectId} | Pool: ${config.agent.poolSize} | Runner: ${config.agent.runner} | Pickup: ${config.agent.pickupColumns.join(", ")}`);
|
|
16167
16382
|
log45.info(TAG41, `Config: ${contextSourceLine(config)}`);
|
|
16168
16383
|
if (config.agent.review.enabled) {
|
|
16169
16384
|
log45.info(TAG41, `Review: enabled | Columns: ${config.agent.review.pickupColumns.join(", ")} | → ${config.agent.review.moveToColumn} / ${config.agent.review.failColumn}`);
|
|
16170
16385
|
}
|
|
16171
16386
|
let failed = false;
|
|
16387
|
+
let modelLogged = false;
|
|
16172
16388
|
return {
|
|
16173
16389
|
setProjectName(_name) {},
|
|
16174
16390
|
setGitProvider(provider) {
|
|
@@ -16177,6 +16393,10 @@ function jsonBanner(config, version) {
|
|
|
16177
16393
|
setHttpPort(port) {
|
|
16178
16394
|
log45.info(TAG41, `HTTP server on port ${port}`);
|
|
16179
16395
|
},
|
|
16396
|
+
setModel(resolved) {
|
|
16397
|
+
modelLogged = true;
|
|
16398
|
+
log45.info(TAG41, `Model: ${startupModelLine(resolved)}`);
|
|
16399
|
+
},
|
|
16180
16400
|
check(message) {
|
|
16181
16401
|
log45.info(TAG41, message);
|
|
16182
16402
|
},
|
|
@@ -16189,6 +16409,8 @@ function jsonBanner(config, version) {
|
|
|
16189
16409
|
async ready(message) {
|
|
16190
16410
|
if (failed)
|
|
16191
16411
|
return;
|
|
16412
|
+
if (!modelLogged)
|
|
16413
|
+
log45.info(TAG41, `Model: ${localModelLine(config)}`);
|
|
16192
16414
|
log45.info(TAG41, message);
|
|
16193
16415
|
}
|
|
16194
16416
|
};
|
|
@@ -16200,6 +16422,7 @@ function renderPretty(input) {
|
|
|
16200
16422
|
projectName,
|
|
16201
16423
|
gitProvider,
|
|
16202
16424
|
httpPort,
|
|
16425
|
+
model,
|
|
16203
16426
|
checks,
|
|
16204
16427
|
readyMessage
|
|
16205
16428
|
} = input;
|
|
@@ -16207,7 +16430,7 @@ function renderPretty(input) {
|
|
|
16207
16430
|
lines.push("");
|
|
16208
16431
|
lines.push(titleRule(`Harmony Agent Daemon v${version}`));
|
|
16209
16432
|
lines.push("");
|
|
16210
|
-
for (const row of configRows(config, projectName, gitProvider, httpPort)) {
|
|
16433
|
+
for (const row of configRows(config, projectName, gitProvider, httpPort, model)) {
|
|
16211
16434
|
lines.push(` ${dim(row.label.padEnd(9))} ${row.value}`);
|
|
16212
16435
|
}
|
|
16213
16436
|
lines.push("");
|
|
@@ -16221,7 +16444,7 @@ function renderPretty(input) {
|
|
|
16221
16444
|
return lines.join(`
|
|
16222
16445
|
`);
|
|
16223
16446
|
}
|
|
16224
|
-
function configRows(config, projectName, gitProvider, httpPort) {
|
|
16447
|
+
function configRows(config, projectName, gitProvider, httpPort, model) {
|
|
16225
16448
|
const rows = [];
|
|
16226
16449
|
const projectLabel = projectName ? `${projectName} (${shortenId(config.projectId)})` : shortenId(config.projectId);
|
|
16227
16450
|
rows.push({ label: "Project", value: projectLabel });
|
|
@@ -16230,8 +16453,7 @@ function configRows(config, projectName, gitProvider, httpPort) {
|
|
|
16230
16453
|
const reviewEnabled = config.agent.review.enabled;
|
|
16231
16454
|
const poolDesc = reviewEnabled ? `Pool ${config.agent.poolSize} impl + ${config.agent.review.poolSize} review` : `Pool ${config.agent.poolSize} impl`;
|
|
16232
16455
|
const flowDesc = reviewEnabled ? `Pickup ${config.agent.pickupColumns[0]} → ${config.agent.completion.moveToColumn} → ${config.agent.review.moveToColumn}` : `Pickup ${config.agent.pickupColumns.join(", ")}`;
|
|
16233
|
-
const
|
|
16234
|
-
const modelDesc = model === escalateModel ? model : `${model} → ${escalateModel} on retry/high · review ${reviewModel}`;
|
|
16456
|
+
const modelDesc = model ? startupModelLine(model) : localModelLine(config);
|
|
16235
16457
|
rows.push({
|
|
16236
16458
|
label: "Model",
|
|
16237
16459
|
value: `${modelDesc} · ${poolDesc} · ${flowDesc}`
|
|
@@ -16265,6 +16487,11 @@ function titleRule(title) {
|
|
|
16265
16487
|
const suffix = "─".repeat(Math.max(3, RULE_WIDTH - prefix.length - title.length - surround.length));
|
|
16266
16488
|
return dim(`${prefix}${title}${surround}${suffix}`);
|
|
16267
16489
|
}
|
|
16490
|
+
function localModelLine(config) {
|
|
16491
|
+
const { model, escalateModel, reviewModel } = config.agent.claude;
|
|
16492
|
+
const models = model === escalateModel ? model : `${model} → ${escalateModel} on retry/high · review ${reviewModel}`;
|
|
16493
|
+
return `${models} (local config only; database model settings not read)`;
|
|
16494
|
+
}
|
|
16268
16495
|
function runnerDesc(runner) {
|
|
16269
16496
|
return runner === "sdk" ? "sdk (Agent SDK)" : "cli (Claude CLI)";
|
|
16270
16497
|
}
|
|
@@ -16285,6 +16512,7 @@ function yellow(s) {
|
|
|
16285
16512
|
var TAG41 = "daemon", RULE_WIDTH = 70, ANSI;
|
|
16286
16513
|
var init_startup_banner = __esm(() => {
|
|
16287
16514
|
init_port_registry();
|
|
16515
|
+
init_startup_model();
|
|
16288
16516
|
ANSI = {
|
|
16289
16517
|
reset: "\x1B[0m",
|
|
16290
16518
|
dim: "\x1B[2m",
|
|
@@ -17642,6 +17870,11 @@ async function main() {
|
|
|
17642
17870
|
} catch (err) {
|
|
17643
17871
|
log50.warn(TAG46, `Could not report model config: ${err instanceof Error ? err.message : String(err)}`);
|
|
17644
17872
|
}
|
|
17873
|
+
const startupModel = await resolveStartupModel(config, client);
|
|
17874
|
+
banner.setModel(startupModel);
|
|
17875
|
+
for (const warning of startupModel.warnings) {
|
|
17876
|
+
banner.warn(warning);
|
|
17877
|
+
}
|
|
17645
17878
|
migrateConfigDir();
|
|
17646
17879
|
try {
|
|
17647
17880
|
migrateLegacyStateFile({
|
|
@@ -17964,6 +18197,7 @@ var init_src2 = __esm(() => {
|
|
|
17964
18197
|
init_recovery();
|
|
17965
18198
|
init_review_worktree();
|
|
17966
18199
|
init_startup_banner();
|
|
18200
|
+
init_startup_model();
|
|
17967
18201
|
init_state_migration();
|
|
17968
18202
|
init_state_store();
|
|
17969
18203
|
init_stream_parser_selftest();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gethmy/agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.47.0",
|
|
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",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@anthropic-ai/claude-agent-sdk": "^0.3.178",
|
|
48
|
-
"@gethmy/harness": "1.19.
|
|
49
|
-
"@gethmy/mcp": "3.
|
|
48
|
+
"@gethmy/harness": "1.19.1",
|
|
49
|
+
"@gethmy/mcp": "3.16.0",
|
|
50
50
|
"@supabase/supabase-js": "2.95.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|